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 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
>();
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
) {
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, ""));
128 super.processKeyEvent(e
);
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
) {
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
);
223 updateControlState();
226 public DocumentationComponent(final DocumentationManager manager
) {
230 public synchronized boolean isEmpty() {
234 public synchronized void startWait() {
238 private void setControlPanelVisible(boolean visible
) {
239 if (visible
== myControlPanelVisible
) return;
241 add(myControlPanel
, BorderLayout
.NORTH
);
244 remove(myControlPanel
);
246 myControlPanelVisible
= visible
;
249 public void setHint(JBPopup hint
) {
253 public JComponent
getComponent() {
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
);
277 if (clearHistory
) clearHistory();
280 private void clearHistory() {
281 myForwardStack
.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
)
295 if (element
!= null) {
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;
315 if (!myIsShown
&& myHint
!= null) {
316 myEditorPane
.setText(text
);
317 myManager
.showHint(myHint
);
318 myIsShown
= justShown
= true;
322 myEditorPane
.setText(text
);
329 SwingUtilities
.invokeLater(new Runnable() {
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
) {
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
) {
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
));
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() {
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() {
517 public void dispose() {
519 myForwardStack
.clear();
520 myKeyboardActions
.clear();