001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 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.ui.internal.dialogs;
011:
012: import java.io.IOException;
013: import java.net.MalformedURLException;
014: import java.net.URL;
015: import java.util.ArrayList;
016: import java.util.StringTokenizer;
017:
018: import org.eclipse.core.runtime.IStatus;
019: import org.eclipse.core.runtime.Platform;
020: import org.eclipse.jface.dialogs.TrayDialog;
021: import org.eclipse.jface.resource.JFaceColors;
022: import org.eclipse.osgi.util.NLS;
023: import org.eclipse.swt.SWT;
024: import org.eclipse.swt.custom.StyleRange;
025: import org.eclipse.swt.custom.StyledText;
026: import org.eclipse.swt.events.KeyAdapter;
027: import org.eclipse.swt.events.KeyEvent;
028: import org.eclipse.swt.events.MouseAdapter;
029: import org.eclipse.swt.events.MouseEvent;
030: import org.eclipse.swt.events.MouseMoveListener;
031: import org.eclipse.swt.events.TraverseEvent;
032: import org.eclipse.swt.events.TraverseListener;
033: import org.eclipse.swt.graphics.Color;
034: import org.eclipse.swt.graphics.Cursor;
035: import org.eclipse.swt.graphics.Point;
036: import org.eclipse.swt.widgets.Shell;
037: import org.eclipse.ui.PartInitException;
038: import org.eclipse.ui.PlatformUI;
039: import org.eclipse.ui.browser.IWebBrowser;
040: import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
041: import org.eclipse.ui.internal.WorkbenchMessages;
042: import org.eclipse.ui.internal.WorkbenchPlugin;
043: import org.eclipse.ui.internal.about.AboutItem;
044: import org.eclipse.ui.internal.misc.StatusUtil;
045: import org.eclipse.ui.statushandlers.StatusManager;
046:
047: /**
048: * Abstract superclass of about dialogs
049: */
050:
051: public abstract class ProductInfoDialog extends TrayDialog {
052:
053: private AboutItem item;
054:
055: private Cursor handCursor;
056:
057: private Cursor busyCursor;
058:
059: private boolean mouseDown = false;
060:
061: private boolean dragEvent = false;
062:
063: /**
064: * Create an instance of this Dialog
065: */
066: public ProductInfoDialog(Shell parentShell) {
067: super (parentShell);
068: }
069:
070: /**
071: * Adds listeners to the given styled text
072: */
073: protected void addListeners(StyledText styledText) {
074: styledText.addMouseListener(new MouseAdapter() {
075: public void mouseDown(MouseEvent e) {
076: if (e.button != 1) {
077: return;
078: }
079: mouseDown = true;
080: }
081:
082: public void mouseUp(MouseEvent e) {
083: mouseDown = false;
084: StyledText text = (StyledText) e.widget;
085: int offset = text.getCaretOffset();
086: if (dragEvent) {
087: // don't activate a link during a drag/mouse up operation
088: dragEvent = false;
089: if (item != null && item.isLinkAt(offset)) {
090: text.setCursor(handCursor);
091: }
092: } else if (item != null && item.isLinkAt(offset)) {
093: text.setCursor(busyCursor);
094: openLink(item.getLinkAt(offset));
095: StyleRange selectionRange = getCurrentRange(text);
096: text.setSelectionRange(selectionRange.start,
097: selectionRange.length);
098: text.setCursor(null);
099: }
100: }
101: });
102:
103: styledText.addMouseMoveListener(new MouseMoveListener() {
104: public void mouseMove(MouseEvent e) {
105: // Do not change cursor on drag events
106: if (mouseDown) {
107: if (!dragEvent) {
108: StyledText text = (StyledText) e.widget;
109: text.setCursor(null);
110: }
111: dragEvent = true;
112: return;
113: }
114: StyledText text = (StyledText) e.widget;
115: int offset = -1;
116: try {
117: offset = text.getOffsetAtLocation(new Point(e.x,
118: e.y));
119: } catch (IllegalArgumentException ex) {
120: // leave value as -1
121: }
122: if (offset == -1) {
123: text.setCursor(null);
124: } else if (item != null && item.isLinkAt(offset)) {
125: text.setCursor(handCursor);
126: } else {
127: text.setCursor(null);
128: }
129: }
130: });
131:
132: styledText.addTraverseListener(new TraverseListener() {
133: public void keyTraversed(TraverseEvent e) {
134: StyledText text = (StyledText) e.widget;
135: switch (e.detail) {
136: case SWT.TRAVERSE_ESCAPE:
137: e.doit = true;
138: break;
139: case SWT.TRAVERSE_TAB_NEXT:
140: //Previously traverse out in the backward direction?
141: Point nextSelection = text.getSelection();
142: int charCount = text.getCharCount();
143: if ((nextSelection.x == charCount)
144: && (nextSelection.y == charCount)) {
145: text.setSelection(0);
146: }
147: StyleRange nextRange = findNextRange(text);
148: if (nextRange == null) {
149: // Next time in start at beginning, also used by
150: // TRAVERSE_TAB_PREVIOUS to indicate we traversed out
151: // in the forward direction
152: text.setSelection(0);
153: e.doit = true;
154: } else {
155: text.setSelectionRange(nextRange.start,
156: nextRange.length);
157: e.doit = true;
158: e.detail = SWT.TRAVERSE_NONE;
159: }
160: break;
161: case SWT.TRAVERSE_TAB_PREVIOUS:
162: //Previously traverse out in the forward direction?
163: Point previousSelection = text.getSelection();
164: if ((previousSelection.x == 0)
165: && (previousSelection.y == 0)) {
166: text.setSelection(text.getCharCount());
167: }
168: StyleRange previousRange = findPreviousRange(text);
169: if (previousRange == null) {
170: // Next time in start at the end, also used by
171: // TRAVERSE_TAB_NEXT to indicate we traversed out
172: // in the backward direction
173: text.setSelection(text.getCharCount());
174: e.doit = true;
175: } else {
176: text.setSelectionRange(previousRange.start,
177: previousRange.length);
178: e.doit = true;
179: e.detail = SWT.TRAVERSE_NONE;
180: }
181: break;
182: default:
183: break;
184: }
185: }
186: });
187:
188: //Listen for Tab and Space to allow keyboard navigation
189: styledText.addKeyListener(new KeyAdapter() {
190: public void keyPressed(KeyEvent event) {
191: StyledText text = (StyledText) event.widget;
192: if (event.character == ' ' || event.character == SWT.CR) {
193: if (item != null) {
194: //Be sure we are in the selection
195: int offset = text.getSelection().x + 1;
196:
197: if (item.isLinkAt(offset)) {
198: text.setCursor(busyCursor);
199: openLink(item.getLinkAt(offset));
200: StyleRange selectionRange = getCurrentRange(text);
201: text.setSelectionRange(
202: selectionRange.start,
203: selectionRange.length);
204: text.setCursor(null);
205: }
206: }
207: return;
208: }
209: }
210: });
211: }
212:
213: /**
214: * Gets the busy cursor.
215: * @return the busy cursor
216: */
217: protected Cursor getBusyCursor() {
218: return busyCursor;
219: }
220:
221: /**
222: * Sets the busy cursor.
223: * @param busyCursor the busy cursor
224: */
225: protected void setBusyCursor(Cursor busyCursor) {
226: this .busyCursor = busyCursor;
227: }
228:
229: /**
230: * Gets the hand cursor.
231: * @return Returns a hand cursor
232: */
233: protected Cursor getHandCursor() {
234: return handCursor;
235: }
236:
237: /**
238: * Sets the hand cursor.
239: * @param handCursor The hand cursor to set
240: */
241: protected void setHandCursor(Cursor handCursor) {
242: this .handCursor = handCursor;
243: }
244:
245: /**
246: * Gets the about item.
247: * @return the about item
248: */
249: protected AboutItem getItem() {
250: return item;
251: }
252:
253: /**
254: * Sets the about item.
255: * @param item about item
256: */
257: protected void setItem(AboutItem item) {
258: this .item = item;
259: }
260:
261: /**
262: * Find the range of the current selection.
263: */
264: protected StyleRange getCurrentRange(StyledText text) {
265: StyleRange[] ranges = text.getStyleRanges();
266: int currentSelectionEnd = text.getSelection().y;
267: int currentSelectionStart = text.getSelection().x;
268:
269: for (int i = 0; i < ranges.length; i++) {
270: if ((currentSelectionStart >= ranges[i].start)
271: && (currentSelectionEnd <= (ranges[i].start + ranges[i].length))) {
272: return ranges[i];
273: }
274: }
275: return null;
276: }
277:
278: /**
279: * Find the next range after the current
280: * selection.
281: */
282: protected StyleRange findNextRange(StyledText text) {
283: StyleRange[] ranges = text.getStyleRanges();
284: int currentSelectionEnd = text.getSelection().y;
285:
286: for (int i = 0; i < ranges.length; i++) {
287: if (ranges[i].start >= currentSelectionEnd) {
288: return ranges[i];
289: }
290: }
291: return null;
292: }
293:
294: /**
295: * Find the previous range before the current selection.
296: */
297: protected StyleRange findPreviousRange(StyledText text) {
298: StyleRange[] ranges = text.getStyleRanges();
299: int currentSelectionStart = text.getSelection().x;
300:
301: for (int i = ranges.length - 1; i > -1; i--) {
302: if ((ranges[i].start + ranges[i].length - 1) < currentSelectionStart) {
303: return ranges[i];
304: }
305: }
306: return null;
307: }
308:
309: /**
310: * display an error message
311: */
312: private void openWebBrowserError(final String href,
313: final Throwable t) {
314: String title = WorkbenchMessages.ProductInfoDialog_errorTitle;
315: String msg = NLS
316: .bind(
317: WorkbenchMessages.ProductInfoDialog_unableToOpenWebBrowser,
318: href);
319: IStatus status = WorkbenchPlugin.getStatus(t);
320: StatusUtil.handleStatus(status,
321: title + ": " + msg, StatusManager.SHOW, //$NON-NLS-1$
322: getShell());
323: }
324:
325: /**
326: * Open a link
327: */
328: protected void openLink(String href) {
329: // format the href for an html file (file:///<filename.html>
330: // required for Mac only.
331: if (href.startsWith("file:")) { //$NON-NLS-1$
332: href = href.substring(5);
333: while (href.startsWith("/")) { //$NON-NLS-1$
334: href = href.substring(1);
335: }
336: href = "file:///" + href; //$NON-NLS-1$
337: }
338: IWorkbenchBrowserSupport support = PlatformUI.getWorkbench()
339: .getBrowserSupport();
340: try {
341: IWebBrowser browser = support.getExternalBrowser();
342: browser.openURL(new URL(urlEncodeForSpaces(href
343: .toCharArray())));
344: } catch (MalformedURLException e) {
345: openWebBrowserError(href, e);
346: } catch (PartInitException e) {
347: openWebBrowserError(href, e);
348: }
349: }
350:
351: /**
352: * This method encodes the url, removes the spaces from the url and replaces
353: * the same with <code>"%20"</code>. This method is required to fix Bug
354: * 77840.
355: *
356: * @since 3.0.2
357: */
358: private String urlEncodeForSpaces(char[] input) {
359: StringBuffer retu = new StringBuffer(input.length);
360: for (int i = 0; i < input.length; i++) {
361: if (input[i] == ' ') {
362: retu.append("%20"); //$NON-NLS-1$
363: } else {
364: retu.append(input[i]);
365: }
366: }
367: return retu.toString();
368: }
369:
370: /**
371: * Open a browser with the argument title on the argument url. If the url refers to a
372: * resource within a bundle, then a temp copy of the file will be extracted and opened.
373: * @see <code>Platform.asLocalUrl</code>
374: * @param url The target url to be displayed, null will be safely ignored
375: * @return true if the url was successfully displayed and false otherwise
376: */
377: protected boolean openBrowser(URL url) {
378: if (url != null) {
379: try {
380: url = Platform.asLocalURL(url);
381: } catch (IOException e) {
382: return false;
383: }
384: }
385: if (url == null) {
386: return false;
387: }
388: openLink(url.toString());
389: return true;
390: }
391:
392: /**
393: * Sets the styled text's bold ranges
394: */
395: protected void setBoldRanges(StyledText styledText,
396: int[][] boldRanges) {
397: for (int i = 0; i < boldRanges.length; i++) {
398: StyleRange r = new StyleRange(boldRanges[i][0],
399: boldRanges[i][1], null, null, SWT.BOLD);
400: styledText.setStyleRange(r);
401: }
402: }
403:
404: /**
405: * Sets the styled text's link (blue) ranges
406: */
407: protected void setLinkRanges(StyledText styledText,
408: int[][] linkRanges) {
409: Color fg = JFaceColors.getHyperlinkText(styledText.getShell()
410: .getDisplay());
411: for (int i = 0; i < linkRanges.length; i++) {
412: StyleRange r = new StyleRange(linkRanges[i][0],
413: linkRanges[i][1], fg, null);
414: styledText.setStyleRange(r);
415: }
416: }
417:
418: /**
419: * Scan the contents of the about text
420: */
421: protected AboutItem scan(String s) {
422: ArrayList linkRanges = new ArrayList();
423: ArrayList links = new ArrayList();
424:
425: // slightly modified version of jface url detection
426: // see org.eclipse.jface.text.hyperlink.URLHyperlinkDetector
427:
428: int urlSeparatorOffset = s.indexOf("://"); //$NON-NLS-1$
429: while (urlSeparatorOffset >= 0) {
430:
431: boolean startDoubleQuote = false;
432:
433: // URL protocol (left to "://")
434: int urlOffset = urlSeparatorOffset;
435: char ch;
436: do {
437: urlOffset--;
438: ch = ' ';
439: if (urlOffset > -1)
440: ch = s.charAt(urlOffset);
441: startDoubleQuote = ch == '"';
442: } while (Character.isUnicodeIdentifierStart(ch));
443: urlOffset++;
444:
445: // Right to "://"
446: StringTokenizer tokenizer = new StringTokenizer(s
447: .substring(urlSeparatorOffset + 3),
448: " \t\n\r\f<>", false); //$NON-NLS-1$
449: if (!tokenizer.hasMoreTokens())
450: return null;
451:
452: int urlLength = tokenizer.nextToken().length() + 3
453: + urlSeparatorOffset - urlOffset;
454:
455: if (startDoubleQuote) {
456: int endOffset = -1;
457: int nextDoubleQuote = s.indexOf('"', urlOffset);
458: int nextWhitespace = s.indexOf(' ', urlOffset);
459: if (nextDoubleQuote != -1 && nextWhitespace != -1)
460: endOffset = Math.min(nextDoubleQuote,
461: nextWhitespace);
462: else if (nextDoubleQuote != -1)
463: endOffset = nextDoubleQuote;
464: else if (nextWhitespace != -1)
465: endOffset = nextWhitespace;
466: if (endOffset != -1)
467: urlLength = endOffset - urlOffset;
468: }
469:
470: linkRanges.add(new int[] { urlOffset, urlLength });
471: links.add(s.substring(urlOffset, urlOffset + urlLength));
472:
473: urlSeparatorOffset = s.indexOf(
474: "://", urlOffset + urlLength + 1); //$NON-NLS-1$
475: }
476: return new AboutItem(s, (int[][]) linkRanges
477: .toArray(new int[linkRanges.size()][2]),
478: (String[]) links.toArray(new String[links.size()]));
479: }
480:
481: /*
482: * (non-Javadoc)
483: * @see org.eclipse.jface.dialogs.Dialog#isResizable()
484: */
485: protected boolean isResizable() {
486: return true;
487: }
488: }
|