comparison dwtx/jface/internal/text/link/contentassist/ContextInformationPopup2.d @ 129:eb30df5ca28b

Added JFace Text sources
author Frank Benoit <benoit@tionex.de>
date Sat, 23 Aug 2008 19:10:48 +0200
parents
children c4fb132a086c
comparison
equal deleted inserted replaced
128:8df1d4193877 129:eb30df5ca28b
1 /*******************************************************************************
2 * Copyright (c) 2000, 2008 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 * Port to the D programming language:
11 * Frank Benoit <benoit@tionex.de>
12 *******************************************************************************/
13
14 module dwtx.jface.internal.text.link.contentassist.ContextInformationPopup2;
15
16 import dwt.dwthelper.utils;
17
18
19 import java.util.Stack;
20
21 import dwt.DWT;
22 import dwt.custom.BusyIndicator;
23 import dwt.custom.StyledText;
24 import dwt.events.KeyEvent;
25 import dwt.events.SelectionEvent;
26 import dwt.events.SelectionListener;
27 import dwt.events.VerifyEvent;
28 import dwt.graphics.Color;
29 import dwt.graphics.Point;
30 import dwt.layout.GridData;
31 import dwt.layout.GridLayout;
32 import dwt.widgets.Control;
33 import dwt.widgets.Display;
34 import dwt.widgets.Shell;
35 import dwt.widgets.Table;
36 import dwt.widgets.TableItem;
37 import dwtx.jface.text.ITextViewer;
38 import dwtx.jface.text.TextPresentation;
39 import dwtx.jface.text.contentassist.IContextInformation;
40 import dwtx.jface.text.contentassist.IContextInformationExtension;
41 import dwtx.jface.text.contentassist.IContextInformationPresenter;
42 import dwtx.jface.text.contentassist.IContextInformationValidator;
43
44
45 /**
46 * This class is used to present context information to the user.
47 * If multiple contexts are valid at the current cursor location,
48 * a list is presented from which the user may choose one context.
49 * Once the user makes their choice, or if there was only a single
50 * possible context, the context information is shown in a tooltip like popup. <p>
51 * If the tooltip is visible and the user wants to see context information of
52 * a context embedded into the one for which context information is displayed,
53 * context information for the embedded context is shown. As soon as the
54 * cursor leaves the embedded context area, the context information for
55 * the embedding context is shown again.
56 *
57 * @see IContextInformation
58 * @see IContextInformationValidator
59 */
60 class ContextInformationPopup2 : IContentAssistListener2 {
61
62
63
64 /**
65 * Represents the state necessary for embedding contexts.
66 * @since 2.0
67 */
68 static class ContextFrame {
69 public int fBeginOffset;
70 public int fOffset;
71 public int fVisibleOffset;
72 public IContextInformation fInformation;
73 public IContextInformationValidator fValidator;
74 public IContextInformationPresenter fPresenter;
75 }
76
77 private ITextViewer fViewer;
78 private ContentAssistant2 fContentAssistant;
79
80 private PopupCloser2 fPopupCloser= new PopupCloser2();
81 private Shell fContextSelectorShell;
82 private Table fContextSelectorTable;
83 private IContextInformation[] fContextSelectorInput;
84 private String fLineDelimiter= null;
85
86 private Shell fContextInfoPopup;
87 private StyledText fContextInfoText;
88 private TextPresentation fTextPresentation;
89
90 private Stack fContextFrameStack= new Stack();
91
92
93 /**
94 * Creates a new context information popup.
95 *
96 * @param contentAssistant the content assist for computing the context information
97 * @param viewer the viewer on top of which the context information is shown
98 */
99 public ContextInformationPopup2(ContentAssistant2 contentAssistant, ITextViewer viewer) {
100 fContentAssistant= contentAssistant;
101 fViewer= viewer;
102 }
103
104 /**
105 * Shows all possible contexts for the given cursor position of the viewer.
106 *
107 * @param autoActivated <code>true</code> if auto activated
108 * @return a potential error message or <code>null</code> in case of no error
109 */
110 public String showContextProposals(final bool autoActivated) {
111 final StyledText styledText= fViewer.getTextWidget();
112 BusyIndicator.showWhile(styledText.getDisplay(), new Runnable() {
113 public void run() {
114
115 int position= fViewer.getSelectedRange().x;
116
117 IContextInformation[] contexts= computeContextInformation(position);
118 int count = (contexts is null ? 0 : contexts.length);
119 if (count is 1) {
120
121 // Show context information directly
122 internalShowContextInfo(contexts[0], position);
123
124 } else if (count > 0) {
125 // Precise context must be selected
126
127 if (fLineDelimiter is null)
128 fLineDelimiter= styledText.getLineDelimiter();
129
130 createContextSelector();
131 setContexts(contexts);
132 displayContextSelector();
133 hideContextInfoPopup();
134
135 } else if (!autoActivated) {
136 styledText.getDisplay().beep();
137 }
138 }
139 });
140
141 return getErrorMessage();
142 }
143
144 /**
145 * Displays the given context information for the given offset.
146 *
147 * @param info the context information
148 * @param position the offset
149 * @since 2.0
150 */
151 public void showContextInformation(final IContextInformation info, final int position) {
152 Control control= fViewer.getTextWidget();
153 BusyIndicator.showWhile(control.getDisplay(), new Runnable() {
154 public void run() {
155 internalShowContextInfo(info, position);
156 hideContextSelector();
157 }
158 });
159 }
160
161 /**
162 * Displays the given context information for the given offset.
163 *
164 * @param information the context information
165 * @param offset the offset
166 * @since 2.0
167 */
168
169 private void internalShowContextInfo(IContextInformation information, int offset) {
170
171 IContextInformationValidator validator= fContentAssistant.getContextInformationValidator(fViewer, offset);
172
173 if (validator !is null) {
174 ContextFrame current= new ContextFrame();
175 current.fInformation= information;
176 current.fBeginOffset= (information instanceof IContextInformationExtension) ? ((IContextInformationExtension) information).getContextInformationPosition() : offset;
177 if (current.fBeginOffset is -1) current.fBeginOffset= offset;
178 current.fOffset= offset;
179 current.fVisibleOffset= fViewer.getTextWidget().getSelectionRange().x - (offset - current.fBeginOffset);
180 current.fValidator= validator;
181 current.fPresenter= fContentAssistant.getContextInformationPresenter(fViewer, offset);
182
183 fContextFrameStack.push(current);
184
185 internalShowContextFrame(current, fContextFrameStack.size() is 1);
186 }
187 }
188
189 /**
190 * Shows the given context frame.
191 *
192 * @param frame the frane to display
193 * @param initial <code>true</code> if this is the first frame to be displayed
194 * @since 2.0
195 */
196 private void internalShowContextFrame(ContextFrame frame, bool initial) {
197
198 frame.fValidator.install(frame.fInformation, fViewer, frame.fOffset);
199
200 if (frame.fPresenter !is null) {
201 if (fTextPresentation is null)
202 fTextPresentation= new TextPresentation();
203 frame.fPresenter.install(frame.fInformation, fViewer, frame.fBeginOffset);
204 frame.fPresenter.updatePresentation(frame.fOffset, fTextPresentation);
205 }
206
207 createContextInfoPopup();
208
209 fContextInfoText.setText(frame.fInformation.getInformationDisplayString());
210 if (fTextPresentation !is null)
211 TextPresentation.applyTextPresentation(fTextPresentation, fContextInfoText);
212 resize();
213
214 if (initial) {
215 if (fContentAssistant.addContentAssistListener(this, ContentAssistant2.CONTEXT_INFO_POPUP)) {
216 fContentAssistant.addToLayout(this, fContextInfoPopup, ContentAssistant2.LayoutManager.LAYOUT_CONTEXT_INFO_POPUP, frame.fVisibleOffset);
217 fContextInfoPopup.setVisible(true);
218 }
219 } else {
220 fContentAssistant.layout(ContentAssistant2.LayoutManager.LAYOUT_CONTEXT_INFO_POPUP, frame.fVisibleOffset);
221 }
222 }
223
224 /**
225 * Computes all possible context information for the given offset.
226 *
227 * @param position the offset
228 * @return all possible context information for the given offset
229 * @since 2.0
230 */
231 private IContextInformation[] computeContextInformation(int position) {
232 return fContentAssistant.computeContextInformation(fViewer, position);
233 }
234
235 /**
236 *Returns the error message generated while computing context information.
237 *
238 * @return the error message
239 */
240 private String getErrorMessage() {
241 return fContentAssistant.getErrorMessage();
242 }
243
244 /**
245 * Creates the context information popup. This is the tooltip like overlay window.
246 */
247 private void createContextInfoPopup() {
248 if (Helper2.okToUse(fContextInfoPopup))
249 return;
250
251 Control control= fViewer.getTextWidget();
252 Display display= control.getDisplay();
253
254 fContextInfoPopup= new Shell(control.getShell(), DWT.NO_TRIM | DWT.ON_TOP);
255 fContextInfoPopup.setBackground(display.getSystemColor(DWT.COLOR_BLACK));
256
257 fContextInfoText= new StyledText(fContextInfoPopup, DWT.MULTI | DWT.READ_ONLY);
258
259 Color c= fContentAssistant.getContextInformationPopupBackground();
260 if (c is null)
261 c= display.getSystemColor(DWT.COLOR_INFO_BACKGROUND);
262 fContextInfoText.setBackground(c);
263
264 c= fContentAssistant.getContextInformationPopupForeground();
265 if (c is null)
266 c= display.getSystemColor(DWT.COLOR_INFO_FOREGROUND);
267 fContextInfoText.setForeground(c);
268 }
269
270 /**
271 * Resizes the context information popup.
272 * @since 2.0
273 */
274 private void resize() {
275 Point size= fContextInfoText.computeSize(DWT.DEFAULT, DWT.DEFAULT, true);
276 size.x += 3;
277 fContextInfoText.setSize(size);
278 fContextInfoText.setLocation(1,1);
279 size.x += 2;
280 size.y += 2;
281 fContextInfoPopup.setSize(size);
282 }
283
284 /**
285 *Hides the context information popup.
286 */
287 private void hideContextInfoPopup() {
288
289 if (Helper2.okToUse(fContextInfoPopup)) {
290
291 int size= fContextFrameStack.size();
292 if (size > 0) {
293 fContextFrameStack.pop();
294 -- size;
295 }
296
297 if (size > 0) {
298 ContextFrame current= (ContextFrame) fContextFrameStack.peek();
299 internalShowContextFrame(current, false);
300 } else {
301
302 fContentAssistant.removeContentAssistListener(this, ContentAssistant2.CONTEXT_INFO_POPUP);
303
304 fContextInfoPopup.setVisible(false);
305 fContextInfoPopup.dispose();
306 fContextInfoPopup= null;
307
308 if (fTextPresentation !is null) {
309 fTextPresentation.clear();
310 fTextPresentation= null;
311 }
312 }
313 }
314
315 if (fContextInfoPopup is null)
316 fContentAssistant.contextInformationClosed();
317 }
318
319 /**
320 * Creates the context selector in case the user has the choice between multiple valid contexts
321 * at a given offset.
322 */
323 private void createContextSelector() {
324 if (Helper2.okToUse(fContextSelectorShell))
325 return;
326
327 Control control= fViewer.getTextWidget();
328 fContextSelectorShell= new Shell(control.getShell(), DWT.NO_TRIM | DWT.ON_TOP);
329 GridLayout layout= new GridLayout();
330 layout.marginWidth= 0;
331 layout.marginHeight= 0;
332 fContextSelectorShell.setLayout(layout);
333 fContextSelectorShell.setBackground(control.getDisplay().getSystemColor(DWT.COLOR_BLACK));
334
335
336 fContextSelectorTable= new Table(fContextSelectorShell, DWT.H_SCROLL | DWT.V_SCROLL);
337 fContextSelectorTable.setLocation(1, 1);
338 GridData gd= new GridData(GridData.FILL_BOTH);
339 gd.heightHint= fContextSelectorTable.getItemHeight() * 10;
340 gd.widthHint= 300;
341 fContextSelectorTable.setLayoutData(gd);
342
343 fContextSelectorShell.pack(true);
344
345 Color c= fContentAssistant.getContextSelectorBackground();
346 if (c is null)
347 c= control.getDisplay().getSystemColor(DWT.COLOR_INFO_BACKGROUND);
348 fContextSelectorTable.setBackground(c);
349
350 c= fContentAssistant.getContextSelectorForeground();
351 if (c is null)
352 c= control.getDisplay().getSystemColor(DWT.COLOR_INFO_FOREGROUND);
353 fContextSelectorTable.setForeground(c);
354
355 fContextSelectorTable.addSelectionListener(new SelectionListener() {
356 public void widgetSelected(SelectionEvent e) {
357 }
358
359 public void widgetDefaultSelected(SelectionEvent e) {
360 insertSelectedContext();
361 hideContextSelector();
362 }
363 });
364
365 fPopupCloser.install(fContentAssistant, fContextSelectorTable);
366
367 fContextSelectorTable.setHeaderVisible(false);
368 fContentAssistant.addToLayout(this, fContextSelectorShell, ContentAssistant2.LayoutManager.LAYOUT_CONTEXT_SELECTOR, fContentAssistant.getSelectionOffset());
369 }
370
371 /**
372 * Causes the context information of the context selected in the context selector
373 * to be displayed in the context information popup.
374 */
375 private void insertSelectedContext() {
376 int i= fContextSelectorTable.getSelectionIndex();
377
378 if (i < 0 || i >= fContextSelectorInput.length)
379 return;
380
381 int position= fViewer.getSelectedRange().x;
382 internalShowContextInfo(fContextSelectorInput[i], position);
383 }
384
385 /**
386 * Sets the contexts in the context selector to the given set.
387 *
388 * @param contexts the possible contexts
389 */
390 private void setContexts(IContextInformation[] contexts) {
391 if (Helper2.okToUse(fContextSelectorTable)) {
392
393 fContextSelectorInput= contexts;
394
395 fContextSelectorTable.setRedraw(false);
396 fContextSelectorTable.removeAll();
397
398 TableItem item;
399 IContextInformation t;
400 for (int i= 0; i < contexts.length; i++) {
401 t= contexts[i];
402 item= new TableItem(fContextSelectorTable, DWT.NULL);
403 if (t.getImage() !is null)
404 item.setImage(t.getImage());
405 item.setText(t.getContextDisplayString());
406 }
407
408 fContextSelectorTable.select(0);
409 fContextSelectorTable.setRedraw(true);
410 }
411 }
412
413 /**
414 * Displays the context selector.
415 */
416 private void displayContextSelector() {
417 if (fContentAssistant.addContentAssistListener(this, ContentAssistant2.CONTEXT_SELECTOR))
418 fContextSelectorShell.setVisible(true);
419 }
420
421 /**
422 * Hodes the context selector.
423 */
424 private void hideContextSelector() {
425 if (Helper2.okToUse(fContextSelectorShell)) {
426 fContentAssistant.removeContentAssistListener(this, ContentAssistant2.CONTEXT_SELECTOR);
427
428 fPopupCloser.uninstall();
429 fContextSelectorShell.setVisible(false);
430 fContextSelectorShell.dispose();
431 fContextSelectorShell= null;
432 }
433
434 if (!Helper2.okToUse(fContextInfoPopup))
435 fContentAssistant.contextInformationClosed();
436 }
437
438 /**
439 *Returns whether the context selector has the focus.
440 *
441 * @return <code>true</code> if teh context selector has the focus
442 */
443 public bool hasFocus() {
444 if (Helper2.okToUse(fContextSelectorShell))
445 return (fContextSelectorShell.isFocusControl() || fContextSelectorTable.isFocusControl());
446
447 return false;
448 }
449
450 /**
451 * Hides context selector and context information popup.
452 */
453 public void hide() {
454 hideContextSelector();
455 hideContextInfoPopup();
456 }
457
458 /**
459 * Returns whether this context information popup is active. I.e., either
460 * a context selector or context information is displayed.
461 *
462 * @return <code>true</code> if the context selector is active
463 */
464 public bool isActive() {
465 return (Helper2.okToUse(fContextInfoPopup) || Helper2.okToUse(fContextSelectorShell));
466 }
467
468 /*
469 * @see IContentAssistListener#verifyKey(VerifyEvent)
470 */
471 public bool verifyKey(VerifyEvent e) {
472 if (Helper2.okToUse(fContextSelectorShell))
473 return contextSelectorKeyPressed(e);
474 if (Helper2.okToUse(fContextInfoPopup))
475 return contextInfoPopupKeyPressed(e);
476 return true;
477 }
478
479 /**
480 * Processes a key stroke in the context selector.
481 *
482 * @param e the verify event describing the key stroke
483 * @return <code>true</code> if processing can be stopped
484 */
485 private bool contextSelectorKeyPressed(VerifyEvent e) {
486
487 char key= e.character;
488 if (key is 0) {
489
490 int change;
491 int visibleRows= (fContextSelectorTable.getSize().y / fContextSelectorTable.getItemHeight()) - 1;
492 int selection= fContextSelectorTable.getSelectionIndex();
493
494 switch (e.keyCode) {
495
496 case DWT.ARROW_UP:
497 change= (fContextSelectorTable.getSelectionIndex() > 0 ? -1 : 0);
498 break;
499
500 case DWT.ARROW_DOWN:
501 change= (fContextSelectorTable.getSelectionIndex() < fContextSelectorTable.getItemCount() - 1 ? 1 : 0);
502 break;
503
504 case DWT.PAGE_DOWN :
505 change= visibleRows;
506 if (selection + change >= fContextSelectorTable.getItemCount())
507 change= fContextSelectorTable.getItemCount() - selection;
508 break;
509
510 case DWT.PAGE_UP :
511 change= -visibleRows;
512 if (selection + change < 0)
513 change= -selection;
514 break;
515
516 case DWT.HOME :
517 change= -selection;
518 break;
519
520 case DWT.END :
521 change= fContextSelectorTable.getItemCount() - selection;
522 break;
523
524 default:
525 if (e.keyCode !is DWT.MOD1 && e.keyCode !is DWT.MOD2 && e.keyCode !is DWT.MOD3 && e.keyCode !is DWT.MOD4)
526 hideContextSelector();
527 return true;
528 }
529
530 fContextSelectorTable.setSelection(selection + change);
531 fContextSelectorTable.showSelection();
532 e.doit= false;
533 return false;
534
535 } else if ('\t' is key) {
536 // switch focus to selector shell
537 e.doit= false;
538 fContextSelectorShell.setFocus();
539 return false;
540 } else if (key is DWT.ESC) {
541 e.doit= false;
542 hideContextSelector();
543 }
544
545 return true;
546 }
547
548 /**
549 * Processes a key stroke while the info popup is up.
550 *
551 * @param e the verify event describing the key stroke
552 * @return <code>true</code> if processing can be stopped
553 */
554 private bool contextInfoPopupKeyPressed(KeyEvent e) {
555
556 char key= e.character;
557 if (key is 0) {
558
559 switch (e.keyCode) {
560 case DWT.ARROW_LEFT:
561 case DWT.ARROW_RIGHT:
562 validateContextInformation();
563 break;
564 default:
565 if (e.keyCode !is DWT.MOD1 && e.keyCode !is DWT.MOD2 && e.keyCode !is DWT.MOD3 && e.keyCode !is DWT.MOD4)
566 hideContextInfoPopup();
567 break;
568 }
569
570 } else if (key is DWT.ESC) {
571 e.doit= false;
572 hideContextInfoPopup();
573 } else {
574 validateContextInformation();
575 }
576 return true;
577 }
578
579 /*
580 * @see IEventConsumer#processEvent(VerifyEvent)
581 */
582 public void processEvent(VerifyEvent event) {
583 if (Helper2.okToUse(fContextSelectorShell))
584 contextSelectorProcessEvent(event);
585 if (Helper2.okToUse(fContextInfoPopup))
586 contextInfoPopupProcessEvent(event);
587 }
588
589 /**
590 * Processes a key stroke in the context selector.
591 *
592 * @param e the verify event describing the key stroke
593 */
594 private void contextSelectorProcessEvent(VerifyEvent e) {
595
596 if (e.start is e.end && e.text !is null && e.text.equals(fLineDelimiter)) {
597 e.doit= false;
598 insertSelectedContext();
599 }
600
601 hideContextSelector();
602 }
603
604 /**
605 * Processes a key stroke while the info popup is up.
606 *
607 * @param e the verify event describing the key stroke
608 */
609 private void contextInfoPopupProcessEvent(VerifyEvent e) {
610 if (e.start !is e.end && (e.text is null || e.text.length() is 0))
611 validateContextInformation();
612 }
613
614 /**
615 * Validates the context information for the viewer's actual cursor position.
616 */
617 private void validateContextInformation() {
618 /*
619 * Post the code in the event queue in order to ensure that the
620 * action described by this verify key event has already beed executed.
621 * Otherwise, we'd validate the context information based on the
622 * pre-key-stroke state.
623 */
624 fContextInfoPopup.getDisplay().asyncExec(new Runnable() {
625
626 private ContextFrame fFrame= (ContextFrame) fContextFrameStack.peek();
627
628 public void run() {
629 if (Helper2.okToUse(fContextInfoPopup) && fFrame is fContextFrameStack.peek()) {
630 int offset= fViewer.getSelectedRange().x;
631 if (fFrame.fValidator is null || !fFrame.fValidator.isContextInformationValid(offset)) {
632 hideContextInfoPopup();
633 } else if (fFrame.fPresenter !is null && fFrame.fPresenter.updatePresentation(offset, fTextPresentation)) {
634 TextPresentation.applyTextPresentation(fTextPresentation, fContextInfoText);
635 resize();
636 }
637 }
638 }
639 });
640 }
641 }