All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jaxx.runtime.swing.help.JAXXHelpBroker Maven / Gradle / Ivy

There is a newer version: 3.0-alpha-6
Show newest version
/*
 * #%L
 * JAXX :: Runtime
 * 
 * $Id: JAXXHelpBroker.java 2118 2010-10-26 17:44:57Z tchemit $
 * $HeadURL: http://svn.nuiton.org/svn/jaxx/tags/jaxx-2.3/jaxx-runtime/src/main/java/jaxx/runtime/swing/help/JAXXHelpBroker.java $
 * %%
 * Copyright (C) 2008 - 2010 CodeLutin
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public 
 * License along with this program.  If not, see
 * .
 * #L%
 */
package jaxx.runtime.swing.help;

import jaxx.runtime.JAXXContext;
import jaxx.runtime.JAXXObject;
import jaxx.runtime.SwingUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.help.CSH;
import javax.help.HelpBroker;
import javax.help.HelpSet;
import javax.swing.AbstractButton;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import java.applet.Applet;
import java.awt.AWTEvent;
import java.awt.ActiveEvent;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.MenuComponent;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
import java.util.Vector;

/**
 * La classe pour encapsuler l'aide de l'application.
 *
 * @author tchemit 
 * @since 1.4
 */
public class JAXXHelpBroker {

    public static final String JAXX_CONTEXT_ENTRY = "jaxxcontext";

    /** Logger */
    static private Log log = LogFactory.getLog(JAXXHelpBroker.class);

    /** name of helpset */
    protected final String helpsetName;

    /** default id to use if none given */
    protected final String defaultID;

    /** help key */
    protected final String helpKey;

    /** helpset to use */
    protected HelpSet helpset;

    /** help broker */
    protected HelpBroker helpBroker;

    /** current locale used */
    protected Locale locale;

    /** cache of cursors modified when in context-sensitive mode */
    protected Hashtable cursors;

    /** cursor to use in context-sensitive mode */
    protected Cursor onItemCursor;

    /** cache of component which cursor have been modified */
    protected final Map cache;

    /** help ui handler */
    protected JAXXHelpUIHandler handler;

    public JAXXHelpBroker(String helpsetName, String helpKey, String defaultID, JAXXHelpUIHandler handler) {
        this(null, helpsetName, helpKey, defaultID, handler);
    }

    public JAXXHelpBroker(Locale locale, String helpsetName, String helpKey, String defaultID, JAXXHelpUIHandler handler) {
        if (helpsetName == null) {
            throw new NullPointerException("parameter helpsetName can not be null!");
        }
        if (handler == null) {
            throw new NullPointerException("handler can not be null");
        }
        this.locale = locale;
        this.helpsetName = helpsetName;
        this.helpKey = helpKey;
        this.defaultID = defaultID;
        this.handler = handler;
        this.cache = new HashMap();
    }

    public void prepareUI(JAXXObject c) {
        if (c == null) {
            throw new NullPointerException("parameter c can not be null!");
        }

        // l'ui doit avoir un boutton showHelp
        AbstractButton help = getShowHelpButton(c);

        if (help == null) {
            // no showHelp button
            return;
        }

        // attach context to button
        if (log.isDebugEnabled()) {
            log.debug("attach context to showhelp button " + c);
        }
        help.putClientProperty(JAXX_CONTEXT_ENTRY, c);

        // add tracking action
        ActionListener listener = getShowHelpAction();
        if (log.isDebugEnabled()) {
            log.debug("adding tracking action " + listener);
        }
        help.addActionListener(listener);

        if (log.isDebugEnabled()) {
            log.debug("done for " + c);
        }


        getHelpBroker().enableHelpKey(((Component) c), getDefaultID(), getHelpset());
    }

    public void showHelp(JAXXContext context, String helpId) {
        getHandler().showHelp(context, this, helpId);
    }

    public JAXXHelpUIHandler getHandler() {
        return handler;
    }

