| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.help.impl; |
| |
| import com.intellij.openapi.wm.IdeFocusManager; |
| import com.intellij.ui.AppUIUtil; |
| import com.intellij.ui.ScreenUtil; |
| import org.jetbrains.annotations.NotNull; |
| |
| import javax.help.*; |
| import javax.help.Map.ID; |
| import javax.help.UnsupportedOperationException; |
| import javax.swing.*; |
| import java.awt.*; |
| import java.awt.event.*; |
| import java.lang.reflect.Method; |
| import java.net.URL; |
| import java.util.Enumeration; |
| import java.util.Locale; |
| |
| /** |
| * It a dirty patch! Help system is so ugly that it hangs when it open some "external" links. |
| * To prevent this we open "external" links in nornal WEB browser. |
| * This is copy-pasted version of DefaultHelpBroker class. |
| * |
| * @author Vladimir Kondratyev |
| */ |
| class IdeaHelpBroker extends DefaultHelpBroker implements KeyListener{ |
| |
| private HelpSet myHelpSet=null; |
| private JFrame myFrame=null; |
| private JHelp jhelp=null; |
| private Locale myLocale=null; |
| private Font myFont=null; |
| /** |
| * The container for modally activated help |
| * @since 1.1 |
| */ |
| private JDialog myDialog=null; |
| /** |
| * The modal Window that activated help |
| * @since 1.1 |
| */ |
| private Window myOwnerWindow=null; |
| /** |
| * The flag for modally activated help. If true, help was activated from |
| * a modal dialog. Can not be set to true for V1.1. |
| * @since 1.1 |
| */ |
| private boolean myModallyActivated=false; |
| |
| /** |
| * Constructor |
| */ |
| |
| public IdeaHelpBroker(HelpSet hs){ |
| setHelpSet(hs); |
| } |
| |
| /** |
| * Returns the default HelpSet |
| */ |
| public HelpSet getHelpSet(){ |
| return myHelpSet; |
| } |
| |
| /** |
| * Changes the HelpSet for this broker. |
| * @param hs The HelpSet to set for this broker. |
| * A null hs is valid parameter. |
| */ |
| public void setHelpSet(HelpSet hs){ |
| // If we already have a jhelp check if the HelpSet has changed. |
| // If so change the model on the jhelp viewer. |
| // This could be made smarter to cache the helpmodels per HelpSet |
| if(hs!=null&&myHelpSet!=hs){ |
| if(jhelp!=null){ |
| TextHelpModel model=new DefaultHelpModel(hs); |
| jhelp.setModel(model); |
| } |
| myHelpSet=hs; |
| |
| } |
| } |
| |
| |
| /** |
| * Gets the locale of this component. |
| * @return This component's locale. If this component does not |
| * have a locale, the defaultLocale is returned. |
| * @see #setLocale |
| */ |
| public Locale getLocale(){ |
| if(myLocale==null){ |
| return Locale.getDefault(); |
| } |
| return myLocale; |
| } |
| |
| /** |
| * Sets the locale of this HelpBroker. The locale is propagated to |
| * the presentation. |
| * @param l The locale to become this component's locale. A null locale |
| * is the same as the defaultLocale. |
| * @see #getLocale |
| */ |
| public void setLocale(Locale l){ |
| myLocale=l; |
| if(jhelp!=null){ |
| jhelp.setLocale(myLocale); |
| } |
| } |
| |
| /** |
| * Gets the font for this HelpBroker. |
| */ |
| public Font getFont(){ |
| createHelpWindow(); |
| |
| if(myFont==null){ |
| return jhelp.getFont(); |
| } |
| return myFont; |
| } |
| |
| /** |
| * Sets the font for this this HelpBroker. |
| * @param f The font. |
| */ |
| public void setFont(Font f){ |
| myFont=f; |
| if(jhelp!=null){ |
| jhelp.setFont(myFont); |
| } |
| } |
| |
| /** |
| * Set the currentView to the navigator with the same |
| * name as the <tt>name</tt> parameter. |
| * |
| * @param name The name of the navigator to set as the |
| * current view. If nav is null or not a valid Navigator |
| * in this HelpBroker then an |
| * IllegalArgumentException is thrown. |
| * @throws IllegalArgumentException if nav is null or not a valid Navigator. |
| */ |
| public void setCurrentView(String name){ |
| createHelpWindow(); |
| JHelpNavigator nav=null; |
| |
| for(Enumeration e=jhelp.getHelpNavigators(); |
| e.hasMoreElements();){ |
| nav=(JHelpNavigator)e.nextElement(); |
| if(nav.getNavigatorName().equals(name)){ |
| break; |
| } |
| nav=null; |
| } |
| |
| if(nav==null){ |
| throw new IllegalArgumentException("Invalid view name"); |
| } |
| jhelp.setCurrentNavigator(nav); |
| } |
| |
| /** |
| * Determines the current navigator. |
| */ |
| public String getCurrentView(){ |
| createHelpWindow(); |
| return jhelp.getCurrentNavigator().getNavigatorName(); |
| } |
| |
| |
| /** |
| * Initializes the presentation. |
| * This method allows the presentation to be initialized but not displayed. |
| * Typically this would be done in a separate thread to reduce the |
| * intialization time. |
| */ |
| public void initPresentation(){ |
| createHelpWindow(); |
| } |
| |
| /** |
| * Displays the presentation to the user. |
| */ |
| public void setDisplayed(boolean visible){ |
| createHelpWindow(); |
| if(myModallyActivated){ |
| myDialog.setVisible(visible); |
| if(visible){ |
| myDialog.setLocationRelativeTo(myDialog.getOwner()); |
| } |
| } else{ |
| //myFrame.setLocationRelativeTo(null); |
| myFrame.setVisible(visible); |
| myFrame.setState(JFrame.NORMAL); |
| IdeFocusManager focusManager = IdeFocusManager.findInstance(); |
| JComponent target = focusManager.getFocusTargetFor(myFrame.getRootPane()); |
| focusManager.requestFocus(target != null ? target : myFrame, true); |
| } |
| } |
| |
| /** |
| * Determines if the presentation is displayed. |
| */ |
| public boolean isDisplayed(){ |
| if(myModallyActivated){ |
| if(myDialog!=null){ |
| return myDialog.isShowing(); |
| } else{ |
| return false; |
| } |
| } else{ |
| if(myFrame!=null){ |
| if(!myFrame.isVisible()){ |
| return false; |
| } else{ |
| return myFrame.getState() == JFrame.NORMAL; |
| } |
| } else{ |
| return false; |
| } |
| } |
| } |
| |
| /** |
| * Requests the presentation be located at a given position. |
| * This operation may throw an UnsupportedOperationException if the |
| * underlying implementation does not allow this. |
| */ |
| public void setLocation(Point p) throws UnsupportedOperationException{ |
| createHelpWindow(); |
| if(myModallyActivated){ |
| myDialog.setLocation(p); |
| } else{ |
| myFrame.setLocation(p); |
| } |
| } |
| |
| /** |
| * Requests the location of the presentation. |
| * @throws UnsupportedOperationException If the underlying implementation |
| * does not allow this. |
| * @throws IllegalComponentStateException If the presentation is not |
| * displayed. |
| * @return Point the location of the presentation. |
| */ |
| public Point getLocation() throws UnsupportedOperationException{ |
| if(jhelp==null){ |
| throw new java.awt.IllegalComponentStateException("presentation not displayed"); |
| } |
| if(myModallyActivated){ |
| if(myDialog!=null){ |
| return myDialog.getLocation(); |
| } |
| } else{ |
| if(myFrame!=null){ |
| return myFrame.getLocation(); |
| } |
| } |
| return null; |
| |
| } |
| |
| /** |
| * Requests the presentation be set to a given size. |
| * This operation may throw an UnsupportedOperationException if the |
| * underlying implementation does not allow this. |
| */ |
| public void setSize(Dimension d) throws UnsupportedOperationException{ |
| HELP_WIDTH=d.width; |
| HELP_HEIGHT=d.height; |
| createHelpWindow(); |
| if(myModallyActivated){ |
| myDialog.setSize(d); |
| myDialog.validate(); |
| } else{ |
| myFrame.setSize(d); |
| myFrame.validate(); |
| } |
| } |
| |
| /** |
| * Requests the size of the presentation. |
| * @throws UnsupportedOperationException If the underlying implementation |
| * does not allow this. |
| * @throws IllegalComponentStateException If the presentation is not |
| * displayed. |
| * @return Point the location of the presentation. |
| */ |
| public Dimension getSize() throws UnsupportedOperationException{ |
| if(jhelp==null){ |
| throw new java.awt.IllegalComponentStateException("presentation not displayed"); |
| } |
| if(myModallyActivated){ |
| if(myDialog!=null){ |
| return myDialog.getSize(); |
| } |
| } else{ |
| if(myFrame!=null){ |
| return myFrame.getSize(); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Hides/Shows view. |
| */ |
| public void setViewDisplayed(boolean displayed){ |
| createHelpWindow(); |
| jhelp.setNavigatorDisplayed(displayed); |
| } |
| |
| /** |
| * Determines if the current view is visible. |
| */ |
| public boolean isViewDisplayed(){ |
| createHelpWindow(); |
| return jhelp.isNavigatorDisplayed(); |
| } |
| |
| /** |
| * Shows this ID as content relative to the (top) HelpSet for the HelpBroker |
| * instance--HelpVisitListeners are notified. |
| * |
| * @param id A string that identifies the topic to show for the loaded (top) HelpSet |
| * @exception BadIDException The ID is not valid for the HelpSet |
| */ |
| public void setCurrentID(String id) throws BadIDException{ |
| try{ |
| setCurrentID(ID.create(id,myHelpSet)); |
| } catch(InvalidHelpSetContextException ex){ |
| // this should not happen |
| new Error("internal error?"); |
| } |
| } |
| |
| /** |
| * Displays this ID--HelpVisitListeners are notified. |
| * |
| * @param id a Map.ID indicating the URL to display |
| * @exception InvalidHelpSetContextException if the current helpset does not contain |
| * id.helpset |
| */ |
| public void setCurrentID(ID id) throws InvalidHelpSetContextException{ |
| createJHelp(); |
| jhelp.getModel().setCurrentID(id); |
| } |
| |
| /** |
| * Determines which ID is displayed (if any). |
| */ |
| public ID getCurrentID(){ |
| if(jhelp!=null){ |
| return jhelp.getModel().getCurrentID(); |
| } else{ |
| return null; |
| } |
| } |
| |
| /** |
| * Displays this URL. |
| * HelpVisitListeners are notified. |
| * The currentID changes if there is a mathing ID for this URL |
| * @param url The url to display. A null URL is a valid url. |
| */ |
| public void setCurrentURL(URL url){ |
| createHelpWindow(); |
| |
| jhelp.getModel().setCurrentURL(url); |
| if(myModallyActivated){ |
| myDialog.setVisible(true); |
| myDialog.setVisible(true); |
| } else{ |
| myFrame.setVisible(true); |
| } |
| } |
| |
| /** |
| * Determines which URL is displayed. |
| */ |
| public URL getCurrentURL(){ |
| return jhelp.getModel().getCurrentURL(); |
| } |
| |
| |
| // Context-Senstive methods |
| /** |
| * Enables the Help key on a Component. This method works best when |
| * the component is the |
| * rootPane of a JFrame in Swing implementations, or a java.awt.Window |
| * (or subclass thereof) in AWT implementations. |
| * This method sets the default |
| * helpID and HelpSet for the Component and registers keyboard actions |
| * to trap the "Help" keypress. When the "Help" key is pressed, if the |
| * object with the current focus has a helpID, the helpID is displayed. |
| * otherwise the default helpID is displayed. |
| * |
| * @param comp the Component to enable the keyboard actions on. |
| * @param id the default HelpID to be displayed |
| * @param hs the default HelpSet to be displayed. If hs is null the default HelpSet |
| * will be assumed. |
| */ |
| public void enableHelpKey(Component comp,@NotNull String id,HelpSet hs){ |
| CSH.setHelpIDString(comp,id); |
| if(hs!=null){ |
| CSH.setHelpSet(comp,hs); |
| } |
| if(comp instanceof JComponent){ |
| JComponent root=(JComponent)comp; |
| root.registerKeyboardAction(getDisplayHelpFromFocus(), |
| KeyStroke.getKeyStroke(KeyEvent.VK_HELP,0), |
| JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); |
| root.registerKeyboardAction(getDisplayHelpFromFocus(), |
| KeyStroke.getKeyStroke(KeyEvent.VK_F1,0), |
| JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); |
| } else{ |
| comp.addKeyListener(this); |
| } |
| } |
| |
| /** |
| * Invoked when a key is typed. This event occurs when a |
| * key press is followed by a key release. Not intended to be overridden or extended. |
| */ |
| public void keyTyped(KeyEvent e){ |
| //ignore |
| } |
| |
| /** |
| * Invoked when a key is pressed. Not intended to be overridden or extended. |
| */ |
| public void keyPressed(KeyEvent e){ |
| //ingore |
| } |
| |
| |
| /** |
| * Invoked when a key is released. Not intended to be overridden or extended. |
| */ |
| public void keyReleased(KeyEvent e){ |
| // simulate what is done in JComponents registerKeyboardActions. |
| int code=e.getKeyCode(); |
| if(code==KeyEvent.VK_F1||code==KeyEvent.VK_HELP){ |
| ActionListener al=getDisplayHelpFromFocus(); |
| al.actionPerformed(new ActionEvent(e.getComponent(), |
| ActionEvent.ACTION_PERFORMED, |
| null)); |
| } |
| |
| } |
| |
| /** |
| * Enables help for a Component. This method sets a |
| * component's helpID and HelpSet. |
| * |
| * @param comp the Component to set the id and hs on. |
| * @param id the String value of an Map.ID. |
| * @param hs the HelpSet the id is in. If hs is null the default HelpSet |
| * will be assumed. |
| */ |
| public void enableHelp(Component comp,@NotNull String id,HelpSet hs){ |
| CSH.setHelpIDString(comp,id); |
| if(hs!=null){ |
| CSH.setHelpSet(comp,hs); |
| } |
| } |
| |
| /** |
| * Enables help for a MenuItem. This method sets a |
| * component's helpID and HelpSet. |
| * |
| * @param comp the MenuItem to set the id and hs on. |
| * @param id the String value of an Map.ID. |
| * @param hs the HelpSet the id is in. If hs is null the default HelpSet |
| * will be assumed. |
| */ |
| public void enableHelp(MenuItem comp,@NotNull String id,HelpSet hs){ |
| CSH.setHelpIDString(comp,id); |
| if(hs!=null){ |
| CSH.setHelpSet(comp,hs); |
| } |
| } |
| |
| /** |
| * Enables help for a Component. This method sets a |
| * Component's helpID and HelpSet and adds an ActionListener. |
| * When an action is performed |
| * it displays the Component's helpID and HelpSet in the default viewer. |
| * |
| * @param comp the Component to set the id and hs on. If the Component is not |
| * a javax.swing.AbstractButton or a |
| * java.awt.Button an IllegalArgumentException is thrown. |
| * @param id the String value of an Map.ID. |
| * @param hs the HelpSet the id is in. If hs is null the default HelpSet |
| * will be assumed. |
| * |
| * @see javax.swing.AbstractButton |
| * @see java.awt.Button |
| * @throws IllegalArgumentException if comp is not a button. |
| */ |
| public void enableHelpOnButton(Component comp,@NotNull String id,HelpSet hs){ |
| if(!(comp instanceof AbstractButton)&&!(comp instanceof Button)){ |
| throw new IllegalArgumentException("Invalid Component. comp must be either a javax.swing.AbstractButton or a java.awt.Button"); |
| } |
| CSH.setHelpIDString(comp,id); |
| if(hs!=null){ |
| CSH.setHelpSet(comp,hs); |
| } |
| if(comp instanceof AbstractButton){ |
| AbstractButton button=(AbstractButton)comp; |
| button.addActionListener(getDisplayHelpFromSource()); |
| }else{ |
| Button button=(Button)comp; |
| button.addActionListener(getDisplayHelpFromSource()); |
| } |
| } |
| |
| /** |
| * Enables help for a MenuItem. This method sets a |
| * Component's helpID and HelpSet and adds an ActionListener. |
| * When an action is performed |
| * it displays the Component's helpID and HelpSet in the default viewer. |
| * |
| * @param comp the MenuItem to set the id and hs on. If comp is null |
| * an IllegalAgrumentException is thrown. |
| * @param id the String value of an Map.ID. |
| * @param hs the HelpSet the id is in. If hs is null the default HelpSet |
| * will be assumed. |
| * @see java.awt.MenuItem |
| * @throws IllegalArgumentException if comp is null. |
| */ |
| public void enableHelpOnButton(@NotNull MenuItem comp,@NotNull String id,HelpSet hs){ |
| CSH.setHelpIDString(comp,id); |
| if(hs!=null){ |
| CSH.setHelpSet(comp,hs); |
| } |
| comp.addActionListener(getDisplayHelpFromSource()); |
| } |
| |
| /** |
| * Returns the default DisplayHelpFromFocus listener. |
| */ |
| protected ActionListener getDisplayHelpFromFocus(){ |
| if(displayHelpFromFocus==null){ |
| displayHelpFromFocus=new CSH.DisplayHelpFromFocus(this); |
| } |
| return displayHelpFromFocus; |
| } |
| |
| /** |
| * Returns the default DisplayHelpFromSource listener. |
| */ |
| protected ActionListener getDisplayHelpFromSource(){ |
| if(displayHelpFromSource==null){ |
| displayHelpFromSource=new CSH.DisplayHelpFromSource(this); |
| } |
| return displayHelpFromSource; |
| } |
| |
| /** |
| * Set the activation window. If the window is an instance of a |
| * Dialog and the is modal, modallyActivated help is set to true and |
| * ownerDialog is set to the window. In all other instances |
| * modallyActivated is set to false and ownerDialog is set to null. |
| * @param window the activating window |
| * @since 1.1 |
| */ |
| public void setActivationWindow(Window window){ |
| if (window instanceof Dialog) { |
| Dialog tmpDialog = (Dialog)window; |
| if (tmpDialog.isModal()) { |
| myOwnerWindow = window; |
| myModallyActivated = true; |
| } |
| else { |
| myOwnerWindow = null; |
| myModallyActivated = false; |
| } |
| } |
| else { |
| myOwnerWindow = null; |
| myModallyActivated = false; |
| } |
| } |
| |
| |
| /** |
| * Private methods. |
| */ |
| private int HELP_WIDTH = (int)(ScreenUtil.getMainScreenBounds().width * 0.8); |
| private int HELP_HEIGHT = (int)(ScreenUtil.getMainScreenBounds().height * 0.8); |
| |
| private synchronized void createJHelp(){ |
| if(jhelp==null){ |
| jhelp=new IdeaJHelp(myHelpSet); |
| if(myFont!=null){ |
| jhelp.setFont(myFont); |
| } |
| if(myLocale!=null){ |
| jhelp.setLocale(myLocale); |
| } |
| } |
| } |
| |
| private WindowListener dl; |
| private boolean modalDeactivated=true; |
| |
| private synchronized void createHelpWindow(){ |
| JDialog tmpDialog=null; |
| Dimension size=null; |
| Point pos=null; |
| boolean resize = false; |
| |
| createJHelp(); |
| // Get the title from the HelpSet |
| String helpTitle=myHelpSet.getTitle(); |
| |
| if(myModallyActivated){ |
| // replace dialog.getOwner() with the following code |
| Window owner=null; |
| try{ |
| Method m=Window.class.getMethod("getOwner",null); |
| |
| if(m!=null&&myDialog!=null){ |
| owner=(Window)m.invoke(myDialog,null); |
| } |
| } catch(NoSuchMethodError ex){ |
| // as in JDK1.1 |
| } catch(NoSuchMethodException ex){ |
| // as in JDK1.1 |
| } catch(java.lang.reflect.InvocationTargetException ex){ |
| // |
| } catch(java.lang.IllegalAccessException ex){ |
| // |
| } |
| |
| if(myDialog==null||owner!=myOwnerWindow||modalDeactivated){ |
| if(myFrame!=null){ |
| pos=myFrame.getLocation(); |
| size=myFrame.getSize(); |
| myFrame.dispose(); |
| } |
| if(myDialog!=null){ |
| pos=myDialog.getLocation(); |
| size=myDialog.getSize(); |
| tmpDialog=myDialog; |
| } |
| |
| myDialog=new JDialog((Dialog)myOwnerWindow,helpTitle); |
| |
| // Modal dialogs are really tricky. When the modal dialog |
| // is dismissed the JDialog will be dismissed as well. |
| // When that happens we need to make sure the ownerWindow |
| // is set to null so that a new dialog will be created so |
| // that events aren't blocked in the HelpViewer. |
| dl=new WindowAdapter(){ |
| public void windowClosing(WindowEvent e){ |
| // JDK1.2.1 bug not closing owned windows |
| if(myDialog.isShowing()){ |
| myDialog.hide(); |
| } |
| if (myOwnerWindow != null) { |
| myOwnerWindow.removeWindowListener(dl); |
| } |
| myOwnerWindow=null; |
| modalDeactivated=true; |
| } |
| }; |
| myOwnerWindow.addWindowListener(dl); |
| modalDeactivated=false; |
| |
| if(size!=null){ |
| myDialog.setSize(size); |
| } else{ |
| myDialog.setSize(HELP_WIDTH,HELP_HEIGHT); |
| } |
| if(pos!=null){ |
| myDialog.setLocation(pos); |
| } |
| myDialog.getContentPane().add(jhelp); |
| if(tmpDialog!=null){ |
| tmpDialog.dispose(); |
| } |
| } |
| } else{ |
| if (myFrame == null) { |
| myFrame = new JFrame(helpTitle); |
| resize = true; |
| AppUIUtil.updateWindowIcon(myFrame); |
| WindowListener l = new WindowAdapter() { |
| public void windowClosing(WindowEvent e) { |
| myFrame.setVisible(false); |
| } |
| |
| public void windowClosed(WindowEvent e) { |
| myFrame.setVisible(false); |
| } |
| }; |
| myFrame.addWindowListener(l); |
| } |
| else { |
| pos = myFrame.getLocation(); |
| } |
| if(myDialog!=null){ |
| pos=myDialog.getLocation(); |
| size=myDialog.getSize(); |
| myDialog.dispose(); |
| myDialog=null; |
| myOwnerWindow=null; |
| } |
| if(size!=null){ |
| myFrame.setSize(size); |
| } else if (resize) { |
| myFrame.setSize(HELP_WIDTH,HELP_HEIGHT); |
| } |
| if(pos!=null){ |
| myFrame.setLocation(pos); |
| } |
| myFrame.getContentPane().add(jhelp); |
| myFrame.setTitle(myHelpSet.getTitle()); |
| } |
| |
| } |
| |
| // the listeners. |
| private ActionListener displayHelpFromFocus; |
| private ActionListener displayHelpFromSource; |
| } |
| |