back/forward context for documentation (IDEA-13126)
[fedora-idea.git] / platform / lang-impl / src / com / intellij / codeInsight / documentation / DocumentationComponent.java
blobeecfd73bac42e5a71449f70e905610dcbc590cb7
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.codeInsight.documentation;
19 import com.intellij.codeInsight.CodeInsightBundle;
20 import com.intellij.codeInsight.hint.ElementLocationUtil;
21 import com.intellij.codeInsight.hint.HintManagerImpl;
22 import com.intellij.codeInsight.hint.HintUtil;
23 import com.intellij.ide.actions.ExternalJavaDocAction;
24 import com.intellij.lang.documentation.DocumentationProvider;
25 import com.intellij.openapi.Disposable;
26 import com.intellij.openapi.actionSystem.*;
27 import com.intellij.openapi.ui.popup.JBPopup;
28 import com.intellij.openapi.util.Disposer;
29 import com.intellij.openapi.util.IconLoader;
30 import com.intellij.openapi.wm.ex.WindowManagerEx;
31 import com.intellij.psi.PsiElement;
32 import com.intellij.psi.SmartPointerManager;
33 import com.intellij.psi.SmartPsiElementPointer;
34 import com.intellij.ui.EdgeBorder;
35 import com.intellij.util.containers.HashMap;
36 import com.intellij.util.ui.UIUtil;
37 import org.jetbrains.annotations.Nullable;
39 import javax.swing.*;
40 import javax.swing.event.HyperlinkEvent;
41 import javax.swing.event.HyperlinkListener;
42 import javax.swing.text.View;
43 import java.awt.*;
44 import java.awt.event.*;
45 import java.util.List;
46 import java.util.Stack;
48 public class DocumentationComponent extends JPanel implements Disposable{
50 private static final int MAX_WIDTH = 500;
51 private static final int MAX_HEIGHT = 300;
52 private static final int MIN_HEIGHT = 45;
54 private DocumentationManager myManager;
55 private SmartPsiElementPointer myElement;
57 private final Stack<Context> myBackStack = new Stack<Context>();
58 private final Stack<Context> myForwardStack = new Stack<Context>();
59 private final ActionToolbar myToolBar;
60 private boolean myIsEmpty;
61 private boolean myIsShown;
62 private final JLabel myElementLabel;
64 private static class Context {
65 final SmartPsiElementPointer element;
66 final String text;
67 final Rectangle viewRect;
69 public Context(SmartPsiElementPointer element, String text, Rectangle viewRect) {
70 this.element = element;
71 this.text = text;
72 this.viewRect = viewRect;
76 private final JScrollPane myScrollPane;
77 private final JEditorPane myEditorPane;
78 private String myText; // myEditorPane.getText() surprisingly crashes.., let's cache the text
79 private final JPanel myControlPanel;
80 private boolean myControlPanelVisible;
81 private final ExternalDocAction myExternalDocAction;
83 private JBPopup myHint;
85 private final HashMap<KeyStroke,ActionListener> myKeyboardActions = new HashMap<KeyStroke, ActionListener>(); // KeyStroke --> ActionListener
87 public boolean requestFocusInWindow() {
88 return myScrollPane.requestFocusInWindow();
92 public void requestFocus() {
93 myScrollPane.requestFocus();
96 public DocumentationComponent(final DocumentationManager manager) {
97 myManager = manager;
98 myIsEmpty = true;
99 myIsShown = false;
101 myEditorPane = new JEditorPane(UIUtil.HTML_MIME, "") {
102 public Dimension getPreferredScrollableViewportSize() {
103 if (getWidth() == 0 || getHeight() == 0) {
104 setSize(MAX_WIDTH, MAX_HEIGHT);
106 Insets ins = myEditorPane.getInsets();
107 View rootView = myEditorPane.getUI().getRootView(myEditorPane);
108 rootView.setSize(MAX_WIDTH, MAX_HEIGHT); // Necessary! Without this line, size will not increase then you go from small page to bigger one
109 int prefHeight = (int) rootView.getPreferredSpan(View.Y_AXIS);
110 prefHeight += ins.bottom + ins.top + myScrollPane.getHorizontalScrollBar().getMaximumSize().height;
111 return new Dimension(MAX_WIDTH, Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, prefHeight)));
115 enableEvents(KeyEvent.KEY_EVENT_MASK);
118 protected void processKeyEvent(KeyEvent e) {
119 KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(e);
120 ActionListener listener = myKeyboardActions.get(keyStroke);
121 if (listener != null) {
122 listener.actionPerformed(new ActionEvent(DocumentationComponent.this, 0, ""));
123 e.consume();
124 return;
126 super.processKeyEvent(e);
129 myText = "";
130 myEditorPane.setEditable(false);
131 myEditorPane.setBackground(HintUtil.INFORMATION_COLOR);
133 myScrollPane = new JScrollPane(myEditorPane);
134 myScrollPane.setBorder(null);
136 final MouseAdapter mouseAdapter = new MouseAdapter() {
137 public void mousePressed(MouseEvent e) {
138 myManager.requestFocus();
141 myEditorPane.addMouseListener(mouseAdapter);
142 Disposer.register(this, new Disposable() {
143 public void dispose() {
144 myEditorPane.removeMouseListener(mouseAdapter);
148 final FocusAdapter focusAdapter = new FocusAdapter() {
149 public void focusLost(FocusEvent e) {
150 Component previouslyFocused = WindowManagerEx.getInstanceEx().getFocusedComponent(manager.getProject(getElement()));
152 if (!(previouslyFocused == myEditorPane)) {
153 myHint.cancel();
157 myEditorPane.addFocusListener(focusAdapter);
159 Disposer.register(this, new Disposable() {
160 public void dispose() {
161 myEditorPane.removeFocusListener(focusAdapter);
165 setLayout(new BorderLayout());
166 add(myScrollPane, BorderLayout.CENTER);
167 myScrollPane.setBorder(BorderFactory.createEmptyBorder(0, 2, 2, 2));
169 DefaultActionGroup group = new DefaultActionGroup();
170 group.add(new BackAction());
171 group.add(new ForwardAction());
172 group.add(myExternalDocAction = new ExternalDocAction());
173 myToolBar = ActionManager.getInstance().createActionToolbar(ActionPlaces.JAVADOC_TOOLBAR, group, true);
175 myControlPanel = new JPanel();
176 myControlPanel.setLayout(new BorderLayout());
177 myControlPanel.setBorder(new EdgeBorder(EdgeBorder.EDGE_BOTTOM));
178 JPanel dummyPanel = new JPanel();
180 myElementLabel = new JLabel();
182 dummyPanel.setLayout(new BorderLayout());
183 dummyPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
185 dummyPanel.add(myElementLabel, BorderLayout.EAST);
187 myControlPanel.add(myToolBar.getComponent(), BorderLayout.WEST);
188 myControlPanel.add(dummyPanel, BorderLayout.CENTER);
189 myControlPanelVisible = false;
191 final HyperlinkListener hyperlinkListener = new HyperlinkListener() {
192 public void hyperlinkUpdate(HyperlinkEvent e) {
193 HyperlinkEvent.EventType type = e.getEventType();
194 if (type == HyperlinkEvent.EventType.ACTIVATED) {
195 manager.navigateByLink(DocumentationComponent.this, e.getDescription());
197 else if (type == HyperlinkEvent.EventType.ENTERED) {
198 myEditorPane.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
200 else if (type == HyperlinkEvent.EventType.EXITED) {
201 myEditorPane.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
205 myEditorPane.addHyperlinkListener(hyperlinkListener);
206 Disposer.register(this, new Disposable() {
207 public void dispose() {
208 myEditorPane.removeHyperlinkListener(hyperlinkListener);
212 registerActions();
214 updateControlState();
217 public synchronized boolean isEmpty() {
218 return myIsEmpty;
221 public synchronized void startWait() {
222 myIsEmpty = true;
225 private void setControlPanelVisible(boolean visible) {
226 if (visible == myControlPanelVisible) return;
227 if (visible) {
228 add(myControlPanel, BorderLayout.NORTH);
229 } else {
230 remove(myControlPanel);
232 myControlPanelVisible = visible;
235 public void setHint(JBPopup hint) {
236 myHint = hint;
239 public JComponent getComponent() {
240 return myEditorPane;
243 @Nullable
244 public PsiElement getElement() {
245 return myElement != null ? myElement.getElement() : null;
248 public void setText(String text) {
249 setText(text, false);
252 public void setText(String text, boolean clean) {
253 if (clean && myElement != null) {
254 myBackStack.push(saveContext());
255 myForwardStack.clear();
257 updateControlState();
258 setDataInternal(myElement, text, new Rectangle(0, 0), !clean);
259 if (clean) {
260 myIsEmpty = false;
264 public void setData(PsiElement _element, String text) {
265 if (myElement != null) {
266 myBackStack.push(saveContext());
267 myForwardStack.clear();
270 final SmartPsiElementPointer element = _element != null && _element.isValid() ?
271 SmartPointerManager.getInstance(_element.getProject()).createSmartPsiElementPointer(_element):
272 null;
274 if (element != null) {
275 myElement = element;
278 myIsEmpty = false;
279 updateControlState();
280 setDataInternal(element, text, new Rectangle(0, 0));
283 private void setDataInternal(SmartPsiElementPointer element, String text, final Rectangle viewRect) {
284 setDataInternal(element, text, viewRect, false);
287 private void setDataInternal(SmartPsiElementPointer element, String text, final Rectangle viewRect, boolean skip) {
288 boolean justShown = false;
290 myElement = element;
292 if (!myIsShown && myHint != null) {
293 myEditorPane.setText(text);
294 myManager.showHint(myHint);
295 myIsShown = justShown = true;
298 if (!justShown) {
299 myEditorPane.setText(text);
302 if (!skip) {
303 myText = text;
306 SwingUtilities.invokeLater(new Runnable() {
307 public void run() {
308 myEditorPane.scrollRectToVisible(viewRect);
313 private void goBack() {
314 if (myBackStack.isEmpty()) return;
315 Context context = myBackStack.pop();
316 myForwardStack.push(saveContext());
317 restoreContext(context);
318 updateControlState();
321 private void goForward() {
322 if (myForwardStack.isEmpty()) return;
323 Context context = myForwardStack.pop();
324 myBackStack.push(saveContext());
325 restoreContext(context);
326 updateControlState();
329 private Context saveContext() {
330 Rectangle rect = myScrollPane.getViewport().getViewRect();
331 return new Context(myElement, myText, rect);
334 private void restoreContext(Context context) {
335 setDataInternal(context.element, context.text, context.viewRect);
338 private void updateControlState() {
339 ElementLocationUtil.customizeElementLabel(myElement != null ? myElement.getElement():null, myElementLabel);
340 myToolBar.updateActionsImmediately(); // update faster
341 setControlPanelVisible(true);//(!myBackStack.isEmpty() || !myForwardStack.isEmpty());
344 private class BackAction extends AnAction implements HintManagerImpl.ActionToIgnore {
345 public BackAction() {
346 super(CodeInsightBundle.message("javadoc.action.back"), null, IconLoader.getIcon("/actions/back.png"));
349 public void actionPerformed(AnActionEvent e) {
350 goBack();
353 public void update(AnActionEvent e) {
354 Presentation presentation = e.getPresentation();
355 presentation.setEnabled(!myBackStack.isEmpty());
359 private class ForwardAction extends AnAction implements HintManagerImpl.ActionToIgnore {
360 public ForwardAction() {
361 super(CodeInsightBundle.message("javadoc.action.forward"), null, IconLoader.getIcon("/actions/forward.png"));
364 public void actionPerformed(AnActionEvent e) {
365 goForward();
368 public void update(AnActionEvent e) {
369 Presentation presentation = e.getPresentation();
370 presentation.setEnabled(!myForwardStack.isEmpty());
374 private class ExternalDocAction extends AnAction implements HintManagerImpl.ActionToIgnore {
375 public ExternalDocAction() {
376 super(CodeInsightBundle.message("javadoc.action.view.external"), null, IconLoader.getIcon("/actions/browser-externalJavaDoc.png"));
377 registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EXTERNAL_JAVADOC).getShortcutSet(), null);
380 public void actionPerformed(AnActionEvent e) {
381 if (myElement != null) {
382 final PsiElement element = myElement.getElement();
383 final DocumentationProvider provider = DocumentationManager.getProviderFromElement(element);
384 final List<String> urls = provider.getUrlFor(element, DocumentationManager.getOriginalElement(element));
385 assert urls != null;
386 assert !urls.isEmpty();
387 ExternalJavaDocAction.showExternalJavadoc(urls);
391 public void update(AnActionEvent e) {
392 final Presentation presentation = e.getPresentation();
393 presentation.setEnabled(false);
394 if (myElement != null) {
395 final PsiElement element = myElement.getElement();
396 final DocumentationProvider provider = DocumentationManager.getProviderFromElement(element);
397 final List<String> urls = provider.getUrlFor(element, DocumentationManager.getOriginalElement(element));
398 presentation.setEnabled(element != null && urls!= null && !urls.isEmpty());
403 private void registerActions() {
404 myExternalDocAction.registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EXTERNAL_JAVADOC).getShortcutSet(),
405 myEditorPane);
407 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0),
408 new ActionListener() {
409 public void actionPerformed(ActionEvent e) {
410 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
411 int value = scrollBar.getValue() - scrollBar.getUnitIncrement(-1);
412 value = Math.max(value, 0);
413 scrollBar.setValue(value);
417 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0),
418 new ActionListener() {
419 public void actionPerformed(ActionEvent e) {
420 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
421 int value = scrollBar.getValue() + scrollBar.getUnitIncrement(+1);
422 value = Math.min(value, scrollBar.getMaximum());
423 scrollBar.setValue(value);
427 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0),
428 new ActionListener() {
429 public void actionPerformed(ActionEvent e) {
430 JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar();
431 int value = scrollBar.getValue() - scrollBar.getUnitIncrement(-1);
432 value = Math.max(value, 0);
433 scrollBar.setValue(value);
437 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0),
438 new ActionListener() {
439 public void actionPerformed(ActionEvent e) {
440 JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar();
441 int value = scrollBar.getValue() + scrollBar.getUnitIncrement(+1);
442 value = Math.min(value, scrollBar.getMaximum());
443 scrollBar.setValue(value);
447 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0),
448 new ActionListener() {
449 public void actionPerformed(ActionEvent e) {
450 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
451 int value = scrollBar.getValue() - scrollBar.getBlockIncrement(-1);
452 value = Math.max(value, 0);
453 scrollBar.setValue(value);
457 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0),
458 new ActionListener() {
459 public void actionPerformed(ActionEvent e) {
460 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
461 int value = scrollBar.getValue() + scrollBar.getBlockIncrement(+1);
462 value = Math.min(value, scrollBar.getMaximum());
463 scrollBar.setValue(value);
467 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0),
468 new ActionListener() {
469 public void actionPerformed(ActionEvent e) {
470 JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar();
471 scrollBar.setValue(0);
475 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0),
476 new ActionListener() {
477 public void actionPerformed(ActionEvent e) {
478 JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar();
479 scrollBar.setValue(scrollBar.getMaximum());
483 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, KeyEvent.CTRL_MASK),
484 new ActionListener() {
485 public void actionPerformed(ActionEvent e) {
486 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
487 scrollBar.setValue(0);
491 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, KeyEvent.CTRL_MASK),
492 new ActionListener() {
493 public void actionPerformed(ActionEvent e) {
494 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
495 scrollBar.setValue(scrollBar.getMaximum());
500 public String getText() {
501 return myText;
504 public void dispose() {
505 myBackStack.clear();
506 myForwardStack.clear();
507 myKeyboardActions.clear();
508 myElement = null;
509 myManager = null;
510 myHint = null;