    public HelpBroker getHelpBroker() {
        if (helpBroker == null) {
            helpBroker = getHelpset().createHelpBroker();
        }
        return helpBroker;
    }

    public String getHelpKey() {
        return helpKey;
    }

    public HelpSet getHelpset() {
        if (helpset == null) {
            try {
                ClassLoader cl = getClass().getClassLoader();
                URL url = HelpSet.findHelpSet(cl, helpsetName, locale);
                helpset = new HelpSet(cl, url);
            } catch (Exception ee) {
                throw new IllegalStateException("could not find help set " + helpsetName + " for reason " + ee.getMessage(), ee);
            }
        }
        return helpset;
    }

    public String getHelpsetName() {
        return helpsetName;
    }

    public String getDefaultID() {
        return defaultID;
    }

    public void setLocale(Locale locale) {
        this.locale = locale;
        // need to reload helpset and helpbroker
        helpset = null;
        helpBroker = null;
        getHelpset();
        getHelpBroker();
    }

    public void showHelpSet() {
        if (log.isDebugEnabled()) {
            log.debug(this);
        }
        new CSH.DisplayHelpFromSource(getHelpBroker());
    }

    public void installUI(Component comp, String helpId) {
        CSH.setHelpIDString(comp, helpId);
        if (log.isDebugEnabled()) {
            log.debug(helpId + " : " + comp.getName());
        }
        cache.put(comp, helpId);
    }

    public class ShowHelpForTrackedComponentAction implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            AbstractButton source = (AbstractButton) e.getSource();

            JAXXContext context = (JAXXContext) source.getClientProperty(JAXX_CONTEXT_ENTRY);

            // prepare cursor
            onItemCursor = (Cursor) UIManager.get("HelpOnItemCursor");
            Vector topComponents = null;
            cursors = null;

            if (onItemCursor != null) {
                cursors = new Hashtable();
                topComponents = getTopContainers(source);
                Enumeration enums = topComponents.elements();
                while (enums.hasMoreElements()) {
                    setAndStoreCursors((Container) enums.nextElement(), onItemCursor);
                }
            }

            // get the tracked component
            Component comp = null;
            try {
                MouseEvent event = getMouseEvent();
                if (event == null) {
                    // tracking canceled
                    return;
                }
                comp = (Component) event.getSource();
                if (log.isDebugEnabled()) {
                    log.debug("component traking " + comp.getName() + " : " + comp.getClass().getName());
                }
                comp = SwingUtil.getDeepestObjectAt(comp, event.getX(), event.getY());
                if (log.isDebugEnabled()) {
                    log.debug("deepest component " + comp.getName() + " : " + comp.getClass().getName());
                }
            } finally {
                // restore the old cursors
                if (topComponents != null) {
                    Enumeration containers = topComponents.elements();
                    while (containers.hasMoreElements()) {
                        resetAndRestoreCursors((Container) containers.nextElement());
                    }
                }
                cursors = null;
            }

