tmf: Update setting of minimum size of dialog box
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / views / timegraph / TimeGraphFindDialog.java
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;
9
10 import java.util.ArrayList;
11 import java.util.HashMap;
12 import java.util.HashSet;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.Set;
16 import java.util.regex.Pattern;
17 import java.util.regex.PatternSyntaxException;
18
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;
56
57 import com.google.common.collect.BiMap;
58 import com.google.common.collect.HashBiMap;
59
60 /**
61 * Find dialog to search entries into a time graph. This implementation is based
62 * on the org.eclipse.ui.texteditor.FindReplaceDialog of Eclipse.
63 *
64 * @author Jean-Christian Kouame
65 */
66 class TimeGraphFindDialog extends Dialog {
67
68 /**
69 * Updates the find dialog on activation changes.
70 */
71 class ActivationListener extends ShellAdapter {
72 /*
73 * @see ShellListener#shellActivated(ShellEvent)
74 */
75 @Override
76 public void shellActivated(ShellEvent e) {
77 fActiveShell = (Shell) e.widget;
78 updateButtonState();
79
80 if (fGiveFocusToFindField && getShell() == fActiveShell && okToUse(fFindField)) {
81 fFindField.setFocus();
82 }
83
84 }
85
86 /*
87 * @see ShellListener#shellDeactivated(ShellEvent)
88 */
89 @Override
90 public void shellDeactivated(ShellEvent e) {
91 fGiveFocusToFindField = false;
92
93 storeSettings();
94
95 fActiveShell = null;
96 updateButtonState();
97 }
98 }
99
100 /**
101 * Modify listener to update the search result in case of incremental
102 * search.
103 */
104 private class FindModifyListener implements ModifyListener {
105
106 /*
107 * @see ModifyListener#modifyText(ModifyEvent)
108 */
109 @Override
110 public void modifyText(ModifyEvent e) {
111 updateButtonState();
112 }
113 }
114
115 /** The size of the dialogs search history. */
116 private static final int HISTORY_SIZE = 5;
117
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$
123
124 private boolean fWrapInit;
125 private boolean fCaseInit;
126 private boolean fWholeWordInit;
127 private boolean fForwardInit;
128 private boolean fIsRegExInit;
129
130 private @NonNull List<String> fFindHistory;
131
132 private static Shell fParentShell;
133 private Shell fActiveShell;
134
135 private final ActivationListener fActivationListener = new ActivationListener();
136 private final FindModifyListener fFindModifyListener = new FindModifyListener();
137
138 private Label fStatusLabel;
139 private Button fForwardRadioButton;
140 private Button fCaseCheckBox;
141 private Button fWrapCheckBox;
142 private Button fWholeWordCheckBox;
143 private Button fIsRegExCheckBox;
144
145 private Button fFindNextButton;
146 private Combo fFindField;
147
148 /**
149 * Find command adapters.
150 */
151 private ContentAssistCommandAdapter fContentAssistFindField;
152
153 private Rectangle fDialogPositionInit;
154
155 private IDialogSettings fDialogSettings;
156 /**
157 * <code>true</code> if the find field should receive focus the next time
158 * the dialog is activated, <code>false</code> otherwise.
159 */
160 private boolean fGiveFocusToFindField = true;
161
162 /**
163 * Holds the mnemonic/button pairs for all buttons.
164 */
165 private HashMap<Character, Button> fMnemonicButtonMap = new HashMap<>();
166
167 private @Nullable FindTarget fFindTarget;
168
169 /**
170 * Creates a new dialog with the given shell as parent.
171 *
172 * @param parentShell
173 * the parent shell
174 */
175 public TimeGraphFindDialog(Shell parentShell) {
176 super(parentShell);
177
178 fParentShell = null;
179 fFindTarget = null;
180
181 fDialogPositionInit = null;
182 fFindHistory = new ArrayList<>(HISTORY_SIZE - 1);
183
184 fWrapInit = true;
185 fCaseInit = false;
186 fIsRegExInit = false;
187 fWholeWordInit = false;
188 fForwardInit = true;
189
190 readConfiguration();
191 setShellStyle(getShellStyle() & ~SWT.APPLICATION_MODAL);
192 setBlockOnOpen(false);
193 }
194
195 @Override
196 protected boolean isResizable() {
197 return true;
198 }
199
200 /**
201 * Returns <code>true</code> if control can be used.
202 *
203 * @param control
204 * the control to be checked
205 * @return <code>true</code> if control can be used
206 */
207 private static boolean okToUse(Control control) {
208 return control != null && !control.isDisposed();
209 }
210
211 @Override
212 public void create() {
213 super.create();
214
215 Shell shell = getShell();
216 shell.addShellListener(fActivationListener);
217
218 // fill in combo contents
219 fFindField.removeModifyListener(fFindModifyListener);
220 updateCombo(fFindField, fFindHistory);
221 fFindField.addModifyListener(fFindModifyListener);
222
223 // get find string
224 initFindStringFromSelection();
225
226 shell.setMinimumSize(shell.getSize());
227
228 // set dialog position
229 if (fDialogPositionInit != null) {
230 shell.setBounds(fDialogPositionInit);
231 }
232
233 shell.setText(Messages.TimeGraphFindDialog_FindTitle);
234 }
235
236 /**
237 * Creates the options configuration section of the find dialog.
238 *
239 * @param parent
240 * the parent composite
241 * @return the options configuration section
242 */
243 private Composite createConfigPanel(Composite parent) {
244
245 Composite panel = new Composite(parent, SWT.NONE);
246 GridLayout layout = new GridLayout();
247 panel.setLayout(layout);
248
249 Composite directionGroup = createDirectionGroup(panel);
250 setGridData(directionGroup, SWT.FILL, true, SWT.FILL, false);
251
252 Composite optionsGroup = createOptionsGroup(panel);
253 setGridData(optionsGroup, SWT.FILL, true, SWT.FILL, true);
254 ((GridData) optionsGroup.getLayoutData()).horizontalSpan = 2;
255
256 return panel;
257 }
258
259 @Override
260 protected Control createContents(Composite parent) {
261
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));
268
269 Composite inputPanel = createInputPanel(panel);
270 setGridData(inputPanel, SWT.FILL, true, SWT.TOP, false);
271
272 Composite configPanel = createConfigPanel(panel);
273 setGridData(configPanel, SWT.FILL, true, SWT.TOP, true);
274
275 Composite statusBar = createStatusAndCloseButton(panel);
276 setGridData(statusBar, SWT.FILL, true, SWT.BOTTOM, false);
277
278 panel.addTraverseListener(new TraverseListener() {
279 @Override
280 public void keyTraversed(TraverseEvent e) {
281 if (e.detail == SWT.TRAVERSE_RETURN) {
282 if (!Util.isMac()) {
283 Control controlWithFocus = getShell().getDisplay().getFocusControl();
284 if (controlWithFocus != null && (controlWithFocus.getStyle() & SWT.PUSH) == SWT.PUSH) {
285 return;
286 }
287 }
288 Event event = new Event();
289 event.type = SWT.Selection;
290 event.stateMask = e.stateMask;
291 fFindNextButton.notifyListeners(SWT.Selection, event);
292 e.doit = false;
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);
308 }
309 }
310 button.setSelection(true);
311 } else {
312 button.setSelection(!button.getSelection());
313 }
314 button.notifyListeners(SWT.Selection, event);
315 e.detail = SWT.TRAVERSE_NONE;
316 e.doit = true;
317 }
318 }
319 }
320 }
321 });
322
323 updateButtonState();
324
325 applyDialogFont(panel);
326
327 return panel;
328 }
329
330 private void setContentAssistsEnablement(boolean enable) {
331 fContentAssistFindField.setEnabled(enable);
332 }
333
334 /**
335 * Creates the direction defining part of the options defining section of
336 * the find dialog.
337 *
338 * @param parent
339 * the parent composite
340 * @return the direction defining part
341 */
342 private Composite createDirectionGroup(Composite parent) {
343
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);
349
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));
355
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);
360
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);
365
366 backwardRadioButton.setSelection(!fForwardInit);
367 fForwardRadioButton.setSelection(fForwardInit);
368
369 return panel;
370 }
371
372 /**
373 * Creates the panel where the user specifies the text to search for
374 *
375 * @param parent
376 * the parent composite
377 * @return the input panel
378 */
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);
384
385 Label findLabel = new Label(panel, SWT.LEFT);
386 findLabel.setText(Messages.TimeGraphFindDialog_FindLabel);
387 setGridData(findLabel, SWT.LEFT, false, SWT.CENTER, false);
388
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(
394 fFindField,
395 contentAdapter,
396 findProposer,
397 ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS,
398 new char[0],
399 true);
400 setGridData(fFindField, SWT.FILL, true, SWT.CENTER, false);
401 fFindField.addModifyListener(fFindModifyListener);
402
403 return panel;
404 }
405
406 /**
407 * Creates the functional options part of the options defining section of
408 * the find dialog.
409 *
410 * @param parent
411 * the parent composite
412 * @return the options group
413 */
414 private Composite createOptionsGroup(Composite parent) {
415
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);
421
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));
429
430 SelectionListener selectionListener = new SelectionListener() {
431 @Override
432 public void widgetSelected(SelectionEvent e) {
433 storeSettings();
434 }
435
436 @Override
437 public void widgetDefaultSelected(SelectionEvent e) {
438 }
439 };
440
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);
447
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);
454
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);
461
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() {
468 @Override
469 public void widgetSelected(SelectionEvent e) {
470 boolean newState = fIsRegExCheckBox.getSelection();
471 updateButtonState();
472 storeSettings();
473 setContentAssistsEnablement(newState);
474 }
475 });
476 storeButtonWithMnemonicInMap(fIsRegExCheckBox);
477 fWholeWordCheckBox.setEnabled(!isRegExSearch());
478 fWholeWordCheckBox.addSelectionListener(new SelectionAdapter() {
479 @Override
480 public void widgetSelected(SelectionEvent e) {
481 updateButtonState();
482 }
483 });
484 return panel;
485 }
486
487 /**
488 * Creates the status and close section of the dialog.
489 *
490 * @param parent
491 * the parent composite
492 * @return the status and close button
493 */
494 private Composite createStatusAndCloseButton(Composite parent) {
495
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);
502
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$
509
510 Composite buttonSection = new Composite(panel, SWT.NULL);
511 GridLayout buttonLayout = new GridLayout();
512 buttonLayout.numColumns = 2;
513 buttonSection.setLayout(buttonLayout);
514
515 String label = Messages.TimeGraphFindDialog_CloseButtonLabel;
516 Button closeButton = createButton(buttonSection, 101, label, false);
517 setGridData(closeButton, SWT.RIGHT, false, SWT.BOTTOM, false);
518
519 fFindNextButton = makeButton(buttonSection, Messages.TimeGraphFindDialog_FindNextButtonLabel, 102, true, new SelectionAdapter() {
520 @Override
521 public void widgetSelected(SelectionEvent e) {
522 performSearch(((e.stateMask & SWT.SHIFT) != 0) ^ isForwardSearch());
523 updateFindHistory();
524 }
525 });
526 setGridData(fFindNextButton, SWT.FILL, true, SWT.FILL, false);
527
528 return panel;
529 }
530
531 @Override
532 protected void buttonPressed(int buttonID) {
533 if (buttonID == 101) {
534 close();
535 }
536 }
537
538 /**
539 * Update the dialog data (parentShell, listener, input, ...)
540 *
541 * @param findTarget
542 * the new find target
543 */
544 public void update(@NonNull FindTarget findTarget) {
545 updateTarget(findTarget, true);
546 }
547
548 // ------- action invocation ---------------------------------------
549
550 /**
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
553 * given options.
554 *
555 * @param findString
556 * the string to search for
557 * @param startIndex
558 * the index at which to start the search
559 * @param items
560 * The map of items in the time graph view
561 * @param options
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
565 */
566 private int findNext(String findString, int startIndex, BiMap<ITimeGraphEntry, Integer> items, SearchOptions options) {
567 int index;
568 if (options.forwardSearch) {
569 index = startIndex == items.size() - 1 ? -1 : findNext(startIndex + 1, findString, items, options);
570 } else {
571 index = startIndex == 0 ? -1 : findNext(startIndex - 1, findString, items, options);
572 }
573
574 if (index == -1) {
575 if (okToUse(getShell())) {
576 getShell().getDisplay().beep();
577 }
578 if (options.wrapSearch) {
579 statusMessage(Messages.TimeGraphFindDialog_StatusWrappedLabel);
580 index = findNext(-1, findString, items, options);
581 }
582 }
583 return index;
584 }
585
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) {
589 return -1;
590 }
591
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)) {
598 return index;
599 }
600 index = options.forwardSearch ? ++index : --index;
601 }
602 }
603 return -1;
604 }
605
606 /**
607 * Returns whether the specified search string can be found using the given
608 * options.
609 *
610 * @param findString
611 * the string to search for
612 * @param options
613 * The search options
614 * @return <code>true</code> if the search string can be found using the
615 * given options
616 *
617 */
618 private boolean findAndSelect(String findString, SearchOptions options) {
619 FindTarget findTarget = fFindTarget;
620 if (findTarget == null) {
621 return false;
622 }
623 ITimeGraphEntry[] topInput = findTarget.getEntries();
624 BiMap<@NonNull ITimeGraphEntry, @NonNull Integer> items = HashBiMap.create();
625 for (ITimeGraphEntry entry : topInput) {
626 listEntries(items, entry);
627 }
628 int startPosition = findTarget.getSelection() == null ? 0 : NonNullUtils.checkNotNull(items.get(findTarget.getSelection()));
629
630 int index = findNext(findString, startPosition, items, options);
631
632 if (index == -1) {
633 statusMessage(Messages.TimeGraphFindDialog_StatusNoMatchLabel);
634 return false;
635 }
636
637 if (options.forwardSearch && index >= startPosition || !options.forwardSearch && index <= startPosition) {
638 statusMessage(""); //$NON-NLS-1$
639 }
640
641 // Send the entry found to target
642 findTarget.selectAndReveal(NonNullUtils.checkNotNull(items.inverse().get(index)));
643 return true;
644 }
645
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);
650 }
651 }
652
653 // ------- accessors ---------------------------------------
654
655 /**
656 * Retrieves the string to search for from the appropriate text input field
657 * and returns it.
658 *
659 * @return the search string
660 */
661 private String getFindString() {
662 if (okToUse(fFindField)) {
663 return fFindField.getText();
664 }
665 return ""; //$NON-NLS-1$
666 }
667
668 /**
669 * Returns the dialog's boundaries.
670 *
671 * @return the dialog's boundaries
672 */
673 private Rectangle getDialogBoundaries() {
674 if (okToUse(getShell())) {
675 return getShell().getBounds();
676 }
677 return fDialogPositionInit;
678 }
679
680 // ------- init / close ---------------------------------------
681 @Override
682 public boolean close() {
683 handleDialogClose();
684 return super.close();
685 }
686
687 /**
688 * Removes focus changed listener from browser and stores settings for
689 * re-open.
690 */
691 private void handleDialogClose() {
692
693 // remove listeners
694 if (okToUse(fFindField)) {
695 fFindField.removeModifyListener(fFindModifyListener);
696 }
697 updateTarget(null, false);
698
699 if (fParentShell != null) {
700 fParentShell.removeShellListener(fActivationListener);
701 fParentShell = null;
702 }
703
704 if (getShell() != null && !getShell().isDisposed()) {
705 getShell().removeShellListener(fActivationListener);
706 }
707
708 // store current settings in case of re-open
709 storeSettings();
710
711 // prevent leaks
712 fActiveShell = null;
713
714 }
715
716 /**
717 * Writes the current selection to the dialog settings.
718 */
719 private void writeSelection() {
720 final FindTarget input = fFindTarget;
721 if (input == null) {
722 return;
723 }
724
725 IDialogSettings s = getDialogSettings();
726 final ITimeGraphEntry selection = input.getSelection();
727 if (selection != null) {
728 s.put("selection", selection.getName()); //$NON-NLS-1$
729 }
730 }
731
732 /**
733 * Stores the current state in the dialog settings.
734 */
735 private void storeSettings() {
736 fDialogPositionInit = getDialogBoundaries();
737 fWrapInit = isWrapSearch();
738 fWholeWordInit = isWholeWordSetting();
739 fCaseInit = isCaseSensitiveSearch();
740 fIsRegExInit = isRegExSearch();
741 fForwardInit = isForwardSearch();
742
743 writeConfiguration();
744 }
745
746 /**
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.
749 */
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);
759 }
760 } else {
761 if ("".equals(fFindField.getText())) { //$NON-NLS-1$
762 if (fFindHistory.size() > 0) {
763 fFindField.setText(fFindHistory.get(0));
764 } else {
765 fFindField.setText(""); //$NON-NLS-1$
766 }
767 }
768 }
769 fFindField.setSelection(new Point(0, fFindField.getText().length()));
770 fFindField.addModifyListener(fFindModifyListener);
771 }
772 }
773
774 // ------- Options ---------------------------------------
775
776 /**
777 * Retrieves and returns the option case sensitivity from the appropriate
778 * check box.
779 *
780 * @return <code>true</code> if case sensitive
781 */
782 private boolean isCaseSensitiveSearch() {
783 if (okToUse(fCaseCheckBox)) {
784 return fCaseCheckBox.getSelection();
785 }
786 return fCaseInit;
787 }
788
789 /**
790 * Retrieves and returns the regEx option from the appropriate check box.
791 *
792 * @return <code>true</code> if case sensitive
793 */
794 private boolean isRegExSearch() {
795 if (okToUse(fIsRegExCheckBox)) {
796 return fIsRegExCheckBox.getSelection();
797 }
798 return fIsRegExInit;
799 }
800
801 /**
802 * Retrieves and returns the option search direction from the appropriate
803 * check box.
804 *
805 * @return <code>true</code> if searching forward
806 */
807 private boolean isForwardSearch() {
808 if (okToUse(fForwardRadioButton)) {
809 return fForwardRadioButton.getSelection();
810 }
811 return fForwardInit;
812 }
813
814 /**
815 * Retrieves and returns the option search whole words from the appropriate
816 * check box.
817 *
818 * @return <code>true</code> if searching for whole words
819 */
820 private boolean isWholeWordSetting() {
821 if (okToUse(fWholeWordCheckBox)) {
822 return fWholeWordCheckBox.getSelection();
823 }
824 return fWholeWordInit;
825 }
826
827 /**
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.
832 *
833 * @return <code>true</code> if the search is restricted to whole words
834 */
835 private boolean isWholeWordSearch() {
836 return isWholeWordSetting() && !isRegExSearch() && (okToUse(fWholeWordCheckBox) ? fWholeWordCheckBox.isEnabled() : true);
837 }
838
839 /**
840 * Retrieves and returns the option wrap search from the appropriate check
841 * box.
842 *
843 * @return <code>true</code> if wrapping while searching
844 */
845 private boolean isWrapSearch() {
846 if (okToUse(fWrapCheckBox)) {
847 return fWrapCheckBox.getSelection();
848 }
849 return fWrapInit;
850 }
851
852 /**
853 * Creates a button.
854 *
855 * @param parent
856 * the parent control
857 * @param label
858 * the button label
859 * @param id
860 * the button id
861 * @param dfltButton
862 * is this button the default button
863 * @param listener
864 * a button pressed listener
865 * @return the new button
866 */
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);
871 return button;
872 }
873
874 /**
875 * Stores the button and its mnemonic in {@link #fMnemonicButtonMap}.
876 *
877 * @param button
878 * button whose mnemonic has to be stored
879 */
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);
884 }
885 }
886
887 /**
888 * Sets the given status message in the status line.
889 *
890 * @param message
891 * the message
892 */
893 private void statusMessage(String message) {
894 fStatusLabel.setText(message);
895 getShell().getDisplay().beep();
896 }
897
898 /**
899 * Locates the user's findString in the entries information of the time
900 * graph view.
901 *
902 * @param forwardSearch
903 * the search direction
904 */
905 private void performSearch(boolean forwardSearch) {
906
907 String findString = getFindString();
908 if (findString != null && findString.length() > 0) {
909 findAndSelect(findString, getSearchOptions(forwardSearch));
910 }
911 writeSelection();
912 updateButtonState();
913 }
914
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();
922 return options;
923 }
924
925 // ------- UI creation ---------------------------------------
926
927 /**
928 * Attaches the given layout specification to the <code>component</code>.
929 *
930 * @param component
931 * the component
932 * @param horizontalAlignment
933 * horizontal alignment
934 * @param grabExcessHorizontalSpace
935 * grab excess horizontal space
936 * @param verticalAlignment
937 * vertical alignment
938 * @param grabExcessVerticalSpace
939 * grab excess vertical space
940 */
941 private static void setGridData(Control component, int horizontalAlignment, boolean grabExcessHorizontalSpace, int verticalAlignment, boolean grabExcessVerticalSpace) {
942 GridData gd;
943 if (component instanceof Button && (((Button) component).getStyle() & SWT.PUSH) != 0) {
944 gd = (GridData) component.getLayoutData();
945 gd.horizontalAlignment = GridData.FILL;
946 gd.widthHint = 100;
947 } else {
948 gd = new GridData();
949 component.setLayoutData(gd);
950 gd.horizontalAlignment = horizontalAlignment;
951 gd.grabExcessHorizontalSpace = grabExcessHorizontalSpace;
952 }
953 gd.verticalAlignment = verticalAlignment;
954 gd.grabExcessVerticalSpace = grabExcessVerticalSpace;
955 }
956
957 /**
958 * Updates the enabled state of the buttons.
959 */
960 private void updateButtonState() {
961 if (okToUse(getShell()) && okToUse(fFindNextButton)) {
962
963 boolean enable = fFindTarget != null && (fActiveShell == fParentShell || fActiveShell == getShell());
964 String str = getFindString();
965 boolean findString = str != null && str.length() > 0;
966
967 fWholeWordCheckBox.setEnabled(isWord(str) && !isRegExSearch());
968
969 fFindNextButton.setEnabled(enable && findString);
970 }
971 }
972
973 /**
974 * Tests whether each character in the given string is a letter.
975 *
976 * @param str
977 * the string to check
978 * @return <code>true</code> if the given string is a word
979 */
980 private static boolean isWord(String str) {
981 if (str == null || str.length() == 0) {
982 return false;
983 }
984
985 for (int i = 0; i < str.length(); i++) {
986 if (!Character.isJavaIdentifierPart(str.charAt(i))) {
987 return false;
988 }
989 }
990 return true;
991 }
992
993 /**
994 * Updates the given combo with the given content.
995 *
996 * @param combo
997 * combo to be updated
998 * @param content
999 * to be put into the combo
1000 */
1001 private static void updateCombo(Combo combo, List<String> content) {
1002 combo.removeAll();
1003 for (int i = 0; i < content.size(); i++) {
1004 combo.add(content.get(i).toString());
1005 }
1006 }
1007
1008 // ------- open / reopen ---------------------------------------
1009
1010 /**
1011 * Called after executed find action to update the history.
1012 */
1013 private void updateFindHistory() {
1014 if (okToUse(fFindField)) {
1015 fFindField.removeModifyListener(fFindModifyListener);
1016
1017 updateHistory(fFindField, fFindHistory);
1018 fFindField.addModifyListener(fFindModifyListener);
1019 }
1020 }
1021
1022 /**
1023 * Updates the combo with the history.
1024 *
1025 * @param combo
1026 * to be updated
1027 * @param history
1028 * to be put into the combo
1029 */
1030 private static void updateHistory(Combo combo, List<String> history) {
1031 String findString = combo.getText();
1032 int index = history.indexOf(findString);
1033 if (index != 0) {
1034 if (index != -1) {
1035 history.remove(index);
1036 }
1037 history.add(0, findString);
1038 Point selection = combo.getSelection();
1039 updateCombo(combo, history);
1040 combo.setText(findString);
1041 combo.setSelection(selection);
1042 }
1043 }
1044
1045 /**
1046 * Sets the parent shell of this dialog to be the given shell.
1047 *
1048 * @param shell
1049 * the new parent shell
1050 */
1051 @Override
1052 public void setParentShell(Shell shell) {
1053 if (shell != fParentShell) {
1054
1055 if (fParentShell != null) {
1056 fParentShell.removeShellListener(fActivationListener);
1057 }
1058
1059 fParentShell = shell;
1060 fParentShell.addShellListener(fActivationListener);
1061 }
1062
1063 fActiveShell = shell;
1064 }
1065
1066 // --------------- configuration handling --------------
1067
1068 /**
1069 * Returns the dialog settings object used to share state of the find
1070 * dialog.
1071 *
1072 * @return the dialog settings to be used
1073 */
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());
1079 }
1080 return fDialogSettings;
1081 }
1082
1083 /**
1084 * Initializes itself from the dialog settings with the same state as at the
1085 * previous invocation.
1086 */
1087 private void readConfiguration() {
1088 IDialogSettings s = getDialogSettings();
1089
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);
1094
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]);
1100 }
1101 }
1102 }
1103
1104 /**
1105 * Stores its current configuration in the dialog store.
1106 */
1107 private void writeConfiguration() {
1108 IDialogSettings s = getDialogSettings();
1109
1110 s.put(WRAP, fWrapInit);
1111 s.put(CASE_SENSITIVE, fCaseInit);
1112 s.put(WHOLE_WORD, fWholeWordInit);
1113 s.put(CASE_SENSITIVE, fIsRegExInit);
1114
1115 String findString = getFindString();
1116 if (findString.length() > 0) {
1117 fFindHistory.add(0, findString);
1118 }
1119 writeHistory(fFindHistory, s, FIND_HISTORY);
1120 }
1121
1122 /**
1123 * Writes the given history into the given dialog store.
1124 *
1125 * @param history
1126 * the history
1127 * @param settings
1128 * the dialog settings
1129 * @param sectionName
1130 * the section name
1131 */
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--);
1139 itemCount--;
1140 } else {
1141 distinctItems.add(item);
1142 }
1143 }
1144
1145 while (history.size() > 8) {
1146 history.remove(8);
1147 }
1148
1149 String[] names = new String[history.size()];
1150 history.toArray(names);
1151 settings.put(sectionName, names);
1152
1153 }
1154
1155 /**
1156 * Update the time graph viewer this dialog search in
1157 *
1158 * @param findTarget
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
1163 * otherwise
1164 */
1165 public void updateTarget(@Nullable FindTarget findTarget, boolean initFindString) {
1166 fFindTarget = findTarget;
1167 if (initFindString) {
1168 initFindStringFromSelection();
1169 }
1170 updateButtonState();
1171 }
1172
1173 /**
1174 * Test if the shell to test is the parent shell of the dialog
1175 *
1176 * @param shell
1177 * The shell to test
1178 * @return <code>true</code> if the shell to test is the parent shell,
1179 * <code>false</code> otherwise
1180 */
1181 public boolean isDialogParentShell(Shell shell) {
1182 return shell == getParentShell();
1183 }
1184
1185 /**
1186 * Adjust the index where to start the search
1187 *
1188 * @param previousIndex
1189 * The previous index
1190 * @param count
1191 * The number of items to search into
1192 * @param forwardSearch
1193 * The search direction
1194 * @return The adjusted index
1195 */
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;
1200 } else {
1201 index = index >= count || index < 0 ? count - 1 : index;
1202 }
1203 return index;
1204 }
1205
1206 /**
1207 * Create a pattern from the string to find and with options provided
1208 *
1209 * This implementation is drawn from the jface implementation of
1210 * org.eclipse.jface.text.FindReplaceDocumentAdapter
1211 *
1212 * @param findString
1213 * The string to find
1214 * @param caseSensitive
1215 * Tells if the pattern will activate the case sensitive flag
1216 * @param wholeWord
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
1221 */
1222 private static @NonNull Pattern getPattern(String findString, SearchOptions options) {
1223 String toFind = findString;
1224
1225 int patternFlags = 0;
1226
1227 if (options.regExSearch) {
1228 toFind = substituteLinebreak(toFind);
1229 }
1230
1231 if (!options.caseSensitive) {
1232 patternFlags |= Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE;
1233 }
1234
1235 if (!options.regExSearch) {
1236 toFind = asRegPattern(toFind);
1237 }
1238
1239 if (options.wholeWord) {
1240 toFind = "\\b" + toFind + "\\b"; //$NON-NLS-1$ //$NON-NLS-2$
1241 }
1242 Pattern pattern = Pattern.compile(toFind, patternFlags);
1243 return pattern;
1244 }
1245
1246 /**
1247 * Substitutes \R in a regex find pattern with (?>\r\n?|\n)
1248 *
1249 * This implementation is drawn from the jface implementation of
1250 * org.eclipse.jface.text.FindReplaceDocumentAdapter
1251 *
1252 * @param findString
1253 * the original find pattern
1254 * @return the transformed find pattern
1255 */
1256 private static String substituteLinebreak(String findString) {
1257 int length = findString.length();
1258 StringBuffer buf = new StringBuffer(length);
1259
1260 int inCharGroup = 0;
1261 int inBraces = 0;
1262 boolean inQuote = false;
1263 for (int i = 0; i < length; i++) {
1264 char ch = findString.charAt(i);
1265 switch (ch) {
1266 case '[':
1267 buf.append(ch);
1268 if (!inQuote) {
1269 inCharGroup++;
1270 }
1271 break;
1272
1273 case ']':
1274 buf.append(ch);
1275 if (!inQuote) {
1276 inCharGroup--;
1277 }
1278 break;
1279
1280 case '{':
1281 buf.append(ch);
1282 if (!inQuote && inCharGroup == 0) {
1283 inBraces++;
1284 }
1285 break;
1286
1287 case '}':
1288 buf.append(ch);
1289 if (!inQuote && inCharGroup == 0) {
1290 inBraces--;
1291 }
1292 break;
1293
1294 case '\\':
1295 if (i + 1 < length) {
1296 char ch1 = findString.charAt(i + 1);
1297 if (inQuote) {
1298 if (ch1 == 'E') {
1299 inQuote = false;
1300 }
1301 buf.append(ch).append(ch1);
1302 i++;
1303
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);
1308 }
1309 buf.append("(?>\\r\\n?|\\n)"); //$NON-NLS-1$
1310 i++;
1311
1312 } else {
1313 if (ch1 == 'Q') {
1314 inQuote = true;
1315 }
1316 buf.append(ch).append(ch1);
1317 i++;
1318 }
1319 } else {
1320 buf.append(ch);
1321 }
1322 break;
1323
1324 default:
1325 buf.append(ch);
1326 break;
1327 }
1328
1329 }
1330 return buf.toString();
1331 }
1332
1333 /**
1334 * Converts a non-regex string to a pattern that can be used with the regex
1335 * search engine.
1336 *
1337 * This implementation is drawn from the jface implementation of
1338 * org.eclipse.jface.text.FindReplaceDocumentAdapter
1339 *
1340 * @param string
1341 * the non-regex pattern
1342 * @return the string converted to a regex pattern
1343 */
1344 private static String asRegPattern(String string) {
1345 StringBuffer out = new StringBuffer(string.length());
1346 boolean quoting = false;
1347
1348 for (int i = 0, length = string.length(); i < length; i++) {
1349 char ch = string.charAt(i);
1350 if (ch == '\\') {
1351 if (quoting) {
1352 out.append("\\E"); //$NON-NLS-1$
1353 quoting = false;
1354 }
1355 out.append("\\\\"); //$NON-NLS-1$
1356 continue;
1357 }
1358 if (!quoting) {
1359 out.append("\\Q"); //$NON-NLS-1$
1360 quoting = true;
1361 }
1362 out.append(ch);
1363 }
1364 if (quoting) {
1365 out.append("\\E"); //$NON-NLS-1$
1366 }
1367
1368 return out.toString();
1369 }
1370
1371 private class SearchOptions {
1372 boolean forwardSearch;
1373 boolean caseSensitive;
1374 boolean wrapSearch;
1375 boolean wholeWord;
1376 boolean regExSearch;
1377 }
1378 }
This page took 0.065612 seconds and 5 git commands to generate.