/******************************************************************************* * 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; import java.util.ArrayList; import java.util.Map; import java.util.StringTokenizer; import java.util.Vector; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IRegistryChangeEvent; import org.eclipse.core.runtime.Platform; import org.eclipse.swt.SWTError; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.IMemento; import org.eclipse.ui.internal.intro.impl.IntroPlugin; 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.presentations.BrowserIntroPartImplementation; import org.eclipse.ui.internal.intro.impl.presentations.FormIntroPartImplementation; import org.eclipse.ui.internal.intro.impl.presentations.TextIntroPartImplementation; import org.eclipse.ui.internal.intro.impl.util.Log; import org.eclipse.ui.internal.intro.impl.util.StringUtil; import org.eclipse.ui.intro.IIntroPart; /** * This class models the presentation element contributed to a config extension point. The * Presentation class delegates UI creation to the actual Implementation class, and passes the * IntroPart along to this implementation class. Also, dynamic awarness is honored here. * <p> * Rules: * <ul> * <li>There is no model class for the "implementation" markup element. This presentation class * inherits information from the implementation class that is picked (based on OS, ...).</li> * <li>ID attribute of this model class is the id of the picked implementation element.</li> * <li>Style attribute in this model class is the style of the picked implementation element.</li> * <li>HTMLHeadContent in this model class is the HEAD element under the picked implementation * element, only if the implementation element is a Browser implmenetation.</li> * <li>The UI model class, AbstractIntroPartImplementation, that represents the IntroPart * implementation is cached here for quick access. It is used by intro url actions for manipulation * of UI.<br> * INTRO:This really should be in a UI model class. * <ul> */ public class IntroPartPresentation extends AbstractIntroElement { protected static final String TAG_PRESENTATION = "presentation"; //$NON-NLS-1$ private static final String TAG_IMPLEMENTATION = "implementation"; //$NON-NLS-1$ private static final String ATT_KIND = "kind"; //$NON-NLS-1$ private static final String ATT_STYLE = "style"; //$NON-NLS-1$ private static final String ATT_OS = "os"; //$NON-NLS-1$ private static final String ATT_WS = "ws"; //$NON-NLS-1$ protected static final String ATT_HOME_PAGE_ID = "home-page-id"; //$NON-NLS-1$ protected static final String ATT_STANDBY_PAGE_ID = "standby-page-id"; //$NON-NLS-1$ public static final String BROWSER_IMPL_KIND = "html"; //$NON-NLS-1$ public static final String FORMS_IMPL_KIND = "swt"; //$NON-NLS-1$ // this implementation kind if not public api. Only used internally for // debugging. private static final String TEXT_IMPL_KIND = "text"; //$NON-NLS-1$ // private String title; private String [] implementationStyles; private String implementationKind; private String homePageId; private String standbyPageId; // The Head contributions to this preentation (inherited from child // implementation). private IntroHead head; private AbstractIntroPartImplementation implementation; private IntroLaunchBarElement launchBar; // CustomizableIntroPart and memento instances. Passed to the Implementation // classes. private IIntroPart introPart; private IMemento memento; /** * */ IntroPartPresentation(IConfigurationElement element) { super(element); homePageId = element.getAttribute(ATT_HOME_PAGE_ID); standbyPageId = element.getAttribute(ATT_STANDBY_PAGE_ID); } private void updatePresentationAttributes(IConfigurationElement element) { if (element != null) { // reset (ie: inherit) type and style to be implementation type and // style. Then handle HEAD content for the case of HTML Browser. String value = element.getAttribute(ATT_STYLE); if (value!=null) { IntroModelRoot root = getModelRoot(); ArrayList<String> list = new ArrayList<>(); StringTokenizer stok = new StringTokenizer(value, ","); //$NON-NLS-1$ for (;stok.hasMoreTokens();) { String oneStyle = stok.nextToken().trim(); if (root!=null) oneStyle = root.resolveVariables(oneStyle); list.add(oneStyle); } implementationStyles = (String[])list.toArray(new String[list.size()]); } implementationKind = element.getAttribute(ATT_KIND); // get Head contribution, regardless of implementation class. // Implementation class is created lazily by UI. head = getHead(element); // Resolve. if (implementationStyles!=null) { for (int i=0; i<implementationStyles.length; i++) { implementationStyles[i] = ModelUtil.resolveURL(implementationStyles[i], element); } } } } /** * Returns the styles associated with the Presentation. May be null if no shared presentation * style is needed, or in the case of static HTML OOBE. * * @return Returns the array of styles or null if not defined. */ public String[] getImplementationStyles() { return implementationStyles; } /** * Returns the type attribute of the implementation picked by this presentation. * * @return Returns the implementationKind. */ public String getImplementationKind() { return implementationKind; } public AbstractIntroPartImplementation getIntroPartImplementation() { return implementation; } /** * Returns the model class for the Head element under an implementation. Returns null if there * is no head contribution. * * @param element * @return */ private IntroHead getHead(IConfigurationElement element) { try { // There should only be one head element. Since elements where // obtained by name, no point validating name. IConfigurationElement[] headElements = element.getChildren(IntroHead.TAG_HEAD); if (headElements.length == 0) // no contributions. done. return null; IntroHead head = new IntroHead(headElements[0]); head.setParent(this); return head; } catch (Exception e) { Log.error(e.getMessage(), e); return null; } } /** * Returns the launch bar element if defined in this presentation, or <code>null</code> * otherwise. * * @since 3.1 * @return */ public IntroLaunchBarElement getLaunchBarElement() { if (launchBar != null) return launchBar; IConfigurationElement[] children = getCfgElement().getChildren("launchBar"); //$NON-NLS-1$ if (children.length > 0) { launchBar = new IntroLaunchBarElement(children[0]); launchBar.setParent(this); if (children.length > 1) Log .warning("Mutiple Intro Launch bars defined when only one is allowed. Only first one was loaded. "); //$NON-NLS-1$ } return launchBar; } /** * @param introPart */ public void init(IIntroPart introPart, IMemento memento) { // REVISIT: Called when the actual UI needs to be created. Incomplete // separation of model / UI. Will change later. should not get here if // there is no valid implementation. this.introPart = introPart; this.memento = memento; } /** * Creates the UI based on the implementation class. * * @see org.eclipse.ui.IWorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite) */ public void createPartControl(Composite parent) { Vector validImplementations = getValidImplementationElements(getCfgElement()); IConfigurationElement implementationElement = null; for (int i = 0; i < validImplementations.size(); i++) { implementationElement = (IConfigurationElement) validImplementations.elementAt(i); // you want to pass primed model. updatePresentationAttributes(implementationElement); try { implementation = createIntroPartImplementation(getImplementationKind()); if (implementation == null) // failed to create executable. continue; implementation.init(introPart, memento); implementation.createPartControl(parent); IntroModelRoot model = getModelRoot(); if (model != null && model.getConfigurer() != null) { IntroTheme theme = model.getTheme(); Map<String, String> properties = theme != null ? theme.getProperties() : null; model.getConfigurer().init(introPart.getIntroSite(), properties); } if (Log.logInfo) Log.info("Loading Intro UI implementation from: " //$NON-NLS-1$ + ModelLoaderUtil.getLogString(implementationElement, "kind")); //$NON-NLS-1$ break; } catch (SWTError e) { Log.warning("Failed to create Intro UI implementation from: " //$NON-NLS-1$ + ModelLoaderUtil.getLogString(implementationElement, "kind") + e.getMessage()); //$NON-NLS-1$ implementation = null; implementationElement = null; } catch (Exception e) { Log.error("Failed to create Intro UI implementation from: " //$NON-NLS-1$ + ModelLoaderUtil.getLogString(implementationElement, "kind"), e); //$NON-NLS-1$ implementation = null; implementationElement = null; } } if (implementationElement == null) { // worst case scenario. We failed in all cases. implementation = new FormIntroPartImplementation(); try { implementation.init(introPart, memento); // simply set the presentation kind since all other attributes // will be null. implementationKind = FORMS_IMPL_KIND; } catch (Exception e) { // should never be here. Log.error(e.getMessage(), e); return; } implementation.createPartControl(parent); Log.warning("Loaded UI Forms implementation as a default UI implementation."); //$NON-NLS-1$ } } /** * Retruns a list of valid implementation elements of the config. Choose correct implementation * element based on os atrributes. Rules: get current OS, choose first contributrion, with os * that matches OS. Otherwise, choose first contribution with no os. Returns null if no valid * implementation is found. */ private Vector getValidImplementationElements(IConfigurationElement configElement) { Vector<IConfigurationElement> validList = new Vector<>(); // There can be more than one implementation contribution. Add each // valid one. First start with OS, then WS then no OS. IConfigurationElement[] implementationElements = configElement.getChildren(TAG_IMPLEMENTATION); // IConfigurationElement implementationElement = null; if (implementationElements.length == 0) // no contributions. done. return validList; String currentOS = Platform.getOS(); String currentWS = Platform.getWS(); // first loop through all to find one with matching OS, with or // without WS. for (int i = 0; i < implementationElements.length; i++) { String os = implementationElements[i].getAttribute(ATT_OS); if (os == null) // no os, no match. continue; if (listValueHasValue(os, currentOS)) { // found implementation with correct OS. Now try if WS // matches. String ws = implementationElements[i].getAttribute(ATT_WS); if (ws == null) { // good OS, and they do not care about WS. we have a // match. validList.add(implementationElements[i]); } else { // good OS, and we have WS. if (listValueHasValue(ws, currentWS)) validList.add(implementationElements[i]); } } } // now loop through all to find one with no OS defined, but with a // matching WS. for (int i = 0; i < implementationElements.length; i++) { String os = implementationElements[i].getAttribute(ATT_OS); if (os == null) { // found implementation with no OS. Now try if WS // matches. String ws = implementationElements[i].getAttribute(ATT_WS); if (ws == null) { // no OS, and they do not care about WS. we have a // match. validList.add(implementationElements[i]); } else { // no OS, and we have WS. if (listValueHasValue(ws, currentWS)) validList.add(implementationElements[i]); } } } return validList; } /** * Util method that searches for the given value in a comma separated list of values. The list * is retrieved as an attribute value of OS, WS. * */ private boolean listValueHasValue(String stringValue, String value) { String[] attributeValues = StringUtil.split(stringValue, ","); //$NON-NLS-1$ for (int i = 0; i < attributeValues.length; i++) { if (attributeValues[i].equalsIgnoreCase(value)) return true; } return false; } /** * Util method to load shared style from given kind. */ public String getSharedStyle(String kind) { // There can be more than one implementation contribution. IConfigurationElement[] implementationElements = getCfgElement().getChildren(TAG_IMPLEMENTATION); // IConfigurationElement implementationElement = null; if (implementationElements.length == 0) // no implementations. done. return null; // loop through all to find one with matching kind. for (int i = 0; i < implementationElements.length; i++) { String aKind = implementationElements[i].getAttribute(ATT_KIND); if (aKind.equals(kind)) { // found implementation with matching kind. String style = implementationElements[i].getAttribute(ATT_STYLE); return ModelUtil.resolveURL(style, getCfgElement()); } } return null; } /** * Creates the actual implementation class. Returns null on failure. * */ private AbstractIntroPartImplementation createIntroPartImplementation(String implementationType) { // quick exits if (implementationType == null) return null; if (!implementationType.equals(BROWSER_IMPL_KIND) && !implementationType.equals(FORMS_IMPL_KIND) && !implementationType.equals(TEXT_IMPL_KIND)) return null; if (implementationType.equals(BROWSER_IMPL_KIND) && IntroPlugin.DEBUG_NO_BROWSER) return null; AbstractIntroPartImplementation implementation = null; try { if (implementationType.equals(BROWSER_IMPL_KIND)) implementation = //null; new BrowserIntroPartImplementation(); else if (implementationType.equals(FORMS_IMPL_KIND)) implementation = new FormIntroPartImplementation(); else implementation = new TextIntroPartImplementation(); } catch (Exception e) { Log.error("Could not instantiate implementation " //$NON-NLS-1$ + implementationType, e); } return implementation; } /** * Returns the the Customizable Intro Part. may return null if init() has not been called yet on * the presentation. * * @return Returns the introPart. */ public IIntroPart getIntroPart() { return introPart; } /** * Save the current state of the intro. Delegate to the implementation to do the work, as * different implementations may have different requirements. * * @param memento * the memento in which to store state information */ public void saveState(IMemento memento) { if (implementation != null) implementation.saveState(memento); } public void setFocus() { if (implementation != null) implementation.setFocus(); } public void standbyStateChanged(boolean standby, boolean isStandbyPartNeeded) { if (implementation != null) implementation.standbyStateChanged(standby, isStandbyPartNeeded); } public void updateHistory(AbstractIntroPage page) { if (implementation != null) implementation.updateHistory(page); } public boolean navigateForward() { if (implementation != null) return implementation.navigateForward(); return false; } public boolean navigateBackward() { if (implementation != null) return implementation.navigateBackward(); return false; } public boolean navigateHome() { if (implementation != null) return implementation.navigateHome(); return false; } /** * Called when the IntroPart is disposed. Forwards the call to the implementation class. */ public void dispose() { if (implementation != null) implementation.dispose(); } /** * Support dynamic awarness. Clear cached models first, then update UI by delegating to * implementation. * * @see org.eclipse.core.runtime.IRegistryChangeListener#registryChanged(org.eclipse.core.runtime.IRegistryChangeEvent) */ public void registryChanged(IRegistryChangeEvent event) { if (implementation != null) implementation.registryChanged(event); } /** * @return Returns the homePageId. */ public String getHomePageId() { return homePageId; } /** * @return Returns the homePageId. */ public String getStandbyPageId() { return standbyPageId; } @Override public int getType() { return AbstractIntroElement.PRESENTATION; } /** * @return Returns the HTML head conttent to be added to each dynamic html page in this * presentation.. */ public IntroHead getHead() { return head; } }