            String helpID = findHelpId(comp);
            showHelp(context, helpID);
        }

        public String findHelpId(Component comp) {
            String helpID = CSH.getHelpIDString(comp);
            if (defaultID.equals(helpID)) {
                String id = cache.get(comp);
                // on verifie qu'on est bien sur sur le bon id
                if (helpID.equals(id)) {
                    // ok
                    return helpID;
                }
                if (log.isDebugEnabled()) {
                    log.debug("will try to find better id for comp : " + comp.getName());
                }
                // on est pas sur le bon id
                // on recherche parmis les parents
                helpID = findExtactHelpId(comp);
            }
            if (log.isInfoEnabled()) {
                log.info("helpID " + helpID + " for comp " + comp.getName() + " : " + comp.getClass().getName());
            }
            return helpID;
        }

        protected String findExtactHelpId(Component comp) {
            Container parent = comp.getParent();
            while (parent != null) {
                String id = cache.get(parent);
                if (id == null) {
                    // ce container n'a pas d'id
                    // on va directement sur le parent
                    parent = parent.getParent();
                    continue;
                }
                // le parent possède un id
                // on utilise cet id
                return id;
            }
            // on a pas trouve d'id
            // on retourne l'id par defaut
            return defaultID;
        }
    }

    protected AbstractButton getShowHelpButton(JAXXObject c) {
        return (AbstractButton) c.getObjectById("showHelp");
    }

    protected ActionListener getShowHelpAction() {
        return new ShowHelpForTrackedComponentAction();
    }

    //-------------------------------------------------------------------------
    //--- Copy CSH code but with accessible modifiers and little improvments
    //-------------------------------------------------------------------------
    /*
     * Get all top level containers to change it's cursors
     */

    protected Vector getTopContainers(Object source) {
        // This method is used to obtain all top level components of application
        // for which the changing of cursor to question mark is wanted.
        // Method Frame.getFrames() is used to get list of Frames and
        // Frame.getOwnedWindows() method on elements of the list
        // returns all Windows, Dialogs etc. It works correctly in application.
        // Problem is in applets. There is no way how to get reference to applets
        // from elsewhere than applet itself. So, if request for CSH (this means
        // pressing help button or select help menu item) does't come from component
        // in a Applet, cursor for applets is not changed to question mark. Only for
        // Frames, Windows and Dialogs is cursor changed properly.

        Vector containers = new Vector();
        Component topComponent = null;
        topComponent = getRoot(source);
        if (topComponent instanceof Applet) {
            try {
                Enumeration applets = ((Applet) topComponent).getAppletContext().getApplets();
                while (applets.hasMoreElements()) {
                    containers.add(applets.nextElement());
                }
            } catch (NullPointerException npe) {
                containers.add(topComponent);
            }
        }
        Frame frames[] = Frame.getFrames();
        for (int i = 0; i < frames.length; i++) {
            Window[] windows = frames[i].getOwnedWindows();
            for (int j = 0; j < windows.length; j++) {
                containers.add(windows[j]);
            }
            if (!containers.contains(frames[i])) {
                containers.add(frames[i]);
            }
        }
        return containers;
    }

    protected Component getRoot(Object comp) {
        Object parent = comp;
        while (parent != null) {
            comp = parent;
            if (comp instanceof MenuComponent) {
                parent = ((MenuComponent) comp).getParent();
            } else if (comp instanceof Component) {
                if (comp instanceof Window) {
                    break;
                }
                if (comp instanceof Applet) {
                    break;
                }
                parent = ((Component) comp).getParent();
            } else {
                break;
            }
        }
        if (comp instanceof Component) {
            return ((Component) comp);
        }
        return null;
    }

    /*
     * Set the cursor for a component and its children.
     * Store the old cursors for future resetting
     */

    protected void setAndStoreCursors(Component comp, Cursor cursor) {
        if (comp == null) {
            return;
        }
        Cursor compCursor = comp.getCursor();
        if (compCursor != cursor) {
            cursors.put(comp, compCursor);
            log.debug("set cursor on " + comp);
            comp.setCursor(cursor);
        }
        if (comp instanceof Container) {
            Component component[] = ((Container) comp).getComponents();
            for (int i = 0; i < component.length; i++) {
                setAndStoreCursors(component[i], cursor);
            }
        }
    }

    /*
     * Actually restore the cursor for a component and its children
     */

    protected void resetAndRestoreCursors(Component comp) {
        if (comp == null) {
            return;
        }
        Cursor oldCursor = cursors.get(comp);
        if (oldCursor != null) {
            log.debug("restored cursor " + oldCursor + " on " + comp);
            comp.setCursor(oldCursor);
        }
        if (comp instanceof Container) {
            Component component[] = ((Container) comp).getComponents();
            for (int i = 0; i < component.length; i++) {
                resetAndRestoreCursors(component[i]);
            }
        }
    }

    /**
     * Context Sensitive Event Tracking
     * 

* Creates a new EventDispatchThread from which to dispatch events. This * method returns when stopModal is invoked. * * @return MouseEvent The mouse event occurred. Null if * cancelled on an undetermined object. */ public static MouseEvent getMouseEvent() { // Should the cursor change to a quesiton mark here or // require the user to change the cursor externally to this method? // The problem is that each component can have it's own cursor. // For that reason it might be better to have the user change the // cusor rather than us. // To track context-sensitive events get the event queue and process // the events the same way EventDispatchThread does. Filter out // ContextSensitiveEvents SelectObject & Cancel (MouseDown & ???). // Note: This code only handles mouse events. Accessiblity might // require additional functionality or event trapping // If the eventQueue can't be retrieved, the thread gets interrupted, // or the thread isn't a instanceof EventDispatchThread then return // a null as we won't be able to trap events. try { if (EventQueue.isDispatchThread()) { EventQueue eq = null; // Find the eventQueue. If we can't get to it then just return // null since we won't be able to trap any events. try { eq = Toolkit.getDefaultToolkit().getSystemEventQueue(); } catch (Exception ee) { log.debug(ee); } // Safe guard if (eq == null) { return null; } int eventNumber = -1; // Process the events until an object has been selected or // the context-sensitive search has been canceled. while (true) { // This is essentially the body of EventDispatchThread // modified to trap context-senstive events and act // appropriately eventNumber++; AWTEvent event = eq.getNextEvent(); Object src = event.getSource(); // can't call eq.dispatchEvent // so I pasted it's body here if (log.isDebugEnabled()) { log.debug(event); } // Not sure if I should suppress ActiveEvents or not // Modal dialogs do. For now we will not suppress the // ActiveEvent events if (event instanceof ActiveEvent) { ((ActiveEvent) event).dispatch(); continue; } if (src instanceof Component) { // Trap the context-sensitive events here if (event instanceof KeyEvent) { KeyEvent e = (KeyEvent) event; // if this is the cancel key then exit // otherwise pass all other keys up if (e.getKeyCode() == KeyEvent.VK_CANCEL || e.getKeyCode() == KeyEvent.VK_ESCAPE) { e.consume(); return null; } else { e.consume(); // dispatchEvent(event); } } else if (event instanceof MouseEvent) { MouseEvent e = (MouseEvent) event; int eID = e.getID(); if ((eID == MouseEvent.MOUSE_CLICKED || eID == MouseEvent.MOUSE_PRESSED || eID == MouseEvent.MOUSE_RELEASED) && SwingUtilities.isRightMouseButton(e)) { // cancel tracking e.consume(); if (log.isDebugEnabled()) { log.debug("tracking canceled!!!"); } return null; } if ((eID == MouseEvent.MOUSE_CLICKED || eID == MouseEvent.MOUSE_PRESSED || eID == MouseEvent.MOUSE_RELEASED) && SwingUtilities.isLeftMouseButton(e)) { if (eID == MouseEvent.MOUSE_CLICKED) { if (eventNumber == 0) { dispatchEvent(event); continue; } } e.consume(); return e; } else { e.consume(); } } else { dispatchEvent(event); } } else if (src instanceof MenuComponent) { if (event instanceof InputEvent) { ((InputEvent) event).consume(); } } else { log.error("unable to dispatch event: " + event); } } } } catch (InterruptedException e) { if (log.isDebugEnabled()) { log.debug(e); } } if (log.isDebugEnabled()) { log.debug("Fall Through code"); } return null; } private static void dispatchEvent(AWTEvent event) { Object src = event.getSource(); if (event instanceof ActiveEvent) { // This could become the sole method of dispatching in time. ((ActiveEvent) event).dispatch(); } else if (src instanceof Component) { ((Component) src).dispatchEvent(event); } else if (src instanceof MenuComponent) { ((MenuComponent) src).dispatchEvent(event); } else { log.error("unable to dispatch event: " + event); } } }