/******************************************************************************* * Copyright (c) 2000, 2016 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.help.ui.internal; import org.eclipse.core.runtime.Platform; import org.eclipse.help.IContext; import org.eclipse.help.IContext2; import org.eclipse.help.IHelpResource; import org.eclipse.help.UAContentFilter; import org.eclipse.help.internal.base.BaseHelpSystem; import org.eclipse.help.internal.base.HelpEvaluationContext; import org.eclipse.help.ui.internal.views.HelpTray; import org.eclipse.osgi.service.environment.Constants; import org.eclipse.swt.SWT; import org.eclipse.swt.accessibility.ACC; import org.eclipse.swt.accessibility.Accessible; import org.eclipse.swt.accessibility.AccessibleAdapter; import org.eclipse.swt.accessibility.AccessibleControlAdapter; import org.eclipse.swt.accessibility.AccessibleControlEvent; import org.eclipse.swt.accessibility.AccessibleEvent; import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; /** * ContextHelpDialog */ public class ContextHelpDialog { //private static ImageRegistry imgRegistry = null; private Color backgroundColour = null; private IContext context; private Color foregroundColour = null; private Color linkColour = null; private static HyperlinkHandler linkManager = new HyperlinkHandler(); protected Shell parentShell; protected Shell shell; protected String infopopText; /** * Listener for hyperlink selection. */ class LinkListener extends HyperlinkAdapter { IHelpResource topic; public LinkListener(IHelpResource topic) { this.topic = topic; } @Override public void linkActivated(Control c) { launchLinks(topic); } } /** * Constructor: * * @param context * an array of String or an array of IContext * @param x * the x mouse location in the current display * @param y * the y mouse location in the current display */ ContextHelpDialog(IContext context, int x, int y) { this.context = context; Display display = Display.getCurrent(); if (display == null) { return; } backgroundColour = display.getSystemColor(SWT.COLOR_INFO_BACKGROUND); foregroundColour = display.getSystemColor(SWT.COLOR_INFO_FOREGROUND); linkColour = display.getSystemColor(SWT.COLOR_BLUE); parentShell = PlatformUI.getWorkbench().getModalDialogShellProvider().getShell(); if (parentShell != null) { boolean isModal = 0 < (parentShell.getStyle() & (SWT.APPLICATION_MODAL | SWT.PRIMARY_MODAL | SWT.SYSTEM_MODAL)); if (HelpUIPlugin.DEBUG_INFOPOP) { System.out .println("ContextHelpDialog.ContextHelpDialog(): ParentShell: " //$NON-NLS-1$ + parentShell.toString() + " is " //$NON-NLS-1$ + (isModal ? "modal" : "modeless")); //$NON-NLS-1$ //$NON-NLS-2$ } } shell = new Shell(parentShell, SWT.NONE); if (HelpUIPlugin.DEBUG_INFOPOP) { System.out .println("ContextHelpDialog.ContextHelpDialog(): Shell is:" //$NON-NLS-1$ + shell.toString()); } PlatformUI.getWorkbench().getHelpSystem().setHelp(shell, IHelpUIConstants.F1_SHELL); shell.addListener(SWT.Deactivate, e -> { if (HelpUIPlugin.DEBUG_INFOPOP) { System.out.println("ContextHelpDialog shell deactivate listener: SWT.Deactivate called. "); //$NON-NLS-1$ } close(); }); shell.addTraverseListener(e -> { if (e.detail == SWT.TRAVERSE_ESCAPE) { if (HelpUIPlugin.DEBUG_INFOPOP) { System.out.println( "ContextHelpDialog: shell traverse listener: SWT.TRAVERSE_ESCAPE called. "); //$NON-NLS-1$ } e.doit = true; } }); shell.addControlListener(new ControlAdapter() { @Override public void controlMoved(ControlEvent e) { if (HelpUIPlugin.DEBUG_INFOPOP) { System.out .println("ContextHelpDialog: shell control adapter called."); //$NON-NLS-1$ } Rectangle clientArea = shell.getClientArea(); shell.redraw(clientArea.x, clientArea.y, clientArea.width, clientArea.height, true); shell.update(); } }); if (HelpUIPlugin.DEBUG_INFOPOP) { System.out .println("ContextHelpDialog.ContextHelpDialog(): Focus owner is: " //$NON-NLS-1$ + Display.getCurrent().getFocusControl().toString()); } linkManager .setHyperlinkUnderlineMode(HyperlinkHandler.UNDERLINE_ALWAYS); createContents(shell); shell.pack(); // Correct x and y of the shell if it not contained within the screen int width = shell.getBounds().width; int height = shell.getBounds().height; Rectangle screen = display.getClientArea(); // check lower boundaries x = x >= screen.x ? x : screen.x; y = y >= screen.y ? y : screen.y; // check upper boundaries x = x + width <= screen.width ? x : screen.width - width; y = y + height <= screen.height ? y : screen.height - height; shell.setLocation(x, y); initAccessible(shell); } public synchronized void close() { try { if (HelpUIPlugin.DEBUG_INFOPOP) { System.out.println("ContextHelpDialog.close()"); //$NON-NLS-1$ } if (shell != null) { shell.close(); if (!shell.isDisposed()) shell.dispose(); shell = null; } } catch (Throwable ex) { } } protected Control createContents(Composite contents) { initAccessible(contents); contents.setBackground(backgroundColour); GridLayout layout = new GridLayout(); layout.marginHeight = 5; layout.marginWidth = 5; contents.setLayout(layout); contents.setLayoutData(new GridData(GridData.FILL_BOTH)); // create the dialog area and button bar createInfoArea(contents); Control c = createLinksArea(contents); if (c != null) { // links exist, make them the only focusable controls contents.setTabList(new Control[] { c }); } return contents; } private Control createInfoArea(Composite parent) { // Create the text field. String styledText = null; if (context instanceof IContext2) { styledText = ((IContext2) context).getStyledText(); } if (styledText == null && context.getText() != null) { styledText = context.getText(); styledText= styledText.replaceAll("<b>","<@#\\$b>"); //$NON-NLS-1$ //$NON-NLS-2$ styledText= styledText.replaceAll("</b>", "</@#\\$b>"); //$NON-NLS-1$ //$NON-NLS-2$ } if (styledText == null) { // no description found in context objects. styledText = Messages.ContextHelpPart_noDescription; } Description text = new Description(parent, SWT.MULTI | SWT.READ_ONLY); text.addTraverseListener(e -> { if (e.detail == SWT.TRAVERSE_ESCAPE) { if (HelpUIPlugin.DEBUG_INFOPOP) { System.out.println( "ContextHelpDialog text TraverseListener.handleEvent(): SWT.TRAVERSE_ESCAPE."); //$NON-NLS-1$ } e.doit = true; } }); text.getCaret().setVisible(false); text.setBackground(backgroundColour); text.setForeground(foregroundColour); text.setFont(parent.getFont()); int linkWidth = getLinksWidth(text); StyledLineWrapper content = new StyledLineWrapper(styledText, text, linkWidth + 70); text.setContent(content); text.setStyleRanges(content.getStyles()); infopopText = text.getText(); initAccessible(text); return text; } /** * Measures the longest label of related links * * @param text * @return */ private int getLinksWidth(Description text) { int linkWidth = 0; IHelpResource relatedTopics[] = context.getRelatedTopics(); if (relatedTopics != null) { GC gc = new GC(text); for (int i = 0; i < relatedTopics.length; i++) { linkWidth = Math.max(linkWidth, gc.textExtent(relatedTopics[i] .getLabel()).x); } gc.dispose(); } return linkWidth; } private Control createLink(Composite parent, IHelpResource topic) { Label image = new Label(parent, SWT.NONE); image.setImage(getImage()); image.setBackground(backgroundColour); GridData data = new GridData(); data.horizontalAlignment = GridData.HORIZONTAL_ALIGN_BEGINNING; data.verticalAlignment = GridData.VERTICAL_ALIGN_BEGINNING; //data.horizontalIndent = 4; image.setLayoutData(data); HyperlinkLabel link = new HyperlinkLabel(parent, SWT.NONE); link.setText(topic.getLabel()); link.setBackground(backgroundColour); link.setForeground(linkColour); link.setFont(parent.getFont()); linkManager.registerHyperlink(link, new LinkListener(topic)); return link; } private Control createLinksArea(Composite parent) { IHelpResource[] relatedTopics = context.getRelatedTopics(); if (relatedTopics == null) return null; // Create control Composite composite = new Composite(parent, SWT.NONE); initAccessible(composite); composite.setBackground(backgroundColour); GridLayout layout = new GridLayout(); layout.marginHeight = 2; layout.marginWidth = 0; layout.verticalSpacing = 3; layout.horizontalSpacing = 2; layout.numColumns = 2; composite.setLayout(layout); composite.setFont(parent.getFont()); GridData data = new GridData(GridData.FILL_BOTH | GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.VERTICAL_ALIGN_CENTER); composite.setLayoutData(data); // Create separator. Label label = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL); label.setBackground(backgroundColour); label.setForeground(foregroundColour); data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.VERTICAL_ALIGN_BEGINNING | GridData.FILL_HORIZONTAL); data.horizontalSpan = 2; label.setLayoutData(data); // Create related links for (int i = 0; i < relatedTopics.length; i++) { if (!UAContentFilter.isFiltered(relatedTopics[i], HelpEvaluationContext.getContext())) { createLink(composite, relatedTopics[i]); } } // create dynamic help link if current context allows dynamic help IWorkbenchWindow wbWindow = HelpUIPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow(); if (DefaultHelpUI.isActiveShell(parentShell, wbWindow) || HelpTray.isAppropriateFor(parentShell)) { // Create separator. label = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL); label.setBackground(backgroundColour); label.setForeground(foregroundColour); data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.VERTICAL_ALIGN_BEGINNING | GridData.FILL_HORIZONTAL); data.horizontalSpan = 2; label.setLayoutData(data); // create link to the dynamic help createDynamicHelpLink(composite); } return composite; } private Control createDynamicHelpLink(Composite parent) { Label image = new Label(parent, SWT.NONE); Image img = HelpUIResources.getImage(IHelpUIConstants.IMAGE_DHELP); image.setImage(img); image.setBackground(backgroundColour); GridData data = new GridData(); data.horizontalAlignment = GridData.HORIZONTAL_ALIGN_BEGINNING; data.verticalAlignment = GridData.VERTICAL_ALIGN_BEGINNING; //data.horizontalIndent = 4; image.setLayoutData(data); HyperlinkLabel link = new HyperlinkLabel(parent, SWT.NONE); link.setText(Messages.ContextHelpDialog_showInDynamicHelp); link.setBackground(backgroundColour); link.setForeground(linkColour); link.setFont(parent.getFont()); linkManager.registerHyperlink(link, new HyperlinkAdapter() { @Override public void linkActivated(Control label) { openDynamicHelp(); } }); return link; } /** * Called when related link has been chosen Opens help viewer with list of * all related topics */ protected void launchLinks(IHelpResource selectedTopic) { close(); if (HelpUIPlugin.DEBUG_INFOPOP) { System.out.println("ContextHelpDialog.launchLinks(): closed shell"); //$NON-NLS-1$ } BaseHelpSystem.getHelpDisplay().displayHelp( context, selectedTopic, DefaultHelpUI.isDisplayModal(parentShell) && !Constants.OS_WIN32.equalsIgnoreCase(Platform .getOS())); } private void openDynamicHelp() { BusyIndicator.showWhile(shell.getDisplay(), () -> { close(); DefaultHelpUI.getInstance().displayContext(context, 0, 0, true); }); } public synchronized void open() { try { shell.open(); if (HelpUIPlugin.DEBUG_INFOPOP) { System.out .println("ContextHelpDialog.open(): Focus owner after open is: " //$NON-NLS-1$ + Display.getCurrent().getFocusControl() .toString()); } } catch (Throwable e) { HelpUIPlugin .logError( "An error occurred when opening context-sensitive help pop-up.", //$NON-NLS-1$ e); } } private Image getImage() { return HelpUIResources.getImage(IHelpUIConstants.IMAGE_FILE_F1TOPIC); } public boolean isShowing() { return (shell != null && !shell.isDisposed() && shell.isVisible()); } private void initAccessible(final Control control) { Accessible accessible = control.getAccessible(); accessible.addAccessibleListener(new AccessibleAdapter() { @Override public void getName(AccessibleEvent e) { e.result = infopopText; } @Override public void getHelp(AccessibleEvent e) { e.result = control.getToolTipText(); } }); accessible.addAccessibleControlListener(new AccessibleControlAdapter() { @Override public void getChildAtPoint(AccessibleControlEvent e) { Point pt = control.toControl(new Point(e.x, e.y)); e.childID = (control.getBounds().contains(pt)) ? ACC.CHILDID_MULTIPLE : ACC.CHILDID_NONE; } @Override public void getLocation(AccessibleControlEvent e) { Rectangle location = control.getBounds(); Point pt = control.toDisplay(new Point(location.x, location.y)); e.x = pt.x; e.y = pt.y; e.width = location.width; e.height = location.height; } @Override public void getChildCount(AccessibleControlEvent e) { e.detail = 1; } @Override public void getRole(AccessibleControlEvent e) { e.detail = ACC.ROLE_LABEL; } @Override public void getState(AccessibleControlEvent e) { e.detail = ACC.STATE_READONLY; } }); } public class Description extends StyledText { /** * @param parent * @param style */ public Description(Composite parent, int style) { super(parent, style); } @Override public boolean setFocus() { return false; } @Override public boolean isFocusControl() { return false; } } }