blob: 40734c93667542c12bade211024e5eb2d87e03a2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 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.ui.internal.intro.impl.model.url;
import java.util.Enumeration;
import java.util.Properties;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.commands.common.CommandException;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.util.Geometry;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IActionDelegate;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.IWorkbenchWindowActionDelegate;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.internal.AnimationEngine;
import org.eclipse.ui.internal.intro.impl.IIntroConstants;
import org.eclipse.ui.internal.intro.impl.IntroPlugin;
import org.eclipse.ui.internal.intro.impl.Messages;
import org.eclipse.ui.internal.intro.impl.model.AbstractIntroElement;
import org.eclipse.ui.internal.intro.impl.model.AbstractIntroPage;
import org.eclipse.ui.internal.intro.impl.model.IntroLaunchBarElement;
import org.eclipse.ui.internal.intro.impl.model.IntroModelRoot;
import org.eclipse.ui.internal.intro.impl.model.IntroPartPresentation;
import org.eclipse.ui.internal.intro.impl.model.IntroURLAction;
import org.eclipse.ui.internal.intro.impl.model.loader.ExtensionPointManager;
import org.eclipse.ui.internal.intro.impl.model.loader.ModelLoaderUtil;
import org.eclipse.ui.internal.intro.impl.model.util.ModelUtil;
import org.eclipse.ui.internal.intro.impl.parts.StandbyPart;
import org.eclipse.ui.internal.intro.impl.presentations.BrowserIntroPartImplementation;
import org.eclipse.ui.internal.intro.impl.presentations.IntroLaunchBar;
import org.eclipse.ui.internal.intro.impl.util.DialogUtil;
import org.eclipse.ui.internal.intro.impl.util.Log;
import org.eclipse.ui.internal.intro.impl.util.StringUtil;
import org.eclipse.ui.internal.intro.impl.util.Util;
import org.eclipse.ui.intro.IIntroPart;
import org.eclipse.ui.intro.IIntroSite;
import org.eclipse.ui.intro.config.CustomizableIntroPart;
import org.eclipse.ui.intro.config.IIntroAction;
import org.eclipse.ui.intro.config.IIntroURL;
import org.eclipse.ui.intro.config.IntroURLFactory;
/**
* An intro url. An intro URL is a valid http url, with org.eclipse.ui.intro as
* a host. This class holds all logic to execute Intro URL commands, ie: an
* Intro URL knows how to execute itself.
*/
public class IntroURL implements IIntroURL {
/**
* Intro URL constants.
*/
public static final String INTRO_PROTOCOL = "http"; //$NON-NLS-1$
public static final String INTRO_HOST_ID = "org.eclipse.ui.intro"; //$NON-NLS-1$
/**
* Constants that represent Intro URL actions.
*/
public static final String SET_STANDBY_MODE = "setStandbyMode"; //$NON-NLS-1$
public static final String SHOW_STANDBY = "showStandby"; //$NON-NLS-1$
public static final String CLOSE = "close"; //$NON-NLS-1$
public static final String SHOW_HELP_TOPIC = "showHelpTopic"; //$NON-NLS-1$
public static final String SHOW_HELP = "showHelp"; //$NON-NLS-1$
public static final String OPEN_BROWSER = "openBrowser"; //$NON-NLS-1$
public static final String OPEN_URL = "openURL"; //$NON-NLS-1$
public static final String RUN_ACTION = "runAction"; //$NON-NLS-1$
public static final String SHOW_PAGE = "showPage"; //$NON-NLS-1$
public static final String SHOW_MESSAGE = "showMessage"; //$NON-NLS-1$
public static final String NAVIGATE = "navigate"; //$NON-NLS-1$
public static final String SWITCH_TO_LAUNCH_BAR = "switchToLaunchBar"; //$NON-NLS-1$
public static final String EXECUTE = "execute"; //$NON-NLS-1$
/**
* Constants that represent valid action keys.
*/
public static final String KEY_ID = "id"; //$NON-NLS-1$
public static final String KEY_PLUGIN_ID = "pluginId"; //$NON-NLS-1$
public static final String KEY_CLASS = "class"; //$NON-NLS-1$
public static final String KEY_STANDBY = "standby"; //$NON-NLS-1$
public static final String KEY_PART_ID = "partId"; //$NON-NLS-1$
public static final String KEY_INPUT = "input"; //$NON-NLS-1$
public static final String KEY_MESSAGE = "message"; //$NON-NLS-1$
public static final String KEY_URL = "url"; //$NON-NLS-1$
public static final String KEY_DIRECTION = "direction"; //$NON-NLS-1$
public static final String KEY_EMBED = "embed"; //$NON-NLS-1$
public static final String KEY_EMBED_TARGET = "embedTarget"; //$NON-NLS-1$
public static final String KEY_DECODE = "decode"; //$NON-NLS-1$
public static final String KEY_COMAND = "command"; //$NON-NLS-1$
public static final String VALUE_BACKWARD = "backward"; //$NON-NLS-1$
public static final String VALUE_FORWARD = "forward"; //$NON-NLS-1$
public static final String VALUE_HOME = "home"; //$NON-NLS-1$
public static final String VALUE_TRUE = "true"; //$NON-NLS-1$
public static final String VALUE_FALSE = "false"; //$NON-NLS-1$
public static final String VALUE_CLOSE = "close"; //$NON-NLS-1$
public static final String VALUE_STANDBY = "standby"; //$NON-NLS-1$
public static final String VALUE_LAUNCHBAR = "launchbar"; //$NON-NLS-1$
private String action = null;
private Properties parameters = null;
/**
* Prevent creation. Must be created through an IntroURLParser. This
* constructor assumed we have a valid intro url.
*
* @param url
*/
IntroURL(String action, Properties parameters) {
this.action = action;
this.parameters = parameters;
}
/**
* Executes whatever valid Intro action is embedded in this Intro URL.
*
*/
@Override
public boolean execute() {
final boolean[] result = new boolean[1];
Display display = Display.getCurrent();
BusyIndicator.showWhile(display, () -> result[0] = doExecute());
return result[0];
}
protected boolean doExecute() {
if (Log.logInfo) {
String msg = StringUtil.concat("Running Intro URL action ", action, //$NON-NLS-1$
" with parameters: ", parameters.toString()).toString(); //$NON-NLS-1$
Log.info(msg);
}
// check for all supported Intro actions first.
if (action.equals(CLOSE))
return closeIntro();
else if (action.equals(SET_STANDBY_MODE))
// Sets the state of the intro part. Does not care about passing
// input to the part.
return setStandbyState(getParameter(KEY_STANDBY));
else if (action.equals(SHOW_STANDBY))
return handleStandbyState(getParameter(KEY_PART_ID),
getParameter(KEY_INPUT));
else if (action.equals(SHOW_HELP))
// display the full Help System.
return showHelp();
else if (action.equals(SHOW_HELP_TOPIC))
// display a Help System Topic. It can be displayed in the Help
// system window, or embedded as an intro page.
// return showHelpTopic(getParameter(KEY_ID));
return showHelpTopic(getParameter(KEY_ID), getParameter(KEY_EMBED),
getParameter(KEY_EMBED_TARGET));
else if (action.equals(OPEN_BROWSER))
// display url in external browser
return openBrowser(getParameter(KEY_URL),
getParameter(KEY_PLUGIN_ID));
if (action.equals(OPEN_URL))
// display url embedded in intro browser.
return openURL(getParameter(KEY_URL), getParameter(KEY_PLUGIN_ID));
else if (action.equals(RUN_ACTION))
// run an Intro action. Get the pluginId and the class keys. Pass
// the parameters and the standby state.
return runAction(getParameter(KEY_PLUGIN_ID),
getParameter(KEY_CLASS), parameters, getParameter(KEY_STANDBY));
else if (action.equals(EXECUTE))
// execute a serialized command
return executeCommand(getParameter(KEY_COMAND), getParameter(KEY_STANDBY));
else if (action.equals(SHOW_PAGE))
// display an Intro Page.
return showPage(getParameter(KEY_ID), getParameter(KEY_STANDBY));
else if (action.equals(SHOW_MESSAGE))
return showMessage(getParameter(KEY_MESSAGE));
else if (action.equals(NAVIGATE))
return navigate(getParameter(KEY_DIRECTION));
else if (action.equals(SWITCH_TO_LAUNCH_BAR))
return switchToLaunchBar();
else
return handleCustomAction();
}
private boolean closeIntro() {
// Relies on Workbench.
return IntroPlugin.closeIntro();
}
/**
* Sets the into part to standby, and shows the passed standby part, with
* the given input. Forces the Intro view to open, if not yet created.
*
* @param partId
* @param input
*/
private boolean handleStandbyState(String partId, String input) {
// set intro to standby mode. we know we have a customizable part.
CustomizableIntroPart introPart = (CustomizableIntroPart) IntroPlugin
.getIntro();
if (introPart == null)
introPart = (CustomizableIntroPart) IntroPlugin.showIntro(true);
// store the flag to indicate that standbypart is needed.
introPart.getControl().setData(IIntroConstants.SHOW_STANDBY_PART,
VALUE_TRUE);
IntroPlugin.setIntroStandby(true);
StandbyPart standbyPart = (StandbyPart) introPart
.getAdapter(StandbyPart.class);
boolean success = standbyPart.showContentPart(partId, input);
if (success)
return true;
// we do not have a valid partId or we failed to instantiate part or
// create the part content, empty part will be shown. Signal failure.
return false;
}
/**
* Set the Workbench Intro Part state. Forces the Intro view to open, if not yet created.
*
* Historically this value was "true" (show standby) or "false" (show normal). In Neon we add
* "close", "standby" and "launchbar".
*
* @param state
* @return true if the intro was shown, or false if the intro could not be shown
*/
private boolean setStandbyState(String state) {
if (state == null)
return false;
if (state.equals(VALUE_CLOSE)) {
return IntroPlugin.closeIntro();
} else if (state.equals(VALUE_LAUNCHBAR)) {
return switchToLaunchBar();
}
boolean standby = state.equals(VALUE_TRUE) || state.equals(VALUE_STANDBY);
IIntroPart introPart = IntroPlugin.showIntro(standby);
if (introPart == null)
return false;
return true;
}
/**
* Run an action
*/
private boolean runAction(String pluginId, String className,
Properties parameters, String standbyState) {
Object actionObject = ModelLoaderUtil.createClassInstance(pluginId,
className);
try {
if (actionObject instanceof IIntroAction) {
IIntroAction introAction = (IIntroAction) actionObject;
IIntroSite site = IntroPlugin.getDefault().getIntroModelRoot()
.getPresentation().getIntroPart().getIntroSite();
introAction.run(site, parameters);
} else if (actionObject instanceof IAction) {
IAction action = (IAction) actionObject;
action.run();
} else if (actionObject instanceof IActionDelegate) {
final IActionDelegate delegate = (IActionDelegate) actionObject;
if (delegate instanceof IWorkbenchWindowActionDelegate)
((IWorkbenchWindowActionDelegate) delegate).init(PlatformUI
.getWorkbench().getActiveWorkbenchWindow());
Action proxy = new Action(this.action) {
@Override
public void run() {
delegate.run(this);
}
};
proxy.run();
} else
// we could not create the class.
return false;
// ran action successfully. Now set intro intro standby if needed.
if (standbyState == null)
return true;
return setStandbyState(standbyState);
} catch (Exception e) {
Log.error("Could not run action: " + className, e); //$NON-NLS-1$
return false;
}
}
/**
* Executes a serialized <code>ParameterizedCommand</code>. Uses
* {@link ICommandService#deserialize(String)} to convert the <code>command</code> argument
* into the parameterized command.
*/
private boolean executeCommand(String command, String standbyState) {
ICommandService commandService = getCommandService();
IHandlerService handlerService = getHandlerService();
if (commandService == null || handlerService == null) {
Log.error("Could not get ICommandService or IHandlerService while trying to execute: " + command, null); //$NON-NLS-1$
return false;
}
try {
ParameterizedCommand pCommand = commandService.deserialize(command);
handlerService.executeCommand(pCommand, null);
// Executed command successfully. Now set intro standby if needed.
if (standbyState == null)
return true;
return setStandbyState(standbyState);
} catch (CommandException ex) {
Log.error("Could not execute command: " + command, ex); //$NON-NLS-1$
return false;
}
}
private ICommandService getCommandService() {
IWorkbench wb = PlatformUI.getWorkbench();
if (wb != null) {
Object serviceObject = wb.getAdapter(ICommandService.class);
if (serviceObject != null) {
ICommandService service = (ICommandService)serviceObject;
return service;
}
}
return null;
}
private IHandlerService getHandlerService() {
IWorkbench wb = PlatformUI.getWorkbench();
if (wb != null) {
Object serviceObject = wb.getAdapter(IHandlerService.class);
if (serviceObject != null) {
IHandlerService service = (IHandlerService)serviceObject;
return service;
}
}
return null;
}
/**
* Open a help topic. If embed="true", open the help href as an intro page.
* If false, open the href in the Help system window. If embedTarget is set,
* then the Help System topic is embedded instead of the content of the
* specified div.<br>
* In the case of SWT presentation, embedd flag is ignored and the topic is
* opened in the Help system window.
*/
private boolean showHelpTopic(String href, String embed, String embedTarget) {
return new ShowHelpURLHandler(this).showHelpTopic(href, embed,
embedTarget);
}
/**
* Open the help system.
*/
private boolean showHelp() {
PlatformUI.getWorkbench().getHelpSystem().displayHelp();
return true;
}
/**
* Launch external browser.
*/
private boolean openBrowser(String url, String pluginId) {
// no need to decode url because we will create another url from this
// url anyway. Resolve the url just in case we are trying to load a
// plugin relative file.
url = ModelUtil.resolveURL(url, pluginId);
return Util.openBrowser(url);
}
/**
* Show a URL in an intro page. This is the embedded version of the intro
* action openBrowser(). It is useful when trying to show an html file
* relative to another plugin. When the presentation is UI forms
* presentation, this call behaves exactly as the openBrowser intro action.
*/
private boolean openURL(String url, String pluginId) {
IntroModelRoot model = IntroPlugin.getDefault().getIntroModelRoot();
String presentationStyle = model.getPresentation()
.getImplementationKind();
if (presentationStyle.equals(IntroPartPresentation.BROWSER_IMPL_KIND)) {
// HTML presentation
url = ModelUtil.resolveURL(url, pluginId);
BrowserIntroPartImplementation impl = (BrowserIntroPartImplementation) IntroPlugin
.getDefault().getIntroModelRoot().getPresentation()
.getIntroPartImplementation();
Browser browser = impl.getBrowser();
return browser.setUrl(url);
}
{
// SWT presentation.
return openBrowser(url, pluginId);
}
}
private boolean showMessage(String message) {
if (message == null)
return false;
DialogUtil.displayInfoMessage(null, message);
return true;
}
/**
* Display an Intro Page.
* <p>
* INTRO: revisit picking first page.
*/
boolean showPage(String pageId, String standbyState) {
// set the current page id in the model. This will trigger appropriate
// listener event to the UI. If setting the page in the model fails (ie:
// the page was not found in the current model, look for it in loaded
// models. return false if failed.
// avoid flicker.
CustomizableIntroPart currentIntroPart = (CustomizableIntroPart) IntroPlugin
.getIntro();
currentIntroPart.getControl().setRedraw(false);
IntroModelRoot modelRoot = IntroPlugin.getDefault().getIntroModelRoot();
boolean success = modelRoot.setCurrentPageId(pageId);
if (!success)
success = includePageToShow(modelRoot, pageId);
// we turned drawing off. Turn it on again.
currentIntroPart.getControl().setRedraw(true);
if (success) {
// found page. Set the history
modelRoot.getPresentation().updateHistory(
modelRoot.getCurrentPage());
// ran action successfully. Now set intro intro standby if needed.
if (standbyState == null)
return true;
return setStandbyState(standbyState);
}
// could not find referenced page.
return false;
}
/**
* Finds the target page and includes it in passed model.
*
* @param pageId
* @return
*/
private boolean includePageToShow(IntroModelRoot model, String pageId) {
AbstractIntroPage page = findPageToShow(pageId);
if (page == null) {
Log.warning("Could not find an Intro page with id=" + pageId); //$NON-NLS-1$
return false;
}
// now clone the target page because original model should be kept
// intact. Resolve target page first to resolve its includes
// properly. Insert presentation shared style at the top of the shared
// styles list because once reparented, the shared style is lost.
// Finally, add clone page to current model.
page.getChildren();
// current kind.
String currentPresentationKind = model.getPresentation()
.getImplementationKind();
// load shared style corresponding to same presentation kind from target
// model.
IntroPartPresentation targetPresentation = ((IntroModelRoot) page
.getParent()).getPresentation();
String targetSharedStyle = targetPresentation
.getSharedStyle(currentPresentationKind);
// clone.
AbstractIntroPage clonedPage = null;
try {
clonedPage = (AbstractIntroPage) page.clone();
} catch (CloneNotSupportedException ex) {
// should never be here.
Log.error("Failed to clone Intro model node.", ex); //$NON-NLS-1$
return false;
}
// reparent cloned target to current model.
clonedPage.setParent(model);
// REVISIT: SWT presentation does not support multiple shared
// styles.
if (targetSharedStyle != null)
// add target model shared style.
clonedPage.insertStyle(targetSharedStyle, 0);
model.addChild(clonedPage);
return model.setCurrentPageId(clonedPage.getId());
}
/**
* Searches all loaded models for the first page with the given id.
*
* @param pageId
* @return
*/
private AbstractIntroPage findPageToShow(String pageId) {
// check all cached models.
for (IntroModelRoot model : ExtensionPointManager.getInst().getIntroModels().values()) {
AbstractIntroPage page = (AbstractIntroPage) model.findChild(
pageId, AbstractIntroElement.ABSTRACT_PAGE);
if (page != null)
return page;
}
// could not find page in any model.
return null;
}
/**
* Navigate foward in the presentation, whichever one it is.
*
* @return
*/
private boolean navigate(String direction) {
// set intro to standby mode. we know we have a customizable part.
CustomizableIntroPart introPart = (CustomizableIntroPart) IntroPlugin
.getIntro();
if (introPart == null)
// intro is closed. Do nothing.
return false;
IntroPartPresentation presentation = (IntroPartPresentation) introPart
.getAdapter(IntroPartPresentation.class);
if (direction.equalsIgnoreCase(VALUE_BACKWARD))
return presentation.navigateBackward();
else if (direction.equalsIgnoreCase(VALUE_FORWARD))
return presentation.navigateForward();
else if (direction.equalsIgnoreCase(VALUE_HOME))
return presentation.navigateHome();
return false;
}
/**
* @return Returns the action imbedded in this URL.
*/
@Override
public String getAction() {
return action;
}
/**
* Return a parameter defined in the Intro URL. Returns null if the
* parameter is not defined. If this intro url has a decode=true parameter,
* then all parameters are returned decoded using UTF-8.
*
* @param parameterId
* @return
*/
@Override
public String getParameter(String parameterId) {
// make sure to decode only on return, since we may need to recreate the
// url when handling custom urls.
String value = parameters.getProperty(parameterId);
String decode = parameters.getProperty(KEY_DECODE);
if (value != null)
try {
if (decode!=null && decode.equalsIgnoreCase(VALUE_TRUE))
// we are told to decode the parameters of the url through
// the decode parameter. Assume that parameters are
// UTF-8 encoded.
return StringUtil.decode(value, "UTF-8"); //$NON-NLS-1$
return value;
} catch (Exception e) {
// should never be here.
Log.error("Failed to decode URL: " + parameterId, e); //$NON-NLS-1$
}
return value;
}
private boolean handleCustomAction() {
IntroURLAction command = ExtensionPointManager.getInst()
.getSharedConfigExtensionsManager().getCommand(action);
if (command == null) {
String message = Messages.IntroURL_badCommand;
DialogUtil.displayInfoMessage(null, message,
new Object[] { action });
return false;
}
// custom command. execute it.
StringBuffer url = new StringBuffer();
url.append("http://org.eclipse.ui.intro/"); //$NON-NLS-1$
url.append(command.getReplaceValue().trim());
if (command.getReplaceValue().indexOf("?") == -1) //$NON-NLS-1$
// command does not have parameters.
url.append("?"); //$NON-NLS-1$
else
// command already has parameters.
url.append("&"); //$NON-NLS-1$
url.append(retrieveInitialQuery());
IIntroURL introURL = IntroURLFactory.createIntroURL(url.toString());
if (introURL != null)
return introURL.execute();
return false;
}
/**
* Recreate the initial query passed to this URL.
*
* @return
*/
private String retrieveInitialQuery() {
StringBuffer query = new StringBuffer();
Enumeration keys = parameters.keys();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
query.append(key);
query.append("="); //$NON-NLS-1$
query.append(parameters.get(key));
if (keys.hasMoreElements())
query.append("&"); //$NON-NLS-1$
}
return query.toString();
}
private boolean switchToLaunchBar() {
IIntroPart intro = PlatformUI.getWorkbench().getIntroManager()
.getIntro();
if (intro == null)
return false;
CustomizableIntroPart cpart = (CustomizableIntroPart) intro;
IntroModelRoot modelRoot = IntroPlugin.getDefault().getIntroModelRoot();
Rectangle bounds = cpart.getControl().getBounds();
Rectangle startBounds = Geometry.toDisplay(cpart.getControl()
.getParent(), bounds);
IntroLaunchBarElement launchBarElement = modelRoot.getPresentation().getLaunchBarElement();
if (launchBarElement == null)
return true;
IWorkbenchWindow window = intro.getIntroSite().getWorkbenchWindow();
IntroLaunchBar launchBar = IntroLaunchBar.create(window, modelRoot, launchBarElement);
PlatformUI.getWorkbench().getIntroManager().setIntroStandby(intro, true);
closeIntro();
Rectangle endBounds = Geometry.toDisplay(launchBar.getControl()
.getParent(), launchBar.getControl().getBounds());
AnimationEngine.createTweakedAnimation(window.getShell(), 400, startBounds, endBounds);
return true;
}
}