8bcf274fd5b30375b2943cb8bc98608247005c4e
[fedora-idea.git] / platform / lang-impl / src / com / intellij / codeInsight / documentation / DocumentationComponent.java
blob8bcf274fd5b30375b2943cb8bc98608247005c4e
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 updateControlState();
254 setDataInternal(myElement, text, new Rectangle(0, 0), true);
255 if (clean) {
256 myIsEmpty = false;
260 public void setData(PsiElement _element, String text) {
261 if (myElement != null) {
262 myBackStack.push(saveContext());
263 myForwardStack.clear();
266 final SmartPsiElementPointer element = _element != null && _element.isValid() ?
267 SmartPointerManager.getInstance(_element.getProject()).createSmartPsiElementPointer(_element):
268 null;
270 if (element != null) {
271 myElement = element;
274 myIsEmpty = false;
275 updateControlState();
276 setDataInternal(element, text, new Rectangle(0, 0));
279 private void setDataInternal(SmartPsiElementPointer element, String text, final Rectangle viewRect) {
280 setDataInternal(element, text, viewRect, false);
283 private void setDataInternal(SmartPsiElementPointer element, String text, final Rectangle viewRect, boolean skip) {
284 boolean justShown = false;
286 myElement = element;
288 if (!myIsShown && myHint != null) {
289 myEditorPane.setText(text);
290 myManager.showHint(myHint);
291 myIsShown = justShown = true;
294 if (!justShown) {
295 myEditorPane.setText(text);
298 if (!skip) {
299 myText = text;
302 SwingUtilities.invokeLater(new Runnable() {
303 public void run() {
304 myEditorPane.scrollRectToVisible(viewRect);
309 private void goBack() {
310 if (myBackStack.isEmpty()) return;
311 Context context = myBackStack.pop();
312 myForwardStack.push(saveContext());
313 restoreContext(context);
314 updateControlState();
317 private void goForward() {
318 if (myForwardStack.isEmpty()) return;
319 Context context = myForwardStack.pop();
320 myBackStack.push(saveContext());
321 restoreContext(context);
322 updateControlState();
325 private Context saveContext() {
326 Rectangle rect = myScrollPane.getViewport().getViewRect();
327 return new Context(myElement, myText, rect);
330 private void restoreContext(Context context) {
331 setDataInternal(context.element, context.text, context.viewRect);
334 private void updateControlState() {
335 ElementLocationUtil.customizeElementLabel(myElement != null ? myElement.getElement():null, myElementLabel);
336 myToolBar.updateActionsImmediately(); // update faster
337 setControlPanelVisible(true);//(!myBackStack.isEmpty() || !myForwardStack.isEmpty());
340 private class BackAction extends AnAction implements HintManagerImpl.ActionToIgnore {
341 public BackAction() {
342 super(CodeInsightBundle.message("javadoc.action.back"), null, IconLoader.getIcon("/actions/back.png"));
345 public void actionPerformed(AnActionEvent e) {
346 goBack();
349 public void update(AnActionEvent e) {
350 Presentation presentation = e.getPresentation();
351 presentation.setEnabled(!myBackStack.isEmpty());
355 private class ForwardAction extends AnAction implements HintManagerImpl.ActionToIgnore {
356 public ForwardAction() {
357 super(CodeInsightBundle.message("javadoc.action.forward"), null, IconLoader.getIcon("/actions/forward.png"));
360 public void actionPerformed(AnActionEvent e) {
361 goForward();
364 public void update(AnActionEvent e) {
365 Presentation presentation = e.getPresentation();
366 presentation.setEnabled(!myForwardStack.isEmpty());
370 private class ExternalDocAction extends AnAction implements HintManagerImpl.ActionToIgnore {
371 public ExternalDocAction() {
372 super(CodeInsightBundle.message("javadoc.action.view.external"), null, IconLoader.getIcon("/actions/browser-externalJavaDoc.png"));
373 registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EXTERNAL_JAVADOC).getShortcutSet(), null);
376 public void actionPerformed(AnActionEvent e) {
377 if (myElement != null) {
378 final PsiElement element = myElement.getElement();
379 final DocumentationProvider provider = DocumentationManager.getProviderFromElement(element);
380 final List<String> urls = provider.getUrlFor(element, DocumentationManager.getOriginalElement(element));
381 assert urls != null;
382 assert !urls.isEmpty();
383 ExternalJavaDocAction.showExternalJavadoc(urls);
387 public void update(AnActionEvent e) {
388 final Presentation presentation = e.getPresentation();
389 presentation.setEnabled(false);
390 if (myElement != null) {
391 final PsiElement element = myElement.getElement();
392 final DocumentationProvider provider = DocumentationManager.getProviderFromElement(element);
393 final List<String> urls = provider.getUrlFor(element, DocumentationManager.getOriginalElement(element));
394 presentation.setEnabled(element != null && urls!= null && !urls.isEmpty());
399 private void registerActions() {
400 myExternalDocAction.registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EXTERNAL_JAVADOC).getShortcutSet(),
401 myEditorPane);
403 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0),
404 new ActionListener() {
405 public void actionPerformed(ActionEvent e) {
406 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
407 int value = scrollBar.getValue() - scrollBar.getUnitIncrement(-1);
408 value = Math.max(value, 0);
409 scrollBar.setValue(value);
413 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0),
414 new ActionListener() {
415 public void actionPerformed(ActionEvent e) {
416 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
417 int value = scrollBar.getValue() + scrollBar.getUnitIncrement(+1);
418 value = Math.min(value, scrollBar.getMaximum());
419 scrollBar.setValue(value);
423 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0),
424 new ActionListener() {
425 public void actionPerformed(ActionEvent e) {
426 JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar();
427 int value = scrollBar.getValue() - scrollBar.getUnitIncrement(-1);
428 value = Math.max(value, 0);
429 scrollBar.setValue(value);
433 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0),
434 new ActionListener() {
435 public void actionPerformed(ActionEvent e) {
436 JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar();
437 int value = scrollBar.getValue() + scrollBar.getUnitIncrement(+1);
438 value = Math.min(value, scrollBar.getMaximum());
439 scrollBar.setValue(value);
443 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0),
444 new ActionListener() {
445 public void actionPerformed(ActionEvent e) {
446 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
447 int value = scrollBar.getValue() - scrollBar.getBlockIncrement(-1);
448 value = Math.max(value, 0);
449 scrollBar.setValue(value);
453 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0),
454 new ActionListener() {
455 public void actionPerformed(ActionEvent e) {
456 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
457 int value = scrollBar.getValue() + scrollBar.getBlockIncrement(+1);
458 value = Math.min(value, scrollBar.getMaximum());
459 scrollBar.setValue(value);
463 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0),
464 new ActionListener() {
465 public void actionPerformed(ActionEvent e) {
466 JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar();
467 scrollBar.setValue(0);
471 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0),
472 new ActionListener() {
473 public void actionPerformed(ActionEvent e) {
474 JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar();
475 scrollBar.setValue(scrollBar.getMaximum());
479 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, KeyEvent.CTRL_MASK),
480 new ActionListener() {
481 public void actionPerformed(ActionEvent e) {
482 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
483 scrollBar.setValue(0);
487 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, KeyEvent.CTRL_MASK),
488 new ActionListener() {
489 public void actionPerformed(ActionEvent e) {
490 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
491 scrollBar.setValue(scrollBar.getMaximum());
496 public String getText() {
497 return myText;
500 public void dispose() {
501 myBackStack.clear();
502 myForwardStack.clear();
503 myKeyboardActions.clear();
504 myElement = null;
505 myManager = null;
506 myHint = null;