001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 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.jface.util;
011:
012: import org.eclipse.core.runtime.ListenerList;
013: import org.eclipse.swt.SWT;
014: import org.eclipse.swt.custom.TableTree;
015: import org.eclipse.swt.custom.TableTreeItem;
016: import org.eclipse.swt.events.SelectionEvent;
017: import org.eclipse.swt.events.SelectionListener;
018: import org.eclipse.swt.graphics.Point;
019: import org.eclipse.swt.widgets.Control;
020: import org.eclipse.swt.widgets.Display;
021: import org.eclipse.swt.widgets.Event;
022: import org.eclipse.swt.widgets.Listener;
023: import org.eclipse.swt.widgets.Table;
024: import org.eclipse.swt.widgets.TableItem;
025: import org.eclipse.swt.widgets.Tree;
026: import org.eclipse.swt.widgets.TreeItem;
027: import org.eclipse.swt.widgets.Widget;
028:
029: /**
030: * Implementation of single-click and double-click strategies.
031: * <p>
032: * Usage:
033: * <pre>
034: * OpenStrategy handler = new OpenStrategy(control);
035: * handler.addOpenListener(new IOpenEventListener() {
036: * public void handleOpen(SelectionEvent e) {
037: * ... // code to handle the open event.
038: * }
039: * });
040: * </pre>
041: * </p>
042: */
043: public class OpenStrategy {
044: /**
045: * Default behavior. Double click to open the item.
046: */
047: public static final int DOUBLE_CLICK = 0;
048:
049: /**
050: * Single click will open the item.
051: */
052: public static final int SINGLE_CLICK = 1;
053:
054: /**
055: * Hover will select the item.
056: */
057: public static final int SELECT_ON_HOVER = 1 << 1;
058:
059: /**
060: * Open item when using arrow keys
061: */
062: public static final int ARROW_KEYS_OPEN = 1 << 2;
063:
064: /** A single click will generate
065: * an open event but key arrows will not do anything.
066: *
067: * @deprecated
068: */
069: public static final int NO_TIMER = SINGLE_CLICK;
070:
071: /** A single click will generate an open
072: * event and key arrows will generate an open event after a
073: * small time.
074: *
075: * @deprecated
076: */
077: public static final int FILE_EXPLORER = SINGLE_CLICK
078: | ARROW_KEYS_OPEN;
079:
080: /** Pointing to an item will change the selection
081: * and a single click will gererate an open event
082: *
083: * @deprecated
084: */
085: public static final int ACTIVE_DESKTOP = SINGLE_CLICK
086: | SELECT_ON_HOVER;
087:
088: // Time used in FILE_EXPLORER and ACTIVE_DESKTOP
089: private static final int TIME = 500;
090:
091: /* SINGLE_CLICK or DOUBLE_CLICK;
092: * In case of SINGLE_CLICK, the bits SELECT_ON_HOVER and ARROW_KEYS_OPEN
093: * my be set as well. */
094: private static int CURRENT_METHOD = DOUBLE_CLICK;
095:
096: private Listener eventHandler;
097:
098: private ListenerList openEventListeners = new ListenerList();
099:
100: private ListenerList selectionEventListeners = new ListenerList();
101:
102: private ListenerList postSelectionEventListeners = new ListenerList();
103:
104: /**
105: * @param control the control the strategy is applied to
106: */
107: public OpenStrategy(Control control) {
108: initializeHandler(control.getDisplay());
109: addListener(control);
110: }
111:
112: /**
113: * Adds an IOpenEventListener to the collection of openEventListeners
114: * @param listener the listener to add
115: */
116: public void addOpenListener(IOpenEventListener listener) {
117: openEventListeners.add(listener);
118: }
119:
120: /**
121: * Removes an IOpenEventListener to the collection of openEventListeners
122: * @param listener the listener to remove
123: */
124: public void removeOpenListener(IOpenEventListener listener) {
125: openEventListeners.remove(listener);
126: }
127:
128: /**
129: * Adds an SelectionListener to the collection of selectionEventListeners
130: * @param listener the listener to add
131: */
132: public void addSelectionListener(SelectionListener listener) {
133: selectionEventListeners.add(listener);
134: }
135:
136: /**
137: * Removes an SelectionListener to the collection of selectionEventListeners
138: * @param listener the listener to remove
139: */
140: public void removeSelectionListener(SelectionListener listener) {
141: selectionEventListeners.remove(listener);
142: }
143:
144: /**
145: * Adds an SelectionListener to the collection of selectionEventListeners
146: * @param listener the listener to add
147: */
148: public void addPostSelectionListener(SelectionListener listener) {
149: postSelectionEventListeners.add(listener);
150: }
151:
152: /**
153: * Removes an SelectionListener to the collection of selectionEventListeners
154: * @param listener the listener to remove
155: */
156: public void removePostSelectionListener(SelectionListener listener) {
157: postSelectionEventListeners.remove(listener);
158: }
159:
160: /**
161: * This method is internal to the framework; it should not be implemented outside
162: * the framework.
163: * @return the current used single/double-click method
164: *
165: */
166: public static int getOpenMethod() {
167: return CURRENT_METHOD;
168: }
169:
170: /**
171: * Set the current used single/double-click method.
172: *
173: * This method is internal to the framework; it should not be implemented outside
174: * the framework.
175: * @param method the method to be used
176: * @see OpenStrategy#DOUBLE_CLICK
177: * @see OpenStrategy#SINGLE_CLICK
178: * @see OpenStrategy#SELECT_ON_HOVER
179: * @see OpenStrategy#ARROW_KEYS_OPEN
180: */
181: public static void setOpenMethod(int method) {
182: if (method == DOUBLE_CLICK) {
183: CURRENT_METHOD = method;
184: return;
185: }
186: if ((method & SINGLE_CLICK) == 0) {
187: throw new IllegalArgumentException("Invalid open mode"); //$NON-NLS-1$
188: }
189: if ((method & (SINGLE_CLICK | SELECT_ON_HOVER | ARROW_KEYS_OPEN)) == 0) {
190: throw new IllegalArgumentException("Invalid open mode"); //$NON-NLS-1$
191: }
192: CURRENT_METHOD = method;
193: }
194:
195: /**
196: * @return true if editors should be activated when opened.
197: */
198: public static boolean activateOnOpen() {
199: return getOpenMethod() == DOUBLE_CLICK;
200: }
201:
202: /*
203: * Adds all needed listener to the control in order to implement
204: * single-click/double-click strategies.
205: */
206: private void addListener(Control c) {
207: c.addListener(SWT.MouseEnter, eventHandler);
208: c.addListener(SWT.MouseExit, eventHandler);
209: c.addListener(SWT.MouseMove, eventHandler);
210: c.addListener(SWT.MouseDown, eventHandler);
211: c.addListener(SWT.MouseUp, eventHandler);
212: c.addListener(SWT.KeyDown, eventHandler);
213: c.addListener(SWT.Selection, eventHandler);
214: c.addListener(SWT.DefaultSelection, eventHandler);
215: c.addListener(SWT.Collapse, eventHandler);
216: c.addListener(SWT.Expand, eventHandler);
217: }
218:
219: /*
220: * Fire the selection event to all selectionEventListeners
221: */
222: private void fireSelectionEvent(SelectionEvent e) {
223: if (e.item != null && e.item.isDisposed()) {
224: return;
225: }
226: Object l[] = selectionEventListeners.getListeners();
227: for (int i = 0; i < l.length; i++) {
228: ((SelectionListener) l[i]).widgetSelected(e);
229: }
230: }
231:
232: /*
233: * Fire the default selection event to all selectionEventListeners
234: */
235: private void fireDefaultSelectionEvent(SelectionEvent e) {
236: Object l[] = selectionEventListeners.getListeners();
237: for (int i = 0; i < l.length; i++) {
238: ((SelectionListener) l[i]).widgetDefaultSelected(e);
239: }
240: }
241:
242: /*
243: * Fire the post selection event to all postSelectionEventListeners
244: */
245: private void firePostSelectionEvent(SelectionEvent e) {
246: if (e.item != null && e.item.isDisposed()) {
247: return;
248: }
249: Object l[] = postSelectionEventListeners.getListeners();
250: for (int i = 0; i < l.length; i++) {
251: ((SelectionListener) l[i]).widgetSelected(e);
252: }
253: }
254:
255: /*
256: * Fire the open event to all openEventListeners
257: */
258: private void fireOpenEvent(SelectionEvent e) {
259: if (e.item != null && e.item.isDisposed()) {
260: return;
261: }
262: Object l[] = openEventListeners.getListeners();
263: for (int i = 0; i < l.length; i++) {
264: ((IOpenEventListener) l[i]).handleOpen(e);
265: }
266: }
267:
268: //Initialize event handler.
269: private void initializeHandler(final Display display) {
270: eventHandler = new Listener() {
271: boolean timerStarted = false;
272:
273: Event mouseUpEvent = null;
274:
275: Event mouseMoveEvent = null;
276:
277: SelectionEvent selectionPendent = null;
278:
279: boolean enterKeyDown = false;
280:
281: SelectionEvent defaultSelectionPendent = null;
282:
283: boolean arrowKeyDown = false;
284:
285: final int[] count = new int[1];
286:
287: long startTime = System.currentTimeMillis();
288:
289: boolean collapseOccurred = false;
290:
291: boolean expandOccurred = false;
292:
293: public void handleEvent(final Event e) {
294: if (e.type == SWT.DefaultSelection) {
295: SelectionEvent event = new SelectionEvent(e);
296: fireDefaultSelectionEvent(event);
297: if (CURRENT_METHOD == DOUBLE_CLICK) {
298: fireOpenEvent(event);
299: } else {
300: if (enterKeyDown) {
301: fireOpenEvent(event);
302: enterKeyDown = false;
303: defaultSelectionPendent = null;
304: } else {
305: defaultSelectionPendent = event;
306: }
307: }
308: return;
309: }
310:
311: switch (e.type) {
312: case SWT.MouseEnter:
313: case SWT.MouseExit:
314: mouseUpEvent = null;
315: mouseMoveEvent = null;
316: selectionPendent = null;
317: break;
318: case SWT.MouseMove:
319: if ((CURRENT_METHOD & SELECT_ON_HOVER) == 0) {
320: return;
321: }
322: if (e.stateMask != 0) {
323: return;
324: }
325: if (e.widget.getDisplay().getFocusControl() != e.widget) {
326: return;
327: }
328: mouseMoveEvent = e;
329: final Runnable runnable[] = new Runnable[1];
330: runnable[0] = new Runnable() {
331: public void run() {
332: long time = System.currentTimeMillis();
333: int diff = (int) (time - startTime);
334: if (diff <= TIME) {
335: display.timerExec(diff * 2 / 3,
336: runnable[0]);
337: } else {
338: timerStarted = false;
339: setSelection(mouseMoveEvent);
340: }
341: }
342: };
343: startTime = System.currentTimeMillis();
344: if (!timerStarted) {
345: timerStarted = true;
346: display.timerExec(TIME * 2 / 3, runnable[0]);
347: }
348: break;
349: case SWT.MouseDown:
350: mouseUpEvent = null;
351: arrowKeyDown = false;
352: break;
353: case SWT.Expand:
354: expandOccurred = true;
355: break;
356: case SWT.Collapse:
357: collapseOccurred = true;
358: break;
359: case SWT.MouseUp:
360: mouseMoveEvent = null;
361: if ((e.button != 1)
362: || ((e.stateMask & ~SWT.BUTTON1) != 0)) {
363: return;
364: }
365: if (selectionPendent != null
366: && !(collapseOccurred || expandOccurred)) {
367: mouseSelectItem(selectionPendent);
368: } else {
369: mouseUpEvent = e;
370: collapseOccurred = false;
371: expandOccurred = false;
372: }
373: break;
374: case SWT.KeyDown:
375: mouseMoveEvent = null;
376: mouseUpEvent = null;
377: arrowKeyDown = ((e.keyCode == SWT.ARROW_UP) || (e.keyCode == SWT.ARROW_DOWN))
378: && e.stateMask == 0;
379: if (e.character == SWT.CR) {
380: if (defaultSelectionPendent != null) {
381: fireOpenEvent(new SelectionEvent(e));
382: enterKeyDown = false;
383: defaultSelectionPendent = null;
384: } else {
385: enterKeyDown = true;
386: }
387: }
388: break;
389: case SWT.Selection:
390: SelectionEvent event = new SelectionEvent(e);
391: fireSelectionEvent(event);
392: mouseMoveEvent = null;
393: if (mouseUpEvent != null) {
394: mouseSelectItem(event);
395: } else {
396: selectionPendent = event;
397: }
398: count[0]++;
399: final int id = count[0];
400: // In the case of arrowUp/arrowDown when in the arrowKeysOpen mode, we
401: // want to delay any selection until the last arrowDown/Up occurs. This
402: // handles the case where the user presses arrowDown/Up successively.
403: // We only want to open an editor for the last selected item.
404: display.asyncExec(new Runnable() {
405: public void run() {
406: if (arrowKeyDown) {
407: display.timerExec(TIME, new Runnable() {
408:
409: public void run() {
410: if (id == count[0]) {
411: firePostSelectionEvent(new SelectionEvent(
412: e));
413: if ((CURRENT_METHOD & ARROW_KEYS_OPEN) != 0) {
414: fireOpenEvent(new SelectionEvent(
415: e));
416: }
417: }
418: }
419: });
420: } else {
421: firePostSelectionEvent(new SelectionEvent(
422: e));
423: }
424: }
425: });
426: break;
427: }
428: }
429:
430: void mouseSelectItem(SelectionEvent e) {
431: if ((CURRENT_METHOD & SINGLE_CLICK) != 0) {
432: fireOpenEvent(e);
433: }
434: mouseUpEvent = null;
435: selectionPendent = null;
436: }
437:
438: void setSelection(Event e) {
439: if (e == null) {
440: return;
441: }
442: Widget w = e.widget;
443: if (w.isDisposed()) {
444: return;
445: }
446:
447: SelectionEvent selEvent = new SelectionEvent(e);
448:
449: /*ISSUE: May have to create a interface with method:
450: setSelection(Point p) so that user's custom widgets
451: can use this class. If we keep this option. */
452: if (w instanceof Tree) {
453: Tree tree = (Tree) w;
454: TreeItem item = tree.getItem(new Point(e.x, e.y));
455: if (item != null) {
456: tree.setSelection(new TreeItem[] { item });
457: }
458: selEvent.item = item;
459: } else if (w instanceof Table) {
460: Table table = (Table) w;
461: TableItem item = table.getItem(new Point(e.x, e.y));
462: if (item != null) {
463: table.setSelection(new TableItem[] { item });
464: }
465: selEvent.item = item;
466: } else if (w instanceof TableTree) {
467: TableTree table = (TableTree) w;
468: TableTreeItem item = table.getItem(new Point(e.x,
469: e.y));
470: if (item != null) {
471: table
472: .setSelection(new TableTreeItem[] { item });
473: }
474: selEvent.item = item;
475: } else {
476: return;
477: }
478: if (selEvent.item == null) {
479: return;
480: }
481: fireSelectionEvent(selEvent);
482: firePostSelectionEvent(selEvent);
483: }
484: };
485: }
486: }
|