sticky documentation popup [take 1]
[fedora-idea.git] / platform / lang-impl / src / com / intellij / codeInsight / documentation / DocumentationComponent.java
blobbd47348f1e2c830544778a3b98c2cd3f13b02434
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 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>();
86 // KeyStroke --> ActionListener
88 public boolean requestFocusInWindow() {
89 return myScrollPane.requestFocusInWindow();
93 public void requestFocus() {
94 myScrollPane.requestFocus();
97 public DocumentationComponent(final DocumentationManager manager, final AnAction[] additionalActions) {
98 myManager = manager;
99 myIsEmpty = true;
100 myIsShown = false;
102 myEditorPane = new JEditorPane(UIUtil.HTML_MIME, "") {
103 public Dimension getPreferredScrollableViewportSize() {
104 if (getWidth() == 0 || getHeight() == 0) {
105 setSize(MAX_WIDTH, MAX_HEIGHT);
107 Insets ins = myEditorPane.getInsets();
108 View rootView = myEditorPane.getUI().getRootView(myEditorPane);
109 rootView.setSize(MAX_WIDTH,
110 MAX_HEIGHT); // Necessary! Without this line, size will not increase then you go from small page to bigger one
111 int prefHeight = (int)rootView.getPreferredSpan(View.Y_AXIS);
112 prefHeight += ins.bottom + ins.top + myScrollPane.getHorizontalScrollBar().getMaximumSize().height;
113 return new Dimension(MAX_WIDTH, Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, prefHeight)));
117 enableEvents(KeyEvent.KEY_EVENT_MASK);
120 protected void processKeyEvent(KeyEvent e) {
121 KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(e);
122 ActionListener listener = myKeyboardActions.get(keyStroke);
123 if (listener != null) {
124 listener.actionPerformed(new ActionEvent(DocumentationComponent.this, 0, ""));
125 e.consume();
126 return;
128 super.processKeyEvent(e);
131 myText = "";
132 myEditorPane.setEditable(false);
133 myEditorPane.setBackground(HintUtil.INFORMATION_COLOR);
135 myScrollPane = new JScrollPane(myEditorPane);
136 myScrollPane.setBorder(null);
138 final MouseAdapter mouseAdapter = new MouseAdapter() {
139 public void mousePressed(MouseEvent e) {
140 myManager.requestFocus();
143 myEditorPane.addMouseListener(mouseAdapter);
144 Disposer.register(this, new Disposable() {
145 public void dispose() {
146 myEditorPane.removeMouseListener(mouseAdapter);
150 final FocusAdapter focusAdapter = new FocusAdapter() {
151 public void focusLost(FocusEvent e) {
152 Component previouslyFocused = WindowManagerEx.getInstanceEx().getFocusedComponent(manager.getProject(getElement()));
154 if (!(previouslyFocused == myEditorPane)) {
155 if (myHint != null && !myHint.isDisposed()) myHint.cancel();
159 myEditorPane.addFocusListener(focusAdapter);
161 Disposer.register(this, new Disposable() {
162 public void dispose() {
163 myEditorPane.removeFocusListener(focusAdapter);
167 setLayout(new BorderLayout());
168 add(myScrollPane, BorderLayout.CENTER);
169 myScrollPane.setBorder(BorderFactory.createEmptyBorder(0, 2, 2, 2));
171 final DefaultActionGroup actions = new DefaultActionGroup();
172 actions.add(new BackAction());
173 actions.add(new ForwardAction());
174 actions.add(myExternalDocAction = new ExternalDocAction());
176 if (additionalActions != null) {
177 for (final AnAction action : additionalActions) {
178 actions.add(action);
182 myToolBar = ActionManager.getInstance().createActionToolbar(ActionPlaces.JAVADOC_TOOLBAR, actions, true);
184 myControlPanel = new JPanel();
185 myControlPanel.setLayout(new BorderLayout());
186 myControlPanel.setBorder(new EdgeBorder(EdgeBorder.EDGE_BOTTOM));
187 JPanel dummyPanel = new JPanel();
189 myElementLabel = new JLabel();
191 dummyPanel.setLayout(new BorderLayout());
192 dummyPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
194 dummyPanel.add(myElementLabel, BorderLayout.EAST);
196 myControlPanel.add(myToolBar.getComponent(), BorderLayout.WEST);
197 myControlPanel.add(dummyPanel, BorderLayout.CENTER);
198 myControlPanelVisible = false;
200 final HyperlinkListener hyperlinkListener = new HyperlinkListener() {
201 public void hyperlinkUpdate(HyperlinkEvent e) {
202 HyperlinkEvent.EventType type = e.getEventType();
203 if (type == HyperlinkEvent.EventType.ACTIVATED) {
204 manager.navigateByLink(DocumentationComponent.this, e.getDescription());
206 else if (type == HyperlinkEvent.EventType.ENTERED) {
207 myEditorPane.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
209 else if (type == HyperlinkEvent.EventType.EXITED) {
210 myEditorPane.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
214 myEditorPane.addHyperlinkListener(hyperlinkListener);
215 Disposer.register(this, new Disposable() {
216 public void dispose() {
217 myEditorPane.removeHyperlinkListener(hyperlinkListener);
221 registerActions();
223 updateControlState();
226 public DocumentationComponent(final DocumentationManager manager) {
227 this(manager, null);
230 public synchronized boolean isEmpty() {
231 return myIsEmpty;
234 public synchronized void startWait() {
235 myIsEmpty = true;
238 private void setControlPanelVisible(boolean visible) {
239 if (visible == myControlPanelVisible) return;
240 if (visible) {
241 add(myControlPanel, BorderLayout.NORTH);
243 else {
244 remove(myControlPanel);
246 myControlPanelVisible = visible;
249 public void setHint(JBPopup hint) {
250 myHint = hint;
253 public JComponent getComponent() {
254 return myEditorPane;
257 @Nullable
258 public PsiElement getElement() {
259 return myElement != null ? myElement.getElement() : null;
262 public void setText(String text, PsiElement element, boolean clearHistory) {
263 setText(text, element, false, clearHistory);
266 public void setText(String text, PsiElement element, boolean clean, boolean clearHistory) {
267 if (clean && myElement != null) {
268 myBackStack.push(saveContext());
269 myForwardStack.clear();
271 updateControlState();
272 setData(element, text, !clean);
273 if (clean) {
274 myIsEmpty = false;
277 if (clearHistory) clearHistory();
280 private void clearHistory() {
281 myForwardStack.clear();
282 myBackStack.clear();
285 public void setData(PsiElement _element, String text, final boolean clearHistory) {
286 if (myElement != null) {
287 myBackStack.push(saveContext());
288 myForwardStack.clear();
291 final SmartPsiElementPointer element = _element != null && _element.isValid()
292 ? SmartPointerManager.getInstance(_element.getProject()).createSmartPsiElementPointer(_element)
293 : null;
295 if (element != null) {
296 myElement = element;
299 myIsEmpty = false;
300 updateControlState();
301 setDataInternal(element, text, new Rectangle(0, 0));
303 if (clearHistory) clearHistory();
306 private void setDataInternal(SmartPsiElementPointer element, String text, final Rectangle viewRect) {
307 setDataInternal(element, text, viewRect, false);
310 private void setDataInternal(SmartPsiElementPointer element, String text, final Rectangle viewRect, boolean skip) {
311 boolean justShown = false;
313 myElement = element;
315 if (!myIsShown && myHint != null) {
316 myEditorPane.setText(text);
317 myManager.showHint(myHint);
318 myIsShown = justShown = true;
321 if (!justShown) {
322 myEditorPane.setText(text);
325 if (!skip) {
326 myText = text;
329 SwingUtilities.invokeLater(new Runnable() {
330 public void run() {
331 myEditorPane.scrollRectToVisible(viewRect);
336 private void goBack() {
337 if (myBackStack.isEmpty()) return;
338 Context context = myBackStack.pop();
339 myForwardStack.push(saveContext());
340 restoreContext(context);
341 updateControlState();
344 private void goForward() {
345 if (myForwardStack.isEmpty()) return;
346 Context context = myForwardStack.pop();
347 myBackStack.push(saveContext());
348 restoreContext(context);
349 updateControlState();
352 private Context saveContext() {
353 Rectangle rect = myScrollPane.getViewport().getViewRect();
354 return new Context(myElement, myText, rect);
357 private void restoreContext(Context context) {
358 setDataInternal(context.element, context.text, context.viewRect);
361 private void updateControlState() {
362 ElementLocationUtil.customizeElementLabel(myElement != null ? myElement.getElement() : null, myElementLabel);
363 myToolBar.updateActionsImmediately(); // update faster
364 setControlPanelVisible(true);//(!myBackStack.isEmpty() || !myForwardStack.isEmpty());
367 private class BackAction extends AnAction implements HintManagerImpl.ActionToIgnore {
368 public BackAction() {
369 super(CodeInsightBundle.message("javadoc.action.back"), null, IconLoader.getIcon("/actions/back.png"));
372 public void actionPerformed(AnActionEvent e) {
373 goBack();
376 public void update(AnActionEvent e) {
377 Presentation presentation = e.getPresentation();
378 presentation.setEnabled(!myBackStack.isEmpty());
382 private class ForwardAction extends AnAction implements HintManagerImpl.ActionToIgnore {
383 public ForwardAction() {
384 super(CodeInsightBundle.message("javadoc.action.forward"), null, IconLoader.getIcon("/actions/forward.png"));
387 public void actionPerformed(AnActionEvent e) {
388 goForward();
391 public void update(AnActionEvent e) {
392 Presentation presentation = e.getPresentation();
393 presentation.setEnabled(!myForwardStack.isEmpty());
397 private class ExternalDocAction extends AnAction implements HintManagerImpl.ActionToIgnore {
398 public ExternalDocAction() {
399 super(CodeInsightBundle.message("javadoc.action.view.external"), null, IconLoader.getIcon("/actions/browser-externalJavaDoc.png"));
400 registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EXTERNAL_JAVADOC).getShortcutSet(), null);
403 public void actionPerformed(AnActionEvent e) {
404 if (myElement != null) {
405 final PsiElement element = myElement.getElement();
406 final DocumentationProvider provider = DocumentationManager.getProviderFromElement(element);
407 final List<String> urls = provider.getUrlFor(element, DocumentationManager.getOriginalElement(element));
408 assert urls != null;
409 assert !urls.isEmpty();
410 ExternalJavaDocAction.showExternalJavadoc(urls);
414 public void update(AnActionEvent e) {
415 final Presentation presentation = e.getPresentation();
416 presentation.setEnabled(false);
417 if (myElement != null) {
418 final PsiElement element = myElement.getElement();
419 final DocumentationProvider provider = DocumentationManager.getProviderFromElement(element);
420 final List<String> urls = provider.getUrlFor(element, DocumentationManager.getOriginalElement(element));
421 presentation.setEnabled(element != null && urls != null && !urls.isEmpty());
426 private void registerActions() {
427 myExternalDocAction
428 .registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EXTERNAL_JAVADOC).getShortcutSet(), myEditorPane);
430 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), new ActionListener() {
431 public void actionPerformed(ActionEvent e) {
432 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
433 int value = scrollBar.getValue() - scrollBar.getUnitIncrement(-1);
434 value = Math.max(value, 0);
435 scrollBar.setValue(value);
439 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), new ActionListener() {
440 public void actionPerformed(ActionEvent e) {
441 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
442 int value = scrollBar.getValue() + scrollBar.getUnitIncrement(+1);
443 value = Math.min(value, scrollBar.getMaximum());
444 scrollBar.setValue(value);
448 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), new ActionListener() {
449 public void actionPerformed(ActionEvent e) {
450 JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar();
451 int value = scrollBar.getValue() - scrollBar.getUnitIncrement(-1);
452 value = Math.max(value, 0);
453 scrollBar.setValue(value);
457 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), new ActionListener() {
458 public void actionPerformed(ActionEvent e) {
459 JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar();
460 int value = scrollBar.getValue() + scrollBar.getUnitIncrement(+1);
461 value = Math.min(value, scrollBar.getMaximum());
462 scrollBar.setValue(value);
466 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), new ActionListener() {
467 public void actionPerformed(ActionEvent e) {
468 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
469 int value = scrollBar.getValue() - scrollBar.getBlockIncrement(-1);
470 value = Math.max(value, 0);
471 scrollBar.setValue(value);
475 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), new ActionListener() {
476 public void actionPerformed(ActionEvent e) {
477 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
478 int value = scrollBar.getValue() + scrollBar.getBlockIncrement(+1);
479 value = Math.min(value, scrollBar.getMaximum());
480 scrollBar.setValue(value);
484 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0), new ActionListener() {
485 public void actionPerformed(ActionEvent e) {
486 JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar();
487 scrollBar.setValue(0);
491 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0), new ActionListener() {
492 public void actionPerformed(ActionEvent e) {
493 JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar();
494 scrollBar.setValue(scrollBar.getMaximum());
498 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, KeyEvent.CTRL_MASK), new ActionListener() {
499 public void actionPerformed(ActionEvent e) {
500 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
501 scrollBar.setValue(0);
505 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, KeyEvent.CTRL_MASK), new ActionListener() {
506 public void actionPerformed(ActionEvent e) {
507 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
508 scrollBar.setValue(scrollBar.getMaximum());
513 public String getText() {
514 return myText;
517 public void dispose() {
518 myBackStack.clear();
519 myForwardStack.clear();
520 myKeyboardActions.clear();
521 myElement = null;
522 myManager = null;
523 myHint = null;