1 /*****************************************************************************
2 * Copyright (c) 2016 Ericsson
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *****************************************************************************/
8 package org
.eclipse
.tracecompass
.tmf
.ui
.views
.timegraph
;
10 import java
.util
.ArrayList
;
11 import java
.util
.HashMap
;
12 import java
.util
.HashSet
;
13 import java
.util
.List
;
16 import java
.util
.regex
.Pattern
;
17 import java
.util
.regex
.PatternSyntaxException
;
19 import org
.eclipse
.jdt
.annotation
.NonNull
;
20 import org
.eclipse
.jdt
.annotation
.Nullable
;
21 import org
.eclipse
.jface
.action
.LegacyActionTools
;
22 import org
.eclipse
.jface
.dialogs
.Dialog
;
23 import org
.eclipse
.jface
.dialogs
.IDialogSettings
;
24 import org
.eclipse
.jface
.fieldassist
.ComboContentAdapter
;
25 import org
.eclipse
.jface
.text
.FindReplaceDocumentAdapterContentProposalProvider
;
26 import org
.eclipse
.jface
.util
.Util
;
27 import org
.eclipse
.swt
.SWT
;
28 import org
.eclipse
.swt
.events
.ModifyEvent
;
29 import org
.eclipse
.swt
.events
.ModifyListener
;
30 import org
.eclipse
.swt
.events
.SelectionAdapter
;
31 import org
.eclipse
.swt
.events
.SelectionEvent
;
32 import org
.eclipse
.swt
.events
.SelectionListener
;
33 import org
.eclipse
.swt
.events
.ShellAdapter
;
34 import org
.eclipse
.swt
.events
.ShellEvent
;
35 import org
.eclipse
.swt
.events
.TraverseEvent
;
36 import org
.eclipse
.swt
.events
.TraverseListener
;
37 import org
.eclipse
.swt
.graphics
.Point
;
38 import org
.eclipse
.swt
.graphics
.Rectangle
;
39 import org
.eclipse
.swt
.layout
.GridData
;
40 import org
.eclipse
.swt
.layout
.GridLayout
;
41 import org
.eclipse
.swt
.widgets
.Button
;
42 import org
.eclipse
.swt
.widgets
.Combo
;
43 import org
.eclipse
.swt
.widgets
.Composite
;
44 import org
.eclipse
.swt
.widgets
.Control
;
45 import org
.eclipse
.swt
.widgets
.Event
;
46 import org
.eclipse
.swt
.widgets
.Group
;
47 import org
.eclipse
.swt
.widgets
.Label
;
48 import org
.eclipse
.swt
.widgets
.Shell
;
49 import org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
;
50 import org
.eclipse
.tracecompass
.internal
.tmf
.ui
.Activator
;
51 import org
.eclipse
.tracecompass
.internal
.tmf
.ui
.Messages
;
52 import org
.eclipse
.tracecompass
.tmf
.ui
.views
.timegraph
.AbstractTimeGraphView
.FindTarget
;
53 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.ITimeGraphEntry
;
54 import org
.eclipse
.ui
.fieldassist
.ContentAssistCommandAdapter
;
55 import org
.eclipse
.ui
.texteditor
.ITextEditorActionDefinitionIds
;
57 import com
.google
.common
.collect
.BiMap
;
58 import com
.google
.common
.collect
.HashBiMap
;
61 * Find dialog to search entries into a time graph. This implementation is based
62 * on the org.eclipse.ui.texteditor.FindReplaceDialog of Eclipse.
64 * @author Jean-Christian Kouame
66 class TimeGraphFindDialog
extends Dialog
{
69 * Updates the find dialog on activation changes.
71 class ActivationListener
extends ShellAdapter
{
73 * @see ShellListener#shellActivated(ShellEvent)
76 public void shellActivated(ShellEvent e
) {
77 fActiveShell
= (Shell
) e
.widget
;
80 if (fGiveFocusToFindField
&& getShell() == fActiveShell
&& okToUse(fFindField
)) {
81 fFindField
.setFocus();
87 * @see ShellListener#shellDeactivated(ShellEvent)
90 public void shellDeactivated(ShellEvent e
) {
91 fGiveFocusToFindField
= false;
101 * Modify listener to update the search result in case of incremental
104 private class FindModifyListener
implements ModifyListener
{
107 * @see ModifyListener#modifyText(ModifyEvent)
110 public void modifyText(ModifyEvent e
) {
115 /** The size of the dialogs search history. */
116 private static final int HISTORY_SIZE
= 5;
118 private final String WRAP
= "wrap"; //$NON-NLS-1$
119 private final String CASE_SENSITIVE
= "casesensitive"; //$NON-NLS-1$
120 private final String WHOLE_WORD
= "wholeword"; //$NON-NLS-1$
121 private final String IS_REGEX_EXPRESSION
= "isRegEx"; //$NON-NLS-1$
122 private final String FIND_HISTORY
= "findhistory"; //$NON-NLS-1$
124 private boolean fWrapInit
;
125 private boolean fCaseInit
;
126 private boolean fWholeWordInit
;
127 private boolean fForwardInit
;
128 private boolean fIsRegExInit
;
130 private @NonNull List
<String
> fFindHistory
;
132 private static Shell fParentShell
;
133 private Shell fActiveShell
;
135 private final ActivationListener fActivationListener
= new ActivationListener();
136 private final FindModifyListener fFindModifyListener
= new FindModifyListener();
138 private Label fStatusLabel
;
139 private Button fForwardRadioButton
;
140 private Button fCaseCheckBox
;
141 private Button fWrapCheckBox
;
142 private Button fWholeWordCheckBox
;
143 private Button fIsRegExCheckBox
;
145 private Button fFindNextButton
;
146 private Combo fFindField
;
149 * Find command adapters.
151 private ContentAssistCommandAdapter fContentAssistFindField
;
153 private Rectangle fDialogPositionInit
;
155 private IDialogSettings fDialogSettings
;
157 * <code>true</code> if the find field should receive focus the next time
158 * the dialog is activated, <code>false</code> otherwise.
160 private boolean fGiveFocusToFindField
= true;
163 * Holds the mnemonic/button pairs for all buttons.
165 private HashMap
<Character
, Button
> fMnemonicButtonMap
= new HashMap
<>();
167 private @Nullable FindTarget fFindTarget
;
170 * Creates a new dialog with the given shell as parent.
175 public TimeGraphFindDialog(Shell parentShell
) {
181 fDialogPositionInit
= null;
182 fFindHistory
= new ArrayList
<>(HISTORY_SIZE
- 1);
186 fIsRegExInit
= false;
187 fWholeWordInit
= false;
191 setShellStyle(getShellStyle() & ~SWT
.APPLICATION_MODAL
);
192 setBlockOnOpen(false);
196 protected boolean isResizable() {
201 * Returns <code>true</code> if control can be used.
204 * the control to be checked
205 * @return <code>true</code> if control can be used
207 private static boolean okToUse(Control control
) {
208 return control
!= null && !control
.isDisposed();
212 public void create() {
215 Shell shell
= getShell();
216 shell
.addShellListener(fActivationListener
);
218 // fill in combo contents
219 fFindField
.removeModifyListener(fFindModifyListener
);
220 updateCombo(fFindField
, fFindHistory
);
221 fFindField
.addModifyListener(fFindModifyListener
);
224 initFindStringFromSelection();
226 shell
.setMinimumSize(shell
.getSize());
228 // set dialog position
229 if (fDialogPositionInit
!= null) {
230 shell
.setBounds(fDialogPositionInit
);
233 shell
.setText(Messages
.TimeGraphFindDialog_FindTitle
);
237 * Creates the options configuration section of the find dialog.
240 * the parent composite
241 * @return the options configuration section
243 private Composite
createConfigPanel(Composite parent
) {
245 Composite panel
= new Composite(parent
, SWT
.NONE
);
246 GridLayout layout
= new GridLayout();
247 panel
.setLayout(layout
);
249 Composite directionGroup
= createDirectionGroup(panel
);
250 setGridData(directionGroup
, SWT
.FILL
, true, SWT
.FILL
, false);
252 Composite optionsGroup
= createOptionsGroup(panel
);
253 setGridData(optionsGroup
, SWT
.FILL
, true, SWT
.FILL
, true);
254 ((GridData
) optionsGroup
.getLayoutData()).horizontalSpan
= 2;
260 protected Control
createContents(Composite parent
) {
262 Composite panel
= new Composite(parent
, SWT
.NULL
);
263 GridLayout layout
= new GridLayout();
264 layout
.numColumns
= 1;
265 layout
.makeColumnsEqualWidth
= true;
266 panel
.setLayout(layout
);
267 panel
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, true, true));
269 Composite inputPanel
= createInputPanel(panel
);
270 setGridData(inputPanel
, SWT
.FILL
, true, SWT
.TOP
, false);
272 Composite configPanel
= createConfigPanel(panel
);
273 setGridData(configPanel
, SWT
.FILL
, true, SWT
.TOP
, true);
275 Composite statusBar
= createStatusAndCloseButton(panel
);
276 setGridData(statusBar
, SWT
.FILL
, true, SWT
.BOTTOM
, false);
278 panel
.addTraverseListener(new TraverseListener() {
280 public void keyTraversed(TraverseEvent e
) {
281 if (e
.detail
== SWT
.TRAVERSE_RETURN
) {
283 Control controlWithFocus
= getShell().getDisplay().getFocusControl();
284 if (controlWithFocus
!= null && (controlWithFocus
.getStyle() & SWT
.PUSH
) == SWT
.PUSH
) {
288 Event event
= new Event();
289 event
.type
= SWT
.Selection
;
290 event
.stateMask
= e
.stateMask
;
291 fFindNextButton
.notifyListeners(SWT
.Selection
, event
);
293 } else if (e
.detail
== SWT
.TRAVERSE_MNEMONIC
) {
294 Character mnemonic
= new Character(Character
.toLowerCase(e
.character
));
295 Button button
= fMnemonicButtonMap
.get(mnemonic
);
296 if (button
!= null) {
297 if ((fFindField
.isFocusControl() || (button
.getStyle() & SWT
.PUSH
) != 0)
298 && button
.isEnabled()) {
299 Event event
= new Event();
300 event
.type
= SWT
.Selection
;
301 event
.stateMask
= e
.stateMask
;
302 if ((button
.getStyle() & SWT
.RADIO
) != 0) {
303 Composite buttonParent
= button
.getParent();
304 if (buttonParent
!= null) {
305 Control
[] children
= buttonParent
.getChildren();
306 for (int i
= 0; i
< children
.length
; i
++) {
307 ((Button
) children
[i
]).setSelection(false);
310 button
.setSelection(true);
312 button
.setSelection(!button
.getSelection());
314 button
.notifyListeners(SWT
.Selection
, event
);
315 e
.detail
= SWT
.TRAVERSE_NONE
;
325 applyDialogFont(panel
);
330 private void setContentAssistsEnablement(boolean enable
) {
331 fContentAssistFindField
.setEnabled(enable
);
335 * Creates the direction defining part of the options defining section of
339 * the parent composite
340 * @return the direction defining part
342 private Composite
createDirectionGroup(Composite parent
) {
344 Composite panel
= new Composite(parent
, SWT
.NONE
);
345 GridLayout layout
= new GridLayout();
346 layout
.marginWidth
= 0;
347 layout
.marginHeight
= 0;
348 panel
.setLayout(layout
);
350 Group group
= new Group(panel
, SWT
.SHADOW_ETCHED_IN
);
351 group
.setText(Messages
.TimeGraphFindDialog_Direction
);
352 GridLayout groupLayout
= new GridLayout();
353 group
.setLayout(groupLayout
);
354 group
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, true, true));
356 fForwardRadioButton
= new Button(group
, SWT
.RADIO
| SWT
.LEFT
);
357 fForwardRadioButton
.setText(Messages
.TimeGraphFindDialog_ForwardRadioButtonLabel
);
358 setGridData(fForwardRadioButton
, SWT
.LEFT
, false, SWT
.CENTER
, false);
359 storeButtonWithMnemonicInMap(fForwardRadioButton
);
361 Button backwardRadioButton
= new Button(group
, SWT
.RADIO
| SWT
.LEFT
);
362 backwardRadioButton
.setText(Messages
.TimeGraphFindDialog_BackwardRadioButtonLabel
);
363 setGridData(backwardRadioButton
, SWT
.LEFT
, false, SWT
.CENTER
, false);
364 storeButtonWithMnemonicInMap(backwardRadioButton
);
366 backwardRadioButton
.setSelection(!fForwardInit
);
367 fForwardRadioButton
.setSelection(fForwardInit
);
373 * Creates the panel where the user specifies the text to search for
376 * the parent composite
377 * @return the input panel
379 private Composite
createInputPanel(Composite parent
) {
380 Composite panel
= new Composite(parent
, SWT
.NULL
);
381 GridLayout layout
= new GridLayout();
382 layout
.numColumns
= 2;
383 panel
.setLayout(layout
);
385 Label findLabel
= new Label(panel
, SWT
.LEFT
);
386 findLabel
.setText(Messages
.TimeGraphFindDialog_FindLabel
);
387 setGridData(findLabel
, SWT
.LEFT
, false, SWT
.CENTER
, false);
389 // Create the find content assist field
390 ComboContentAdapter contentAdapter
= new ComboContentAdapter();
391 FindReplaceDocumentAdapterContentProposalProvider findProposer
= new FindReplaceDocumentAdapterContentProposalProvider(true);
392 fFindField
= new Combo(panel
, SWT
.DROP_DOWN
| SWT
.BORDER
);
393 fContentAssistFindField
= new ContentAssistCommandAdapter(
397 ITextEditorActionDefinitionIds
.CONTENT_ASSIST_PROPOSALS
,
400 setGridData(fFindField
, SWT
.FILL
, true, SWT
.CENTER
, false);
401 fFindField
.addModifyListener(fFindModifyListener
);
407 * Creates the functional options part of the options defining section of
411 * the parent composite
412 * @return the options group
414 private Composite
createOptionsGroup(Composite parent
) {
416 Composite panel
= new Composite(parent
, SWT
.NONE
);
417 GridLayout layout
= new GridLayout();
418 layout
.marginWidth
= 0;
419 layout
.marginHeight
= 0;
420 panel
.setLayout(layout
);
422 Group group
= new Group(panel
, SWT
.SHADOW_NONE
);
423 group
.setText(Messages
.TimeGraphFindDialog_Options
);
424 GridLayout groupLayout
= new GridLayout();
425 groupLayout
.numColumns
= 1;
426 groupLayout
.makeColumnsEqualWidth
= true;
427 group
.setLayout(groupLayout
);
428 group
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, true, true));
430 SelectionListener selectionListener
= new SelectionListener() {
432 public void widgetSelected(SelectionEvent e
) {
437 public void widgetDefaultSelected(SelectionEvent e
) {
441 fCaseCheckBox
= new Button(group
, SWT
.CHECK
| SWT
.LEFT
);
442 fCaseCheckBox
.setText(Messages
.TimeGraphFindDialog_CaseCheckBoxLabel
);
443 setGridData(fCaseCheckBox
, SWT
.LEFT
, false, SWT
.CENTER
, false);
444 fCaseCheckBox
.setSelection(fCaseInit
);
445 fCaseCheckBox
.addSelectionListener(selectionListener
);
446 storeButtonWithMnemonicInMap(fCaseCheckBox
);
448 fWrapCheckBox
= new Button(group
, SWT
.CHECK
| SWT
.LEFT
);
449 fWrapCheckBox
.setText(Messages
.TimeGraphFindDialog_WrapCheckBoxLabel
);
450 setGridData(fWrapCheckBox
, SWT
.LEFT
, false, SWT
.CENTER
, false);
451 fWrapCheckBox
.setSelection(fWrapInit
);
452 fWrapCheckBox
.addSelectionListener(selectionListener
);
453 storeButtonWithMnemonicInMap(fWrapCheckBox
);
455 fWholeWordCheckBox
= new Button(group
, SWT
.CHECK
| SWT
.LEFT
);
456 fWholeWordCheckBox
.setText(Messages
.TimeGraphFindDialog_WholeWordCheckBoxLabel
);
457 setGridData(fWholeWordCheckBox
, SWT
.LEFT
, false, SWT
.CENTER
, false);
458 fWholeWordCheckBox
.setSelection(fWholeWordInit
);
459 fWholeWordCheckBox
.addSelectionListener(selectionListener
);
460 storeButtonWithMnemonicInMap(fWholeWordCheckBox
);
462 fIsRegExCheckBox
= new Button(group
, SWT
.CHECK
| SWT
.LEFT
);
463 fIsRegExCheckBox
.setText(Messages
.TimeGraphFindDialog_REgExCheckBoxLabel
);
464 setGridData(fIsRegExCheckBox
, SWT
.LEFT
, false, SWT
.CENTER
, false);
465 ((GridData
) fIsRegExCheckBox
.getLayoutData()).horizontalSpan
= 2;
466 fIsRegExCheckBox
.setSelection(fIsRegExInit
);
467 fIsRegExCheckBox
.addSelectionListener(new SelectionAdapter() {
469 public void widgetSelected(SelectionEvent e
) {
470 boolean newState
= fIsRegExCheckBox
.getSelection();
473 setContentAssistsEnablement(newState
);
476 storeButtonWithMnemonicInMap(fIsRegExCheckBox
);
477 fWholeWordCheckBox
.setEnabled(!isRegExSearch());
478 fWholeWordCheckBox
.addSelectionListener(new SelectionAdapter() {
480 public void widgetSelected(SelectionEvent e
) {
488 * Creates the status and close section of the dialog.
491 * the parent composite
492 * @return the status and close button
494 private Composite
createStatusAndCloseButton(Composite parent
) {
496 Composite panel
= new Composite(parent
, SWT
.NULL
);
497 GridLayout layout
= new GridLayout();
498 layout
.numColumns
= 2;
499 layout
.marginWidth
= 0;
500 layout
.marginHeight
= 0;
501 panel
.setLayout(layout
);
503 fStatusLabel
= new Label(panel
, SWT
.LEFT
);
504 fStatusLabel
.setText(Messages
.TimeGraphFindDialog_StatusWrappedLabel
);
505 setGridData(fStatusLabel
, SWT
.FILL
, true, SWT
.CENTER
, false);
506 GridData gd
= (GridData
) fStatusLabel
.getLayoutData();
507 gd
.widthHint
= fStatusLabel
.computeSize(SWT
.DEFAULT
, SWT
.DEFAULT
).x
;
508 fStatusLabel
.setText(""); //$NON-NLS-1$
510 Composite buttonSection
= new Composite(panel
, SWT
.NULL
);
511 GridLayout buttonLayout
= new GridLayout();
512 buttonLayout
.numColumns
= 2;
513 buttonSection
.setLayout(buttonLayout
);
515 String label
= Messages
.TimeGraphFindDialog_CloseButtonLabel
;
516 Button closeButton
= createButton(buttonSection
, 101, label
, false);
517 setGridData(closeButton
, SWT
.RIGHT
, false, SWT
.BOTTOM
, false);
519 fFindNextButton
= makeButton(buttonSection
, Messages
.TimeGraphFindDialog_FindNextButtonLabel
, 102, true, new SelectionAdapter() {
521 public void widgetSelected(SelectionEvent e
) {
522 performSearch(((e
.stateMask
& SWT
.SHIFT
) != 0) ^
isForwardSearch());
526 setGridData(fFindNextButton
, SWT
.FILL
, true, SWT
.FILL
, false);
532 protected void buttonPressed(int buttonID
) {
533 if (buttonID
== 101) {
539 * Update the dialog data (parentShell, listener, input, ...)
542 * the new find target
544 public void update(@NonNull FindTarget findTarget
) {
545 updateTarget(findTarget
, true);
548 // ------- action invocation ---------------------------------------
551 * Returns the index of the entry that match the specified search string, or
552 * <code>-1</code> if the string can not be found when searching using the
556 * the string to search for
558 * the index at which to start the search
560 * The map of items in the time graph view
562 * The options use for the search
563 * @return the index of the find entry following the options or
564 * <code>-1</code> if nothing found
566 private int findNext(String findString
, int startIndex
, BiMap
<ITimeGraphEntry
, Integer
> items
, SearchOptions options
) {
568 if (options
.forwardSearch
) {
569 index
= startIndex
== items
.size() - 1 ?
-1 : findNext(startIndex
+ 1, findString
, items
, options
);
571 index
= startIndex
== 0 ?
-1 : findNext(startIndex
- 1, findString
, items
, options
);
575 if (okToUse(getShell())) {
576 getShell().getDisplay().beep();
578 if (options
.wrapSearch
) {
579 statusMessage(Messages
.TimeGraphFindDialog_StatusWrappedLabel
);
580 index
= findNext(-1, findString
, items
, options
);
586 private int findNext(int startIndex
, String findString
, BiMap
<ITimeGraphEntry
, Integer
> items
, SearchOptions options
) {
587 if (fFindTarget
!= null) {
588 if (findString
== null || findString
.length() == 0) {
592 final @NonNull Pattern pattern
= getPattern(findString
, options
);
593 int index
= adjustIndex(startIndex
, items
.size(), options
.forwardSearch
);
594 BiMap
<Integer
, ITimeGraphEntry
> entries
= items
.inverse();
595 while (index
>= 0 && index
< entries
.size()) {
596 final @Nullable ITimeGraphEntry entry
= entries
.get(index
);
597 if (entry
!= null && entry
.matches(pattern
)) {
600 index
= options
.forwardSearch ?
++index
: --index
;
607 * Returns whether the specified search string can be found using the given
611 * the string to search for
614 * @return <code>true</code> if the search string can be found using the
618 private boolean findAndSelect(String findString
, SearchOptions options
) {
619 FindTarget findTarget
= fFindTarget
;
620 if (findTarget
== null) {
623 ITimeGraphEntry
[] topInput
= findTarget
.getEntries();
624 BiMap
<@NonNull ITimeGraphEntry
, @NonNull Integer
> items
= HashBiMap
.create();
625 for (ITimeGraphEntry entry
: topInput
) {
626 listEntries(items
, entry
);
628 int startPosition
= findTarget
.getSelection() == null ?
0 : NonNullUtils
.checkNotNull(items
.get(findTarget
.getSelection()));
630 int index
= findNext(findString
, startPosition
, items
, options
);
633 statusMessage(Messages
.TimeGraphFindDialog_StatusNoMatchLabel
);
637 if (options
.forwardSearch
&& index
>= startPosition
|| !options
.forwardSearch
&& index
<= startPosition
) {
638 statusMessage(""); //$NON-NLS-1$
641 // Send the entry found to target
642 findTarget
.selectAndReveal(NonNullUtils
.checkNotNull(items
.inverse().get(index
)));
646 private void listEntries(Map
<ITimeGraphEntry
, Integer
> items
, ITimeGraphEntry root
) {
647 items
.put(root
, items
.size());
648 for (ITimeGraphEntry child
: root
.getChildren()) {
649 listEntries(items
, child
);
653 // ------- accessors ---------------------------------------
656 * Retrieves the string to search for from the appropriate text input field
659 * @return the search string
661 private String
getFindString() {
662 if (okToUse(fFindField
)) {
663 return fFindField
.getText();
665 return ""; //$NON-NLS-1$
669 * Returns the dialog's boundaries.
671 * @return the dialog's boundaries
673 private Rectangle
getDialogBoundaries() {
674 if (okToUse(getShell())) {
675 return getShell().getBounds();
677 return fDialogPositionInit
;
680 // ------- init / close ---------------------------------------
682 public boolean close() {
684 return super.close();
688 * Removes focus changed listener from browser and stores settings for
691 private void handleDialogClose() {
694 if (okToUse(fFindField
)) {
695 fFindField
.removeModifyListener(fFindModifyListener
);
697 updateTarget(null, false);
699 if (fParentShell
!= null) {
700 fParentShell
.removeShellListener(fActivationListener
);
704 if (getShell() != null && !getShell().isDisposed()) {
705 getShell().removeShellListener(fActivationListener
);
708 // store current settings in case of re-open
717 * Writes the current selection to the dialog settings.
719 private void writeSelection() {
720 final FindTarget input
= fFindTarget
;
725 IDialogSettings s
= getDialogSettings();
726 final ITimeGraphEntry selection
= input
.getSelection();
727 if (selection
!= null) {
728 s
.put("selection", selection
.getName()); //$NON-NLS-1$
733 * Stores the current state in the dialog settings.
735 private void storeSettings() {
736 fDialogPositionInit
= getDialogBoundaries();
737 fWrapInit
= isWrapSearch();
738 fWholeWordInit
= isWholeWordSetting();
739 fCaseInit
= isCaseSensitiveSearch();
740 fIsRegExInit
= isRegExSearch();
741 fForwardInit
= isForwardSearch();
743 writeConfiguration();
747 * Initializes the string to search for and the appropriate text in the Find
748 * field based on the selection found in the timegraph view.
750 private void initFindStringFromSelection() {
751 FindTarget findTarget
= fFindTarget
;
752 if (findTarget
!= null && okToUse(fFindField
)) {
753 final ITimeGraphEntry selection
= findTarget
.getSelection();
754 if (selection
!= null) {
755 String fullSelection
= selection
.getName();
756 fFindField
.removeModifyListener(fFindModifyListener
);
757 if (fullSelection
.length() > 0) {
758 fFindField
.setText(fullSelection
);
761 if ("".equals(fFindField
.getText())) { //$NON-NLS-1$
762 if (fFindHistory
.size() > 0) {
763 fFindField
.setText(fFindHistory
.get(0));
765 fFindField
.setText(""); //$NON-NLS-1$
769 fFindField
.setSelection(new Point(0, fFindField
.getText().length()));
770 fFindField
.addModifyListener(fFindModifyListener
);
774 // ------- Options ---------------------------------------
777 * Retrieves and returns the option case sensitivity from the appropriate
780 * @return <code>true</code> if case sensitive
782 private boolean isCaseSensitiveSearch() {
783 if (okToUse(fCaseCheckBox
)) {
784 return fCaseCheckBox
.getSelection();
790 * Retrieves and returns the regEx option from the appropriate check box.
792 * @return <code>true</code> if case sensitive
794 private boolean isRegExSearch() {
795 if (okToUse(fIsRegExCheckBox
)) {
796 return fIsRegExCheckBox
.getSelection();
802 * Retrieves and returns the option search direction from the appropriate
805 * @return <code>true</code> if searching forward
807 private boolean isForwardSearch() {
808 if (okToUse(fForwardRadioButton
)) {
809 return fForwardRadioButton
.getSelection();
815 * Retrieves and returns the option search whole words from the appropriate
818 * @return <code>true</code> if searching for whole words
820 private boolean isWholeWordSetting() {
821 if (okToUse(fWholeWordCheckBox
)) {
822 return fWholeWordCheckBox
.getSelection();
824 return fWholeWordInit
;
828 * Returns <code>true</code> if searching should be restricted to entire
829 * words, <code>false</code> if not. This is the case if the respective
830 * checkbox is turned on, regex is off, and the checkbox is enabled, i.e.
831 * the current find string is an entire word.
833 * @return <code>true</code> if the search is restricted to whole words
835 private boolean isWholeWordSearch() {
836 return isWholeWordSetting() && !isRegExSearch() && (okToUse(fWholeWordCheckBox
) ? fWholeWordCheckBox
.isEnabled() : true);
840 * Retrieves and returns the option wrap search from the appropriate check
843 * @return <code>true</code> if wrapping while searching
845 private boolean isWrapSearch() {
846 if (okToUse(fWrapCheckBox
)) {
847 return fWrapCheckBox
.getSelection();
862 * is this button the default button
864 * a button pressed listener
865 * @return the new button
867 private Button
makeButton(Composite parent
, String label
, int id
, boolean dfltButton
, SelectionListener listener
) {
868 Button button
= createButton(parent
, id
, label
, dfltButton
);
869 button
.addSelectionListener(listener
);
870 storeButtonWithMnemonicInMap(button
);
875 * Stores the button and its mnemonic in {@link #fMnemonicButtonMap}.
878 * button whose mnemonic has to be stored
880 private void storeButtonWithMnemonicInMap(Button button
) {
881 char mnemonic
= LegacyActionTools
.extractMnemonic(button
.getText());
882 if (mnemonic
!= LegacyActionTools
.MNEMONIC_NONE
) {
883 fMnemonicButtonMap
.put(new Character(Character
.toLowerCase(mnemonic
)), button
);
888 * Sets the given status message in the status line.
893 private void statusMessage(String message
) {
894 fStatusLabel
.setText(message
);
895 getShell().getDisplay().beep();
899 * Locates the user's findString in the entries information of the time
902 * @param forwardSearch
903 * the search direction
905 private void performSearch(boolean forwardSearch
) {
907 String findString
= getFindString();
908 if (findString
!= null && findString
.length() > 0) {
909 findAndSelect(findString
, getSearchOptions(forwardSearch
));
915 private SearchOptions
getSearchOptions(boolean forwardSearch
) {
916 SearchOptions options
= new SearchOptions();
917 options
.forwardSearch
= forwardSearch
;
918 options
.caseSensitive
= isCaseSensitiveSearch();
919 options
.wrapSearch
= isWrapSearch();
920 options
.wholeWord
= isWholeWordSearch();
921 options
.regExSearch
= isRegExSearch();
925 // ------- UI creation ---------------------------------------
928 * Attaches the given layout specification to the <code>component</code>.
932 * @param horizontalAlignment
933 * horizontal alignment
934 * @param grabExcessHorizontalSpace
935 * grab excess horizontal space
936 * @param verticalAlignment
938 * @param grabExcessVerticalSpace
939 * grab excess vertical space
941 private static void setGridData(Control component
, int horizontalAlignment
, boolean grabExcessHorizontalSpace
, int verticalAlignment
, boolean grabExcessVerticalSpace
) {
943 if (component
instanceof Button
&& (((Button
) component
).getStyle() & SWT
.PUSH
) != 0) {
944 gd
= (GridData
) component
.getLayoutData();
945 gd
.horizontalAlignment
= GridData
.FILL
;
949 component
.setLayoutData(gd
);
950 gd
.horizontalAlignment
= horizontalAlignment
;
951 gd
.grabExcessHorizontalSpace
= grabExcessHorizontalSpace
;
953 gd
.verticalAlignment
= verticalAlignment
;
954 gd
.grabExcessVerticalSpace
= grabExcessVerticalSpace
;
958 * Updates the enabled state of the buttons.
960 private void updateButtonState() {
961 if (okToUse(getShell()) && okToUse(fFindNextButton
)) {
963 boolean enable
= fFindTarget
!= null && (fActiveShell
== fParentShell
|| fActiveShell
== getShell());
964 String str
= getFindString();
965 boolean findString
= str
!= null && str
.length() > 0;
967 fWholeWordCheckBox
.setEnabled(isWord(str
) && !isRegExSearch());
969 fFindNextButton
.setEnabled(enable
&& findString
);
974 * Tests whether each character in the given string is a letter.
977 * the string to check
978 * @return <code>true</code> if the given string is a word
980 private static boolean isWord(String str
) {
981 if (str
== null || str
.length() == 0) {
985 for (int i
= 0; i
< str
.length(); i
++) {
986 if (!Character
.isJavaIdentifierPart(str
.charAt(i
))) {
994 * Updates the given combo with the given content.
997 * combo to be updated
999 * to be put into the combo
1001 private static void updateCombo(Combo combo
, List
<String
> content
) {
1003 for (int i
= 0; i
< content
.size(); i
++) {
1004 combo
.add(content
.get(i
).toString());
1008 // ------- open / reopen ---------------------------------------
1011 * Called after executed find action to update the history.
1013 private void updateFindHistory() {
1014 if (okToUse(fFindField
)) {
1015 fFindField
.removeModifyListener(fFindModifyListener
);
1017 updateHistory(fFindField
, fFindHistory
);
1018 fFindField
.addModifyListener(fFindModifyListener
);
1023 * Updates the combo with the history.
1028 * to be put into the combo
1030 private static void updateHistory(Combo combo
, List
<String
> history
) {
1031 String findString
= combo
.getText();
1032 int index
= history
.indexOf(findString
);
1035 history
.remove(index
);
1037 history
.add(0, findString
);
1038 Point selection
= combo
.getSelection();
1039 updateCombo(combo
, history
);
1040 combo
.setText(findString
);
1041 combo
.setSelection(selection
);
1046 * Sets the parent shell of this dialog to be the given shell.
1049 * the new parent shell
1052 public void setParentShell(Shell shell
) {
1053 if (shell
!= fParentShell
) {
1055 if (fParentShell
!= null) {
1056 fParentShell
.removeShellListener(fActivationListener
);
1059 fParentShell
= shell
;
1060 fParentShell
.addShellListener(fActivationListener
);
1063 fActiveShell
= shell
;
1066 // --------------- configuration handling --------------
1069 * Returns the dialog settings object used to share state of the find
1072 * @return the dialog settings to be used
1074 private IDialogSettings
getDialogSettings() {
1075 IDialogSettings settings
= Activator
.getDefault().getDialogSettings();
1076 fDialogSettings
= settings
.getSection(getClass().getName());
1077 if (fDialogSettings
== null) {
1078 fDialogSettings
= settings
.addNewSection(getClass().getName());
1080 return fDialogSettings
;
1084 * Initializes itself from the dialog settings with the same state as at the
1085 * previous invocation.
1087 private void readConfiguration() {
1088 IDialogSettings s
= getDialogSettings();
1090 fWrapInit
= s
.get(WRAP
) == null || s
.getBoolean(WRAP
);
1091 fCaseInit
= s
.getBoolean(CASE_SENSITIVE
);
1092 fWholeWordInit
= s
.getBoolean(WHOLE_WORD
);
1093 fIsRegExInit
= s
.getBoolean(IS_REGEX_EXPRESSION
);
1095 String
[] findHistory
= s
.getArray(FIND_HISTORY
);
1096 if (findHistory
!= null) {
1097 fFindHistory
.clear();
1098 for (int i
= 0; i
< findHistory
.length
; i
++) {
1099 fFindHistory
.add(findHistory
[i
]);
1105 * Stores its current configuration in the dialog store.
1107 private void writeConfiguration() {
1108 IDialogSettings s
= getDialogSettings();
1110 s
.put(WRAP
, fWrapInit
);
1111 s
.put(CASE_SENSITIVE
, fCaseInit
);
1112 s
.put(WHOLE_WORD
, fWholeWordInit
);
1113 s
.put(CASE_SENSITIVE
, fIsRegExInit
);
1115 String findString
= getFindString();
1116 if (findString
.length() > 0) {
1117 fFindHistory
.add(0, findString
);
1119 writeHistory(fFindHistory
, s
, FIND_HISTORY
);
1123 * Writes the given history into the given dialog store.
1128 * the dialog settings
1129 * @param sectionName
1132 private static void writeHistory(List
<String
> history
, IDialogSettings settings
, String sectionName
) {
1133 int itemCount
= history
.size();
1134 Set
<String
> distinctItems
= new HashSet
<>(itemCount
);
1135 for (int i
= 0; i
< itemCount
; i
++) {
1136 String item
= history
.get(i
);
1137 if (distinctItems
.contains(item
)) {
1138 history
.remove(i
--);
1141 distinctItems
.add(item
);
1145 while (history
.size() > 8) {
1149 String
[] names
= new String
[history
.size()];
1150 history
.toArray(names
);
1151 settings
.put(sectionName
, names
);
1156 * Update the time graph viewer this dialog search in
1159 * The find target. Can be <code>null</code>.
1160 * @param initFindString
1161 * This value should be true if the find text field needs to be
1162 * populated with selected entry in the find viewer, false
1165 public void updateTarget(@Nullable FindTarget findTarget
, boolean initFindString
) {
1166 fFindTarget
= findTarget
;
1167 if (initFindString
) {
1168 initFindStringFromSelection();
1170 updateButtonState();
1174 * Test if the shell to test is the parent shell of the dialog
1178 * @return <code>true</code> if the shell to test is the parent shell,
1179 * <code>false</code> otherwise
1181 public boolean isDialogParentShell(Shell shell
) {
1182 return shell
== getParentShell();
1186 * Adjust the index where to start the search
1188 * @param previousIndex
1189 * The previous index
1191 * The number of items to search into
1192 * @param forwardSearch
1193 * The search direction
1194 * @return The adjusted index
1196 private static int adjustIndex(int previousIndex
, int count
, boolean forwardSearch
) {
1197 int index
= previousIndex
;
1198 if (forwardSearch
) {
1199 index
= index
>= count
|| index
< 0 ?
0 : index
;
1201 index
= index
>= count
|| index
< 0 ? count
- 1 : index
;
1207 * Create a pattern from the string to find and with options provided
1209 * This implementation is drawn from the jface implementation of
1210 * org.eclipse.jface.text.FindReplaceDocumentAdapter
1213 * The string to find
1214 * @param caseSensitive
1215 * Tells if the pattern will activate the case sensitive flag
1217 * Tells if the pattern will activate the whole word flag
1218 * @param regExSearch
1219 * Tells if the pattern will activate the regEx flag
1220 * @return The created pattern
1222 private static @NonNull Pattern
getPattern(String findString
, SearchOptions options
) {
1223 String toFind
= findString
;
1225 int patternFlags
= 0;
1227 if (options
.regExSearch
) {
1228 toFind
= substituteLinebreak(toFind
);
1231 if (!options
.caseSensitive
) {
1232 patternFlags
|= Pattern
.CASE_INSENSITIVE
| Pattern
.UNICODE_CASE
;
1235 if (!options
.regExSearch
) {
1236 toFind
= asRegPattern(toFind
);
1239 if (options
.wholeWord
) {
1240 toFind
= "\\b" + toFind
+ "\\b"; //$NON-NLS-1$ //$NON-NLS-2$
1242 Pattern pattern
= Pattern
.compile(toFind
, patternFlags
);
1247 * Substitutes \R in a regex find pattern with (?>\r\n?|\n)
1249 * This implementation is drawn from the jface implementation of
1250 * org.eclipse.jface.text.FindReplaceDocumentAdapter
1253 * the original find pattern
1254 * @return the transformed find pattern
1256 private static String
substituteLinebreak(String findString
) {
1257 int length
= findString
.length();
1258 StringBuffer buf
= new StringBuffer(length
);
1260 int inCharGroup
= 0;
1262 boolean inQuote
= false;
1263 for (int i
= 0; i
< length
; i
++) {
1264 char ch
= findString
.charAt(i
);
1282 if (!inQuote
&& inCharGroup
== 0) {
1289 if (!inQuote
&& inCharGroup
== 0) {
1295 if (i
+ 1 < length
) {
1296 char ch1
= findString
.charAt(i
+ 1);
1301 buf
.append(ch
).append(ch1
);
1304 } else if (ch1
== 'R') {
1305 if (inCharGroup
> 0 || inBraces
> 0) {
1306 String msg
= "TimeGrahViewer.illegalLinebreak"; //$NON-NLS-1$
1307 throw new PatternSyntaxException(msg
, findString
, i
);
1309 buf
.append("(?>\\r\\n?|\\n)"); //$NON-NLS-1$
1316 buf
.append(ch
).append(ch1
);
1330 return buf
.toString();
1334 * Converts a non-regex string to a pattern that can be used with the regex
1337 * This implementation is drawn from the jface implementation of
1338 * org.eclipse.jface.text.FindReplaceDocumentAdapter
1341 * the non-regex pattern
1342 * @return the string converted to a regex pattern
1344 private static String
asRegPattern(String string
) {
1345 StringBuffer out
= new StringBuffer(string
.length());
1346 boolean quoting
= false;
1348 for (int i
= 0, length
= string
.length(); i
< length
; i
++) {
1349 char ch
= string
.charAt(i
);
1352 out
.append("\\E"); //$NON-NLS-1$
1355 out
.append("\\\\"); //$NON-NLS-1$
1359 out
.append("\\Q"); //$NON-NLS-1$
1365 out
.append("\\E"); //$NON-NLS-1$
1368 return out
.toString();
1371 private class SearchOptions
{
1372 boolean forwardSearch
;
1373 boolean caseSensitive
;
1376 boolean regExSearch
;