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
;
40 import javax
.swing
.event
.HyperlinkEvent
;
41 import javax
.swing
.event
.HyperlinkListener
;
42 import javax
.swing
.text
.View
;
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
;
67 final Rectangle viewRect
;
69 public Context(SmartPsiElementPointer element
, String text
, Rectangle viewRect
) {
70 this.element
= element
;
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
) {
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, ""));
126 super.processKeyEvent(e
);
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
)) {
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
);
214 updateControlState();
217 public synchronized boolean isEmpty() {
221 public synchronized void startWait() {
225 private void setControlPanelVisible(boolean visible
) {
226 if (visible
== myControlPanelVisible
) return;
228 add(myControlPanel
, BorderLayout
.NORTH
);
230 remove(myControlPanel
);
232 myControlPanelVisible
= visible
;
235 public void setHint(JBPopup hint
) {
239 public JComponent
getComponent() {
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);
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
):
270 if (element
!= null) {
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;
288 if (!myIsShown
&& myHint
!= null) {
289 myEditorPane
.setText(text
);
290 myManager
.showHint(myHint
);
291 myIsShown
= justShown
= true;
295 myEditorPane
.setText(text
);
302 SwingUtilities
.invokeLater(new Runnable() {
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
) {
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
) {
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
));
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(),
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() {
500 public void dispose() {
502 myForwardStack
.clear();
503 myKeyboardActions
.clear();