blob: 00dca171ae844207a2334aef95fec53d97b7747e [file] [log] [blame]
/*
* 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;
}