001: /*******************************************************************************
002: * Copyright (c) 2006, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.pde.internal.ui.editor.outline;
011:
012: import org.eclipse.jface.action.IMenuManager;
013: import org.eclipse.jface.action.Separator;
014: import org.eclipse.jface.dialogs.Dialog;
015: import org.eclipse.jface.dialogs.PopupDialog;
016: import org.eclipse.jface.text.IInformationControl;
017: import org.eclipse.jface.text.IInformationControlExtension;
018: import org.eclipse.jface.text.IInformationControlExtension2;
019: import org.eclipse.jface.viewers.AbstractTreeViewer;
020: import org.eclipse.jface.viewers.ILabelProvider;
021: import org.eclipse.jface.viewers.IStructuredSelection;
022: import org.eclipse.jface.viewers.ITreeContentProvider;
023: import org.eclipse.jface.viewers.StructuredSelection;
024: import org.eclipse.jface.viewers.TreeViewer;
025: import org.eclipse.jface.viewers.ViewerComparator;
026: import org.eclipse.pde.internal.ui.PDEUIMessages;
027: import org.eclipse.pde.internal.ui.editor.actions.SortAction;
028: import org.eclipse.pde.internal.ui.util.StringMatcher;
029: import org.eclipse.swt.SWT;
030: import org.eclipse.swt.events.DisposeEvent;
031: import org.eclipse.swt.events.DisposeListener;
032: import org.eclipse.swt.events.FocusListener;
033: import org.eclipse.swt.events.KeyEvent;
034: import org.eclipse.swt.events.KeyListener;
035: import org.eclipse.swt.events.ModifyEvent;
036: import org.eclipse.swt.events.ModifyListener;
037: import org.eclipse.swt.events.MouseAdapter;
038: import org.eclipse.swt.events.MouseEvent;
039: import org.eclipse.swt.events.SelectionEvent;
040: import org.eclipse.swt.events.SelectionListener;
041: import org.eclipse.swt.graphics.Color;
042: import org.eclipse.swt.graphics.FontMetrics;
043: import org.eclipse.swt.graphics.GC;
044: import org.eclipse.swt.graphics.Point;
045: import org.eclipse.swt.layout.GridData;
046: import org.eclipse.swt.widgets.Composite;
047: import org.eclipse.swt.widgets.Control;
048: import org.eclipse.swt.widgets.Shell;
049: import org.eclipse.swt.widgets.Text;
050: import org.eclipse.swt.widgets.Tree;
051: import org.eclipse.swt.widgets.TreeItem;
052:
053: /**
054: * AbstractInfoPopupDialog
055: *
056: */
057: public class QuickOutlinePopupDialog extends PopupDialog implements
058: IInformationControl, IInformationControlExtension,
059: IInformationControlExtension2, DisposeListener {
060:
061: private TreeViewer fTreeViewer;
062:
063: private IOutlineContentCreator fOutlineContentCreator;
064:
065: private IOutlineSelectionHandler fOutlineSelectionHandler;
066:
067: private Text fFilterText;
068:
069: private StringMatcher fStringMatcher;
070:
071: private QuickOutlineNamePatternFilter fNamePatternFilter;
072:
073: private SortAction fSortAction;
074:
075: private ITreeContentProvider fTreeContentProvider;
076:
077: private ILabelProvider fTreeLabelProvider;
078:
079: private ViewerComparator fTreeViewerComparator;
080:
081: private ViewerComparator fTreeViewerDefaultComparator;
082:
083: public QuickOutlinePopupDialog(Shell parent, int shellStyle,
084: IOutlineContentCreator creator,
085: IOutlineSelectionHandler handler) {
086: super (parent, shellStyle, true, true, true, true, null, null);
087: // Set outline creator
088: fOutlineContentCreator = creator;
089: // Set outline handler
090: fOutlineSelectionHandler = handler;
091: // Initialize the other fields
092: initialize();
093: // Create all controls early to preserve the life cycle of the original
094: // implementation.
095: create();
096: }
097:
098: /**
099: *
100: */
101: private void initialize() {
102: setInfoText(PDEUIMessages.QuickOutlinePopupDialog_infoTextPressEscToExit);
103:
104: fFilterText = null;
105: fTreeViewer = null;
106: fStringMatcher = null;
107: fNamePatternFilter = null;
108: fSortAction = null;
109: fTreeContentProvider = null;
110: fTreeLabelProvider = null;
111: fTreeViewerComparator = null;
112: fTreeViewerDefaultComparator = null;
113: }
114:
115: /* (non-Javadoc)
116: * @see org.eclipse.jface.dialogs.PopupDialog#createDialogArea(org.eclipse.swt.widgets.Composite)
117: */
118: protected Control createDialogArea(Composite parent) {
119: // Applies only to dialog body - not title. See createTitleControl
120: // Create an empty dialog area, if the source page is not defined
121: if ((fOutlineContentCreator == null)
122: || (fOutlineSelectionHandler == null)) {
123: return super .createDialogArea(parent);
124: }
125: // Create the tree viewer
126: createUIWidgetTreeViewer(parent);
127: // Add listeners to the tree viewer
128: createUIListenersTreeViewer();
129: // Create the actions
130: createUIActions();
131: // Add a dispose listner
132: addDisposeListener(this );
133: // Return the tree
134: return fTreeViewer.getControl();
135: }
136:
137: /**
138: *
139: */
140: private void createUIActions() {
141: // Add sort action to dialog menu
142: fSortAction = new SortAction(
143: fTreeViewer,
144: PDEUIMessages.PDEMultiPageContentOutline_SortingAction_tooltip,
145: fTreeViewerComparator, fTreeViewerDefaultComparator,
146: null);
147: }
148:
149: /* (non-Javadoc)
150: * @see org.eclipse.jface.dialogs.PopupDialog#fillDialogMenu(org.eclipse.jface.action.IMenuManager)
151: */
152: protected void fillDialogMenu(IMenuManager dialogMenu) {
153: // Add the sort action
154: dialogMenu.add(fSortAction);
155: // Separator
156: dialogMenu.add(new Separator());
157: // Add the default actions
158: super .fillDialogMenu(dialogMenu);
159: }
160:
161: /**
162: * @param parent
163: */
164: private void createUIWidgetTreeViewer(Composite parent) {
165:
166: // NOTE: Instructions to implement for PDE form pages:
167: // Need to call PDEFormEditor.getFormOutline()
168: // Specify PDE form editor as input
169: // Need to adjust commandId="org.eclipse.pde.ui.quickOutline"
170: // scope: contextId="org.eclipse.ui.textEditorScope"
171: // SEE org.eclipse.ui.contexts.window
172: // TODO: MP: QO: LOW: Implement bi-directional support between form and source page for manifest
173:
174: int style = SWT.H_SCROLL | SWT.V_SCROLL;
175: // Create the tree
176: Tree widget = new Tree(parent, style);
177: // Configure the layout
178: GridData data = new GridData(GridData.FILL_BOTH);
179: data.heightHint = widget.getItemHeight() * 12;
180: widget.setLayoutData(data);
181: // Create the tree viewer
182: fTreeViewer = new TreeViewer(widget);
183: // Add the name pattern filter
184: fNamePatternFilter = new QuickOutlineNamePatternFilter();
185: fTreeViewer.addFilter(fNamePatternFilter);
186: // Set the content provider
187: fTreeContentProvider = fOutlineContentCreator
188: .createOutlineContentProvider();
189: fTreeViewer.setContentProvider(fTreeContentProvider);
190: // Set the label provider
191: fTreeLabelProvider = fOutlineContentCreator
192: .createOutlineLabelProvider();
193: fTreeViewer.setLabelProvider(fTreeLabelProvider);
194: // Create the outline sorter (to be set on the sort action)
195: fTreeViewerComparator = fOutlineContentCreator
196: .createOutlineComparator();
197: // Set the comparator to null (sort action will be disabled initially
198: // because of this)
199: // Create the default outline sorter (Most like this will just return
200: // null to indicate sorting disabled
201: fTreeViewerDefaultComparator = fOutlineContentCreator
202: .createDefaultOutlineComparator();
203: fTreeViewer.setComparator(fTreeViewerDefaultComparator);
204: fTreeViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
205: fTreeViewer.setUseHashlookup(true);
206: fTreeViewer.setInput(fOutlineContentCreator.getOutlineInput());
207: }
208:
209: /**
210: *
211: */
212: private void createUIListenersTreeViewer() {
213: // Get the underlying tree widget
214: final Tree tree = fTreeViewer.getTree();
215: // Handle key events
216: tree.addKeyListener(new KeyListener() {
217: public void keyPressed(KeyEvent e) {
218: if (e.character == 0x1B) {
219: // Dispose on ESC key press
220: dispose();
221: }
222: }
223:
224: public void keyReleased(KeyEvent e) {
225: // NO-OP
226: }
227: });
228: // Handle mouse clicks
229: tree.addMouseListener(new MouseAdapter() {
230: public void mouseUp(MouseEvent e) {
231: handleTreeViewerMouseUp(tree, e);
232: }
233: });
234: // Handle mouse move events
235: tree.addMouseMoveListener(new QuickOutlineMouseMoveListener(
236: fTreeViewer));
237: // Handle widget selection events
238: tree.addSelectionListener(new SelectionListener() {
239: public void widgetSelected(SelectionEvent e) {
240: // NO-OP
241: }
242:
243: public void widgetDefaultSelected(SelectionEvent e) {
244: gotoSelectedElement();
245: }
246: });
247: }
248:
249: /**
250: * @param tree
251: * @param e
252: */
253: private void handleTreeViewerMouseUp(final Tree tree, MouseEvent e) {
254: // Ensure a selection was made, the first mouse button was
255: // used and the event happened in the tree
256: if ((tree.getSelectionCount() < 1) || (e.button != 1)
257: || (tree.equals(e.getSource()) == false)) {
258: return;
259: }
260: // Selection is made in the selection changed listener
261: Object object = tree.getItem(new Point(e.x, e.y));
262: TreeItem selection = tree.getSelection()[0];
263: if (selection.equals(object)) {
264: gotoSelectedElement();
265: }
266: }
267:
268: /**
269: * @return
270: */
271: private Object getSelectedElement() {
272: if (fTreeViewer == null) {
273: return null;
274: }
275: return ((IStructuredSelection) fTreeViewer.getSelection())
276: .getFirstElement();
277: }
278:
279: /* (non-Javadoc)
280: * @see org.eclipse.jface.text.IInformationControl#addDisposeListener(org.eclipse.swt.events.DisposeListener)
281: */
282: public void addDisposeListener(DisposeListener listener) {
283: getShell().addDisposeListener(listener);
284: }
285:
286: /* (non-Javadoc)
287: * @see org.eclipse.jface.text.IInformationControl#addFocusListener(org.eclipse.swt.events.FocusListener)
288: */
289: public void addFocusListener(FocusListener listener) {
290: getShell().addFocusListener(listener);
291: }
292:
293: /* (non-Javadoc)
294: * @see org.eclipse.jface.text.IInformationControl#computeSizeHint()
295: */
296: public Point computeSizeHint() {
297: // Return the shell's size
298: // Note that it already has the persisted size if persisting is enabled.
299: return getShell().getSize();
300: }
301:
302: /* (non-Javadoc)
303: * @see org.eclipse.jface.text.IInformationControl#dispose()
304: */
305: public void dispose() {
306: close();
307: }
308:
309: /* (non-Javadoc)
310: * @see org.eclipse.jface.text.IInformationControl#isFocusControl()
311: */
312: public boolean isFocusControl() {
313: if (fTreeViewer.getControl().isFocusControl()
314: || fFilterText.isFocusControl()) {
315: return true;
316: }
317: return false;
318: }
319:
320: /* (non-Javadoc)
321: * @see org.eclipse.jface.text.IInformationControl#removeDisposeListener(org.eclipse.swt.events.DisposeListener)
322: */
323: public void removeDisposeListener(DisposeListener listener) {
324: getShell().removeDisposeListener(listener);
325: }
326:
327: /* (non-Javadoc)
328: * @see org.eclipse.jface.text.IInformationControl#removeFocusListener(org.eclipse.swt.events.FocusListener)
329: */
330: public void removeFocusListener(FocusListener listener) {
331: getShell().removeFocusListener(listener);
332: }
333:
334: /* (non-Javadoc)
335: * @see org.eclipse.jface.text.IInformationControl#setBackgroundColor(org.eclipse.swt.graphics.Color)
336: */
337: public void setBackgroundColor(Color background) {
338: applyBackgroundColor(background, getContents());
339: }
340:
341: /* (non-Javadoc)
342: * @see org.eclipse.jface.text.IInformationControl#setFocus()
343: */
344: public void setFocus() {
345: getShell().forceFocus();
346: fFilterText.setFocus();
347: }
348:
349: /* (non-Javadoc)
350: * @see org.eclipse.jface.text.IInformationControl#setForegroundColor(org.eclipse.swt.graphics.Color)
351: */
352: public void setForegroundColor(Color foreground) {
353: applyForegroundColor(foreground, getContents());
354: }
355:
356: /* (non-Javadoc)
357: * @see org.eclipse.jface.text.IInformationControl#setInformation(java.lang.String)
358: */
359: public void setInformation(String information) {
360: // Ignore
361: // See IInformationControlExtension2
362: }
363:
364: /* (non-Javadoc)
365: * @see org.eclipse.jface.text.IInformationControl#setLocation(org.eclipse.swt.graphics.Point)
366: */
367: public void setLocation(Point location) {
368: /*
369: * If the location is persisted, it gets managed by PopupDialog - fine. Otherwise, the location is
370: * computed in Window#getInitialLocation, which will center it in the parent shell / main
371: * monitor, which is wrong for two reasons:
372: * - we want to center over the editor / subject control, not the parent shell
373: * - the center is computed via the initalSize, which may be also wrong since the size may
374: * have been updated since via min/max sizing of AbstractInformationControlManager.
375: * In that case, override the location with the one computed by the manager. Note that
376: * the call to constrainShellSize in PopupDialog.open will still ensure that the shell is
377: * entirely visible.
378: */
379: if ((getPersistBounds() == false)
380: || (getDialogSettings() == null)) {
381: getShell().setLocation(location);
382: }
383: }
384:
385: /* (non-Javadoc)
386: * @see org.eclipse.jface.text.IInformationControl#setSize(int, int)
387: */
388: public void setSize(int width, int height) {
389: getShell().setSize(width, height);
390: }
391:
392: /* (non-Javadoc)
393: * @see org.eclipse.jface.text.IInformationControl#setSizeConstraints(int, int)
394: */
395: public void setSizeConstraints(int maxWidth, int maxHeight) {
396: // Ignore
397: }
398:
399: /* (non-Javadoc)
400: * @see org.eclipse.jface.text.IInformationControl#setVisible(boolean)
401: */
402: public void setVisible(boolean visible) {
403: if (visible) {
404: open();
405: } else {
406: saveDialogBounds(getShell());
407: getShell().setVisible(false);
408: }
409: }
410:
411: /* (non-Javadoc)
412: * @see org.eclipse.jface.text.IInformationControlExtension#hasContents()
413: */
414: public boolean hasContents() {
415: if ((fTreeViewer == null) || (fTreeViewer.getInput() == null)) {
416: return false;
417: }
418: return true;
419: }
420:
421: /* (non-Javadoc)
422: * @see org.eclipse.jface.text.IInformationControlExtension2#setInput(java.lang.Object)
423: */
424: public void setInput(Object input) {
425: // Input comes from PDESourceInfoProvider.getInformation2()
426: // The input should be a model object of some sort
427: // Turn it into a structured selection and set the selection in the tree
428: if (input != null) {
429: fTreeViewer.setSelection(new StructuredSelection(input));
430: }
431: }
432:
433: /* (non-Javadoc)
434: * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
435: */
436: public void widgetDisposed(DisposeEvent e) {
437: // Note: We do not reuse the dialog
438: fTreeViewer = null;
439: fFilterText = null;
440: }
441:
442: /* (non-Javadoc)
443: * @see org.eclipse.jface.dialogs.PopupDialog#createTitleControl(org.eclipse.swt.widgets.Composite)
444: */
445: protected Control createTitleControl(Composite parent) {
446: // Applies only to dialog title - not body. See createDialogArea
447: // Create the text widget
448: createUIWidgetFilterText(parent);
449: // Add listeners to the text widget
450: createUIListenersFilterText();
451: // Return the text widget
452: return fFilterText;
453: }
454:
455: /**
456: * @param parent
457: * @return
458: */
459: private void createUIWidgetFilterText(Composite parent) {
460: // Create the widget
461: fFilterText = new Text(parent, SWT.NONE);
462: // Set the font
463: GC gc = new GC(parent);
464: gc.setFont(parent.getFont());
465: FontMetrics fontMetrics = gc.getFontMetrics();
466: gc.dispose();
467: // Create the layout
468: GridData data = new GridData(GridData.FILL_HORIZONTAL);
469: data.heightHint = Dialog.convertHeightInCharsToPixels(
470: fontMetrics, 1);
471: data.horizontalAlignment = GridData.FILL;
472: data.verticalAlignment = GridData.CENTER;
473: fFilterText.setLayoutData(data);
474: }
475:
476: /**
477: *
478: */
479: private void gotoSelectedElement() {
480: Object selectedElement = getSelectedElement();
481: if (selectedElement == null) {
482: return;
483: }
484: dispose();
485: // Get the content outline page within the content outline view
486: // and select the item there to keep the quick outline in sync with the
487: // main outline and prevent duplicate selection events from occurring
488: fOutlineSelectionHandler.getContentOutline().setSelection(
489: new StructuredSelection(selectedElement));
490: }
491:
492: /**
493: *
494: */
495: private void createUIListenersFilterText() {
496: // Handle key events
497: fFilterText.addKeyListener(new KeyListener() {
498: public void keyPressed(KeyEvent e) {
499: if (e.keyCode == 0x0D) {
500: // Return key was pressed
501: gotoSelectedElement();
502: } else if (e.keyCode == SWT.ARROW_DOWN) {
503: // Down key was pressed
504: fTreeViewer.getTree().setFocus();
505: } else if (e.keyCode == SWT.ARROW_UP) {
506: // Up key was pressed
507: fTreeViewer.getTree().setFocus();
508: } else if (e.character == 0x1B) {
509: // Escape key was pressed
510: dispose();
511: }
512: }
513:
514: public void keyReleased(KeyEvent e) {
515: // NO-OP
516: }
517: });
518: // Handle text modify events
519: fFilterText.addModifyListener(new ModifyListener() {
520: public void modifyText(ModifyEvent e) {
521: String text = ((Text) e.widget).getText();
522: int length = text.length();
523: if (length > 0) {
524: // Append a '*' pattern to the end of the text value if it
525: // does not have one already
526: if (text.charAt(length - 1) != '*') {
527: text = text + '*';
528: }
529: // Prepend a '*' pattern to the beginning of the text value
530: // if it does not have one already
531: if (text.charAt(0) != '*') {
532: text = '*' + text;
533: }
534: }
535: // Set and update the pattern
536: setMatcherString(text, true);
537: }
538: });
539: }
540:
541: /**
542: * Sets the patterns to filter out for the receiver.
543: * <p>
544: * The following characters have special meaning:
545: * ? => any character
546: * * => any string
547: * </p>
548: *
549: * @param pattern the pattern
550: * @param update <code>true</code> if the viewer should be updated
551: */
552: private void setMatcherString(String pattern, boolean update) {
553: if (pattern.length() == 0) {
554: fStringMatcher = null;
555: } else {
556: fStringMatcher = new StringMatcher(pattern, true, false);
557: }
558: // Update the name pattern filter on the tree viewer
559: fNamePatternFilter.setStringMatcher(fStringMatcher);
560: // Update the tree viewer according to the pattern
561: if (update) {
562: stringMatcherUpdated();
563: }
564: }
565:
566: /**
567: * The string matcher has been modified. The default implementation
568: * refreshes the view and selects the first matched element
569: */
570: private void stringMatcherUpdated() {
571: // Refresh the tree viewer to re-filter
572: fTreeViewer.getControl().setRedraw(false);
573: fTreeViewer.refresh();
574: fTreeViewer.expandAll();
575: selectFirstMatch();
576: fTreeViewer.getControl().setRedraw(true);
577: }
578:
579: /**
580: * Selects the first element in the tree which
581: * matches the current filter pattern.
582: */
583: private void selectFirstMatch() {
584: Tree tree = fTreeViewer.getTree();
585: Object element = findFirstMatchToPattern(tree.getItems());
586: if (element != null) {
587: fTreeViewer.setSelection(new StructuredSelection(element),
588: true);
589: } else {
590: fTreeViewer.setSelection(StructuredSelection.EMPTY);
591: }
592: }
593:
594: /**
595: * @param items
596: * @return
597: */
598: private Object findFirstMatchToPattern(TreeItem[] items) {
599: // Match the string pattern against labels
600: ILabelProvider labelProvider = (ILabelProvider) fTreeViewer
601: .getLabelProvider();
602: // Process each item in the tree
603: for (int i = 0; i < items.length; i++) {
604: Object element = items[i].getData();
605: // Return the first element if no pattern is set
606: if (fStringMatcher == null) {
607: return element;
608: }
609: // Return the element if it matches the pattern
610: if (element != null) {
611: String label = labelProvider.getText(element);
612: if (fStringMatcher.match(label)) {
613: return element;
614: }
615: }
616: // Recursively check the elements children for a match
617: element = findFirstMatchToPattern(items[i].getItems());
618: // Return the child element match if found
619: if (element != null) {
620: return element;
621: }
622: }
623: // No match found
624: return null;
625: }
626:
627: }
|