2 * Copyright 2000-2015 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.icons.AllIcons;
24 import com.intellij.ide.DataManager;
25 import com.intellij.ide.actions.BaseNavigateToSourceAction;
26 import com.intellij.ide.actions.ExternalJavaDocAction;
27 import com.intellij.lang.documentation.CompositeDocumentationProvider;
28 import com.intellij.lang.documentation.DocumentationProvider;
29 import com.intellij.lang.documentation.ExternalDocumentationProvider;
30 import com.intellij.openapi.Disposable;
31 import com.intellij.openapi.actionSystem.*;
32 import com.intellij.openapi.actionSystem.impl.ActionButton;
33 import com.intellij.openapi.application.ApplicationBundle;
34 import com.intellij.openapi.application.ApplicationManager;
35 import com.intellij.openapi.diagnostic.Logger;
36 import com.intellij.openapi.editor.colors.EditorColorsManager;
37 import com.intellij.openapi.editor.colors.EditorColorsScheme;
38 import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;
39 import com.intellij.openapi.editor.ex.util.EditorUtil;
40 import com.intellij.openapi.keymap.KeymapUtil;
41 import com.intellij.openapi.options.FontSize;
42 import com.intellij.openapi.ui.popup.JBPopup;
43 import com.intellij.openapi.util.Disposer;
44 import com.intellij.openapi.util.InvalidDataException;
45 import com.intellij.openapi.util.registry.Registry;
46 import com.intellij.openapi.util.text.StringUtil;
47 import com.intellij.openapi.wm.ex.WindowManagerEx;
48 import com.intellij.pom.Navigatable;
49 import com.intellij.psi.PsiElement;
50 import com.intellij.psi.SmartPointerManager;
51 import com.intellij.psi.SmartPsiElementPointer;
52 import com.intellij.psi.util.PsiModificationTracker;
53 import com.intellij.ui.IdeBorderFactory;
54 import com.intellij.ui.JBColor;
55 import com.intellij.ui.SideBorder;
56 import com.intellij.ui.components.JBLayeredPane;
57 import com.intellij.ui.components.JBScrollPane;
58 import com.intellij.util.Consumer;
59 import com.intellij.util.containers.HashMap;
60 import com.intellij.util.ui.GraphicsUtil;
61 import com.intellij.util.ui.JBDimension;
62 import com.intellij.util.ui.JBUI;
63 import com.intellij.util.ui.UIUtil;
64 import org.jetbrains.annotations.NonNls;
65 import org.jetbrains.annotations.NotNull;
66 import org.jetbrains.annotations.Nullable;
69 import javax.swing.event.ChangeEvent;
70 import javax.swing.event.ChangeListener;
71 import javax.swing.event.HyperlinkEvent;
72 import javax.swing.event.HyperlinkListener;
73 import javax.swing.text.*;
74 import javax.swing.text.html.HTML;
75 import javax.swing.text.html.HTMLDocument;
77 import java.awt.event.*;
80 import java.util.List;
82 public class DocumentationComponent extends JPanel implements Disposable, DataProvider {
83 private static Logger LOGGER = Logger.getInstance(DocumentationComponent.class);
85 private static final Highlighter.HighlightPainter LINK_HIGHLIGHTER = new LinkHighlighter();
86 @NonNls private static final String DOCUMENTATION_TOPIC_ID = "reference.toolWindows.Documentation";
88 private static final int PREFERRED_WIDTH_EM = 37;
89 private static final int PREFERRED_HEIGHT_MIN_EM = 7;
90 private static final int PREFERRED_HEIGHT_MAX_EM = 20;
92 private DocumentationManager myManager;
93 private SmartPsiElementPointer myElement;
94 private long myModificationCount;
96 private final Stack<Context> myBackStack = new Stack<Context>();
97 private final Stack<Context> myForwardStack = new Stack<Context>();
98 private final ActionToolbar myToolBar;
99 private volatile boolean myIsEmpty;
100 private boolean myIsShown;
101 private final JLabel myElementLabel;
102 private final MutableAttributeSet myFontSizeStyle = new SimpleAttributeSet();
103 private JSlider myFontSizeSlider;
104 private final JComponent mySettingsPanel;
105 private final MyShowSettingsButton myShowSettingsButton;
106 private boolean myIgnoreFontSizeSliderChange;
107 private String myEffectiveExternalUrl;
108 private final MyDictionary<String, Image> myImageProvider = new MyDictionary<String, Image>() {
110 public Image get(Object key) {
111 if (myManager == null || key == null) return null;
112 PsiElement element = getElement();
113 if (element == null) return null;
115 Image inMemory = myManager.getElementImage(element, url.toExternalForm());
116 return inMemory != null ? inMemory : Toolkit.getDefaultToolkit().createImage(url);
120 private static class Context {
121 private final SmartPsiElementPointer element;
122 private final String text;
123 private final String externalUrl;
124 private final Rectangle viewRect;
125 private final int highlightedLink;
127 public Context(SmartPsiElementPointer element, String text, String externalUrl, Rectangle viewRect, int highlightedLink) {
128 this.element = element;
130 this.externalUrl = externalUrl;
131 this.viewRect = viewRect;
132 this.highlightedLink = highlightedLink;
136 private final JScrollPane myScrollPane;
137 private final JEditorPane myEditorPane;
138 private String myText; // myEditorPane.getText() surprisingly crashes.., let's cache the text
139 private final JPanel myControlPanel;
140 private boolean myControlPanelVisible;
141 private final ExternalDocAction myExternalDocAction;
142 private Consumer<PsiElement> myNavigateCallback;
143 private int myHighlightedLink = -1;
144 private Object myHighlightingTag;
146 private JBPopup myHint;
148 private final Map<KeyStroke, ActionListener> myKeyboardActions = new HashMap<KeyStroke, ActionListener>();
151 public boolean requestFocusInWindow() {
152 return myScrollPane.requestFocusInWindow();
157 public void requestFocus() {
158 myScrollPane.requestFocus();
161 public DocumentationComponent(final DocumentationManager manager, final AnAction[] additionalActions) {
166 myEditorPane = new JEditorPane(UIUtil.HTML_MIME, "") {
168 public Dimension getPreferredScrollableViewportSize() {
169 int em = myEditorPane.getFont().getSize();
170 int prefWidth = PREFERRED_WIDTH_EM * em;
171 int prefHeightMin = PREFERRED_HEIGHT_MIN_EM * em;
172 int prefHeightMax = PREFERRED_HEIGHT_MAX_EM * em;
174 if (getWidth() == 0 || getHeight() == 0) {
175 setSize(prefWidth, prefHeightMax);
178 Insets ins = myEditorPane.getInsets();
179 View rootView = myEditorPane.getUI().getRootView(myEditorPane);
180 rootView.setSize(prefWidth, prefHeightMax); // Necessary! Without this line, the size won't increase when the content does
182 int prefHeight = (int)rootView.getPreferredSpan(View.Y_AXIS) + ins.bottom + ins.top +
183 myScrollPane.getHorizontalScrollBar().getMaximumSize().height;
184 prefHeight = Math.max(prefHeightMin, Math.min(prefHeightMax, prefHeight));
185 return new Dimension(prefWidth, prefHeight);
189 enableEvents(AWTEvent.KEY_EVENT_MASK);
193 protected void processKeyEvent(KeyEvent e) {
194 KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(e);
195 ActionListener listener = myKeyboardActions.get(keyStroke);
196 if (listener != null) {
197 listener.actionPerformed(new ActionEvent(DocumentationComponent.this, 0, ""));
201 super.processKeyEvent(e);
205 protected void paintComponent(Graphics g) {
206 GraphicsUtil.setupAntialiasing(g);
207 super.paintComponent(g);
211 public void setDocument(Document doc) {
212 super.setDocument(doc);
213 if (doc instanceof StyledDocument) {
214 doc.putProperty("imageCache", myImageProvider);
218 DataProvider helpDataProvider = new DataProvider() {
220 public Object getData(@NonNls String dataId) {
221 return PlatformDataKeys.HELP_ID.is(dataId) ? DOCUMENTATION_TOPIC_ID : null;
224 myEditorPane.putClientProperty(DataManager.CLIENT_PROPERTY_DATA_PROVIDER, helpDataProvider);
226 myEditorPane.setEditable(false);
227 myEditorPane.setBackground(HintUtil.INFORMATION_COLOR);
228 myEditorPane.setEditorKit(UIUtil.getHTMLEditorKit(false));
229 myScrollPane = new JBScrollPane(myEditorPane) {
231 protected void processMouseWheelEvent(MouseWheelEvent e) {
232 if (!EditorSettingsExternalizable.getInstance().isWheelFontChangeEnabled() || !EditorUtil.isChangeFontSize(e)) {
233 super.processMouseWheelEvent(e);
237 int change = Math.abs(e.getWheelRotation());
238 boolean increase = e.getWheelRotation() <= 0;
239 EditorColorsManager colorsManager = EditorColorsManager.getInstance();
240 EditorColorsScheme scheme = colorsManager.getGlobalScheme();
241 FontSize newFontSize = scheme.getQuickDocFontSize();
242 for (; change > 0; change--) {
244 newFontSize = newFontSize.larger();
247 newFontSize = newFontSize.smaller();
251 if (newFontSize == scheme.getQuickDocFontSize()) {
255 scheme.setQuickDocFontSize(newFontSize);
257 setFontSizeSliderSize(newFontSize);
260 myScrollPane.setBorder(null);
261 myScrollPane.putClientProperty(DataManager.CLIENT_PROPERTY_DATA_PROVIDER, helpDataProvider);
263 final MouseListener mouseAdapter = new MouseAdapter() {
265 public void mousePressed(MouseEvent e) {
266 myManager.requestFocus();
267 myShowSettingsButton.hideSettings();
270 myEditorPane.addMouseListener(mouseAdapter);
271 Disposer.register(this, new Disposable() {
273 public void dispose() {
274 myEditorPane.removeMouseListener(mouseAdapter);
278 final FocusListener focusAdapter = new FocusAdapter() {
280 public void focusLost(FocusEvent e) {
281 Component previouslyFocused = WindowManagerEx.getInstanceEx().getFocusedComponent(manager.getProject(getElement()));
283 if (previouslyFocused != myEditorPane) {
284 if (myHint != null && !myHint.isDisposed()) myHint.cancel();
288 myEditorPane.addFocusListener(focusAdapter);
290 Disposer.register(this, new Disposable() {
292 public void dispose() {
293 myEditorPane.removeFocusListener(focusAdapter);
297 setLayout(new BorderLayout());
298 JLayeredPane layeredPane = new JBLayeredPane() {
300 public void doLayout() {
301 final Rectangle r = getBounds();
302 for (Component component : getComponents()) {
303 if (component instanceof JScrollPane) {
304 component.setBounds(0, 0, r.width, r.height);
308 Dimension d = component.getPreferredSize();
309 component.setBounds(r.width - d.width - insets, insets, d.width, d.height);
315 public Dimension getPreferredSize() {
316 Dimension editorPaneSize = myEditorPane.getPreferredScrollableViewportSize();
317 Dimension controlPanelSize = myControlPanel.getPreferredSize();
318 return getSize(editorPaneSize, controlPanelSize);
322 public Dimension getMinimumSize() {
323 Dimension editorPaneSize = new JBDimension(20, 20);
324 Dimension controlPanelSize = myControlPanel.getMinimumSize();
325 return getSize(editorPaneSize, controlPanelSize);
328 private Dimension getSize(Dimension editorPaneSize, Dimension controlPanelSize) {
329 return new Dimension(Math.max(editorPaneSize.width, controlPanelSize.width), editorPaneSize.height + controlPanelSize.height);
332 layeredPane.add(myScrollPane);
333 layeredPane.setLayer(myScrollPane, 0);
335 mySettingsPanel = createSettingsPanel();
336 layeredPane.add(mySettingsPanel);
337 layeredPane.setLayer(mySettingsPanel, JLayeredPane.POPUP_LAYER);
338 add(layeredPane, BorderLayout.CENTER);
340 myScrollPane.setViewportBorder(JBScrollPane.createIndentBorder());
342 final DefaultActionGroup actions = new DefaultActionGroup();
343 final BackAction back = new BackAction();
344 final ForwardAction forward = new ForwardAction();
345 EditDocumentationSourceAction edit = new EditDocumentationSourceAction();
347 actions.add(forward);
348 actions.add(myExternalDocAction = new ExternalDocAction());
352 CustomShortcutSet backShortcutSet = new CustomShortcutSet(KeyboardShortcut.fromString("LEFT"),
353 KeymapUtil.parseMouseShortcut("button4"));
354 CustomShortcutSet forwardShortcutSet = new CustomShortcutSet(KeyboardShortcut.fromString("RIGHT"),
355 KeymapUtil.parseMouseShortcut("button5"));
356 back.registerCustomShortcutSet(backShortcutSet, this);
357 forward.registerCustomShortcutSet(forwardShortcutSet, this);
358 // mouse actions are checked only for exact component over which click was performed,
359 // so we need to register shortcuts for myEditorPane as well
360 back.registerCustomShortcutSet(backShortcutSet, myEditorPane);
361 forward.registerCustomShortcutSet(forwardShortcutSet, myEditorPane);
363 catch (InvalidDataException e) {
367 myExternalDocAction.registerCustomShortcutSet(CustomShortcutSet.fromString("UP"), this);
368 edit.registerCustomShortcutSet(CommonShortcuts.getEditSource(), this);
369 if (additionalActions != null) {
370 for (final AnAction action : additionalActions) {
372 ShortcutSet shortcutSet = action.getShortcutSet();
373 if (shortcutSet != null) {
374 action.registerCustomShortcutSet(shortcutSet, this);
379 new NextLinkAction().registerCustomShortcutSet(CustomShortcutSet.fromString("TAB"), this);
380 new PreviousLinkAction().registerCustomShortcutSet(CustomShortcutSet.fromString("shift TAB"), this);
381 new ActivateLinkAction().registerCustomShortcutSet(CustomShortcutSet.fromString("ENTER"), this);
383 myToolBar = ActionManager.getInstance().createActionToolbar(ActionPlaces.JAVADOC_TOOLBAR, actions, true);
385 myControlPanel = new JPanel(new BorderLayout(5, 5));
386 myControlPanel.setBorder(IdeBorderFactory.createBorder(SideBorder.BOTTOM));
388 myElementLabel = new JLabel();
389 myElementLabel.setMinimumSize(new Dimension(100, 0)); // do not recalculate size according to the text
391 myControlPanel.add(myToolBar.getComponent(), BorderLayout.WEST);
392 myControlPanel.add(myElementLabel, BorderLayout.CENTER);
393 myControlPanel.add(myShowSettingsButton = new MyShowSettingsButton(), BorderLayout.EAST);
394 myControlPanelVisible = false;
396 final HyperlinkListener hyperlinkListener = new HyperlinkListener() {
398 public void hyperlinkUpdate(HyperlinkEvent e) {
399 HyperlinkEvent.EventType type = e.getEventType();
400 if (type == HyperlinkEvent.EventType.ACTIVATED) {
401 manager.navigateByLink(DocumentationComponent.this, e.getDescription());
405 myEditorPane.addHyperlinkListener(hyperlinkListener);
406 Disposer.register(this, new Disposable() {
408 public void dispose() {
409 myEditorPane.removeHyperlinkListener(hyperlinkListener);
415 updateControlState();
418 public DocumentationComponent(final DocumentationManager manager) {
423 public Object getData(@NonNls String dataId) {
424 if (DocumentationManager.SELECTED_QUICK_DOC_TEXT.getName().equals(dataId)) {
425 // Javadocs often contain symbols (non-breakable white space). We don't want to copy them as is and replace
426 // with raw white spaces. See IDEA-86633 for more details.
427 String selectedText = myEditorPane.getSelectedText();
428 return selectedText == null? null : selectedText.replace((char)160, ' ');
434 private JComponent createSettingsPanel() {
435 JPanel result = new JPanel(new FlowLayout(FlowLayout.RIGHT, 3, 0));
436 result.add(new JLabel(ApplicationBundle.message("label.font.size")));
437 myFontSizeSlider = new JSlider(SwingConstants.HORIZONTAL, 0, FontSize.values().length - 1, 3);
438 myFontSizeSlider.setMinorTickSpacing(1);
439 myFontSizeSlider.setPaintTicks(true);
440 myFontSizeSlider.setPaintTrack(true);
441 myFontSizeSlider.setSnapToTicks(true);
442 UIUtil.setSliderIsFilled(myFontSizeSlider, true);
443 result.add(myFontSizeSlider);
444 result.setBorder(BorderFactory.createLineBorder(JBColor.border(), 1));
446 myFontSizeSlider.addChangeListener(new ChangeListener() {
448 public void stateChanged(ChangeEvent e) {
449 if (myIgnoreFontSizeSliderChange) {
452 EditorColorsManager colorsManager = EditorColorsManager.getInstance();
453 EditorColorsScheme scheme = colorsManager.getGlobalScheme();
454 scheme.setQuickDocFontSize(FontSize.values()[myFontSizeSlider.getValue()]);
459 String tooltipText = ApplicationBundle.message("quickdoc.tooltip.font.size.by.wheel");
460 result.setToolTipText(tooltipText);
461 myFontSizeSlider.setToolTipText(tooltipText);
462 result.setVisible(false);
463 result.setOpaque(true);
464 myFontSizeSlider.setOpaque(true);
468 private void setFontSizeSliderSize(FontSize fontSize) {
469 myIgnoreFontSizeSliderChange = true;
471 FontSize[] sizes = FontSize.values();
472 for (int i = 0; i < sizes.length; i++) {
473 if (fontSize == sizes[i]) {
474 myFontSizeSlider.setValue(i);
480 myIgnoreFontSizeSliderChange = false;
484 public boolean isEmpty() {
488 public void startWait() {
492 private void setControlPanelVisible(boolean visible) {
493 if (visible == myControlPanelVisible) return;
495 add(myControlPanel, BorderLayout.NORTH);
498 remove(myControlPanel);
500 myControlPanelVisible = visible;
503 public void setHint(JBPopup hint) {
507 public JBPopup getHint() {
511 public JComponent getComponent() {
516 public PsiElement getElement() {
517 return myElement != null ? myElement.getElement() : null;
520 private void setElement(SmartPsiElementPointer element) {
522 myModificationCount = getCurrentModificationCount();
525 public boolean isUpToDate() {
526 return getElement() != null && myModificationCount == getCurrentModificationCount();
529 private long getCurrentModificationCount() {
530 return myElement != null ? PsiModificationTracker.SERVICE.getInstance(myElement.getProject()).getModificationCount() : -1;
533 public void setNavigateCallback(Consumer<PsiElement> navigateCallback) {
534 myNavigateCallback = navigateCallback;
537 public void setText(String text, @Nullable PsiElement element, boolean clearHistory) {
538 setText(text, element, false, clearHistory);
541 public void setText(String text, PsiElement element, boolean clean, boolean clearHistory) {
542 if (clean && myElement != null) {
543 myBackStack.push(saveContext());
544 myForwardStack.clear();
546 updateControlState();
547 setData(element, text, clearHistory, null);
552 if (clearHistory) clearHistory();
555 public void replaceText(String text, PsiElement element) {
556 PsiElement current = getElement();
557 if (current == null || !current.getManager().areElementsEquivalent(current, element)) return;
558 setText(text, element, false);
559 if (!myBackStack.empty()) myBackStack.pop();
562 private void clearHistory() {
563 myForwardStack.clear();
567 public void setData(PsiElement _element, String text, final boolean clearHistory, String effectiveExternalUrl) {
568 setData(_element, text, clearHistory, effectiveExternalUrl, null);
571 public void setData(PsiElement _element, String text, final boolean clearHistory, String effectiveExternalUrl, String ref) {
572 if (myElement != null) {
573 myBackStack.push(saveContext());
574 myForwardStack.clear();
576 myEffectiveExternalUrl = effectiveExternalUrl;
578 final SmartPsiElementPointer element = _element != null && _element.isValid()
579 ? SmartPointerManager.getInstance(_element.getProject()).createSmartPsiElementPointer(_element)
582 if (element != null) {
587 updateControlState();
588 setDataInternal(element, text, new Rectangle(0, 0), ref);
590 if (clearHistory) clearHistory();
593 private void setDataInternal(SmartPsiElementPointer element, String text, final Rectangle viewRect, final String ref) {
598 myEditorPane.setText(text);
601 if (!myIsShown && myHint != null && !ApplicationManager.getApplication().isUnitTestMode()) {
602 myManager.showHint(myHint);
608 //noinspection SSBasedInspection
609 SwingUtilities.invokeLater(new Runnable() {
612 myEditorPane.scrollRectToVisible(viewRect); // if ref is defined but is not found in document, this provides a default location
614 myEditorPane.scrollToReference(ref);
620 private void applyFontSize() {
621 Document document = myEditorPane.getDocument();
622 if (!(document instanceof StyledDocument)) {
626 final StyledDocument styledDocument = (StyledDocument)document;
628 EditorColorsManager colorsManager = EditorColorsManager.getInstance();
629 EditorColorsScheme scheme = colorsManager.getGlobalScheme();
630 StyleConstants.setFontSize(myFontSizeStyle, JBUI.scale(scheme.getQuickDocFontSize().getSize()));
631 if (Registry.is("documentation.component.editor.font")) {
632 StyleConstants.setFontFamily(myFontSizeStyle, scheme.getEditorFontName());
635 ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
638 styledDocument.setCharacterAttributes(0, styledDocument.getLength(), myFontSizeStyle, false);
643 private void goBack() {
644 if (myBackStack.isEmpty()) return;
645 Context context = myBackStack.pop();
646 myForwardStack.push(saveContext());
647 restoreContext(context);
648 updateControlState();
651 private void goForward() {
652 if (myForwardStack.isEmpty()) return;
653 Context context = myForwardStack.pop();
654 myBackStack.push(saveContext());
655 restoreContext(context);
656 updateControlState();
659 private Context saveContext() {
660 Rectangle rect = myScrollPane.getViewport().getViewRect();
661 return new Context(myElement, myText, myEffectiveExternalUrl, rect, myHighlightedLink);
664 private void restoreContext(Context context) {
665 setDataInternal(context.element, context.text, context.viewRect, null);
666 myEffectiveExternalUrl = context.externalUrl;
667 if (myNavigateCallback != null) {
668 final PsiElement element = context.element.getElement();
669 if (element != null) {
670 myNavigateCallback.consume(element);
673 highlightLink(context.highlightedLink);
676 private void updateControlState() {
677 ElementLocationUtil.customizeElementLabel(myElement != null ? myElement.getElement() : null, myElementLabel);
678 myToolBar.updateActionsImmediately(); // update faster
679 setControlPanelVisible(true);//(!myBackStack.isEmpty() || !myForwardStack.isEmpty());
682 private class BackAction extends AnAction implements HintManagerImpl.ActionToIgnore {
683 public BackAction() {
684 super(CodeInsightBundle.message("javadoc.action.back"), null, AllIcons.Actions.Back);
688 public void actionPerformed(AnActionEvent e) {
693 public void update(AnActionEvent e) {
694 Presentation presentation = e.getPresentation();
695 presentation.setEnabled(!myBackStack.isEmpty());
699 private class ForwardAction extends AnAction implements HintManagerImpl.ActionToIgnore {
700 public ForwardAction() {
701 super(CodeInsightBundle.message("javadoc.action.forward"), null, AllIcons.Actions.Forward);
705 public void actionPerformed(AnActionEvent e) {
710 public void update(AnActionEvent e) {
711 Presentation presentation = e.getPresentation();
712 presentation.setEnabled(!myForwardStack.isEmpty());
716 private class EditDocumentationSourceAction extends BaseNavigateToSourceAction {
718 private EditDocumentationSourceAction() {
720 getTemplatePresentation().setIcon(AllIcons.Actions.EditSource);
721 getTemplatePresentation().setText("Edit Source");
725 public void actionPerformed(AnActionEvent e) {
726 super.actionPerformed(e);
727 final JBPopup hint = myHint;
728 if (hint != null && hint.isVisible()) {
735 protected Navigatable[] getNavigatables(DataContext dataContext) {
736 SmartPsiElementPointer element = myElement;
737 if (element != null) {
738 PsiElement psiElement = element.getElement();
739 return psiElement instanceof Navigatable ? new Navigatable[] {(Navigatable)psiElement} : null;
746 private class ExternalDocAction extends AnAction implements HintManagerImpl.ActionToIgnore {
747 private ExternalDocAction() {
748 super(CodeInsightBundle.message("javadoc.action.view.external"), null, AllIcons.Actions.Browser_externalJavaDoc);
749 registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EXTERNAL_JAVADOC).getShortcutSet(), null);
753 public void actionPerformed(AnActionEvent e) {
754 if (myElement == null) {
758 final PsiElement element = myElement.getElement();
759 final DocumentationProvider provider = DocumentationManager.getProviderFromElement(element);
760 final PsiElement originalElement = DocumentationManager.getOriginalElement(element);
761 if (!(provider instanceof CompositeDocumentationProvider &&
762 ((CompositeDocumentationProvider)provider).handleExternal(element, originalElement))) {
764 if (!StringUtil.isEmptyOrSpaces(myEffectiveExternalUrl)) {
765 urls = Collections.singletonList(myEffectiveExternalUrl);
768 urls = provider.getUrlFor(element, originalElement);
769 assert urls != null : provider;
770 assert !urls.isEmpty() : provider;
772 ExternalJavaDocAction.showExternalJavadoc(urls, PlatformDataKeys.CONTEXT_COMPONENT.getData(e.getDataContext()));
777 public void update(AnActionEvent e) {
778 final Presentation presentation = e.getPresentation();
779 presentation.setEnabled(false);
780 if (myElement != null) {
781 final PsiElement element = myElement.getElement();
782 final DocumentationProvider provider = DocumentationManager.getProviderFromElement(element);
783 final PsiElement originalElement = DocumentationManager.getOriginalElement(element);
784 if (provider instanceof ExternalDocumentationProvider) {
785 presentation.setEnabled(element != null && ((ExternalDocumentationProvider)provider).hasDocumentationFor(element, originalElement));
788 final List<String> urls = provider.getUrlFor(element, originalElement);
789 presentation.setEnabled(element != null && urls != null && !urls.isEmpty());
795 private void registerActions() {
797 .registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EXTERNAL_JAVADOC).getShortcutSet(), myEditorPane);
799 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), new ActionListener() {
801 public void actionPerformed(ActionEvent e) {
802 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
803 int value = scrollBar.getValue() - scrollBar.getUnitIncrement(-1);
804 value = Math.max(value, 0);
805 scrollBar.setValue(value);
809 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), new ActionListener() {
811 public void actionPerformed(ActionEvent e) {
812 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
813 int value = scrollBar.getValue() + scrollBar.getUnitIncrement(+1);
814 value = Math.min(value, scrollBar.getMaximum());
815 scrollBar.setValue(value);
819 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), new ActionListener() {
821 public void actionPerformed(ActionEvent e) {
822 JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar();
823 int value = scrollBar.getValue() - scrollBar.getUnitIncrement(-1);
824 value = Math.max(value, 0);
825 scrollBar.setValue(value);
829 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), new ActionListener() {
831 public void actionPerformed(ActionEvent e) {
832 JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar();
833 int value = scrollBar.getValue() + scrollBar.getUnitIncrement(+1);
834 value = Math.min(value, scrollBar.getMaximum());
835 scrollBar.setValue(value);
839 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), new ActionListener() {
841 public void actionPerformed(ActionEvent e) {
842 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
843 int value = scrollBar.getValue() - scrollBar.getBlockIncrement(-1);
844 value = Math.max(value, 0);
845 scrollBar.setValue(value);
849 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), new ActionListener() {
851 public void actionPerformed(ActionEvent e) {
852 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
853 int value = scrollBar.getValue() + scrollBar.getBlockIncrement(+1);
854 value = Math.min(value, scrollBar.getMaximum());
855 scrollBar.setValue(value);
859 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0), new ActionListener() {
861 public void actionPerformed(ActionEvent e) {
862 JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar();
863 scrollBar.setValue(0);
867 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0), new ActionListener() {
869 public void actionPerformed(ActionEvent e) {
870 JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar();
871 scrollBar.setValue(scrollBar.getMaximum());
875 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, InputEvent.CTRL_MASK), new ActionListener() {
877 public void actionPerformed(ActionEvent e) {
878 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
879 scrollBar.setValue(0);
883 myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, InputEvent.CTRL_MASK), new ActionListener() {
885 public void actionPerformed(ActionEvent e) {
886 JScrollBar scrollBar = myScrollPane.getVerticalScrollBar();
887 scrollBar.setValue(scrollBar.getMaximum());
892 public String getText() {
897 public void dispose() {
899 myForwardStack.clear();
900 myKeyboardActions.clear();
904 myNavigateCallback = null;
907 private int getLinkCount() {
908 HTMLDocument document = (HTMLDocument)myEditorPane.getDocument();
910 for (HTMLDocument.Iterator it = document.getIterator(HTML.Tag.A); it.isValid(); it.next()) {
911 if (it.getAttributes().isDefined(HTML.Attribute.HREF)) linkCount++;
917 private HTMLDocument.Iterator getLink(int n) {
919 HTMLDocument document = (HTMLDocument)myEditorPane.getDocument();
921 for (HTMLDocument.Iterator it = document.getIterator(HTML.Tag.A); it.isValid(); it.next()) {
922 if (it.getAttributes().isDefined(HTML.Attribute.HREF) && linkCount++ == n) return it;
928 private void highlightLink(int n) {
929 myHighlightedLink = n;
930 Highlighter highlighter = myEditorPane.getHighlighter();
931 HTMLDocument.Iterator link = getLink(n);
933 int startOffset = link.getStartOffset();
934 int endOffset = link.getEndOffset();
936 if (myHighlightingTag == null) {
937 myHighlightingTag = highlighter.addHighlight(startOffset, endOffset, LINK_HIGHLIGHTER);
940 highlighter.changeHighlight(myHighlightingTag, startOffset, endOffset);
942 myEditorPane.setCaretPosition(startOffset);
944 catch (BadLocationException e) {
945 LOGGER.warn("Error highlighting link", e);
948 else if (myHighlightingTag != null) {
949 highlighter.removeHighlight(myHighlightingTag);
950 myHighlightingTag = null;
954 private void activateLink(int n) {
955 HTMLDocument.Iterator link = getLink(n);
957 String href = (String)link.getAttributes().getAttribute(HTML.Attribute.HREF);
958 myManager.navigateByLink(this, href);
962 private class MyShowSettingsButton extends ActionButton {
964 private MyShowSettingsButton() {
965 this(new MyShowSettingsAction(), new Presentation(), ActionPlaces.JAVADOC_INPLACE_SETTINGS, ActionToolbar.DEFAULT_MINIMUM_BUTTON_SIZE);
968 private MyShowSettingsButton(AnAction action, Presentation presentation, String place, @NotNull Dimension minimumSize) {
969 super(action, presentation, place, minimumSize);
970 myPresentation.setIcon(AllIcons.General.SecondaryGroup);
973 private void hideSettings() {
974 if (!mySettingsPanel.isVisible()) {
977 AnActionEvent event = AnActionEvent.createFromDataContext(myPlace, myPresentation, DataContext.EMPTY_CONTEXT);
978 myAction.actionPerformed(event);
982 private class MyShowSettingsAction extends ToggleAction {
985 public boolean isSelected(AnActionEvent e) {
986 return mySettingsPanel.isVisible();
990 public void setSelected(AnActionEvent e, boolean state) {
992 mySettingsPanel.setVisible(false);
996 EditorColorsManager colorsManager = EditorColorsManager.getInstance();
997 EditorColorsScheme scheme = colorsManager.getGlobalScheme();
998 setFontSizeSliderSize(scheme.getQuickDocFontSize());
999 mySettingsPanel.setVisible(true);
1003 private abstract static class MyDictionary<K, V> extends Dictionary<K, V> {
1006 throw new UnsupportedOperationException();
1010 public boolean isEmpty() {
1011 throw new UnsupportedOperationException();
1015 public Enumeration<K> keys() {
1016 throw new UnsupportedOperationException();
1020 public Enumeration<V> elements() {
1021 throw new UnsupportedOperationException();
1025 public V put(K key, V value) {
1026 throw new UnsupportedOperationException();
1030 public V remove(Object key) {
1031 throw new UnsupportedOperationException();
1035 private class PreviousLinkAction extends AnAction implements HintManagerImpl.ActionToIgnore {
1037 public void actionPerformed(AnActionEvent e) {
1038 int linkCount = getLinkCount();
1039 if (linkCount <= 0) return;
1040 highlightLink(myHighlightedLink < 0 ? (linkCount - 1) : (myHighlightedLink + linkCount - 1) % linkCount);
1044 private class NextLinkAction extends AnAction implements HintManagerImpl.ActionToIgnore {
1046 public void actionPerformed(AnActionEvent e) {
1047 int linkCount = getLinkCount();
1048 if (linkCount <= 0) return;
1049 highlightLink((myHighlightedLink + 1) % linkCount);
1053 private class ActivateLinkAction extends AnAction implements HintManagerImpl.ActionToIgnore {
1055 public void actionPerformed(AnActionEvent e) {
1056 activateLink(myHighlightedLink);
1060 private static class LinkHighlighter implements Highlighter.HighlightPainter {
1061 private static final Stroke STROKE = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1, new float[]{1}, 0);
1064 public void paint(Graphics g, int p0, int p1, Shape bounds, JTextComponent c) {
1066 Rectangle target = c.getUI().getRootView(c).modelToView(p0, Position.Bias.Forward, p1, Position.Bias.Backward, bounds).getBounds();
1067 Graphics2D g2d = (Graphics2D)g.create();
1069 g2d.setStroke(STROKE);
1070 g2d.setColor(c.getSelectionColor());
1071 g2d.drawRect(target.x, target.y, target.width - 1, target.height - 1);
1077 catch (Exception e) {
1078 LOGGER.warn("Error painting link highlight", e);