001: /*******************************************************************************
002: * Copyright (c) 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.pde.internal.ui.editor.contentassist.display;
011:
012: import java.io.IOException;
013: import java.io.StringReader;
014: import java.util.Iterator;
015:
016: import org.eclipse.core.runtime.ListenerList;
017:
018: import org.eclipse.pde.internal.ui.PDEPlugin;
019: import org.eclipse.pde.internal.ui.editor.text.HTMLPrinter;
020: import org.eclipse.swt.SWT;
021: import org.eclipse.swt.SWTError;
022: import org.eclipse.swt.browser.Browser;
023: import org.eclipse.swt.browser.LocationAdapter;
024: import org.eclipse.swt.browser.LocationEvent;
025: import org.eclipse.swt.custom.StyleRange;
026: import org.eclipse.swt.events.DisposeEvent;
027: import org.eclipse.swt.events.DisposeListener;
028: import org.eclipse.swt.events.FocusEvent;
029: import org.eclipse.swt.events.FocusListener;
030: import org.eclipse.swt.events.KeyEvent;
031: import org.eclipse.swt.events.KeyListener;
032: import org.eclipse.swt.graphics.Color;
033: import org.eclipse.swt.graphics.Font;
034: import org.eclipse.swt.graphics.FontData;
035: import org.eclipse.swt.graphics.Point;
036: import org.eclipse.swt.graphics.Rectangle;
037: import org.eclipse.swt.graphics.TextLayout;
038: import org.eclipse.swt.graphics.TextStyle;
039: import org.eclipse.swt.layout.GridData;
040: import org.eclipse.swt.layout.GridLayout;
041: import org.eclipse.swt.widgets.Composite;
042: import org.eclipse.swt.widgets.Display;
043: import org.eclipse.swt.widgets.Event;
044: import org.eclipse.swt.widgets.Label;
045: import org.eclipse.swt.widgets.Listener;
046: import org.eclipse.swt.widgets.Menu;
047: import org.eclipse.swt.widgets.Shell;
048:
049: import org.eclipse.jface.text.IInformationControl;
050: import org.eclipse.jface.text.IInformationControlExtension;
051: import org.eclipse.jface.text.IInformationControlExtension3;
052: import org.eclipse.jface.text.TextPresentation;
053:
054: /**
055: * Displays textual information in a {@link org.eclipse.swt.browser.Browser}
056: * widget.
057: *
058: * <p>
059: * This class may be instantiated; it is not intended to be subclassed.
060: * </p>
061: * <p>
062: * Current problems:
063: * - the size computation is too small
064: * - focusLost event is not sent (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=84532)
065: * </p>
066: *
067: * @since 3.1
068: */
069: public class BrowserInformationControl implements IInformationControl,
070: IInformationControlExtension, IInformationControlExtension3,
071: DisposeListener {
072:
073: /**
074: * Tells whether the SWT Browser widget and hence this information
075: * control is available.
076: *
077: * @param parent the parent component used for checking or <code>null</code> if none
078: * @return <code>true</code> if this control is available
079: */
080: public static boolean isAvailable(Composite parent) {
081: if (!fgAvailabilityChecked) {
082: try {
083: if (parent == null)
084: parent = PDEPlugin.getActiveWorkbenchShell();
085: if (parent == null)
086: return false; // don't store this value - try again later
087:
088: Browser browser = new Browser(parent, SWT.NONE);
089: browser.dispose();
090: fgIsAvailable = true;
091: } catch (SWTError er) {
092: fgIsAvailable = false;
093: } finally {
094: fgAvailabilityChecked = true;
095: }
096: }
097:
098: return fgIsAvailable;
099: }
100:
101: /** Border thickness in pixels. */
102: private static final int BORDER = 1;
103:
104: /**
105: * Minimal size constraints.
106: * @since 3.2
107: */
108: private static final int MIN_WIDTH = 80;
109: private static final int MIN_HEIGHT = 80;
110:
111: /**
112: * Availability checking cache.
113: */
114: private static boolean fgIsAvailable = false;
115: private static boolean fgAvailabilityChecked = false;
116:
117: /** The control's shell */
118: private Shell fShell;
119: /** The control's browser widget */
120: private Browser fBrowser;
121: /** Tells whether the browser has content */
122: private boolean fBrowserHasContent;
123: /** The control width constraint */
124: private int fMaxWidth = -1;
125: /** The control height constraint */
126: private int fMaxHeight = -1;
127: private Font fStatusTextFont;
128: private Label fStatusTextField;
129: private String fStatusFieldText;
130: private boolean fHideScrollBars;
131: private Listener fDeactivateListener;
132: private ListenerList fFocusListeners = new ListenerList();
133: private Label fSeparator;
134: private String fInputText;
135: private TextLayout fTextLayout;
136:
137: private TextStyle fBoldStyle;
138:
139: /**
140: * Creates a default information control with the given shell as parent. The given
141: * information presenter is used to process the information to be displayed. The given
142: * styles are applied to the created styled text widget.
143: *
144: * @param parent the parent shell
145: * @param shellStyle the additional styles for the shell
146: * @param style the additional styles for the styled text widget
147: */
148: public BrowserInformationControl(Shell parent, int shellStyle,
149: int style) {
150: this (parent, shellStyle, style, null);
151: }
152:
153: /**
154: * Creates a default information control with the given shell as parent. The given
155: * information presenter is used to process the information to be displayed. The given
156: * styles are applied to the created styled text widget.
157: *
158: * @param parent the parent shell
159: * @param shellStyle the additional styles for the shell
160: * @param style the additional styles for the styled text widget
161: * @param statusFieldText the text to be used in the optional status field
162: * or <code>null</code> if the status field should be hidden
163: */
164: public BrowserInformationControl(Shell parent, int shellStyle,
165: int style, String statusFieldText) {
166: fStatusFieldText = statusFieldText;
167:
168: fShell = new Shell(parent, SWT.NO_FOCUS | SWT.ON_TOP
169: | shellStyle);
170: Display display = fShell.getDisplay();
171: fShell.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
172: fTextLayout = new TextLayout(display);
173:
174: Composite composite = fShell;
175: GridLayout layout = new GridLayout(1, false);
176: int border = ((shellStyle & SWT.NO_TRIM) == 0) ? 0 : BORDER;
177: layout.marginHeight = border;
178: layout.marginWidth = border;
179: composite.setLayout(layout);
180:
181: if (statusFieldText != null) {
182: composite = new Composite(composite, SWT.NONE);
183: layout = new GridLayout(1, false);
184: layout.marginHeight = 0;
185: layout.marginWidth = 0;
186: layout.verticalSpacing = 1;
187: layout.horizontalSpacing = 1;
188: composite.setLayout(layout);
189:
190: GridData gd = new GridData(GridData.FILL_BOTH);
191: composite.setLayoutData(gd);
192:
193: composite.setForeground(display
194: .getSystemColor(SWT.COLOR_INFO_FOREGROUND));
195: composite.setBackground(display
196: .getSystemColor(SWT.COLOR_INFO_BACKGROUND));
197: }
198:
199: // Browser field
200: fBrowser = new Browser(composite, SWT.NONE);
201: fHideScrollBars = (style & SWT.V_SCROLL) == 0
202: && (style & SWT.H_SCROLL) == 0;
203:
204: GridData gd = new GridData(GridData.BEGINNING
205: | GridData.FILL_BOTH);
206: fBrowser.setLayoutData(gd);
207:
208: fBrowser.setForeground(display
209: .getSystemColor(SWT.COLOR_INFO_FOREGROUND));
210: fBrowser.setBackground(display
211: .getSystemColor(SWT.COLOR_INFO_BACKGROUND));
212: fBrowser.addKeyListener(new KeyListener() {
213:
214: public void keyPressed(KeyEvent e) {
215: if (e.character == 0x1B) // ESC
216: fShell.dispose();
217: }
218:
219: public void keyReleased(KeyEvent e) {
220: }
221: });
222: /*
223: * XXX revisit when the Browser support is better
224: * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=107629. Choosing a link to a
225: * non-available target will show an error dialog behind the ON_TOP shell that seemingly
226: * blocks the workbench. Disable links completely for now.
227: */
228: fBrowser.addLocationListener(new LocationAdapter() {
229: /*
230: * @see org.eclipse.swt.browser.LocationAdapter#changing(org.eclipse.swt.browser.LocationEvent)
231: */
232: public void changing(LocationEvent event) {
233: String location = event.location;
234: /*
235: * Using the Browser.setText API triggers a location change to "about:blank" with
236: * the mozilla widget. The Browser on carbon uses yet another kind of special
237: * initialization URLs.
238: * TODO remove this code once https://bugs.eclipse.org/bugs/show_bug.cgi?id=130314 is fixed
239: */
240: if (!"about:blank".equals(location) && !("carbon".equals(SWT.getPlatform()) && location.startsWith("applewebdata:"))) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
241: event.doit = false;
242: }
243: });
244:
245: // Replace browser's built-in context menu with none
246: fBrowser.setMenu(new Menu(fShell, SWT.NONE));
247:
248: // Status field
249: if (statusFieldText != null) {
250:
251: fSeparator = new Label(composite, SWT.SEPARATOR
252: | SWT.HORIZONTAL | SWT.LINE_DOT);
253: fSeparator.setLayoutData(new GridData(
254: GridData.FILL_HORIZONTAL));
255:
256: // Status field label
257: fStatusTextField = new Label(composite, SWT.RIGHT);
258: fStatusTextField.setText(statusFieldText);
259: Font font = fStatusTextField.getFont();
260: FontData[] fontDatas = font.getFontData();
261: for (int i = 0; i < fontDatas.length; i++)
262: fontDatas[i]
263: .setHeight(fontDatas[i].getHeight() * 9 / 10);
264: fStatusTextFont = new Font(fStatusTextField.getDisplay(),
265: fontDatas);
266: fStatusTextField.setFont(fStatusTextFont);
267: gd = new GridData(GridData.FILL_HORIZONTAL
268: | GridData.HORIZONTAL_ALIGN_BEGINNING
269: | GridData.VERTICAL_ALIGN_BEGINNING);
270: fStatusTextField.setLayoutData(gd);
271:
272: fStatusTextField.setForeground(display
273: .getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW));
274:
275: fStatusTextField.setBackground(display
276: .getSystemColor(SWT.COLOR_INFO_BACKGROUND));
277: }
278:
279: addDisposeListener(this );
280: createTextLayout();
281: }
282:
283: /**
284: * Creates a default information control with the given shell as parent. The given
285: * information presenter is used to process the information to be displayed. The given
286: * styles are applied to the created styled text widget.
287: *
288: * @param parent the parent shell
289: * @param style the additional styles for the browser widget
290: */
291: public BrowserInformationControl(Shell parent, int style) {
292: this (parent, SWT.TOOL | SWT.NO_TRIM, style);
293: }
294:
295: /**
296: * Creates a default information control with the given shell as parent.
297: * No information presenter is used to process the information
298: * to be displayed. No additional styles are applied to the styled text widget.
299: *
300: * @param parent the parent shell
301: */
302: public BrowserInformationControl(Shell parent) {
303: this (parent, SWT.NONE);
304: }
305:
306: /*
307: * @see IInformationControl#setInformation(String)
308: */
309: public void setInformation(String content) {
310: fBrowserHasContent = content != null && content.length() > 0;
311:
312: if (!fBrowserHasContent)
313: content = "<html><body ></html>"; //$NON-NLS-1$
314:
315: fInputText = content;
316:
317: int shellStyle = fShell.getStyle();
318: boolean RTL = (shellStyle & SWT.RIGHT_TO_LEFT) != 0;
319:
320: String[] styles = null;
321: if (RTL && !fHideScrollBars)
322: styles = new String[] {
323: "direction:rtl;", "word-wrap:break-word;" }; //$NON-NLS-1$ //$NON-NLS-2$
324: else if (RTL && fHideScrollBars)
325: styles = new String[] {
326: "direction:rtl;", "overflow:hidden;", "word-wrap:break-word;" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
327: else if (fHideScrollBars && true)
328: styles = new String[] {
329: "overflow:hidden;", "word-wrap: break-word;" }; //$NON-NLS-1$ //$NON-NLS-2$
330:
331: if (styles != null) {
332: StringBuffer buffer = new StringBuffer(content);
333: HTMLPrinter.insertStyles(buffer, styles);
334: content = buffer.toString();
335: }
336:
337: fBrowser.setText(content);
338:
339: }
340:
341: /*
342: * @see org.eclipse.jdt.internal.ui.text.IInformationControlExtension4#setStatusText(java.lang.String)
343: * @since 3.2
344: */
345: public void setStatusText(String statusFieldText) {
346: fStatusFieldText = statusFieldText;
347: }
348:
349: /*
350: * @see IInformationControl#setVisible(boolean)
351: */
352: public void setVisible(boolean visible) {
353: if (fShell.isVisible() == visible)
354: return;
355:
356: if (visible) {
357: if (fStatusTextField != null) {
358: boolean state = fStatusFieldText != null;
359: if (state)
360: fStatusTextField.setText(fStatusFieldText);
361: fStatusTextField.setVisible(state);
362: fSeparator.setVisible(state);
363: }
364: }
365:
366: fShell.setVisible(visible);
367: if (!visible)
368: setInformation(""); //$NON-NLS-1$
369: }
370:
371: /**
372: * Creates and initializes the text layout used
373: * to compute the size hint.
374: *
375: * @since 3.2
376: */
377: private void createTextLayout() {
378: fTextLayout = new TextLayout(fBrowser.getDisplay());
379:
380: // Initialize fonts
381: Font font = fBrowser.getFont();
382: fTextLayout.setFont(font);
383: fTextLayout.setWidth(-1);
384: FontData[] fontData = font.getFontData();
385: for (int i = 0; i < fontData.length; i++)
386: fontData[i].setStyle(SWT.BOLD);
387: font = new Font(fShell.getDisplay(), fontData);
388: fBoldStyle = new TextStyle(font, null, null);
389:
390: // Compute and set tab width
391: fTextLayout.setText(" "); //$NON-NLS-1$
392: int tabWidth = fTextLayout.getBounds().width;
393: fTextLayout.setTabs(new int[] { tabWidth });
394:
395: fTextLayout.setText(""); //$NON-NLS-1$
396: }
397:
398: /*
399: * @see IInformationControl#dispose()
400: */
401: public void dispose() {
402: fTextLayout.dispose();
403: fTextLayout = null;
404: fBoldStyle.font.dispose();
405: fBoldStyle = null;
406: if (fShell != null && !fShell.isDisposed())
407: fShell.dispose();
408: else
409: widgetDisposed(null);
410: }
411:
412: /*
413: * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
414: */
415: public void widgetDisposed(DisposeEvent event) {
416: if (fStatusTextFont != null && !fStatusTextFont.isDisposed())
417: fStatusTextFont.dispose();
418:
419: fShell = null;
420: fBrowser = null;
421: fStatusTextFont = null;
422: }
423:
424: /*
425: * @see IInformationControl#setSize(int, int)
426: */
427: public void setSize(int width, int height) {
428: fShell.setSize(Math.min(width, fMaxWidth), Math.min(height,
429: fMaxHeight));
430: }
431:
432: /*
433: * @see IInformationControl#setLocation(Point)
434: */
435: public void setLocation(Point location) {
436: fShell.setLocation(location);
437: }
438:
439: /*
440: * @see IInformationControl#setSizeConstraints(int, int)
441: */
442: public void setSizeConstraints(int maxWidth, int maxHeight) {
443: fMaxWidth = maxWidth;
444: fMaxHeight = maxHeight;
445: }
446:
447: /*
448: * @see IInformationControl#computeSizeHint()
449: */
450: public Point computeSizeHint() {
451: TextPresentation presentation = new TextPresentation();
452: HTML2TextReader reader = new HTML2TextReader(new StringReader(
453: fInputText), presentation);
454: String text;
455: try {
456: text = reader.getString();
457: } catch (IOException e) {
458: text = ""; //$NON-NLS-1$
459: }
460:
461: fTextLayout.setText(text);
462: Iterator iter = presentation.getAllStyleRangeIterator();
463: while (iter.hasNext()) {
464: StyleRange sr = (StyleRange) iter.next();
465: if (sr.fontStyle == SWT.BOLD)
466: fTextLayout.setStyle(fBoldStyle, sr.start, sr.start
467: + sr.length - 1);
468: }
469: Rectangle bounds = fTextLayout.getBounds();
470: int width = bounds.width;
471: int height = bounds.height;
472:
473: width += 15;
474: height += 25;
475:
476: if (fStatusFieldText != null && fSeparator != null) {
477: fTextLayout.setText(fStatusFieldText);
478: Rectangle statusBounds = fTextLayout.getBounds();
479: Rectangle separatorBounds = fSeparator.getBounds();
480: width = Math.max(width, statusBounds.width);
481: height = height + statusBounds.height
482: + separatorBounds.height;
483: }
484:
485: // Apply size constraints
486: if (fMaxWidth != SWT.DEFAULT)
487: width = Math.min(fMaxWidth, width);
488: if (fMaxHeight != SWT.DEFAULT)
489: height = Math.min(fMaxHeight, height);
490:
491: // Ensure minimal size
492: width = Math.max(MIN_WIDTH, width);
493: height = Math.max(MIN_HEIGHT, height);
494:
495: return new Point(width, height);
496: }
497:
498: /*
499: * @see org.eclipse.jface.text.IInformationControlExtension3#computeTrim()
500: */
501: public Rectangle computeTrim() {
502: return fShell.computeTrim(0, 0, 0, 0);
503: }
504:
505: /*
506: * @see org.eclipse.jface.text.IInformationControlExtension3#getBounds()
507: */
508: public Rectangle getBounds() {
509: return fShell.getBounds();
510: }
511:
512: /*
513: * @see org.eclipse.jface.text.IInformationControlExtension3#restoresLocation()
514: */
515: public boolean restoresLocation() {
516: return false;
517: }
518:
519: /*
520: * @see org.eclipse.jface.text.IInformationControlExtension3#restoresSize()
521: */
522: public boolean restoresSize() {
523: return false;
524: }
525:
526: /*
527: * @see IInformationControl#addDisposeListener(DisposeListener)
528: */
529: public void addDisposeListener(DisposeListener listener) {
530: fShell.addDisposeListener(listener);
531: }
532:
533: /*
534: * @see IInformationControl#removeDisposeListener(DisposeListener)
535: */
536: public void removeDisposeListener(DisposeListener listener) {
537: fShell.removeDisposeListener(listener);
538: }
539:
540: /*
541: * @see IInformationControl#setForegroundColor(Color)
542: */
543: public void setForegroundColor(Color foreground) {
544: fBrowser.setForeground(foreground);
545: }
546:
547: /*
548: * @see IInformationControl#setBackgroundColor(Color)
549: */
550: public void setBackgroundColor(Color background) {
551: fBrowser.setBackground(background);
552: }
553:
554: /*
555: * @see IInformationControl#isFocusControl()
556: */
557: public boolean isFocusControl() {
558: return fBrowser.isFocusControl();
559: }
560:
561: /*
562: * @see IInformationControl#setFocus()
563: */
564: public void setFocus() {
565: fShell.forceFocus();
566: fBrowser.setFocus();
567: }
568:
569: /*
570: * @see IInformationControl#addFocusListener(FocusListener)
571: */
572: public void addFocusListener(final FocusListener listener) {
573: fBrowser.addFocusListener(listener);
574:
575: /*
576: * FIXME: This is a workaround for bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=84532
577: * (Browser widget does not send focusLost event)
578: */
579: if (fFocusListeners.isEmpty()) {
580: fDeactivateListener = new Listener() {
581: public void handleEvent(Event event) {
582: Object[] listeners = fFocusListeners.getListeners();
583: for (int i = 0; i < listeners.length; i++)
584: ((FocusListener) listeners[i])
585: .focusLost(new FocusEvent(event));
586: }
587: };
588: fBrowser.getShell().addListener(SWT.Deactivate,
589: fDeactivateListener);
590: }
591: fFocusListeners.add(listener);
592: }
593:
594: /*
595: * @see IInformationControl#removeFocusListener(FocusListener)
596: */
597: public void removeFocusListener(FocusListener listener) {
598: fBrowser.removeFocusListener(listener);
599:
600: /*
601: * FIXME: This is a workaround for bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=84532
602: * (Browser widget does not send focusLost event)
603: */
604: fFocusListeners.remove(listener);
605: if (fFocusListeners.isEmpty()) {
606: fBrowser.getShell().removeListener(SWT.Deactivate,
607: fDeactivateListener);
608: fDeactivateListener = null;
609: }
610: }
611:
612: /*
613: * @see IInformationControlExtension#hasContents()
614: */
615: public boolean hasContents() {
616: return fBrowserHasContent;
617: }
618: }
|