Commit | Line | Data |
---|---|---|
36299425 JCK |
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 | ||
b566c0b0 BH |
226 | shell.setMinimumSize(shell.getSize()); |
227 | ||
36299425 JCK |
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); | |
b566c0b0 | 504 | fStatusLabel.setText(Messages.TimeGraphFindDialog_StatusWrappedLabel); |
36299425 | 505 | setGridData(fStatusLabel, SWT.FILL, true, SWT.CENTER, false); |
b566c0b0 BH |
506 | GridData gd = (GridData) fStatusLabel.getLayoutData(); |
507 | gd.widthHint = fStatusLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; | |
508 | fStatusLabel.setText(""); //$NON-NLS-1$ | |
36299425 JCK |
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 | } |