blob: df697e9773fc12e3a4cd6972432ebc363526d731 [file] [log] [blame]
/*******************************************************************************
* 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]);
}
}