001: /* uDig - User Friendly Desktop Internet GIS client
002: * http://udig.refractions.net
003: * (C) 2004, Refractions Research Inc.
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation;
008: * version 2.1 of the License.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: */
015: package net.refractions.udig.ui;
016:
017: import java.lang.reflect.InvocationTargetException;
018: import java.util.Collection;
019: import java.util.Collections;
020: import java.util.List;
021: import java.util.concurrent.CopyOnWriteArrayList;
022:
023: import net.refractions.udig.ui.internal.Messages;
024:
025: import org.eclipse.core.runtime.IProgressMonitor;
026: import org.eclipse.core.runtime.IStatus;
027: import org.eclipse.core.runtime.Platform;
028: import org.eclipse.core.runtime.Status;
029: import org.eclipse.core.runtime.jobs.Job;
030: import org.eclipse.jface.action.Action;
031: import org.eclipse.jface.action.IAction;
032: import org.eclipse.jface.action.IMenuManager;
033: import org.eclipse.jface.action.IToolBarManager;
034: import org.eclipse.jface.action.Separator;
035: import org.eclipse.jface.dialogs.IDialogSettings;
036: import org.eclipse.jface.operation.IRunnableWithProgress;
037: import org.eclipse.jface.viewers.IBaseLabelProvider;
038: import org.eclipse.jface.viewers.ISelection;
039: import org.eclipse.jface.viewers.ISelectionChangedListener;
040: import org.eclipse.jface.viewers.IStructuredContentProvider;
041: import org.eclipse.jface.viewers.IStructuredSelection;
042: import org.eclipse.jface.viewers.LabelProvider;
043: import org.eclipse.jface.viewers.ListViewer;
044: import org.eclipse.jface.viewers.SelectionChangedEvent;
045: import org.eclipse.jface.viewers.StructuredSelection;
046: import org.eclipse.jface.viewers.StructuredViewer;
047: import org.eclipse.jface.viewers.Viewer;
048: import org.eclipse.swt.SWT;
049: import org.eclipse.swt.custom.SashForm;
050: import org.eclipse.swt.events.ControlEvent;
051: import org.eclipse.swt.events.ControlListener;
052: import org.eclipse.swt.graphics.Point;
053: import org.eclipse.swt.widgets.Composite;
054: import org.eclipse.swt.widgets.Display;
055: import org.eclipse.ui.IActionBars;
056: import org.eclipse.ui.IMemento;
057: import org.eclipse.ui.IPartListener2;
058: import org.eclipse.ui.IViewSite;
059: import org.eclipse.ui.IWorkbenchPartReference;
060: import org.eclipse.ui.PartInitException;
061: import org.eclipse.ui.part.ISetSelectionTarget;
062: import org.eclipse.ui.part.ViewPart;
063:
064: /**
065: * A ViewPart with support for a background "search" process.
066: * <p>
067: * The part will automatically show the "progress" when the monitored
068: * Job(s) are running.
069: * </p>
070: * @author jgarnett
071: * @since 1.0.0
072: */
073: public class SearchPart extends ViewPart implements ISetSelectionTarget {
074:
075: /** Viewer for search results List/Tree/Table ... */
076: protected StructuredViewer viewer; // often a tree or table viewer
077:
078: /**
079: * Used to cancel current searchMonitor
080: * <p>
081: * Usually available in the toolbar.
082: * </p>
083: */
084: protected IAction cancel;
085:
086: /**
087: * Used to control search jobs.
088: * <p>
089: * Only one "batch" of search jobs is outstanding at anyone
090: * time.
091: * </p>
092: */
093: protected IProgressMonitor searchMonitor;
094:
095: protected SashForm splitter;
096:
097: /** Save state here */
098: protected IMemento save;
099: protected Composite parent;
100:
101: IDialogSettings settings;
102:
103: /** We need dialog settings for persistence */
104: protected SearchPart(IDialogSettings dialogSettings) {
105: settings = dialogSettings;
106: }
107:
108: @Override
109: public void init(IViewSite site, IMemento memento)
110: throws PartInitException {
111: super .init(site, memento);
112: save = memento;
113: }
114:
115: @Override
116: public void saveState(IMemento memento) {
117: if (splitter == null) { // part has not been created
118: if (save != null) { //Keep the old state;
119: memento.putMemento(save);
120: }
121: return;
122: }
123: }
124:
125: private void addResizeListener(Composite parent) {
126: parent.addControlListener(new ControlListener() {
127: public void controlMoved(ControlEvent e) {
128: }
129:
130: public void controlResized(ControlEvent e) {
131: computeOrientation();
132: }
133: });
134: }
135:
136: protected enum Orientation {
137: VERTICAL, HORIZONTAL, SINGLE, AUTOMATIC
138: };
139:
140: protected Orientation orientation = Orientation.VERTICAL;
141:
142: void computeOrientation() {
143: saveSplitterRatio();
144: if (save != null)
145: save.putInteger("orientation", orientation.ordinal()); //$NON-NLS-1$
146: if (orientation != Orientation.AUTOMATIC) {
147: setOrientation(orientation);
148: } else {
149: if (orientation == Orientation.SINGLE)
150: return;
151:
152: Point size = parent.getSize();
153: if (size.x != 0 && size.y != 0) {
154: if (size.x > size.y)
155: setOrientation(Orientation.HORIZONTAL);
156: else
157: setOrientation(Orientation.VERTICAL);
158: }
159: }
160: }
161:
162: private void saveSplitterRatio() {
163: if (splitter != null && !splitter.isDisposed()) {
164: int[] weigths = splitter.getWeights();
165: int ratio = (weigths[0] * 1000) / (weigths[0] + weigths[1]);
166: settings.put("ratio" + orientation, ratio); //$NON-NLS-1$
167: }
168: }
169:
170: private void restoreSplitterRatio() {
171: try {
172: Integer ratio = settings.getInt("ratio" + orientation); //$NON-NLS-1$
173: if (ratio == null)
174: return;
175: splitter.setWeights(new int[] { ratio, 1000 - ratio });
176: } catch (NumberFormatException nan) {
177: // ignore bad setting
178: }
179: }
180:
181: Orientation currentOrientation;
182: private boolean showDetails;
183:
184: /**
185: * called from ToggleOrientationAction (or compute).
186: *
187: * @param orientation Orientation.HORIZONTAL or Orientation.VERTICAL
188: */
189: protected void setOrientation(Orientation orientation) {
190: if (currentOrientation == orientation) {
191: return; // no change needed
192: }
193: if (viewer != null && !viewer.getControl().isDisposed()
194: && splitter != null && !splitter.isDisposed()) {
195:
196: if (orientation == Orientation.SINGLE) {
197: setShowDetails(false);
198: } else {
199: if (currentOrientation == Orientation.SINGLE) {
200: setShowDetails(true);
201: }
202: boolean horizontal = orientation == Orientation.HORIZONTAL;
203: splitter.setOrientation(horizontal ? SWT.HORIZONTAL
204: : SWT.VERTICAL);
205: }
206: splitter.layout();
207: }
208: updateCheckedState();
209:
210: currentOrientation = orientation;
211:
212: restoreSplitterRatio();
213: }
214:
215: private ToggleOrientationAction[] toggleOrientationActions;
216: private Composite details;
217: private IPartListener2 partListener;
218:
219: private void updateCheckedState() {
220: for (ToggleOrientationAction toggle : toggleOrientationActions) {
221: toggle.setChecked(orientation == toggle.getOrientation());
222: }
223: }
224:
225: public void setShowDetails(boolean show) {
226: showDetails = show;
227: showOrHideDetails();
228: }
229:
230: private void showOrHideDetails() {
231: if (showDetails) {
232: splitter.setMaximizedControl(null);
233: } else {
234: splitter.setMaximizedControl(viewer.getControl());
235: }
236: }
237:
238: /**
239: * Creates the SWT controls for this workbench part.
240: * <p>
241: * The details (from IWorkbenchPart.createPartControl( Composite ))
242: * </p>
243: * <p>
244: * Multi-step process:
245: * <ol>
246: * <li>Create one or more controls within the parent.</li>
247: * <li>Set the parent layout as needed.</li>
248: * <li>Register any global actions with the <code>IActionService</code>.</li>
249: * <li>Register any popup menus with the <code>IActionService</code>.</li>
250: * <li>Register a selection provider with the <code>ISelectionService</code> (optional).
251: * </li>
252: * </ol>
253: * </p>
254: *
255: * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
256: * @param parent
257: */
258: public void createPartControl(Composite aParent) {
259: parent = aParent;
260: addResizeListener(parent);
261:
262: // split
263: splitter = new SashForm(parent, SWT.HORIZONTAL);
264: // TODO: key listener
265:
266: viewer = createViewer(splitter);
267: viewer.setContentProvider(createContentProvider());
268: viewer.setLabelProvider(createLabelProvider());
269: viewer.addSelectionChangedListener(createSelectionListener());
270: getSite().setSelectionProvider(viewer);
271: details = createDetails(splitter);
272:
273: initDragAndDrop();
274:
275: makeActions();
276: fillViewMenu();
277: fillActionBars();
278:
279: initOrientation();
280:
281: if (save != null) {
282: restoreState(save);
283: }
284: restoreSplitterRatio();
285: addPartListener();
286: }
287:
288: /**
289: * You can now restore whatever state you need.
290: */
291: private void restoreState(IMemento memento) {
292: //
293: }
294:
295: protected void saveViewSettings() {
296: saveSplitterRatio();
297: settings.put("orientation", orientation.ordinal()); //$NON-NLS-1$
298: }
299:
300: private void addPartListener() {
301: final String ID = getViewSite().getId();
302: partListener = new IPartListener2() {
303: public void partActivated(IWorkbenchPartReference partRef) {
304: }
305:
306: public void partBroughtToTop(IWorkbenchPartReference partRef) {
307: }
308:
309: public void partClosed(IWorkbenchPartReference partRef) {
310: if (ID.equals(partRef.getId()))
311: saveViewSettings();
312: }
313:
314: public void partDeactivated(IWorkbenchPartReference partRef) {
315: if (ID.equals(partRef.getId()))
316: saveViewSettings();
317: }
318:
319: public void partOpened(IWorkbenchPartReference partRef) {
320: }
321:
322: public void partHidden(IWorkbenchPartReference partRef) {
323: }
324:
325: public void partVisible(IWorkbenchPartReference partRef) {
326: }
327:
328: public void partInputChanged(IWorkbenchPartReference partRef) {
329: }
330: };
331: getViewSite().getPage().addPartListener(partListener);
332: }
333:
334: /**
335: * Should do the dialog settings thing here ...
336: */
337: private void initOrientation() {
338: orientation = Orientation.AUTOMATIC;
339: if (save != null) {
340: Integer integer = save.getInteger("orientation"); //$NON-NLS-1$
341: if (integer != null) {
342: orientation = Orientation.values()[integer];
343: }
344: }
345:
346: // force the update
347: currentOrientation = null;
348: setOrientation(orientation);
349: }
350:
351: /**
352: * Subclass should override to provide custom details display.
353: * <p>
354: * Many subclasses use PageBook with page selection based on
355: * the selection in the viewer.
356: * </p>
357: * @param splitter
358: * @return Composite to use for details display
359: */
360: protected Composite createDetails(SashForm splitter) {
361: return new Composite(splitter, SWT.NONE);
362: }
363:
364: /**
365: * Subclass can override to return its kind of details,
366: * example a PageBook.
367: *
368: * @return
369: */
370: protected Composite getDetails() {
371: return details;
372: }
373:
374: /**
375: * Create viewer (default is a ListViewer please override if you want a Tree or Table Viewer.
376: * <p>
377: * Note the following will be called after creation:
378: * <ul>
379: * <li>setContentProvider( createContentProvider() );
380: * <li>viewer.setLabelProvider( createLableProvider() );
381: * </ul>
382: * This allows people who subclass you to do their own thing.
383: * </p>
384: * @param page
385: * @return
386: */
387: protected StructuredViewer createViewer(Composite parent) {
388: ListViewer viewer = new ListViewer(parent);
389: return viewer;
390: }
391:
392: /**
393: * Default implementation calls showDetail( IStructuredSelection ).
394: * <p>
395: * Override if you want to do something else.
396: */
397: protected ISelectionChangedListener createSelectionListener() {
398: return new ISelectionChangedListener() {
399: public void selectionChanged(SelectionChangedEvent event) {
400: ISelection sel = event.getSelection();
401: if (sel instanceof IStructuredSelection) {
402: IStructuredSelection selection = (IStructuredSelection) sel;
403: showDetail(selection.getFirstElement());
404: }
405: }
406: };
407: }
408:
409: /** Allows subclass to focus the detail on this selection */
410: protected void showDetail(Object selection) {
411: //
412: }
413:
414: /**
415: * Default implementation will work for lists, please overide if
416: * you are into the whole tree thing.
417: *
418: * @return
419: */
420: protected IStructuredContentProvider createContentProvider() {
421: return new IStructuredContentProvider() {
422: public Object[] getElements(Object inputElement) {
423: if (inputElement instanceof List) {
424: return ((List) inputElement).toArray();
425: }
426: return null;
427: }
428:
429: public void dispose() {
430: }
431:
432: public void inputChanged(Viewer viewer, Object oldInput,
433: Object newInput) {
434: assert (newInput instanceof List);
435: // lists don't have events for us to watch
436: }
437: };
438: }
439:
440: /**
441: *
442: * @return
443: */
444: protected IBaseLabelProvider createLabelProvider() {
445: return new LabelProvider();
446: }
447:
448: /** Will cancel any outstanding search */
449: synchronized void stopSearch() {
450: if (searchMonitor != null) {
451: IProgressMonitor cancelMonitor = searchMonitor;
452:
453: searchMonitor = null;
454: cancelMonitor.setCanceled(true);
455: }
456: }
457:
458: /**
459: * Subclass should override to support DnD.
460: */
461: protected void initDragAndDrop() {
462: //
463: }
464:
465: protected void makeActions() {
466: cancel = new Action() {
467: public void run() {
468: stopSearch();
469: setEnabled(false);
470: //book.showPage( promptPage );
471: }
472: };
473: Messages.initAction(cancel, "cancel"); //$NON-NLS-1$
474: cancel.setEnabled(false);
475:
476: toggleOrientationActions = new ToggleOrientationAction[] {
477: new ToggleOrientationAction(this , Orientation.VERTICAL),
478: new ToggleOrientationAction(this ,
479: Orientation.HORIZONTAL),
480: new ToggleOrientationAction(this , Orientation.AUTOMATIC),
481: new ToggleOrientationAction(this , Orientation.SINGLE) };
482:
483: }
484:
485: /**
486: *
487: */
488: protected void fillViewMenu() {
489: IActionBars actionBars = getViewSite().getActionBars();
490: IMenuManager viewMenu = actionBars.getMenuManager();
491:
492: viewMenu.add(cancel);
493: viewMenu.add(new Separator());
494: for (ToggleOrientationAction toggle : toggleOrientationActions) {
495: viewMenu.add(toggle);
496: }
497: }
498:
499: /**
500: * Create toolbar with cancel.
501: */
502: protected void fillActionBars() {
503: IActionBars actionBars = getViewSite().getActionBars();
504: IToolBarManager toolBar = actionBars.getToolBarManager();
505:
506: toolBar.add(cancel);
507: }
508:
509: public void quick(String pattern) {
510: // search through existing contents based on label?
511: }
512:
513: Object filter = null;
514: Job looking = new Job(getPartName() + "...") { //$NON-NLS-1$
515: protected IStatus run(IProgressMonitor monitor) {
516: try {
517: final ResultSet set = new ResultSet(SearchPart.this );
518: Display.getDefault().asyncExec(new Runnable() {
519: public void run() {
520: viewer.setInput(set.results);
521: }
522: });
523: PlatformGIS.runBlockingOperation(
524: new IRunnableWithProgress() {
525:
526: public void run(IProgressMonitor monitor)
527: throws InvocationTargetException,
528: InterruptedException {
529: searchImplementation(filter, monitor,
530: set);
531: }
532:
533: }, monitor);
534: Display.getDefault().asyncExec(new Runnable() {
535: public void run() {
536: viewer.setSelection(getSelection(set.results),
537: showSelection());
538: }
539: });
540: } catch (Throwable t) {
541: //book.showPage( promptPage );
542: } finally {
543: cancel.setEnabled(false);
544: }
545: return Status.OK_STATUS;
546: }
547: };
548:
549: /**
550: * Called each time data is added to the result set by the search. Note: Many items or a single item could have been added.
551: *
552: * @param set the results of the search at the current point.
553: * @param newObjects the objects that have been added this time.
554: */
555: protected void notifyChange(final ResultSet set,
556: final Collection<? extends Object> newObjects) {
557: Display.getDefault().asyncExec(new Runnable() {
558: public void run() {
559: if (!newObjects.isEmpty())
560: viewer.refresh(true);
561: }
562: });
563: }
564:
565: /**
566: * Called to determine what object(s) in the input should be the selection in the viewer. This method is called when the search is complete.
567: * The default selection is the first object in the list.
568: *
569: * @param the complete list of objects in the viewer.
570: *
571: * @return the selection that will be set in the viewer. may be null.
572: */
573: protected ISelection getSelection(List<Object> input) {
574: if (input.size() > 0)
575: return new StructuredSelection(input.get(0));
576: else
577: return new StructuredSelection();
578: }
579:
580: /**
581: * Returns true if the selection returned by {@link #getNewSelection(List)} should be shown in the viewer.
582: *
583: * @return true if the selection returned by {@link #getNewSelection(List)} should be shown in the viewer.
584: */
585: protected boolean showSelection() {
586: return true;
587: }
588:
589: /**
590: * Search the catalog for text and update view contents
591: *
592: * @param pattern
593: */
594: public void search(final Object newFilter) {
595: if (newFilter == null) {
596: stopSearch();
597: //book.showPage( promptPage );
598: return;
599: }
600: stopSearch();
601:
602: viewer.setInput(null);
603: filter = newFilter;
604: searchMonitor = Platform.getJobManager().createProgressGroup();
605: searchMonitor.setTaskName(getPartName());
606: looking.setPriority(Job.BUILD);
607: looking.setProgressGroup(searchMonitor,
608: IProgressMonitor.UNKNOWN);
609: cancel.setEnabled(true);
610: looking.schedule(); // do it
611: }
612:
613: protected void searchImplementation(Object filter,
614: IProgressMonitor monitor, ResultSet results) {
615: // No default action.
616: }
617:
618: /*
619: * (non-Javadoc)
620: *
621: * @see org.eclipse.ui.part.ISetSelectionTarget#selectReveal(org.eclipse.jface.viewers.ISelection)
622: */
623: public void selectReveal(ISelection selection) {
624: //viewer.setSelection(selection);
625: }
626:
627: @Override
628: public void setFocus() {
629: if (viewer != null && viewer.getControl().isVisible()) {
630: viewer.getControl().setFocus();
631: }
632: }
633:
634: /**
635: * Toggles the orientationof the layout
636: */
637: static class ToggleOrientationAction extends Action {
638: private SearchPart view;
639: private Orientation orientation;
640:
641: public ToggleOrientationAction(SearchPart searchPart,
642: Orientation orientation) {
643: super ("", AS_RADIO_BUTTON); //$NON-NLS-1$
644: Messages
645: .initAction(
646: this ,
647: "orientation_" + orientation.toString().toLowerCase()); //$NON-NLS-1$
648: view = searchPart;
649: this .orientation = orientation;
650: //PlatformUI.getWorkbench().getHelpSystem().setHelp(this, lookup context id? );
651: }
652:
653: public Orientation getOrientation() {
654: return orientation;
655: }
656:
657: /*
658: * @see Action#actionPerformed
659: */
660: public void run() {
661: if (isChecked()) {
662: view.orientation = orientation;
663: view.computeOrientation();
664: }
665: }
666:
667: }
668:
669: public static class ResultSet {
670: SearchPart owner;
671: List<Object> results = new CopyOnWriteArrayList<Object>();
672: private int changes = 0;
673:
674: private ResultSet(SearchPart owner) {
675: this .owner = owner;
676: }
677:
678: public boolean firstNotification() {
679: return changes == 1;
680: }
681:
682: private void notifyChange(Collection<? extends Object> changed) {
683: changes++;
684: owner.notifyChange(this , changed);
685: }
686:
687: public void add(int arg0, Object arg1) {
688: results.add(arg0, arg1);
689: notifyChange(Collections.singleton(arg1));
690: }
691:
692: public boolean add(Object arg0) {
693: boolean add = results.add(arg0);
694: notifyChange(Collections.singleton(arg0));
695: return add;
696: }
697:
698: public boolean addAll(Collection<? extends Object> arg0) {
699: boolean addAll = results.addAll(arg0);
700: notifyChange(arg0);
701: return addAll;
702: }
703:
704: public boolean addAll(int arg0,
705: Collection<? extends Object> arg1) {
706: boolean addAll = results.addAll(arg0, arg1);
707: notifyChange(arg1);
708: return addAll;
709: }
710:
711: public boolean contains(Object arg0) {
712: return results.contains(arg0);
713: }
714:
715: public boolean containsAll(Collection<?> arg0) {
716: return results.containsAll(arg0);
717: }
718:
719: public Object get(int arg0) {
720: return results.get(arg0);
721: }
722:
723: public int indexOf(Object arg0) {
724: return results.indexOf(arg0);
725: }
726:
727: }
728:
729: }
|