KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jface > text > contentassist > AdditionalInfoController


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 IBM Corporation and others.
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  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.jface.text.contentassist;
12
13 import org.eclipse.swt.SWT;
14 import org.eclipse.swt.events.SelectionEvent;
15 import org.eclipse.swt.events.SelectionListener;
16 import org.eclipse.swt.graphics.Point;
17 import org.eclipse.swt.graphics.Rectangle;
18 import org.eclipse.swt.widgets.Control;
19 import org.eclipse.swt.widgets.Display;
20 import org.eclipse.swt.widgets.Shell;
21 import org.eclipse.swt.widgets.Table;
22 import org.eclipse.swt.widgets.TableItem;
23
24 import org.eclipse.core.runtime.Assert;
25 import org.eclipse.core.runtime.IProgressMonitor;
26 import org.eclipse.core.runtime.IStatus;
27 import org.eclipse.core.runtime.Status;
28 import org.eclipse.core.runtime.jobs.Job;
29
30 import org.eclipse.jface.text.AbstractInformationControlManager;
31 import org.eclipse.jface.text.IInformationControl;
32 import org.eclipse.jface.text.IInformationControlCreator;
33
34
35 /**
36  * Displays the additional information available for a completion proposal.
37  *
38  * @since 2.0
39  */

40 class AdditionalInfoController extends AbstractInformationControlManager {
41     
42     /**
43      * A timer thread.
44      *
45      * @since 3.2
46      */

47     private static abstract class Timer {
48         private static final int DELAY_UNTIL_JOB_IS_SCHEDULED= 50;
49         
50         /**
51          * A <code>Task</code> is {@link Task#run() run} when {@link #delay()} milliseconds have
52          * elapsed after it was scheduled without a {@link #reset(ICompletionProposal) reset}
53          * to occur.
54          */

55         private abstract class Task implements Runnable JavaDoc {
56             /**
57              * @return the delay in milliseconds before this task should be run
58              */

59             public abstract long delay();
60             /**
61              * Runs this task.
62              */

63             public abstract void run();
64             /**
65              * @return the task to be scheduled after this task has been run
66              */

67             public abstract Task nextTask();
68         }
69         
70         /**
71          * IDLE: the initial task, and active whenever the info has been shown. It cannot be run,
72          * but specifies an infinite delay.
73          */

74         private final Task IDLE= new Task() {
75             public void run() {
76                 Assert.isTrue(false);
77             }
78             
79             public Task nextTask() {
80                 Assert.isTrue(false);
81                 return null;
82             }
83             
84             public long delay() {
85                 return Long.MAX_VALUE;
86             }
87             
88             public String JavaDoc toString() {
89                 return "IDLE"; //$NON-NLS-1$
90
}
91         };
92         /**
93          * FIRST_WAIT: Schedules a platform {@link Job} to fetch additional info from an {@link ICompletionProposalExtension5}.
94          */

95         private final Task FIRST_WAIT= new Task() {
96             public void run() {
97                 final ICompletionProposalExtension5 proposal= getCurrentProposalEx();
98                 Job job= new Job(JFaceTextMessages.getString("AdditionalInfoController.job_name")) { //$NON-NLS-1$
99
protected IStatus run(IProgressMonitor monitor) {
100                         Object JavaDoc info;
101                         try {
102                             info= proposal.getAdditionalProposalInfo(monitor);
103                         } catch (RuntimeException JavaDoc x) {
104                             /*
105                              * XXX: This is the safest fix at this point so close to end of 3.2.
106                              * Will be revisited when fixing https://bugs.eclipse.org/bugs/show_bug.cgi?id=101033
107                              */

108                             return new Status(IStatus.WARNING, "org.eclipse.jface.text", IStatus.OK, "", x); //$NON-NLS-1$ //$NON-NLS-2$
109
}
110                         setInfo((ICompletionProposal) proposal, info);
111                         return new Status(IStatus.OK, "org.eclipse.jface.text", IStatus.OK, "", null); //$NON-NLS-1$ //$NON-NLS-2$
112
}
113                 };
114                 job.schedule();
115             }
116             
117             public Task nextTask() {
118                 return SECOND_WAIT;
119             }
120             
121             public long delay() {
122                 return DELAY_UNTIL_JOB_IS_SCHEDULED;
123             }
124             
125             public String JavaDoc toString() {
126                 return "FIRST_WAIT"; //$NON-NLS-1$
127
}
128         };
129         /**
130          * SECOND_WAIT: Allows display of additional info obtained from an
131          * {@link ICompletionProposalExtension5}.
132          */

133         private final Task SECOND_WAIT= new Task() {
134             public void run() {
135                 // show the info
136
allowShowing();
137             }
138             
139             public Task nextTask() {
140                 return IDLE;
141             }
142             
143             public long delay() {
144                 return fDelay - DELAY_UNTIL_JOB_IS_SCHEDULED;
145             }
146             
147             public String JavaDoc toString() {
148                 return "SECOND_WAIT"; //$NON-NLS-1$
149
}
150         };
151         /**
152          * LEGACY_WAIT: Posts a runnable into the display thread to fetch additional info from non-{@link ICompletionProposalExtension5}s.
153          */

154         private final Task LEGACY_WAIT= new Task() {
155             public void run() {
156                 final ICompletionProposal proposal= getCurrentProposal();
157                 if (!fDisplay.isDisposed()) {
158                     fDisplay.asyncExec(new Runnable JavaDoc() {
159                         public void run() {
160                             synchronized (Timer.this) {
161                                 if (proposal == getCurrentProposal()) {
162                                     Object JavaDoc info= proposal.getAdditionalProposalInfo();
163                                     showInformation(proposal, info);
164                                 }
165                             }
166                         }
167                     });
168                 }
169             }
170             
171             public Task nextTask() {
172                 return IDLE;
173             }
174             
175             public long delay() {
176                 return fDelay;
177             }
178             
179             public String JavaDoc toString() {
180                 return "LEGACY_WAIT"; //$NON-NLS-1$
181
}
182         };
183         /**
184          * EXIT: The task that triggers termination of the timer thread.
185          */

186         private final Task EXIT= new Task() {
187             public long delay() {
188                 return 1;
189             }
190
191             public Task nextTask() {
192                 Assert.isTrue(false);
193                 return EXIT;
194             }
195
196             public void run() {
197                 Assert.isTrue(false);
198             }
199             
200             public String JavaDoc toString() {
201                 return "EXIT"; //$NON-NLS-1$
202
}
203         };
204         
205         /** The timer thread. */
206         private final Thread JavaDoc fThread;
207
208         /** The currently waiting / active task. */
209         private Task fTask;
210         /** The next wake up time. */
211         private long fNextWakeup;
212         
213         private ICompletionProposal fCurrentProposal= null;
214         private Object JavaDoc fCurrentInfo= null;
215         private boolean fAllowShowing= false;
216
217         private final Display fDisplay;
218         private final int fDelay;
219         
220         /**
221          * Creates a new timer.
222          *
223          * @param display the display to use for display thread posting.
224          * @param delay the delay until to show additional info
225          */

226         public Timer(Display display, int delay) {
227             fDisplay= display;
228             fDelay= delay;
229             long current= System.currentTimeMillis();
230             schedule(IDLE, current);
231
232             fThread= new Thread JavaDoc(new Runnable JavaDoc() {
233                 public void run() {
234                     try {
235                         loop();
236                     } catch (InterruptedException JavaDoc x) {
237                     }
238                 }
239             }, JFaceTextMessages.getString("InfoPopup.info_delay_timer_name")); //$NON-NLS-1$
240
fThread.start();
241         }
242         
243         /**
244          * Terminates the timer thread.
245          */

246         public synchronized final void terminate() {
247             schedule(EXIT, System.currentTimeMillis());
248             notifyAll();
249         }
250         
251         /**
252          * Resets the timer thread as the selection has changed to a new proposal.
253          *
254          * @param p the new proposal
255          */

256         public final synchronized void reset(ICompletionProposal p) {
257             if (fCurrentProposal != p) {
258                 fCurrentProposal= p;
259                 fCurrentInfo= null;
260                 fAllowShowing= false;
261
262                 long oldWakeup= fNextWakeup;
263                 Task task= taskOnReset(p);
264                 schedule(task, System.currentTimeMillis());
265                 if (fNextWakeup < oldWakeup)
266                     notifyAll();
267             }
268         }
269         
270         private Task taskOnReset(ICompletionProposal p) {
271             if (p == null)
272                 return IDLE;
273             if (isExt5(p))
274                 return FIRST_WAIT;
275             return LEGACY_WAIT;
276         }
277
278         private synchronized void loop() throws InterruptedException JavaDoc {
279             long current= System.currentTimeMillis();
280             Task task= currentTask();
281
282             while (task != EXIT) {
283                 long delay= fNextWakeup - current;
284                 if (delay <= 0) {
285                     task.run();
286                     task= task.nextTask();
287                     schedule(task, current);
288                 } else {
289                     wait(delay);
290                     current= System.currentTimeMillis();
291                     task= currentTask();
292                 }
293             }
294         }
295         
296         private Task currentTask() {
297             return fTask;
298         }
299         
300         private void schedule(Task task, long current) {
301             fTask= task;
302             long nextWakeup= current + task.delay();
303             if (nextWakeup <= current)
304                 fNextWakeup= Long.MAX_VALUE;
305             else
306                 fNextWakeup= nextWakeup;
307         }
308
309         private boolean isExt5(ICompletionProposal p) {
310             return p instanceof ICompletionProposalExtension5;
311         }
312
313         ICompletionProposal getCurrentProposal() {
314             return fCurrentProposal;
315         }
316         
317         ICompletionProposalExtension5 getCurrentProposalEx() {
318             Assert.isTrue(fCurrentProposal instanceof ICompletionProposalExtension5);
319             return (ICompletionProposalExtension5) fCurrentProposal;
320         }
321
322         synchronized void setInfo(ICompletionProposal proposal, Object JavaDoc info) {
323             if (proposal == fCurrentProposal) {
324                 fCurrentInfo= info;
325                 if (fAllowShowing) {
326                     triggerShowing();
327                 }
328             }
329         }
330
331         private void triggerShowing() {
332             final Object JavaDoc info= fCurrentInfo;
333             if (!fDisplay.isDisposed()) {
334                 fDisplay.asyncExec(new Runnable JavaDoc() {
335                     public void run() {
336                         synchronized (Timer.this) {
337                             if (info == fCurrentInfo) {
338                                 showInformation(fCurrentProposal, info);
339                             }
340                         }
341                     }
342                 });
343             }
344         }
345         
346         /**
347          * Called in the display thread to show additional info.
348          *
349          * @param proposal the proposal to show information about
350          * @param info the information about <code>proposal</code>
351          */

352         protected abstract void showInformation(ICompletionProposal proposal, Object JavaDoc info);
353
354         void allowShowing() {
355             fAllowShowing= true;
356             triggerShowing();
357         }
358     }
359     /**
360      * Internal table selection listener.
361      */

362     private class TableSelectionListener implements SelectionListener {
363
364         /*
365          * @see SelectionListener#widgetSelected(SelectionEvent)
366          */

367         public void widgetSelected(SelectionEvent e) {
368             handleTableSelectionChanged();
369         }
370
371         /*
372          * @see SelectionListener#widgetDefaultSelected(SelectionEvent)
373          */

374         public void widgetDefaultSelected(SelectionEvent e) {
375         }
376     }
377
378     /** The proposal table. */
379     private Table fProposalTable;
380     /** The table selection listener */
381     private SelectionListener fSelectionListener= new TableSelectionListener();
382     /** The delay after which additional information is displayed */
383     private final int fDelay;
384     /**
385      * The timer thread.
386      * @since 3.2
387      */

388     private Timer fTimer;
389     /**
390      * The proposal most recently set by {@link #showInformation(ICompletionProposal, Object)},
391      * possibly <code>null</code>.
392      * @since 3.2
393      */

394     private ICompletionProposal fProposal;
395     /**
396      * The information most recently set by {@link #showInformation(ICompletionProposal, Object)},
397      * possibly <code>null</code>.
398      * @since 3.2
399      */

400     private Object JavaDoc fInformation;
401
402     /**
403      * Creates a new additional information controller.
404      *
405      * @param creator the information control creator to be used by this controller
406      * @param delay time in milliseconds after which additional info should be displayed
407      */

408     AdditionalInfoController(IInformationControlCreator creator, int delay) {
409         super(creator);
410         fDelay= delay;
411         setAnchor(ANCHOR_RIGHT);
412         setFallbackAnchors(new Anchor[] { ANCHOR_RIGHT, ANCHOR_LEFT, ANCHOR_BOTTOM });
413         
414         /*
415          * Adjust the location by one pixel towards the proposal popup, so that the single pixel
416          * border of the additional info popup overlays with the border of the popup. This avoids
417          * having a double black line.
418          */

419         int spacing= -1;
420         setMargins(spacing, spacing); // see also adjustment in #computeLocation
421
}
422
423     /*
424      * @see AbstractInformationControlManager#install(Control)
425      */

426     public void install(Control control) {
427
428         if (fProposalTable == control) {
429             // already installed
430
return;
431         }
432
433         super.install(control.getShell());
434
435         Assert.isTrue(control instanceof Table);
436         fProposalTable= (Table) control;
437         fProposalTable.addSelectionListener(fSelectionListener);
438         fTimer= new Timer(fProposalTable.getDisplay(), fDelay) {
439             protected void showInformation(ICompletionProposal proposal, Object JavaDoc info) {
440                 AdditionalInfoController.this.showInformation(proposal, info);
441             }
442         };
443     }
444
445     /*
446      * @see AbstractInformationControlManager#disposeInformationControl()
447      */

448     public void disposeInformationControl() {
449
450         if (fTimer !=null) {
451             fTimer.terminate();
452             fTimer= null;
453         }
454
455         fProposal= null;
456         fInformation= null;
457
458         if (fProposalTable != null && !fProposalTable.isDisposed()) {
459             fProposalTable.removeSelectionListener(fSelectionListener);
460             fProposalTable= null;
461         }
462
463         super.disposeInformationControl();
464     }
465
466     /**
467      *Handles a change of the line selected in the associated selector.
468      */

469     public void handleTableSelectionChanged() {
470
471         if (fProposalTable != null && !fProposalTable.isDisposed() && fProposalTable.isVisible()) {
472             TableItem[] selection= fProposalTable.getSelection();
473             if (selection != null && selection.length > 0) {
474
475                 TableItem item= selection[0];
476
477                 Object JavaDoc d= item.getData();
478                 if (d instanceof ICompletionProposal) {
479                     ICompletionProposal p= (ICompletionProposal) d;
480                     fTimer.reset(p);
481                 }
482             }
483         }
484     }
485
486     void showInformation(ICompletionProposal proposal, Object JavaDoc info) {
487         if (fProposalTable == null || fProposalTable.isDisposed())
488             return;
489         
490         if (fProposal == proposal && ((info == null && fInformation == null) || (info != null && info.equals(fInformation))))
491             return;
492         
493         fInformation= info;
494         fProposal= proposal;
495         showInformation();
496     }
497
498     /*
499      * @see AbstractInformationControlManager#computeInformation()
500      */

501     protected void computeInformation() {
502         if (fProposal instanceof ICompletionProposalExtension3)
503             setCustomInformationControlCreator(((ICompletionProposalExtension3) fProposal).getInformationControlCreator());
504         else
505             setCustomInformationControlCreator(null);
506
507         // compute subject area
508
Point size= computeTrueShellSize(fProposalTable.getShell());
509
510         // set information & subject area
511
setInformation(fInformation, new Rectangle(0, 0, size.x, size.y));
512     }
513
514     /**
515      * Returns the outer size of the given shell, including trim.
516      *
517      * @param shell a shell
518      * @return the shell's outer size
519      * @since 3.2
520      */

521     private Point computeTrueShellSize(Shell shell) {
522         Point size= shell.getSize();
523         if ("gtk".equals(SWT.getPlatform())) { //$NON-NLS-1$
524
/* XXX bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=136332: on GTK, getSize does not include the trim */
525             Rectangle trim= shell.computeTrim(0, 0, 0, 0);
526             size.x += trim.width;
527             size.y += trim.height;
528         }
529         return size;
530     }
531     
532     /*
533      * @see org.eclipse.jface.text.AbstractInformationControlManager#computeLocation(org.eclipse.swt.graphics.Rectangle, org.eclipse.swt.graphics.Point, org.eclipse.jface.text.AbstractInformationControlManager.Anchor)
534      */

535     protected Point computeLocation(Rectangle subjectArea, Point controlSize, Anchor anchor) {
536         Point location= super.computeLocation(subjectArea, controlSize, anchor);
537         
538         /*
539          * The location is computed using subjectControl.toDisplay(), which does not include the
540          * trim of the subject control. As we want the additional info popup aligned with the outer
541          * coordinates of the proposal popup, adjust this here
542          */

543         Rectangle trim= fProposalTable.getShell().computeTrim(0, 0, 0, 0);
544         location.x += trim.x;
545         location.y += trim.y;
546
547         return location;
548     }
549
550     /*
551      * @see org.eclipse.jface.text.AbstractInformationControlManager#computeSizeConstraints(Control, IInformationControl)
552      */

553     protected Point computeSizeConstraints(Control subjectControl, IInformationControl informationControl) {
554         Point sizeConstraint= super.computeSizeConstraints(subjectControl, informationControl);
555         Point size= computeTrueShellSize(subjectControl.getShell());
556
557         if (sizeConstraint.x < size.x)
558             sizeConstraint.x= size.x;
559         if (sizeConstraint.y < size.y)
560             sizeConstraint.y= size.y;
561         return sizeConstraint;
562     }
563 }
564
565
566
Popular Tags