/******************************************************************************* * 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.views; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.runtime.Platform; import org.eclipse.help.HelpSystem; import org.eclipse.help.ICommandLink; import org.eclipse.help.IContext; import org.eclipse.help.IContext2; import org.eclipse.help.IContext3; import org.eclipse.help.IContextProvider; import org.eclipse.help.IHelpResource; import org.eclipse.help.IToc; import org.eclipse.help.ITopic; import org.eclipse.help.UAContentFilter; import org.eclipse.help.internal.HelpPlugin; import org.eclipse.help.internal.base.HelpEvaluationContext; import org.eclipse.help.internal.context.Context; import org.eclipse.help.ui.internal.DefaultHelpUI; import org.eclipse.help.ui.internal.ExecuteCommandAction; import org.eclipse.help.ui.internal.HelpUIPlugin; import org.eclipse.help.ui.internal.HelpUIResources; import org.eclipse.help.ui.internal.IHelpUIConstants; import org.eclipse.help.ui.internal.Messages; import org.eclipse.help.ui.internal.util.EscapeUtils; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.dialogs.IDialogPage; import org.eclipse.jface.dialogs.IPageChangeProvider; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.window.Window; import org.eclipse.jface.wizard.IWizardContainer; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.accessibility.ACC; import org.eclipse.swt.accessibility.AccessibleAdapter; import org.eclipse.swt.accessibility.AccessibleEvent; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.custom.CTabItem; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.TabFolder; import org.eclipse.swt.widgets.TabItem; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IMemento; import org.eclipse.ui.IViewPart; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.forms.IFormColors; import org.eclipse.ui.forms.SectionPart; import org.eclipse.ui.forms.events.ExpansionAdapter; import org.eclipse.ui.forms.events.ExpansionEvent; import org.eclipse.ui.forms.events.HyperlinkEvent; import org.eclipse.ui.forms.events.IHyperlinkListener; import org.eclipse.ui.forms.widgets.FormText; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.Section; import org.eclipse.ui.forms.widgets.TableWrapData; import org.eclipse.ui.forms.widgets.TableWrapLayout; import org.eclipse.ui.internal.IWorkbenchHelpContextIds; public class ContextHelpPart extends SectionPart implements IHelpPart { private ReusableHelpPart parent; private static final String HELP_KEY = "org.eclipse.ui.help"; //$NON-NLS-1$ private static final String MORE_HREF = "__more__"; //$NON-NLS-1$ private FormText text; private Control lastControl; private IContextProvider lastProvider; private IContext lastContext; private IWorkbenchPart lastPart; private String defaultText = ""; //$NON-NLS-1$ private String id; private Font codeFont; private String savedDescription; /** * @param parent * @param toolkit * @param style */ public ContextHelpPart(Composite parent, FormToolkit toolkit) { super(parent, toolkit, getSectionStyle()); Section section = getSection(); section.marginWidth = 5; section.setText(Messages.ContextHelpPart_about); Composite container = toolkit.createComposite(section); section.setClient(container); section.addExpansionListener(new ExpansionAdapter() { @Override public void expansionStateChanged(ExpansionEvent e) { if (e.getState()) { updateText(savedDescription); } } }); TableWrapLayout layout = new TableWrapLayout(); layout.topMargin = layout.bottomMargin = 0; layout.leftMargin = layout.rightMargin = 0; layout.verticalSpacing = 10; container.setLayout(layout); text = toolkit.createFormText(container, false); text.setWhitespaceNormalized(false); text.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); text.setColor(IFormColors.TITLE, toolkit.getColors().getColor( IFormColors.TITLE)); codeFont = createCodeFont(parent.getDisplay(), parent.getFont(), JFaceResources.getTextFont()); text.setFont("code", codeFont); //$NON-NLS-1$ String key = IHelpUIConstants.IMAGE_FILE_F1TOPIC; text.setImage(key, HelpUIResources.getImage(key)); key = IHelpUIConstants.IMAGE_COMMAND_F1TOPIC; text.setImage(key, HelpUIResources.getImage(key)); String searchKey = IHelpUIConstants.IMAGE_HELP_SEARCH; text.setImage(searchKey, HelpUIResources.getImage(searchKey)); text.addHyperlinkListener(new IHyperlinkListener() { @Override public void linkActivated(HyperlinkEvent e) { String href = (String) e.getHref(); if (href.startsWith(MORE_HREF)) { doMore(href.substring(MORE_HREF.length())); } else { doOpenLink(e.getHref()); } } @Override public void linkEntered(HyperlinkEvent e) { ContextHelpPart.this.parent.handleLinkEntered(e); } @Override public void linkExited(HyperlinkEvent e) { ContextHelpPart.this.parent.handleLinkExited(e); } }); text.setText(defaultText, false, false); text.getAccessible().addAccessibleListener(new AccessibleAdapter() { @Override public void getName(AccessibleEvent e) { if (e.childID == ACC.CHILDID_SELF) { String currentName = e.result; e.result = Messages.ReusableHelpPart_contextHelpPage_name + ' ' + getSection().getText()+ ' ' +currentName; } } }); } private static int getSectionStyle() { int style = Section.EXPANDED ; if (RelatedTopicsPart.isUseDynamicHelp()) { style = style | Section.TWISTIE; } return style; } private static Font createCodeFont(Display display, Font regularFont, Font textFont) { FontData[] rfontData = regularFont.getFontData(); FontData[] tfontData = textFont.getFontData(); int height = 0; for (int i=0; i<rfontData.length; i++) { FontData data = rfontData[i]; height = Math.max(height, data.getHeight()); } for (int i = 0; i < tfontData.length; i++) { tfontData[i].setHeight(height); } return new Font(display, tfontData); } @Override public void dispose() { if (codeFont!=null) codeFont.dispose(); codeFont = null; super.dispose(); } @Override public Control getControl() { return getSection(); } @Override public void init(ReusableHelpPart parent, String id, IMemento memento) { this.parent = parent; this.id = id; parent.hookFormText(text); } @Override public String getId() { return id; } @Override public void setVisible(boolean visible) { getSection().setVisible(visible); } /** * @return returns the default text */ public String getDefaultText() { return defaultText; } /** * @param defaultText * The defaultText to set. */ public void setDefaultText(String defaultText) { this.defaultText = defaultText; if (text != null) text.setText(defaultText, false, false); } private void doOpenLink(Object href) { String sHref = (String)href; if (sHref.startsWith("command://")) { //$NON-NLS-1$ doRunCommand(sHref.substring(10)); } else { parent.showURL(sHref); } } private void doRunCommand(String serialization) { ExecuteCommandAction action = new ExecuteCommandAction(); action.setInitializationString(serialization); action.run(); } private void updateDescription(String helpText) { if (getSection().isExpanded()) { updateText(helpText); } savedDescription = helpText; } private void updateSearchExpression() { if (lastProvider != null) { String providerSearchExpression = lastProvider.getSearchExpression(lastControl); if (providerSearchExpression != null) { updateSearchExpression(providerSearchExpression, lastControl); return; } } if (lastContext instanceof IContext2) { String title = ((IContext2)lastContext).getTitle(); if (title!=null) { updateSearchExpression(title, lastControl); return; } } if (lastControl != null) updateSearchExpression(null, lastControl); } public void handleActivation(IContextProvider provider, IContext context, Control c, IWorkbenchPart part, boolean isExplicitRequest) { if (text.isDisposed()) return; if (DefaultHelpUI.isOpeningHelpView()) { return; } if (checkForRecentExplicitActivation(isExplicitRequest)) { return; } lastControl = c; lastProvider = provider; lastContext = context; lastPart = part; if (provider!= null && (context==null || ((context instanceof Context) && IWorkbenchHelpContextIds.MISSING.equals(((Context)context).getId())))) { if (HelpPlugin.DEBUG_CONTEXT) { System.out.println("Getting context from provider"); //$NON-NLS-1$ } lastContext = provider.getContext(c); } updateSearchExpression(); String helpText; if (lastContext!=null) { helpText = formatHelpContext(lastContext); if (HelpPlugin.DEBUG_CONTEXT) { System.out.println("Context Activation, context = " + lastContext.getText()); //$NON-NLS-1$ } } else { if (HelpPlugin.DEBUG_CONTEXT) { System.out.println("Context Activation on control"); //$NON-NLS-1$ } helpText = createContextHelp(c); } updateTitle(c); updateDescription(helpText); if (RelatedTopicsPart.isUseDynamicHelp()) { updateDynamicHelp(); } } private long lastUpdate = 0; private String[] searchTerms; /* * If F1 was pressed within the last half second and this is a context change do not * update dynamic help solely due to a focus change, Bug 159450 */ private boolean checkForRecentExplicitActivation(boolean isExplicitRequest) { if (isExplicitRequest) { lastUpdate = System.currentTimeMillis(); return false; } if (lastUpdate == 0) { return false; } long previousUpdate = lastUpdate; lastUpdate = 0; return System.currentTimeMillis() - previousUpdate < 500; } private void updateTitle(Control c) { String title = null; if (lastContext != null && lastContext instanceof IContext2) { IContext2 c2 = (IContext2)lastContext; title = c2.getTitle(); } if (title==null && lastPart != null) title = NLS.bind(Messages.ContextHelpPart_aboutP, lastPart .getSite().getRegisteredName()); if (title == null) { String[] searchTerms = computeSearchTerms(c); if (searchTerms.length > 0) { title = NLS.bind(Messages.ContextHelpPart_aboutP, searchTerms[0]); } } if (title==null) title = Messages.ContextHelpPart_about; getSection().setText(EscapeUtils.escapeForLabel(title)); } private void updateText(String helpText) { try { text.setText(helpText != null ? helpText : defaultText, helpText != null, false); getSection().layout(); getManagedForm().reflow(true); } catch (Exception e) { HelpUIPlugin.logError("Error displaying context help text " + helpText, e); //$NON-NLS-1$ } } private void updateSearchExpression(String expression, Control c) { if (expression == null) { searchTerms = computeSearchTerms(c); } else { searchTerms = new String[] { expression }; } } private void updateDynamicHelp() { RelatedTopicsPart part = (RelatedTopicsPart) parent .findPart(IHelpUIConstants.HV_RELATED_TOPICS); if (part != null) { if (searchTerms != null) { if (HelpPlugin.DEBUG_CONTEXT) { System.out.println("Dynamic help - search for " + searchTerms); //$NON-NLS-1$ } part.startSearch(buildSearchExpression(searchTerms), lastContext); } } } private String buildSearchExpression(String[] searchTerms) { StringBuffer buff = new StringBuffer(); for (int i = 0; i < searchTerms.length; i++) { if (buff.length() > 0) buff.append(" OR "); //$NON-NLS-1$ buff.append('"'); buff.append(searchTerms[i]); buff.append('"'); } return buff.length() > 0 ? buff.toString().trim() : null; } private class SearchTerms { private List<String> terms = new ArrayList<>(); private Set<String> termSet = new HashSet<>(); public void add(String term) { if (term == null ) return; String lowerCaseTerm = term.toLowerCase(); // Do not allow duplicates if (!termSet.contains(lowerCaseTerm)) { termSet.add(lowerCaseTerm); terms.add(term); } } public String[] toArray() { return terms.toArray(new String[terms.size()]); } } /* * Used for both dynamic help and to get a useful title */ private String[] computeSearchTerms(Control c) { // Search the control and all ancestors until we find a composite // which we can get a search term from Composite container = c instanceof Composite ? (Composite)c : c.getParent(); SearchTerms searchTerms = new SearchTerms(); while (container != null) { Object data = container.getData(); if (data instanceof IWizardContainer) { IWizardContainer wc = (IWizardContainer) data; searchTerms.add(wc.getCurrentPage().getTitle()); searchTerms.add(wc.getCurrentPage().getWizard().getWindowTitle()); break; } else if (data instanceof IWorkbenchWindow) { IWorkbenchWindow window = (IWorkbenchWindow) data; IWorkbenchPage page = window.getActivePage(); if (page != null) { IWorkbenchPart part = lastPart; if (part != null) { if (part instanceof IViewPart) { searchTerms.add(NLS.bind( Messages.ContextHelpPart_query_view, part .getSite().getRegisteredName())); } else if (part instanceof IEditorPart) { if (part.getSite() != null && part.getSite().getRegisteredName() != null) { searchTerms.add(part.getSite().getRegisteredName()); } } } /* // Searching by perspective seems counterproductive - CG IPerspectiveDescriptor persp = page.getPerspective(); if (persp != null) { searchTerms.add(NLS.bind( Messages.ContextHelpPart_query_perspective, persp.getLabel())); } */ } break; } else if (data instanceof Window) { Window w = (Window) data; if (w instanceof IPageChangeProvider) { Object page = ((IPageChangeProvider) w).getSelectedPage(); String pageName = getPageName(c, page); if (pageName != null) { searchTerms.add(pageName); } } searchTerms.add(w.getShell().getText()); break; } container = container.getParent(); } return searchTerms.toArray(); } private String getPageName(Control focusControl, Object page) { if (page instanceof IDialogPage) return ((IDialogPage) page).getTitle(); if (focusControl == null) return null; Composite parent = focusControl.getParent(); while (parent != null) { if (parent instanceof TabFolder) { TabItem[] selection = ((TabFolder) parent).getSelection(); if (selection.length == 1) return stripMnemonic(selection[0].getText()); } else if (parent instanceof CTabFolder) { CTabItem selection = ((CTabFolder) parent).getSelection(); return stripMnemonic(selection.getText()); } parent = parent.getParent(); } return null; } private String stripMnemonic(String name) { int loc = name.indexOf('&'); if (loc!= -1) return name.substring(0, loc)+name.substring(loc+1); return name; } private String createContextHelp(Control page) { String text = null; lastContext = null; if (page != null) { if (page != null /* && page.isVisible() */&& !page.isDisposed()) { IContext helpContext = findHelpContext(page); if (helpContext != null) { text = formatHelpContext(helpContext); lastContext = helpContext; } } } return text; } public static IContext findHelpContext(Control c) { String contextId = null; Control node = c; do { contextId = (String) node.getData(HELP_KEY); if (contextId != null) break; node = node.getParent(); } while (node != null); if (contextId != null) { return HelpSystem.getContext(contextId); } return null; } private String formatHelpContext(IContext context) { String locale = Platform.getNL(); StringBuffer sbuf = new StringBuffer(); sbuf.append("<form>"); //$NON-NLS-1$ sbuf.append("<p>"); //$NON-NLS-1$ sbuf.append(decodeContextBoldTags(context)); sbuf.append("</p>"); //$NON-NLS-1$ ICommandLink[] commands = null; if (context instanceof IContext3) { commands = ((IContext3)context).getRelatedCommands(); } String category = new String(); if (commands != null && commands.length > 0) { for (int i=0;i<commands.length;++i) { if (!UAContentFilter.isFiltered(commands[i], HelpEvaluationContext.getContext())) { if (category != null) { addCategory(sbuf, null); } category = null; sbuf.append("<li style=\"image\" value=\""); //$NON-NLS-1$ sbuf.append(IHelpUIConstants.IMAGE_COMMAND_F1TOPIC); sbuf.append("\" indent=\"21\">"); //$NON-NLS-1$ sbuf.append("<a href=\"command://"); //$NON-NLS-1$ sbuf.append(commands[i].getSerialization()); sbuf.append("\">"); //$NON-NLS-1$ sbuf.append(EscapeUtils.escapeSpecialChars(commands[i].getLabel())); sbuf.append("</a>"); //$NON-NLS-1$ sbuf.append("</li>"); //$NON-NLS-1$ } } } IHelpResource[] links = context.getRelatedTopics(); if (links != null && context instanceof IContext2) { ContextHelpSorter sorter = new ContextHelpSorter((IContext2)context); sorter.sort(null, links); } if (links != null && links.length > 0) { for (int i = 0; i < links.length; i++) { IHelpResource link = links[i]; if (!UAContentFilter.isFiltered(link, HelpEvaluationContext.getContext())) { String cat = null; if (context instanceof IContext2) { cat = ((IContext2)context).getCategory(link); } if (cat == null && category != null || cat != null && category == null || cat != null && category != null && !cat.equals(category)) { addCategory(sbuf, cat); } category = cat; sbuf.append("<li style=\"image\" value=\""); //$NON-NLS-1$ sbuf.append(IHelpUIConstants.IMAGE_FILE_F1TOPIC); sbuf.append("\" indent=\"21\">"); //$NON-NLS-1$ sbuf.append("<a href=\""); //$NON-NLS-1$ sbuf.append(EscapeUtils.escapeAmpersand(link.getHref())); String tcat = getTopicCategory(link.getHref(), locale); if (tcat != null && !Platform.getWS().equals(Platform.WS_GTK)) { sbuf.append("\" alt=\""); //$NON-NLS-1$ sbuf.append(EscapeUtils.escapeSpecialChars(tcat)); } sbuf.append("\">"); //$NON-NLS-1$ sbuf.append(EscapeUtils.escapeSpecialChars(link.getLabel())); sbuf.append("</a>"); //$NON-NLS-1$ sbuf.append("</li>"); //$NON-NLS-1$ } else { if (HelpPlugin.DEBUG_CONTEXT) { System.out.println("Link is filtered out: " + link.getLabel()); //$NON-NLS-1$ } } } } if (!RelatedTopicsPart.isUseDynamicHelp() && searchTerms != null && searchTerms.length > 0) { sbuf.append("<p><span color=\""); //$NON-NLS-1$ sbuf.append(IFormColors.TITLE); sbuf.append("\">"); //$NON-NLS-1$ sbuf.append(Messages.ContextHelpPart_more); sbuf.append("</span></p>"); //$NON-NLS-1$ for (int term = 0; term < searchTerms.length; term++) { sbuf.append("<p><img href=\""); //$NON-NLS-1$ sbuf.append(IHelpUIConstants.IMAGE_HELP_SEARCH); sbuf.append("\"/>"); //$NON-NLS-1$ sbuf.append(" <a href=\""); //$NON-NLS-1$ sbuf.append(MORE_HREF); sbuf.append(term); sbuf.append("\">"); //$NON-NLS-1$ String searchForMessage = NLS.bind(Messages.ContextHelpPart_searchFor, searchTerms[term]); sbuf.append(EscapeUtils.escapeSpecialChars(searchForMessage)); sbuf.append("</a></p>"); //$NON-NLS-1$ } } sbuf.append("</form>"); //$NON-NLS-1$ return sbuf.toString(); } private void addCategory(StringBuffer sbuf, String category) { if (category == null) category = Messages.ContextHelpPart_seeAlso; sbuf.append("<p><span color=\""); //$NON-NLS-1$ sbuf.append(IFormColors.TITLE); sbuf.append("\">"); //$NON-NLS-1$ sbuf.append(category); sbuf.append("</span></p>"); //$NON-NLS-1$ } private String getTopicCategory(String href, String locale) { IToc[] tocs = HelpPlugin.getTocManager().getTocs(locale); for (int i = 0; i < tocs.length; i++) { ITopic topic = tocs[i].getTopic(href); if (topic != null) return tocs[i].getLabel(); } return null; } /** * Make sure to support the Help system bold tag. The help system returns a * regular string for getText(). Use internal apis for now to get bold. * * @param context * @return */ private String decodeContextBoldTags(IContext context) { String styledText; if (context instanceof IContext2) { styledText = ((IContext2) context).getStyledText(); if (styledText == null) { styledText = context.getText(); } } else { styledText = context.getText(); } if (styledText == null) { return Messages.ContextHelpPart_noDescription; } String decodedString = styledText.replaceAll("<@#\\$b>", "<b>"); //$NON-NLS-1$ //$NON-NLS-2$ decodedString = decodedString.replaceAll("</@#\\$b>", "</b>"); //$NON-NLS-1$ //$NON-NLS-2$ decodedString = EscapeUtils.escapeSpecialCharsLeavinggBold(decodedString); decodedString = decodedString.replaceAll("\r\n|\n|\r", "<br/>"); //$NON-NLS-1$ //$NON-NLS-2$ return decodedString; } @Override public boolean setFormInput(Object input) { if (input instanceof ContextHelpProviderInput) { ContextHelpProviderInput chinput = (ContextHelpProviderInput) input; handleActivation(chinput.getProvider(), chinput.getContext(), chinput.getControl(), chinput.getPart(), chinput.isExplicitRequest()); return true; } return false; } @Override public void setFocus() { if (text != null) text.setFocus(); } @Override public boolean fillContextMenu(IMenuManager manager) { return parent.fillFormContextMenu(text, manager); } @Override public boolean hasFocusControl(Control control) { return text.equals(control); } @Override public IAction getGlobalAction(String id) { if (id.equals(ActionFactory.COPY.getId())) return parent.getCopyAction(); return null; } @Override public void stop() { } @Override public void toggleRoleFilter() { } @Override public void refilter() { } @Override public void saveState(IMemento memento) { } private void doMore(String moreText) { int index = Integer.parseInt(moreText); parent.startSearch(searchTerms[index]); } }