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 // set dialog position
227 if (fDialogPositionInit
!= null) {
228 shell
.setBounds(fDialogPositionInit
);
231 shell
.setText(Messages
.TimeGraphFindDialog_FindTitle
);
235 * Creates the options configuration section of the find dialog.
238 * the parent composite
239 * @return the options configuration section
241 private Composite
createConfigPanel(Composite parent
) {
243 Composite panel
= new Composite(parent
, SWT
.NONE
);
244 GridLayout layout
= new GridLayout();
245 panel
.setLayout(layout
);
247 Composite directionGroup
= createDirectionGroup(panel
);
248 setGridData(directionGroup
, SWT
.FILL
, true, SWT
.FILL
, false);
250 Composite optionsGroup
= createOptionsGroup(panel
);
251 setGridData(optionsGroup
, SWT
.FILL
, true, SWT
.FILL
, true);
252 ((GridData
) optionsGroup
.getLayoutData()).horizontalSpan
= 2;
258 protected Control
createContents(Composite parent
) {
260 Composite panel
= new Composite(parent
, SWT
.NULL
);
261 GridLayout layout
= new GridLayout();
262 layout
.numColumns
= 1;
263 layout
.makeColumnsEqualWidth
= true;
264 panel
.setLayout(layout
);
265 panel
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, true, true));
267 Composite inputPanel
= createInputPanel(panel
);
268 setGridData(inputPanel
, SWT
.FILL
, true, SWT
.TOP
, false);
270 Composite configPanel
= createConfigPanel(panel
);
271 setGridData(configPanel
, SWT
.FILL
, true, SWT
.TOP
, true);
273 Composite statusBar
= createStatusAndCloseButton(panel
);
274 setGridData(statusBar
, SWT
.FILL
, true, SWT
.BOTTOM
, false);
276 panel
.addTraverseListener(new TraverseListener() {
278 public void keyTraversed(TraverseEvent e
) {
279 if (e
.detail
== SWT
.TRAVERSE_RETURN
) {
281 Control controlWithFocus
= getShell().getDisplay().getFocusControl();
282 if (controlWithFocus
!= null && (controlWithFocus
.getStyle() & SWT
.PUSH
) == SWT
.PUSH
) {
286 Event event
= new Event();
287 event
.type
= SWT
.Selection
;
288 event
.stateMask
= e
.stateMask
;
289 fFindNextButton
.notifyListeners(SWT
.Selection
, event
);
291 } else if (e
.detail
== SWT
.TRAVERSE_MNEMONIC
) {
292 Character mnemonic
= new Character(Character
.toLowerCase(e
.character
));
293 Button button
= fMnemonicButtonMap
.get(mnemonic
);
294 if (button
!= null) {
295 if ((fFindField
.isFocusControl() || (button
.getStyle() & SWT
.PUSH
) != 0)
296 && button
.isEnabled()) {
297 Event event
= new Event();
298 event
.type
= SWT
.Selection
;
299 event
.stateMask
= e
.stateMask
;
300 if ((button
.getStyle() & SWT
.RADIO
) != 0) {
301 Composite buttonParent
= button
.getParent();
302 if (buttonParent
!= null) {
303 Control
[] children
= buttonParent
.getChildren();
304 for (int i
= 0; i
< children
.length
; i
++) {
305 ((Button
) children
[i
]).setSelection(false);
308 button
.setSelection(true);
310 button
.setSelection(!button
.getSelection());
312 button
.notifyListeners(SWT
.Selection
, event
);
313 e
.detail
= SWT
.TRAVERSE_NONE
;
323 applyDialogFont(panel
);
328 private void setContentAssistsEnablement(boolean enable
) {
329 fContentAssistFindField
.setEnabled(enable
);
333 * Creates the direction defining part of the options defining section of
337 * the parent composite
338 * @return the direction defining part
340 private Composite
createDirectionGroup(Composite parent
) {
342 Composite panel
= new Composite(parent
, SWT
.NONE
);
343 GridLayout layout
= new GridLayout();
344 layout
.marginWidth
= 0;
345 layout
.marginHeight
= 0;
346 panel
.setLayout(layout
);
348 Group group
= new Group(panel
, SWT
.SHADOW_ETCHED_IN
);
349 group
.setText(Messages
.TimeGraphFindDialog_Direction
);
350 GridLayout groupLayout
= new GridLayout();
351 group
.setLayout(groupLayout
);
352 group
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, true, true));
354 fForwardRadioButton
= new Button(group
, SWT
.RADIO
| SWT
.LEFT
);
355 fForwardRadioButton
.setText(Messages
.TimeGraphFindDialog_ForwardRadioButtonLabel
);
356 setGridData(fForwardRadioButton
, SWT
.LEFT
, false, SWT
.CENTER
, false);
357 storeButtonWithMnemonicInMap(fForwardRadioButton
);
359 Button backwardRadioButton
= new Button(group
, SWT
.RADIO
| SWT
.LEFT
);
360 backwardRadioButton
.setText(Messages
.TimeGraphFindDialog_BackwardRadioButtonLabel
);
361 setGridData(backwardRadioButton
, SWT
.LEFT
, false, SWT
.CENTER
, false);
362 storeButtonWithMnemonicInMap(backwardRadioButton
);
364 backwardRadioButton
.setSelection(!fForwardInit
);
365 fForwardRadioButton
.setSelection(fForwardInit
);
371 * Creates the panel where the user specifies the text to search for
374 * the parent composite
375 * @return the input panel
377 private Composite
createInputPanel(Composite parent
) {
378 Composite panel
= new Composite(parent
, SWT
.NULL
);
379 GridLayout layout
= new GridLayout();
380 layout
.numColumns
= 2;
381 panel
.setLayout(layout
);
383 Label findLabel
= new Label(panel
, SWT
.LEFT
);
384 findLabel
.setText(Messages
.TimeGraphFindDialog_FindLabel
);
385 setGridData(findLabel
, SWT
.LEFT
, false, SWT
.CENTER
, false);
387 // Create the find content assist field
388 ComboContentAdapter contentAdapter
= new ComboContentAdapter();
389 FindReplaceDocumentAdapterContentProposalProvider findProposer
= new FindReplaceDocumentAdapterContentProposalProvider(true);
390 fFindField
= new Combo(panel
, SWT
.DROP_DOWN
| SWT
.BORDER
);
391 fContentAssistFindField
= new ContentAssistCommandAdapter(
395 ITextEditorActionDefinitionIds
.CONTENT_ASSIST_PROPOSALS
,
398 setGridData(fFindField
, SWT
.FILL
, true, SWT
.CENTER
, false);
399 fFindField
.addModifyListener(fFindModifyListener
);
405 * Creates the functional options part of the options defining section of
409 * the parent composite
410 * @return the options group
412 private Composite
createOptionsGroup(Composite parent
) {
414 Composite panel
= new Composite(parent
, SWT
.NONE
);
415 GridLayout layout
= new GridLayout();
416 layout
.marginWidth
= 0;
417 layout
.marginHeight
= 0;
418 panel
.setLayout(layout
);
420 Group group
= new Group(panel
, SWT
.SHADOW_NONE
);
421 group
.setText(Messages
.TimeGraphFindDialog_Options
);
422 GridLayout groupLayout
= new GridLayout();
423 groupLayout
.numColumns
= 1;
424 groupLayout
.makeColumnsEqualWidth
= true;
425 group
.setLayout(groupLayout
);
426 group
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, true, true));
428 SelectionListener selectionListener
= new SelectionListener() {
430 public void widgetSelected(SelectionEvent e
) {
435 public void widgetDefaultSelected(SelectionEvent e
) {
439 fCaseCheckBox
= new Button(group
, SWT
.CHECK
| SWT
.LEFT
);
440 fCaseCheckBox
.setText(Messages
.TimeGraphFindDialog_CaseCheckBoxLabel
);
441 setGridData(fCaseCheckBox
, SWT
.LEFT
, false, SWT
.CENTER
, false);
442 fCaseCheckBox
.setSelection(fCaseInit
);
443 fCaseCheckBox
.addSelectionListener(selectionListener
);
444 storeButtonWithMnemonicInMap(fCaseCheckBox
);
446 fWrapCheckBox
= new Button(group
, SWT
.CHECK
| SWT
.LEFT
);
447 fWrapCheckBox
.setText(Messages
.TimeGraphFindDialog_WrapCheckBoxLabel
);
448 setGridData(fWrapCheckBox
, SWT
.LEFT
, false, SWT
.CENTER
, false);
449 fWrapCheckBox
.setSelection(fWrapInit
);
450 fWrapCheckBox
.addSelectionListener(selectionListener
);
451 storeButtonWithMnemonicInMap(fWrapCheckBox
);
453 fWholeWordCheckBox
= new Button(group
, SWT
.CHECK
| SWT
.LEFT
);
454 fWholeWordCheckBox
.setText(Messages
.TimeGraphFindDialog_WholeWordCheckBoxLabel
);
455 setGridData(fWholeWordCheckBox
, SWT
.LEFT
, false, SWT
.CENTER
, false);
456 fWholeWordCheckBox
.setSelection(fWholeWordInit
);
457 fWholeWordCheckBox
.addSelectionListener(selectionListener
);
458 storeButtonWithMnemonicInMap(fWholeWordCheckBox
);
460 fIsRegExCheckBox
= new Button(group
, SWT
.CHECK
| SWT
.LEFT
);
461 fIsRegExCheckBox
.setText(Messages
.TimeGraphFindDialog_REgExCheckBoxLabel
);
462 setGridData(fIsRegExCheckBox
, SWT
.LEFT
, false, SWT
.CENTER
, false);
463 ((GridData
) fIsRegExCheckBox
.getLayoutData()).horizontalSpan
= 2;
464 fIsRegExCheckBox
.setSelection(fIsRegExInit
);
465 fIsRegExCheckBox
.addSelectionListener(new SelectionAdapter() {
467 public void widgetSelected(SelectionEvent e
) {
468 boolean newState
= fIsRegExCheckBox
.getSelection();
471 setContentAssistsEnablement(newState
);
474 storeButtonWithMnemonicInMap(fIsRegExCheckBox
);
475 fWholeWordCheckBox
.setEnabled(!isRegExSearch());
476 fWholeWordCheckBox
.addSelectionListener(new SelectionAdapter() {
478 public void widgetSelected(SelectionEvent e
) {
486 * Creates the status and close section of the dialog.
489 * the parent composite
490 * @return the status and close button
492 private Composite
createStatusAndCloseButton(Composite parent
) {
494 Composite panel
= new Composite(parent
, SWT
.NULL
);
495 GridLayout layout
= new GridLayout();
496 layout
.numColumns
= 2;
497 layout
.marginWidth
= 0;
498 layout
.marginHeight
= 0;
499 panel
.setLayout(layout
);
501 fStatusLabel
= new Label(panel
, SWT
.LEFT
);
502 setGridData(fStatusLabel
, SWT
.FILL
, true, SWT
.CENTER
, false);
504 Composite buttonSection
= new Composite(panel
, SWT
.NULL
);
505 GridLayout buttonLayout
= new GridLayout();
506 buttonLayout
.numColumns
= 2;
507 buttonSection
.setLayout(buttonLayout
);
509 String label
= Messages
.TimeGraphFindDialog_CloseButtonLabel
;
510 Button closeButton
= createButton(buttonSection
, 101, label
, false);
511 setGridData(closeButton
, SWT
.RIGHT
, false, SWT
.BOTTOM
, false);
513 fFindNextButton
= makeButton(buttonSection
, Messages
.TimeGraphFindDialog_FindNextButtonLabel
, 102, true, new SelectionAdapter() {
515 public void widgetSelected(SelectionEvent e
) {
516 performSearch(((e
.stateMask
& SWT
.SHIFT
) != 0) ^
isForwardSearch());
520 setGridData(fFindNextButton
, SWT
.FILL
, true, SWT
.FILL
, false);
526 protected void buttonPressed(int buttonID
) {
527 if (buttonID
== 101) {
533 * Update the dialog data (parentShell, listener, input, ...)
536 * the new find target
538 public void update(@NonNull FindTarget findTarget
) {
539 updateTarget(findTarget
, true);
542 // ------- action invocation ---------------------------------------
545 * Returns the index of the entry that match the specified search string, or
546 * <code>-1</code> if the string can not be found when searching using the
550 * the string to search for
552 * the index at which to start the search
554 * The map of items in the time graph view
556 * The options use for the search
557 * @return the index of the find entry following the options or
558 * <code>-1</code> if nothing found
560 private int findNext(String findString
, int startIndex
, BiMap
<ITimeGraphEntry
, Integer
> items
, SearchOptions options
) {
562 if (options
.forwardSearch
) {
563 index
= startIndex
== items
.size() - 1 ?
-1 : findNext(startIndex
+ 1, findString
, items
, options
);
565 index
= startIndex
== 0 ?
-1 : findNext(startIndex
- 1, findString
, items
, options
);
569 if (okToUse(getShell())) {
570 getShell().getDisplay().beep();
572 if (options
.wrapSearch
) {
573 statusMessage(Messages
.TimeGraphFindDialog_StatusWrappedLabel
);
574 index
= findNext(-1, findString
, items
, options
);
580 private int findNext(int startIndex
, String findString
, BiMap
<ITimeGraphEntry
, Integer
> items
, SearchOptions options
) {
581 if (fFindTarget
!= null) {
582 if (findString
== null || findString
.length() == 0) {
586 final @NonNull Pattern pattern
= getPattern(findString
, options
);
587 int index
= adjustIndex(startIndex
, items
.size(), options
.forwardSearch
);
588 BiMap
<Integer
, ITimeGraphEntry
> entries
= items
.inverse();
589 while (index
>= 0 && index
< entries
.size()) {
590 final @Nullable ITimeGraphEntry entry
= entries
.get(index
);
591 if (entry
!= null && entry
.matches(pattern
)) {
594 index
= options
.forwardSearch ?
++index
: --index
;
601 * Returns whether the specified search string can be found using the given
605 * the string to search for
608 * @return <code>true</code> if the search string can be found using the
612 private boolean findAndSelect(String findString
, SearchOptions options
) {
613 FindTarget findTarget
= fFindTarget
;
614 if (findTarget
== null) {
617 ITimeGraphEntry
[] topInput
= findTarget
.getEntries();
618 BiMap
<@NonNull ITimeGraphEntry
, @NonNull Integer
> items
= HashBiMap
.create();
619 for (ITimeGraphEntry entry
: topInput
) {
620 listEntries(items
, entry
);
622 int startPosition
= findTarget
.getSelection() == null ?
0 : NonNullUtils
.checkNotNull(items
.get(findTarget
.getSelection()));
624 int index
= findNext(findString
, startPosition
, items
, options
);
627 statusMessage(Messages
.TimeGraphFindDialog_StatusNoMatchLabel
);
631 if (options
.forwardSearch
&& index
>= startPosition
|| !options
.forwardSearch
&& index
<= startPosition
) {
632 statusMessage(""); //$NON-NLS-1$
635 // Send the entry found to target
636 findTarget
.selectAndReveal(NonNullUtils
.checkNotNull(items
.inverse().get(index
)));
640 private void listEntries(Map
<ITimeGraphEntry
, Integer
> items
, ITimeGraphEntry root
) {
641 items
.put(root
, items
.size());
642 for (ITimeGraphEntry child
: root
.getChildren()) {
643 listEntries(items
, child
);
647 // ------- accessors ---------------------------------------
650 * Retrieves the string to search for from the appropriate text input field
653 * @return the search string
655 private String
getFindString() {
656 if (okToUse(fFindField
)) {
657 return fFindField
.getText();
659 return ""; //$NON-NLS-1$
663 * Returns the dialog's boundaries.
665 * @return the dialog's boundaries
667 private Rectangle
getDialogBoundaries() {
668 if (okToUse(getShell())) {
669 return getShell().getBounds();
671 return fDialogPositionInit
;
674 // ------- init / close ---------------------------------------
676 public boolean close() {
678 return super.close();
682 * Removes focus changed listener from browser and stores settings for
685 private void handleDialogClose() {
688 if (okToUse(fFindField
)) {
689 fFindField
.removeModifyListener(fFindModifyListener
);
691 updateTarget(null, false);
693 if (fParentShell
!= null) {
694 fParentShell
.removeShellListener(fActivationListener
);
698 if (getShell() != null && !getShell().isDisposed()) {
699 getShell().removeShellListener(fActivationListener
);
702 // store current settings in case of re-open
711 * Writes the current selection to the dialog settings.
713 private void writeSelection() {
714 final FindTarget input
= fFindTarget
;
719 IDialogSettings s
= getDialogSettings();
720 final ITimeGraphEntry selection
= input
.getSelection();
721 if (selection
!= null) {
722 s
.put("selection", selection
.getName()); //$NON-NLS-1$
727 * Stores the current state in the dialog settings.
729 private void storeSettings() {
730 fDialogPositionInit
= getDialogBoundaries();
731 fWrapInit
= isWrapSearch();
732 fWholeWordInit
= isWholeWordSetting();
733 fCaseInit
= isCaseSensitiveSearch();
734 fIsRegExInit
= isRegExSearch();
735 fForwardInit
= isForwardSearch();
737 writeConfiguration();
741 * Initializes the string to search for and the appropriate text in the Find
742 * field based on the selection found in the timegraph view.
744 private void initFindStringFromSelection() {
745 FindTarget findTarget
= fFindTarget
;
746 if (findTarget
!= null && okToUse(fFindField
)) {
747 final ITimeGraphEntry selection
= findTarget
.getSelection();
748 if (selection
!= null) {
749 String fullSelection
= selection
.getName();
750 fFindField
.removeModifyListener(fFindModifyListener
);
751 if (fullSelection
.length() > 0) {
752 fFindField
.setText(fullSelection
);
755 if ("".equals(fFindField
.getText())) { //$NON-NLS-1$
756 if (fFindHistory
.size() > 0) {
757 fFindField
.setText(fFindHistory
.get(0));
759 fFindField
.setText(""); //$NON-NLS-1$
763 fFindField
.setSelection(new Point(0, fFindField
.getText().length()));
764 fFindField
.addModifyListener(fFindModifyListener
);
768 // ------- Options ---------------------------------------
771 * Retrieves and returns the option case sensitivity from the appropriate
774 * @return <code>true</code> if case sensitive
776 private boolean isCaseSensitiveSearch() {
777 if (okToUse(fCaseCheckBox
)) {
778 return fCaseCheckBox
.getSelection();
784 * Retrieves and returns the regEx option from the appropriate check box.
786 * @return <code>true</code> if case sensitive
788 private boolean isRegExSearch() {
789 if (okToUse(fIsRegExCheckBox
)) {
790 return fIsRegExCheckBox
.getSelection();
796 * Retrieves and returns the option search direction from the appropriate
799 * @return <code>true</code> if searching forward
801 private boolean isForwardSearch() {
802 if (okToUse(fForwardRadioButton
)) {
803 return fForwardRadioButton
.getSelection();
809 * Retrieves and returns the option search whole words from the appropriate
812 * @return <code>true</code> if searching for whole words
814 private boolean isWholeWordSetting() {
815 if (okToUse(fWholeWordCheckBox
)) {
816 return fWholeWordCheckBox
.getSelection();
818 return fWholeWordInit
;
822 * Returns <code>true</code> if searching should be restricted to entire
823 * words, <code>false</code> if not. This is the case if the respective
824 * checkbox is turned on, regex is off, and the checkbox is enabled, i.e.
825 * the current find string is an entire word.
827 * @return <code>true</code> if the search is restricted to whole words
829 private boolean isWholeWordSearch() {
830 return isWholeWordSetting() && !isRegExSearch() && (okToUse(fWholeWordCheckBox
) ? fWholeWordCheckBox
.isEnabled() : true);
834 * Retrieves and returns the option wrap search from the appropriate check
837 * @return <code>true</code> if wrapping while searching
839 private boolean isWrapSearch() {
840 if (okToUse(fWrapCheckBox
)) {
841 return fWrapCheckBox
.getSelection();
856 * is this button the default button
858 * a button pressed listener
859 * @return the new button
861 private Button
makeButton(Composite parent
, String label
, int id
, boolean dfltButton
, SelectionListener listener
) {
862 Button button
= createButton(parent
, id
, label
, dfltButton
);
863 button
.addSelectionListener(listener
);
864 storeButtonWithMnemonicInMap(button
);
869 * Stores the button and its mnemonic in {@link #fMnemonicButtonMap}.
872 * button whose mnemonic has to be stored
874 private void storeButtonWithMnemonicInMap(Button button
) {
875 char mnemonic
= LegacyActionTools
.extractMnemonic(button
.getText());
876 if (mnemonic
!= LegacyActionTools
.MNEMONIC_NONE
) {
877 fMnemonicButtonMap
.put(new Character(Character
.toLowerCase(mnemonic
)), button
);
882 * Sets the given status message in the status line.
887 private void statusMessage(String message
) {
888 fStatusLabel
.setText(message
);
889 getShell().getDisplay().beep();
893 * Locates the user's findString in the entries information of the time
896 * @param forwardSearch
897 * the search direction
899 private void performSearch(boolean forwardSearch
) {
901 String findString
= getFindString();
902 if (findString
!= null && findString
.length() > 0) {
903 findAndSelect(findString
, getSearchOptions(forwardSearch
));
909 private SearchOptions
getSearchOptions(boolean forwardSearch
) {
910 SearchOptions options
= new SearchOptions();
911 options
.forwardSearch
= forwardSearch
;
912 options
.caseSensitive
= isCaseSensitiveSearch();
913 options
.wrapSearch
= isWrapSearch();
914 options
.wholeWord
= isWholeWordSearch();
915 options
.regExSearch
= isRegExSearch();
919 // ------- UI creation ---------------------------------------
922 * Attaches the given layout specification to the <code>component</code>.
926 * @param horizontalAlignment
927 * horizontal alignment
928 * @param grabExcessHorizontalSpace
929 * grab excess horizontal space
930 * @param verticalAlignment
932 * @param grabExcessVerticalSpace
933 * grab excess vertical space
935 private static void setGridData(Control component
, int horizontalAlignment
, boolean grabExcessHorizontalSpace
, int verticalAlignment
, boolean grabExcessVerticalSpace
) {
937 if (component
instanceof Button
&& (((Button
) component
).getStyle() & SWT
.PUSH
) != 0) {
938 gd
= (GridData
) component
.getLayoutData();
939 gd
.horizontalAlignment
= GridData
.FILL
;
943 component
.setLayoutData(gd
);
944 gd
.horizontalAlignment
= horizontalAlignment
;
945 gd
.grabExcessHorizontalSpace
= grabExcessHorizontalSpace
;
947 gd
.verticalAlignment
= verticalAlignment
;
948 gd
.grabExcessVerticalSpace
= grabExcessVerticalSpace
;
952 * Updates the enabled state of the buttons.
954 private void updateButtonState() {
955 if (okToUse(getShell()) && okToUse(fFindNextButton
)) {
957 boolean enable
= fFindTarget
!= null && (fActiveShell
== fParentShell
|| fActiveShell
== getShell());
958 String str
= getFindString();
959 boolean findString
= str
!= null && str
.length() > 0;
961 fWholeWordCheckBox
.setEnabled(isWord(str
) && !isRegExSearch());
963 fFindNextButton
.setEnabled(enable
&& findString
);
968 * Tests whether each character in the given string is a letter.
971 * the string to check
972 * @return <code>true</code> if the given string is a word
974 private static boolean isWord(String str
) {
975 if (str
== null || str
.length() == 0) {
979 for (int i
= 0; i
< str
.length(); i
++) {
980 if (!Character
.isJavaIdentifierPart(str
.charAt(i
))) {
988 * Updates the given combo with the given content.
991 * combo to be updated
993 * to be put into the combo
995 private static void updateCombo(Combo combo
, List
<String
> content
) {
997 for (int i
= 0; i
< content
.size(); i
++) {
998 combo
.add(content
.get(i
).toString());
1002 // ------- open / reopen ---------------------------------------
1005 * Called after executed find action to update the history.
1007 private void updateFindHistory() {
1008 if (okToUse(fFindField
)) {
1009 fFindField
.removeModifyListener(fFindModifyListener
);
1011 updateHistory(fFindField
, fFindHistory
);
1012 fFindField
.addModifyListener(fFindModifyListener
);
1017 * Updates the combo with the history.
1022 * to be put into the combo
1024 private static void updateHistory(Combo combo
, List
<String
> history
) {
1025 String findString
= combo
.getText();
1026 int index
= history
.indexOf(findString
);
1029 history
.remove(index
);
1031 history
.add(0, findString
);
1032 Point selection
= combo
.getSelection();
1033 updateCombo(combo
, history
);
1034 combo
.setText(findString
);
1035 combo
.setSelection(selection
);
1040 * Sets the parent shell of this dialog to be the given shell.
1043 * the new parent shell
1046 public void setParentShell(Shell shell
) {
1047 if (shell
!= fParentShell
) {
1049 if (fParentShell
!= null) {
1050 fParentShell
.removeShellListener(fActivationListener
);
1053 fParentShell
= shell
;
1054 fParentShell
.addShellListener(fActivationListener
);
1057 fActiveShell
= shell
;
1060 // --------------- configuration handling --------------
1063 * Returns the dialog settings object used to share state of the find
1066 * @return the dialog settings to be used
1068 private IDialogSettings
getDialogSettings() {
1069 IDialogSettings settings
= Activator
.getDefault().getDialogSettings();
1070 fDialogSettings
= settings
.getSection(getClass().getName());
1071 if (fDialogSettings
== null) {
1072 fDialogSettings
= settings
.addNewSection(getClass().getName());
1074 return fDialogSettings
;
1078 * Initializes itself from the dialog settings with the same state as at the
1079 * previous invocation.
1081 private void readConfiguration() {
1082 IDialogSettings s
= getDialogSettings();
1084 fWrapInit
= s
.get(WRAP
) == null || s
.getBoolean(WRAP
);
1085 fCaseInit
= s
.getBoolean(CASE_SENSITIVE
);
1086 fWholeWordInit
= s
.getBoolean(WHOLE_WORD
);
1087 fIsRegExInit
= s
.getBoolean(IS_REGEX_EXPRESSION
);
1089 String
[] findHistory
= s
.getArray(FIND_HISTORY
);
1090 if (findHistory
!= null) {
1091 fFindHistory
.clear();
1092 for (int i
= 0; i
< findHistory
.length
; i
++) {
1093 fFindHistory
.add(findHistory
[i
]);
1099 * Stores its current configuration in the dialog store.
1101 private void writeConfiguration() {
1102 IDialogSettings s
= getDialogSettings();
1104 s
.put(WRAP
, fWrapInit
);
1105 s
.put(CASE_SENSITIVE
, fCaseInit
);
1106 s
.put(WHOLE_WORD
, fWholeWordInit
);
1107 s
.put(CASE_SENSITIVE
, fIsRegExInit
);
1109 String findString
= getFindString();
1110 if (findString
.length() > 0) {
1111 fFindHistory
.add(0, findString
);
1113 writeHistory(fFindHistory
, s
, FIND_HISTORY
);
1117 * Writes the given history into the given dialog store.
1122 * the dialog settings
1123 * @param sectionName
1126 private static void writeHistory(List
<String
> history
, IDialogSettings settings
, String sectionName
) {
1127 int itemCount
= history
.size();
1128 Set
<String
> distinctItems
= new HashSet
<>(itemCount
);
1129 for (int i
= 0; i
< itemCount
; i
++) {
1130 String item
= history
.get(i
);
1131 if (distinctItems
.contains(item
)) {
1132 history
.remove(i
--);
1135 distinctItems
.add(item
);
1139 while (history
.size() > 8) {
1143 String
[] names
= new String
[history
.size()];
1144 history
.toArray(names
);
1145 settings
.put(sectionName
, names
);
1150 * Update the time graph viewer this dialog search in
1153 * The find target. Can be <code>null</code>.
1154 * @param initFindString
1155 * This value should be true if the find text field needs to be
1156 * populated with selected entry in the find viewer, false
1159 public void updateTarget(@Nullable FindTarget findTarget
, boolean initFindString
) {
1160 fFindTarget
= findTarget
;
1161 if (initFindString
) {
1162 initFindStringFromSelection();
1164 updateButtonState();
1168 * Test if the shell to test is the parent shell of the dialog
1172 * @return <code>true</code> if the shell to test is the parent shell,
1173 * <code>false</code> otherwise
1175 public boolean isDialogParentShell(Shell shell
) {
1176 return shell
== getParentShell();
1180 * Adjust the index where to start the search
1182 * @param previousIndex
1183 * The previous index
1185 * The number of items to search into
1186 * @param forwardSearch
1187 * The search direction
1188 * @return The adjusted index
1190 private static int adjustIndex(int previousIndex
, int count
, boolean forwardSearch
) {
1191 int index
= previousIndex
;
1192 if (forwardSearch
) {
1193 index
= index
>= count
|| index
< 0 ?
0 : index
;
1195 index
= index
>= count
|| index
< 0 ? count
- 1 : index
;
1201 * Create a pattern from the string to find and with options provided
1203 * This implementation is drawn from the jface implementation of
1204 * org.eclipse.jface.text.FindReplaceDocumentAdapter
1207 * The string to find
1208 * @param caseSensitive
1209 * Tells if the pattern will activate the case sensitive flag
1211 * Tells if the pattern will activate the whole word flag
1212 * @param regExSearch
1213 * Tells if the pattern will activate the regEx flag
1214 * @return The created pattern
1216 private static @NonNull Pattern
getPattern(String findString
, SearchOptions options
) {
1217 String toFind
= findString
;
1219 int patternFlags
= 0;
1221 if (options
.regExSearch
) {
1222 toFind
= substituteLinebreak(toFind
);
1225 if (!options
.caseSensitive
) {
1226 patternFlags
|= Pattern
.CASE_INSENSITIVE
| Pattern
.UNICODE_CASE
;
1229 if (!options
.regExSearch
) {
1230 toFind
= asRegPattern(toFind
);
1233 if (options
.wholeWord
) {
1234 toFind
= "\\b" + toFind
+ "\\b"; //$NON-NLS-1$ //$NON-NLS-2$
1236 Pattern pattern
= Pattern
.compile(toFind
, patternFlags
);
1241 * Substitutes \R in a regex find pattern with (?>\r\n?|\n)
1243 * This implementation is drawn from the jface implementation of
1244 * org.eclipse.jface.text.FindReplaceDocumentAdapter
1247 * the original find pattern
1248 * @return the transformed find pattern
1250 private static String
substituteLinebreak(String findString
) {
1251 int length
= findString
.length();
1252 StringBuffer buf
= new StringBuffer(length
);
1254 int inCharGroup
= 0;
1256 boolean inQuote
= false;
1257 for (int i
= 0; i
< length
; i
++) {
1258 char ch
= findString
.charAt(i
);
1276 if (!inQuote
&& inCharGroup
== 0) {
1283 if (!inQuote
&& inCharGroup
== 0) {
1289 if (i
+ 1 < length
) {
1290 char ch1
= findString
.charAt(i
+ 1);
1295 buf
.append(ch
).append(ch1
);
1298 } else if (ch1
== 'R') {
1299 if (inCharGroup
> 0 || inBraces
> 0) {
1300 String msg
= "TimeGrahViewer.illegalLinebreak"; //$NON-NLS-1$
1301 throw new PatternSyntaxException(msg
, findString
, i
);
1303 buf
.append("(?>\\r\\n?|\\n)"); //$NON-NLS-1$
1310 buf
.append(ch
).append(ch1
);
1324 return buf
.toString();
1328 * Converts a non-regex string to a pattern that can be used with the regex
1331 * This implementation is drawn from the jface implementation of
1332 * org.eclipse.jface.text.FindReplaceDocumentAdapter
1335 * the non-regex pattern
1336 * @return the string converted to a regex pattern
1338 private static String
asRegPattern(String string
) {
1339 StringBuffer out
= new StringBuffer(string
.length());
1340 boolean quoting
= false;
1342 for (int i
= 0, length
= string
.length(); i
< length
; i
++) {
1343 char ch
= string
.charAt(i
);
1346 out
.append("\\E"); //$NON-NLS-1$
1349 out
.append("\\\\"); //$NON-NLS-1$
1353 out
.append("\\Q"); //$NON-NLS-1$
1359 out
.append("\\E"); //$NON-NLS-1$
1362 return out
.toString();
1365 private class SearchOptions
{
1366 boolean forwardSearch
;
1367 boolean caseSensitive
;
1370 boolean regExSearch
;