/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.tools.workbench.framework.help; import java.awt.Component; import java.awt.Container; import java.awt.Desktop; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.WeakHashMap; import java.util.prefs.Preferences; import javax.swing.Icon; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import org.eclipse.persistence.tools.workbench.framework.resources.ResourceRepository; import org.eclipse.persistence.tools.workbench.utility.CollectionTools; /** * New and improved CSHManager: * - weak references to UI components * - provides CSH for all the child components of any added component * - only supports a single help book */ class DefaultHelpManager implements InternalHelpManager { /** browser for displaying help content*/ private ExternalBrowserHandler browser; /** help topic URL map */ private HashMap<String, String> topicIdtoUrlMap; /** listen for pop-up menu */ private LocalMouseListener mouseListener; /** listen for F1 */ private KeyListener keyListener; /** help topics, keyed by component */ private Map topicIDs; /** labels, messages, etc. */ private ResourceRepository resourceRepository; /** */ private boolean localHelpFailed; /** This should point to the executable for an HTML browser. */ static final String BROWSER_PREFERENCE = "external web browser"; static final String BROWSER_PREFERENCE_DEFAULT = ""; // ********** constructor/initialization ********** /** * initialize the new instance */ private DefaultHelpManager() { super(); this.initialize(); } /** * construct a help manager that uses the specified OHJ help and book */ DefaultHelpManager(ResourceRepository resourceRepository, Preferences preferences) { this(); this.resourceRepository = resourceRepository; this.localHelpFailed = false; this.initialize(preferences); } protected void initialize() { this.keyListener = this.buildKeyListener(); // allow the entries to be garbage-collected when the // components are no longer referenced anywhere else this.topicIDs = new WeakHashMap(); } private void initialize(Preferences preferences) { Icon icon = this.resourceRepository.getIcon("oracle.logo.large"); this.mouseListener = this.buildMouseListener(this.resourceRepository.getString("CSH_HELP")); this.browser = new ExternalBrowserHandler(preferences); this.topicIdtoUrlMap = initializeTopicMap(); } /** * when F1 is pressed we will display the help topic * for the source component */ private KeyListener buildKeyListener() { return new KeyAdapter() { public void keyPressed(KeyEvent e) { if (( ! e.isConsumed()) && (e.getKeyCode() == KeyEvent.VK_F1)) { DefaultHelpManager.this.showTopic((Component) e.getSource()); e.consume(); } } }; } /** * when a pop-up menu is requested we will add a menu * item for Help to the source component's pop-up menu */ private LocalMouseListener buildMouseListener(String menuItemLabel) { return new LocalMouseListener(menuItemLabel); } // ********** InternalHelpManager implementation ********** /** * @see InternalHelpManager#setLocalHelpFailed(boolean) */ public void setLocalHelpFailed(boolean localHelpFailed) { this.localHelpFailed = localHelpFailed; } /** * The application has completed its launch, notify the user * if we had problems loading his "local" help book. * @see InternalHelpManager#launchComplete() */ public void launchComplete() { if (this.localHelpFailed) { this.showTopic("noHelp"); } } // ********** HelpManager implementation ********** /** * @see HelpManager#showHelp() */ public void showHelp() { this.showTopic("default"); } /** * @see HelpManager#showTopic(String) */ public void showTopic(String topicID) { this.showTopicInternal(topicID); } public void showUrl(String url) { this.browser.handleValue(url); } /** * if the specified component has not been added, we * check for its parent's topic ID and on up the hierarchy * @see HelpManager#showTopic(java.awt.Component) */ public void showTopic(Component component) { String topicID = this.getTopicID(component); if (topicID != null) { this.showTopicInternal(topicID, component); } } /** * @see HelpManager#addTopicIDs(java.util.Map) */ public void addTopicIDs(Map componentsToTopicIDs) { for (Iterator stream = componentsToTopicIDs.entrySet().iterator(); stream.hasNext(); ) { Map.Entry entry = (Map.Entry) stream.next(); this.addTopicID((Component) entry.getKey(), (String) entry.getValue()); } } /** * we listen to the component and all its children; * but we register the topic ID with only the one component * @see HelpManager#addTopicID(java.awt.Component, String) */ public void addTopicID(Component component, String topicID) { this.listenTo(component); this.topicIDs.put(component, topicID); } /** * @see HelpManager#removeTopicIDs(java.util.Map) */ public void removeTopicIDs(Map componentsToTopicIDs) { this.removeTopicIDs(componentsToTopicIDs.keySet()); } /** * @see HelpManager#removeTopicIDs(java.util.Collection) */ public void removeTopicIDs(Collection components) { for (Iterator stream = components.iterator(); stream.hasNext(); ) { this.removeTopicID((Component) stream.next()); } } /** * @see HelpManager#removeTopicID(java.awt.Component) */ public void removeTopicID(Component component) { this.topicIDs.remove(component); this.stopListeningTo(component); } public void addItemsToPopupMenuForComponent(JMenuItem[] menuItems, Component component) { this.mouseListener.addItemsToPopupMenuForComponent(menuItems, component); } /** * @see HelpManager#shutDown() */ public void shutDown() { } // ********** queries ********** private String getTopicID(Component component) { if (component == null) { // stop when we get to the top of the component hierarchy return null; } String topicID = (String) this.topicIDs.get(component); if (topicID == null) { // recurse up component hierarchy return this.getTopicID(component.getParent()); } return topicID; } // ********** behavior ********** /** * Display Help for the specified Topic ID. If there are * problems, a TopicDisplayException will be thrown. */ protected void showTopicInternal(String topicID) { String url = this.topicIdtoUrlMap.get(topicID); if (url == null) { url = this.topicIdtoUrlMap.get("default"); } this.browser.handleValue(url); } /** * Display Help for the specified Topic ID. If there are * problems, a TopicDisplayException will be thrown. */ protected void showTopicInternal(String topicID, Component component) { String url = this.topicIdtoUrlMap.get(topicID); if (url == null) { url = this.topicIdtoUrlMap.get("default"); } this.browser.handleValue(url, component); } private void listenTo(Component component) { // only check for our mouse listener... if (CollectionTools.contains(component.getMouseListeners(), this.mouseListener)) { return; } // *assume* that if we need to add our mouse listener // then we also need to add our key listener component.addMouseListener(this.mouseListener); component.addKeyListener(this.keyListener); // likewise, *assume* that if we are already listening to a component // then we are also already listening to its children if (component instanceof Container) { this.listenTo(((Container) component).getComponents()); } } private void listenTo(Component[] components) { for (int i = components.length; i-- > 0; ) { this.listenTo(components[i]); } } private void stopListeningTo(Component component) { // only check for our mouse listener... if ( ! CollectionTools.contains(component.getMouseListeners(), this.mouseListener)) { return; } // *assume* that if we need to remove our mouse listener // then we also need to remove our key listener component.removeMouseListener(this.mouseListener); component.removeKeyListener(this.keyListener); // likewise, *assume* that if we are listening to a component // then we are also listening to its children if (component instanceof Container) { this.stopListeningTo(((Container) component).getComponents()); } } private void stopListeningTo(Component[] components) { for (int i = components.length; i-- > 0; ) { this.stopListeningTo(components[i]); } } private void showNavigatorWindow(Class navigatorClass) { } // ********** nested classes ********** /** * This listener will respond to a mouse pop-up trigger and * add the Help menu to the source component. Selecting * the Help menu item will trigger the component's help * topic to be displayed. */ private class LocalMouseListener extends MouseAdapter { /** the Help pop-up menu added to the component with focus */ JPopupMenu popupMenu; /** the component with focus - only held temporarily */ private Component component; /** popup menuItems, keyed by component */ private Map popupMenuItems; // ********** constructor/initialization ********** LocalMouseListener(String menuItemLabel) { super(); this.initialize(menuItemLabel); } /** * build the pop-up menu that will be added to any component * that has CSH installed */ private void initialize(String menuItemLabel) { // allow the entries to be garbage-collected when the // components are no longer referenced anywhere else this.popupMenuItems = new WeakHashMap(); JMenuItem item = new JMenuItem(menuItemLabel); item.addActionListener(this.buildMenuItemListener()); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0)); this.popupMenu = new JPopupMenu(); this.popupMenu.add(item); } /** * this is the action that is executed if the user selects * the Help menu item from the pop-up menu */ private ActionListener buildMenuItemListener() { return new ActionListener() { public void actionPerformed(ActionEvent e) { // display help for the component stashed away when the pop-up menu was first displayed LocalMouseListener.this.showHelp(); } }; } // ********** MouseListener implementation ********** public void mousePressed(MouseEvent e) { this.handleMouseEvent(e); } public void mouseClicked(MouseEvent e) { // is this method needed??? copied from CSHManager... this.handleMouseEvent(e); } public void mouseReleased(MouseEvent e) { this.handleMouseEvent(e); } /** * if the mouse event is a pop-up menu trigger, * stash away the source component and add the * Help pop-up menu to the component */ private void handleMouseEvent(MouseEvent e) { if ( ! e.isPopupTrigger()) { return; } if (this.component != null) { //had to handle adding and removing the menuItems here instead of in //a PopupMenuListener. If the popupMenu is already visible //when you right click elsewhere then none of the events get fired (ListChooser might be the problem) removeMenuItemsFor(this.component); } // stash away the component so the action listener // can display the component's help if necessary this.component = (Component) e.getSource(); insertMenuItemsFor(this.component); this.popupMenu.show(this.component, e.getX(), e.getY()); } private synchronized void insertMenuItemsFor(Component c) { JMenuItem[] menuItems = menuItemsFor(c); if (menuItems != null) { for (int i = 0; i < menuItems.length; i++) { this.popupMenu.insert(menuItems[i], i); } } } private synchronized void removeMenuItemsFor(Component c) { JMenuItem[] menuItems = menuItemsFor(c); if (menuItems != null) { for (int i = 0; i < menuItems.length; i++) { this.popupMenu.remove(menuItems[i]); } } } // ********** behavior ********** /** * show help for the pop-up menu's component */ void showHelp() { DefaultHelpManager.this.showTopic(this.component); } void addItemsToPopupMenuForComponent(JMenuItem[] menuItems, Component c) { this.popupMenuItems.put(c, menuItems); } private JMenuItem[] menuItemsFor(Component c) { return (JMenuItem[]) this.popupMenuItems.get(c); } } /** * Private class used to handle invoking an external browser when a link is * clicked on in the HelpContentPanel. The link must have the syntax of: * custom:external:<URL to invoke> */ private class ExternalBrowserHandler { private Preferences preferences; public final static String PROTOCOL_NAME = "external"; public ExternalBrowserHandler(Preferences preferences) { super(); this.preferences = preferences; } // ********** CustomProtocolHandler implementation ********** /** * If the user has specified a specific browser in the preferences, * use that; otherwise use the platform-specific default browser. * @see oracle.help.CustomProtocolHandler#handleValue(String) */ public void handleValue(String value, Component component) { String browser = this.preferences.get(BROWSER_PREFERENCE, BROWSER_PREFERENCE_DEFAULT); try { if (browser.isEmpty()) { Desktop.getDesktop().browse(new URI(value)); } else { Runtime.getRuntime().exec(browser + " " + value); } } catch (IOException | URISyntaxException ex) { if (component == null) { throw new RuntimeException(DefaultHelpManager.this.resourceRepository.getString("CONFIGURE_EXTERNAL_BROWSER")); } showBrowserConfigMessages(component); } } /** * If the user has specified a specific browser in the preferences, * use that; otherwise use the platform-specific default browser. * @see oracle.help.CustomProtocolHandler#handleValue(String) */ public void handleValue(String value) { handleValue(value, null); } private void showBrowserConfigMessages(Component component) { JOptionPane.showMessageDialog(component, DefaultHelpManager.this.resourceRepository.getString("CONFIGURE_EXTERNAL_BROWSER"), DefaultHelpManager.this.resourceRepository.getString("CONFIGURE_EXTERNAL_BROWSER_TITLE"), JOptionPane.WARNING_MESSAGE); //DefaultHelpManager.this.showTopic("help.linux.config.externalbrowser"); } } private HashMap<String, String> initializeTopicMap() { HashMap<String, String> topicMap = new HashMap<String, String>(1); topicMap.put("default", "http://www.eclipse.org/eclipselink/documentation/"); topicMap.put("eclipselink_home", "http://www.eclipse.org/eclipselink/"); topicMap.put("eclipslink_userguide", "http://www.eclipse.org/eclipselink/documentation/"); topicMap.put("eclipselink_api", "http://www.eclipse.org/eclipselink/api/2.6/index.html"); topicMap.put("eclipselink_examples", "http://wiki.eclipse.org/EclipseLink/Examples"); return topicMap; } }