From a5f2dc7cf0772668a723dcca33519df467274874 Mon Sep 17 00:00:00 2001 From: Alexey Pegov Date: Mon, 15 Feb 2010 17:59:41 +0300 Subject: [PATCH] sticky documentation popup [take 1] --- platform/icons/src/general/documentation.png | Bin 0 -> 566 bytes .../documentation/DocumentationComponent.java | 1039 ++++++++++---------- .../documentation/DocumentationManager.java | 292 ++++-- .../intellij/find/actions/ShowUsagesAction.java | 14 +- .../openapi/ui/popup/ComponentPopupBuilder.java | 4 + .../src/com/intellij/openapi/wm/ToolWindowId.java | 1 + .../impl/ui/NotificationsListPanel.java | 13 - .../openapi/actionSystem/ex/ActionManagerEx.java | 8 +- .../actionSystem/impl/ActionManagerImpl.java | 15 + .../intellij/openapi/wm/ex/WindowManagerEx.java | 5 + .../openapi/wm/impl/TestWindowManager.java | 5 + .../openapi/wm/impl/WindowManagerImpl.java | 16 + .../src/com/intellij/ui/popup/AbstractPopup.java | 15 +- .../ui/popup/ComponentPopupBuilderImpl.java | 10 +- .../src/com/intellij/ui/popup/WizardPopup.java | 2 +- .../src/messages/UIBundle.properties | 3 +- 16 files changed, 829 insertions(+), 613 deletions(-) create mode 100644 platform/icons/src/general/documentation.png rewrite platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationComponent.java (88%) diff --git a/platform/icons/src/general/documentation.png b/platform/icons/src/general/documentation.png new file mode 100644 index 0000000000000000000000000000000000000000..8c0df2e9532990f2d9e453dae4d23fdd670cdc92 GIT binary patch literal 566 zcwPZE0?GY}P)F4 zp%4UZNQWX6>EuuiCN4$kAX*h9PU6y`Llzy1Ls2>u9MZUm?NB0^kbpXfLsn}E#(2zm^RaW7CpW>McMQY$4fpKLJlKCGvPG)-#XDHm{cTrU;M7W}<$UWpljm*F3pQQ77RolUzjd1=kqj(qXfPCIcz*JK`!PW7$~ zr$9bR)of3-k;!RDu1(?2&MQ?Y*HptVkfrBsz=Hq-0J!(Zx)UT~rT_o{07*qoM6N<$ Ef)nKUYXATM literal 0 HcwPel00001 diff --git a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationComponent.java b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationComponent.java dissimilarity index 88% index eecfd73bac..bd47348f1e 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationComponent.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationComponent.java @@ -1,513 +1,526 @@ -/* - * Copyright 2000-2009 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.intellij.codeInsight.documentation; - -import com.intellij.codeInsight.CodeInsightBundle; -import com.intellij.codeInsight.hint.ElementLocationUtil; -import com.intellij.codeInsight.hint.HintManagerImpl; -import com.intellij.codeInsight.hint.HintUtil; -import com.intellij.ide.actions.ExternalJavaDocAction; -import com.intellij.lang.documentation.DocumentationProvider; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.ui.popup.JBPopup; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.wm.ex.WindowManagerEx; -import com.intellij.psi.PsiElement; -import com.intellij.psi.SmartPointerManager; -import com.intellij.psi.SmartPsiElementPointer; -import com.intellij.ui.EdgeBorder; -import com.intellij.util.containers.HashMap; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.HyperlinkEvent; -import javax.swing.event.HyperlinkListener; -import javax.swing.text.View; -import java.awt.*; -import java.awt.event.*; -import java.util.List; -import java.util.Stack; - -public class DocumentationComponent extends JPanel implements Disposable{ - - private static final int MAX_WIDTH = 500; - private static final int MAX_HEIGHT = 300; - private static final int MIN_HEIGHT = 45; - - private DocumentationManager myManager; - private SmartPsiElementPointer myElement; - - private final Stack myBackStack = new Stack(); - private final Stack myForwardStack = new Stack(); - private final ActionToolbar myToolBar; - private boolean myIsEmpty; - private boolean myIsShown; - private final JLabel myElementLabel; - - private static class Context { - final SmartPsiElementPointer element; - final String text; - final Rectangle viewRect; - - public Context(SmartPsiElementPointer element, String text, Rectangle viewRect) { - this.element = element; - this.text = text; - this.viewRect = viewRect; - } - } - - private final JScrollPane myScrollPane; - private final JEditorPane myEditorPane; - private String myText; // myEditorPane.getText() surprisingly crashes.., let's cache the text - private final JPanel myControlPanel; - private boolean myControlPanelVisible; - private final ExternalDocAction myExternalDocAction; - - private JBPopup myHint; - - private final HashMap myKeyboardActions = new HashMap(); // KeyStroke --> ActionListener - - public boolean requestFocusInWindow() { - return myScrollPane.requestFocusInWindow(); - } - - - public void requestFocus() { - myScrollPane.requestFocus(); - } - - public DocumentationComponent(final DocumentationManager manager) { - myManager = manager; - myIsEmpty = true; - myIsShown = false; - - myEditorPane = new JEditorPane(UIUtil.HTML_MIME, "") { - public Dimension getPreferredScrollableViewportSize() { - if (getWidth() == 0 || getHeight() == 0) { - setSize(MAX_WIDTH, MAX_HEIGHT); - } - Insets ins = myEditorPane.getInsets(); - View rootView = myEditorPane.getUI().getRootView(myEditorPane); - rootView.setSize(MAX_WIDTH, MAX_HEIGHT); // Necessary! Without this line, size will not increase then you go from small page to bigger one - int prefHeight = (int) rootView.getPreferredSpan(View.Y_AXIS); - prefHeight += ins.bottom + ins.top + myScrollPane.getHorizontalScrollBar().getMaximumSize().height; - return new Dimension(MAX_WIDTH, Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, prefHeight))); - } - - { - enableEvents(KeyEvent.KEY_EVENT_MASK); - } - - protected void processKeyEvent(KeyEvent e) { - KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(e); - ActionListener listener = myKeyboardActions.get(keyStroke); - if (listener != null) { - listener.actionPerformed(new ActionEvent(DocumentationComponent.this, 0, "")); - e.consume(); - return; - } - super.processKeyEvent(e); - } - }; - myText = ""; - myEditorPane.setEditable(false); - myEditorPane.setBackground(HintUtil.INFORMATION_COLOR); - - myScrollPane = new JScrollPane(myEditorPane); - myScrollPane.setBorder(null); - - final MouseAdapter mouseAdapter = new MouseAdapter() { - public void mousePressed(MouseEvent e) { - myManager.requestFocus(); - } - }; - myEditorPane.addMouseListener(mouseAdapter); - Disposer.register(this, new Disposable() { - public void dispose() { - myEditorPane.removeMouseListener(mouseAdapter); - } - }); - - final FocusAdapter focusAdapter = new FocusAdapter() { - public void focusLost(FocusEvent e) { - Component previouslyFocused = WindowManagerEx.getInstanceEx().getFocusedComponent(manager.getProject(getElement())); - - if (!(previouslyFocused == myEditorPane)) { - myHint.cancel(); - } - } - }; - myEditorPane.addFocusListener(focusAdapter); - - Disposer.register(this, new Disposable() { - public void dispose() { - myEditorPane.removeFocusListener(focusAdapter); - } - }); - - setLayout(new BorderLayout()); - add(myScrollPane, BorderLayout.CENTER); - myScrollPane.setBorder(BorderFactory.createEmptyBorder(0, 2, 2, 2)); - - DefaultActionGroup group = new DefaultActionGroup(); - group.add(new BackAction()); - group.add(new ForwardAction()); - group.add(myExternalDocAction = new ExternalDocAction()); - myToolBar = ActionManager.getInstance().createActionToolbar(ActionPlaces.JAVADOC_TOOLBAR, group, true); - - myControlPanel = new JPanel(); - myControlPanel.setLayout(new BorderLayout()); - myControlPanel.setBorder(new EdgeBorder(EdgeBorder.EDGE_BOTTOM)); - JPanel dummyPanel = new JPanel(); - - myElementLabel = new JLabel(); - - dummyPanel.setLayout(new BorderLayout()); - dummyPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5)); - - dummyPanel.add(myElementLabel, BorderLayout.EAST); - - myControlPanel.add(myToolBar.getComponent(), BorderLayout.WEST); - myControlPanel.add(dummyPanel, BorderLayout.CENTER); - myControlPanelVisible = false; - - final HyperlinkListener hyperlinkListener = new HyperlinkListener() { - public void hyperlinkUpdate(HyperlinkEvent e) { - HyperlinkEvent.EventType type = e.getEventType(); - if (type == HyperlinkEvent.EventType.ACTIVATED) { - manager.navigateByLink(DocumentationComponent.this, e.getDescription()); - } - else if (type == HyperlinkEvent.EventType.ENTERED) { - myEditorPane.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - } - else if (type == HyperlinkEvent.EventType.EXITED) { - myEditorPane.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - } - } - }; - myEditorPane.addHyperlinkListener(hyperlinkListener); - Disposer.register(this, new Disposable() { - public void dispose() { - myEditorPane.removeHyperlinkListener(hyperlinkListener); - } - }); - - registerActions(); - - updateControlState(); - } - - public synchronized boolean isEmpty() { - return myIsEmpty; - } - - public synchronized void startWait() { - myIsEmpty = true; - } - - private void setControlPanelVisible(boolean visible) { - if (visible == myControlPanelVisible) return; - if (visible) { - add(myControlPanel, BorderLayout.NORTH); - } else { - remove(myControlPanel); - } - myControlPanelVisible = visible; - } - - public void setHint(JBPopup hint) { - myHint = hint; - } - - public JComponent getComponent() { - return myEditorPane; - } - - @Nullable - public PsiElement getElement() { - return myElement != null ? myElement.getElement() : null; - } - - public void setText(String text) { - setText(text, false); - } - - public void setText(String text, boolean clean) { - if (clean && myElement != null) { - myBackStack.push(saveContext()); - myForwardStack.clear(); - } - updateControlState(); - setDataInternal(myElement, text, new Rectangle(0, 0), !clean); - if (clean) { - myIsEmpty = false; - } - } - - public void setData(PsiElement _element, String text) { - if (myElement != null) { - myBackStack.push(saveContext()); - myForwardStack.clear(); - } - - final SmartPsiElementPointer element = _element != null && _element.isValid() ? - SmartPointerManager.getInstance(_element.getProject()).createSmartPsiElementPointer(_element): - null; - - if (element != null) { - myElement = element; - } - - myIsEmpty = false; - updateControlState(); - setDataInternal(element, text, new Rectangle(0, 0)); - } - - private void setDataInternal(SmartPsiElementPointer element, String text, final Rectangle viewRect) { - setDataInternal(element, text, viewRect, false); - } - - private void setDataInternal(SmartPsiElementPointer element, String text, final Rectangle viewRect, boolean skip) { - boolean justShown = false; - - myElement = element; - - if (!myIsShown && myHint != null) { - myEditorPane.setText(text); - myManager.showHint(myHint); - myIsShown = justShown = true; - } - - if (!justShown) { - myEditorPane.setText(text); - } - - if (!skip) { - myText = text; - } - - SwingUtilities.invokeLater(new Runnable() { - public void run() { - myEditorPane.scrollRectToVisible(viewRect); - } - }); - } - - private void goBack() { - if (myBackStack.isEmpty()) return; - Context context = myBackStack.pop(); - myForwardStack.push(saveContext()); - restoreContext(context); - updateControlState(); - } - - private void goForward() { - if (myForwardStack.isEmpty()) return; - Context context = myForwardStack.pop(); - myBackStack.push(saveContext()); - restoreContext(context); - updateControlState(); - } - - private Context saveContext() { - Rectangle rect = myScrollPane.getViewport().getViewRect(); - return new Context(myElement, myText, rect); - } - - private void restoreContext(Context context) { - setDataInternal(context.element, context.text, context.viewRect); - } - - private void updateControlState() { - ElementLocationUtil.customizeElementLabel(myElement != null ? myElement.getElement():null, myElementLabel); - myToolBar.updateActionsImmediately(); // update faster - setControlPanelVisible(true);//(!myBackStack.isEmpty() || !myForwardStack.isEmpty()); - } - - private class BackAction extends AnAction implements HintManagerImpl.ActionToIgnore { - public BackAction() { - super(CodeInsightBundle.message("javadoc.action.back"), null, IconLoader.getIcon("/actions/back.png")); - } - - public void actionPerformed(AnActionEvent e) { - goBack(); - } - - public void update(AnActionEvent e) { - Presentation presentation = e.getPresentation(); - presentation.setEnabled(!myBackStack.isEmpty()); - } - } - - private class ForwardAction extends AnAction implements HintManagerImpl.ActionToIgnore { - public ForwardAction() { - super(CodeInsightBundle.message("javadoc.action.forward"), null, IconLoader.getIcon("/actions/forward.png")); - } - - public void actionPerformed(AnActionEvent e) { - goForward(); - } - - public void update(AnActionEvent e) { - Presentation presentation = e.getPresentation(); - presentation.setEnabled(!myForwardStack.isEmpty()); - } - } - - private class ExternalDocAction extends AnAction implements HintManagerImpl.ActionToIgnore { - public ExternalDocAction() { - super(CodeInsightBundle.message("javadoc.action.view.external"), null, IconLoader.getIcon("/actions/browser-externalJavaDoc.png")); - registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EXTERNAL_JAVADOC).getShortcutSet(), null); - } - - public void actionPerformed(AnActionEvent e) { - if (myElement != null) { - final PsiElement element = myElement.getElement(); - final DocumentationProvider provider = DocumentationManager.getProviderFromElement(element); - final List urls = provider.getUrlFor(element, DocumentationManager.getOriginalElement(element)); - assert urls != null; - assert !urls.isEmpty(); - ExternalJavaDocAction.showExternalJavadoc(urls); - } - } - - public void update(AnActionEvent e) { - final Presentation presentation = e.getPresentation(); - presentation.setEnabled(false); - if (myElement != null) { - final PsiElement element = myElement.getElement(); - final DocumentationProvider provider = DocumentationManager.getProviderFromElement(element); - final List urls = provider.getUrlFor(element, DocumentationManager.getOriginalElement(element)); - presentation.setEnabled(element != null && urls!= null && !urls.isEmpty()); - } - } - } - - private void registerActions() { - myExternalDocAction.registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EXTERNAL_JAVADOC).getShortcutSet(), - myEditorPane); - - myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), - new ActionListener() { - public void actionPerformed(ActionEvent e) { - JScrollBar scrollBar = myScrollPane.getVerticalScrollBar(); - int value = scrollBar.getValue() - scrollBar.getUnitIncrement(-1); - value = Math.max(value, 0); - scrollBar.setValue(value); - } - }); - - myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), - new ActionListener() { - public void actionPerformed(ActionEvent e) { - JScrollBar scrollBar = myScrollPane.getVerticalScrollBar(); - int value = scrollBar.getValue() + scrollBar.getUnitIncrement(+1); - value = Math.min(value, scrollBar.getMaximum()); - scrollBar.setValue(value); - } - }); - - myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), - new ActionListener() { - public void actionPerformed(ActionEvent e) { - JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar(); - int value = scrollBar.getValue() - scrollBar.getUnitIncrement(-1); - value = Math.max(value, 0); - scrollBar.setValue(value); - } - }); - - myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), - new ActionListener() { - public void actionPerformed(ActionEvent e) { - JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar(); - int value = scrollBar.getValue() + scrollBar.getUnitIncrement(+1); - value = Math.min(value, scrollBar.getMaximum()); - scrollBar.setValue(value); - } - }); - - myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), - new ActionListener() { - public void actionPerformed(ActionEvent e) { - JScrollBar scrollBar = myScrollPane.getVerticalScrollBar(); - int value = scrollBar.getValue() - scrollBar.getBlockIncrement(-1); - value = Math.max(value, 0); - scrollBar.setValue(value); - } - }); - - myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), - new ActionListener() { - public void actionPerformed(ActionEvent e) { - JScrollBar scrollBar = myScrollPane.getVerticalScrollBar(); - int value = scrollBar.getValue() + scrollBar.getBlockIncrement(+1); - value = Math.min(value, scrollBar.getMaximum()); - scrollBar.setValue(value); - } - }); - - myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0), - new ActionListener() { - public void actionPerformed(ActionEvent e) { - JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar(); - scrollBar.setValue(0); - } - }); - - myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0), - new ActionListener() { - public void actionPerformed(ActionEvent e) { - JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar(); - scrollBar.setValue(scrollBar.getMaximum()); - } - }); - - myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, KeyEvent.CTRL_MASK), - new ActionListener() { - public void actionPerformed(ActionEvent e) { - JScrollBar scrollBar = myScrollPane.getVerticalScrollBar(); - scrollBar.setValue(0); - } - }); - - myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, KeyEvent.CTRL_MASK), - new ActionListener() { - public void actionPerformed(ActionEvent e) { - JScrollBar scrollBar = myScrollPane.getVerticalScrollBar(); - scrollBar.setValue(scrollBar.getMaximum()); - } - }); - } - - public String getText() { - return myText; - } - - public void dispose() { - myBackStack.clear(); - myForwardStack.clear(); - myKeyboardActions.clear(); - myElement = null; - myManager = null; - myHint = null; - } - -} +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.codeInsight.documentation; + +import com.intellij.codeInsight.CodeInsightBundle; +import com.intellij.codeInsight.hint.ElementLocationUtil; +import com.intellij.codeInsight.hint.HintManagerImpl; +import com.intellij.codeInsight.hint.HintUtil; +import com.intellij.ide.actions.ExternalJavaDocAction; +import com.intellij.lang.documentation.DocumentationProvider; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.ui.popup.JBPopup; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.wm.ex.WindowManagerEx; +import com.intellij.psi.PsiElement; +import com.intellij.psi.SmartPointerManager; +import com.intellij.psi.SmartPsiElementPointer; +import com.intellij.ui.EdgeBorder; +import com.intellij.util.containers.HashMap; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import javax.swing.text.View; +import java.awt.*; +import java.awt.event.*; +import java.util.List; +import java.util.Stack; + +public class DocumentationComponent extends JPanel implements Disposable { + + private static final int MAX_WIDTH = 500; + private static final int MAX_HEIGHT = 300; + private static final int MIN_HEIGHT = 45; + + private DocumentationManager myManager; + private SmartPsiElementPointer myElement; + + private final Stack myBackStack = new Stack(); + private final Stack myForwardStack = new Stack(); + private ActionToolbar myToolBar; + private boolean myIsEmpty; + private boolean myIsShown; + private final JLabel myElementLabel; + + private static class Context { + final SmartPsiElementPointer element; + final String text; + final Rectangle viewRect; + + public Context(SmartPsiElementPointer element, String text, Rectangle viewRect) { + this.element = element; + this.text = text; + this.viewRect = viewRect; + } + } + + private final JScrollPane myScrollPane; + private final JEditorPane myEditorPane; + private String myText; // myEditorPane.getText() surprisingly crashes.., let's cache the text + private final JPanel myControlPanel; + private boolean myControlPanelVisible; + private final ExternalDocAction myExternalDocAction; + + private JBPopup myHint; + + private final HashMap myKeyboardActions = new HashMap(); + // KeyStroke --> ActionListener + + public boolean requestFocusInWindow() { + return myScrollPane.requestFocusInWindow(); + } + + + public void requestFocus() { + myScrollPane.requestFocus(); + } + + public DocumentationComponent(final DocumentationManager manager, final AnAction[] additionalActions) { + myManager = manager; + myIsEmpty = true; + myIsShown = false; + + myEditorPane = new JEditorPane(UIUtil.HTML_MIME, "") { + public Dimension getPreferredScrollableViewportSize() { + if (getWidth() == 0 || getHeight() == 0) { + setSize(MAX_WIDTH, MAX_HEIGHT); + } + Insets ins = myEditorPane.getInsets(); + View rootView = myEditorPane.getUI().getRootView(myEditorPane); + rootView.setSize(MAX_WIDTH, + MAX_HEIGHT); // Necessary! Without this line, size will not increase then you go from small page to bigger one + int prefHeight = (int)rootView.getPreferredSpan(View.Y_AXIS); + prefHeight += ins.bottom + ins.top + myScrollPane.getHorizontalScrollBar().getMaximumSize().height; + return new Dimension(MAX_WIDTH, Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, prefHeight))); + } + + { + enableEvents(KeyEvent.KEY_EVENT_MASK); + } + + protected void processKeyEvent(KeyEvent e) { + KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(e); + ActionListener listener = myKeyboardActions.get(keyStroke); + if (listener != null) { + listener.actionPerformed(new ActionEvent(DocumentationComponent.this, 0, "")); + e.consume(); + return; + } + super.processKeyEvent(e); + } + }; + myText = ""; + myEditorPane.setEditable(false); + myEditorPane.setBackground(HintUtil.INFORMATION_COLOR); + + myScrollPane = new JScrollPane(myEditorPane); + myScrollPane.setBorder(null); + + final MouseAdapter mouseAdapter = new MouseAdapter() { + public void mousePressed(MouseEvent e) { + myManager.requestFocus(); + } + }; + myEditorPane.addMouseListener(mouseAdapter); + Disposer.register(this, new Disposable() { + public void dispose() { + myEditorPane.removeMouseListener(mouseAdapter); + } + }); + + final FocusAdapter focusAdapter = new FocusAdapter() { + public void focusLost(FocusEvent e) { + Component previouslyFocused = WindowManagerEx.getInstanceEx().getFocusedComponent(manager.getProject(getElement())); + + if (!(previouslyFocused == myEditorPane)) { + if (myHint != null && !myHint.isDisposed()) myHint.cancel(); + } + } + }; + myEditorPane.addFocusListener(focusAdapter); + + Disposer.register(this, new Disposable() { + public void dispose() { + myEditorPane.removeFocusListener(focusAdapter); + } + }); + + setLayout(new BorderLayout()); + add(myScrollPane, BorderLayout.CENTER); + myScrollPane.setBorder(BorderFactory.createEmptyBorder(0, 2, 2, 2)); + + final DefaultActionGroup actions = new DefaultActionGroup(); + actions.add(new BackAction()); + actions.add(new ForwardAction()); + actions.add(myExternalDocAction = new ExternalDocAction()); + + if (additionalActions != null) { + for (final AnAction action : additionalActions) { + actions.add(action); + } + } + + myToolBar = ActionManager.getInstance().createActionToolbar(ActionPlaces.JAVADOC_TOOLBAR, actions, true); + + myControlPanel = new JPanel(); + myControlPanel.setLayout(new BorderLayout()); + myControlPanel.setBorder(new EdgeBorder(EdgeBorder.EDGE_BOTTOM)); + JPanel dummyPanel = new JPanel(); + + myElementLabel = new JLabel(); + + dummyPanel.setLayout(new BorderLayout()); + dummyPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5)); + + dummyPanel.add(myElementLabel, BorderLayout.EAST); + + myControlPanel.add(myToolBar.getComponent(), BorderLayout.WEST); + myControlPanel.add(dummyPanel, BorderLayout.CENTER); + myControlPanelVisible = false; + + final HyperlinkListener hyperlinkListener = new HyperlinkListener() { + public void hyperlinkUpdate(HyperlinkEvent e) { + HyperlinkEvent.EventType type = e.getEventType(); + if (type == HyperlinkEvent.EventType.ACTIVATED) { + manager.navigateByLink(DocumentationComponent.this, e.getDescription()); + } + else if (type == HyperlinkEvent.EventType.ENTERED) { + myEditorPane.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + } + else if (type == HyperlinkEvent.EventType.EXITED) { + myEditorPane.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + } + }; + myEditorPane.addHyperlinkListener(hyperlinkListener); + Disposer.register(this, new Disposable() { + public void dispose() { + myEditorPane.removeHyperlinkListener(hyperlinkListener); + } + }); + + registerActions(); + + updateControlState(); + } + + public DocumentationComponent(final DocumentationManager manager) { + this(manager, null); + } + + public synchronized boolean isEmpty() { + return myIsEmpty; + } + + public synchronized void startWait() { + myIsEmpty = true; + } + + private void setControlPanelVisible(boolean visible) { + if (visible == myControlPanelVisible) return; + if (visible) { + add(myControlPanel, BorderLayout.NORTH); + } + else { + remove(myControlPanel); + } + myControlPanelVisible = visible; + } + + public void setHint(JBPopup hint) { + myHint = hint; + } + + public JComponent getComponent() { + return myEditorPane; + } + + @Nullable + public PsiElement getElement() { + return myElement != null ? myElement.getElement() : null; + } + + public void setText(String text, PsiElement element, boolean clearHistory) { + setText(text, element, false, clearHistory); + } + + public void setText(String text, PsiElement element, boolean clean, boolean clearHistory) { + if (clean && myElement != null) { + myBackStack.push(saveContext()); + myForwardStack.clear(); + } + updateControlState(); + setData(element, text, !clean); + if (clean) { + myIsEmpty = false; + } + + if (clearHistory) clearHistory(); + } + + private void clearHistory() { + myForwardStack.clear(); + myBackStack.clear(); + } + + public void setData(PsiElement _element, String text, final boolean clearHistory) { + if (myElement != null) { + myBackStack.push(saveContext()); + myForwardStack.clear(); + } + + final SmartPsiElementPointer element = _element != null && _element.isValid() + ? SmartPointerManager.getInstance(_element.getProject()).createSmartPsiElementPointer(_element) + : null; + + if (element != null) { + myElement = element; + } + + myIsEmpty = false; + updateControlState(); + setDataInternal(element, text, new Rectangle(0, 0)); + + if (clearHistory) clearHistory(); + } + + private void setDataInternal(SmartPsiElementPointer element, String text, final Rectangle viewRect) { + setDataInternal(element, text, viewRect, false); + } + + private void setDataInternal(SmartPsiElementPointer element, String text, final Rectangle viewRect, boolean skip) { + boolean justShown = false; + + myElement = element; + + if (!myIsShown && myHint != null) { + myEditorPane.setText(text); + myManager.showHint(myHint); + myIsShown = justShown = true; + } + + if (!justShown) { + myEditorPane.setText(text); + } + + if (!skip) { + myText = text; + } + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + myEditorPane.scrollRectToVisible(viewRect); + } + }); + } + + private void goBack() { + if (myBackStack.isEmpty()) return; + Context context = myBackStack.pop(); + myForwardStack.push(saveContext()); + restoreContext(context); + updateControlState(); + } + + private void goForward() { + if (myForwardStack.isEmpty()) return; + Context context = myForwardStack.pop(); + myBackStack.push(saveContext()); + restoreContext(context); + updateControlState(); + } + + private Context saveContext() { + Rectangle rect = myScrollPane.getViewport().getViewRect(); + return new Context(myElement, myText, rect); + } + + private void restoreContext(Context context) { + setDataInternal(context.element, context.text, context.viewRect); + } + + private void updateControlState() { + ElementLocationUtil.customizeElementLabel(myElement != null ? myElement.getElement() : null, myElementLabel); + myToolBar.updateActionsImmediately(); // update faster + setControlPanelVisible(true);//(!myBackStack.isEmpty() || !myForwardStack.isEmpty()); + } + + private class BackAction extends AnAction implements HintManagerImpl.ActionToIgnore { + public BackAction() { + super(CodeInsightBundle.message("javadoc.action.back"), null, IconLoader.getIcon("/actions/back.png")); + } + + public void actionPerformed(AnActionEvent e) { + goBack(); + } + + public void update(AnActionEvent e) { + Presentation presentation = e.getPresentation(); + presentation.setEnabled(!myBackStack.isEmpty()); + } + } + + private class ForwardAction extends AnAction implements HintManagerImpl.ActionToIgnore { + public ForwardAction() { + super(CodeInsightBundle.message("javadoc.action.forward"), null, IconLoader.getIcon("/actions/forward.png")); + } + + public void actionPerformed(AnActionEvent e) { + goForward(); + } + + public void update(AnActionEvent e) { + Presentation presentation = e.getPresentation(); + presentation.setEnabled(!myForwardStack.isEmpty()); + } + } + + private class ExternalDocAction extends AnAction implements HintManagerImpl.ActionToIgnore { + public ExternalDocAction() { + super(CodeInsightBundle.message("javadoc.action.view.external"), null, IconLoader.getIcon("/actions/browser-externalJavaDoc.png")); + registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EXTERNAL_JAVADOC).getShortcutSet(), null); + } + + public void actionPerformed(AnActionEvent e) { + if (myElement != null) { + final PsiElement element = myElement.getElement(); + final DocumentationProvider provider = DocumentationManager.getProviderFromElement(element); + final List urls = provider.getUrlFor(element, DocumentationManager.getOriginalElement(element)); + assert urls != null; + assert !urls.isEmpty(); + ExternalJavaDocAction.showExternalJavadoc(urls); + } + } + + public void update(AnActionEvent e) { + final Presentation presentation = e.getPresentation(); + presentation.setEnabled(false); + if (myElement != null) { + final PsiElement element = myElement.getElement(); + final DocumentationProvider provider = DocumentationManager.getProviderFromElement(element); + final List urls = provider.getUrlFor(element, DocumentationManager.getOriginalElement(element)); + presentation.setEnabled(element != null && urls != null && !urls.isEmpty()); + } + } + } + + private void registerActions() { + myExternalDocAction + .registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EXTERNAL_JAVADOC).getShortcutSet(), myEditorPane); + + myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), new ActionListener() { + public void actionPerformed(ActionEvent e) { + JScrollBar scrollBar = myScrollPane.getVerticalScrollBar(); + int value = scrollBar.getValue() - scrollBar.getUnitIncrement(-1); + value = Math.max(value, 0); + scrollBar.setValue(value); + } + }); + + myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), new ActionListener() { + public void actionPerformed(ActionEvent e) { + JScrollBar scrollBar = myScrollPane.getVerticalScrollBar(); + int value = scrollBar.getValue() + scrollBar.getUnitIncrement(+1); + value = Math.min(value, scrollBar.getMaximum()); + scrollBar.setValue(value); + } + }); + + myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), new ActionListener() { + public void actionPerformed(ActionEvent e) { + JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar(); + int value = scrollBar.getValue() - scrollBar.getUnitIncrement(-1); + value = Math.max(value, 0); + scrollBar.setValue(value); + } + }); + + myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), new ActionListener() { + public void actionPerformed(ActionEvent e) { + JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar(); + int value = scrollBar.getValue() + scrollBar.getUnitIncrement(+1); + value = Math.min(value, scrollBar.getMaximum()); + scrollBar.setValue(value); + } + }); + + myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), new ActionListener() { + public void actionPerformed(ActionEvent e) { + JScrollBar scrollBar = myScrollPane.getVerticalScrollBar(); + int value = scrollBar.getValue() - scrollBar.getBlockIncrement(-1); + value = Math.max(value, 0); + scrollBar.setValue(value); + } + }); + + myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), new ActionListener() { + public void actionPerformed(ActionEvent e) { + JScrollBar scrollBar = myScrollPane.getVerticalScrollBar(); + int value = scrollBar.getValue() + scrollBar.getBlockIncrement(+1); + value = Math.min(value, scrollBar.getMaximum()); + scrollBar.setValue(value); + } + }); + + myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0), new ActionListener() { + public void actionPerformed(ActionEvent e) { + JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar(); + scrollBar.setValue(0); + } + }); + + myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0), new ActionListener() { + public void actionPerformed(ActionEvent e) { + JScrollBar scrollBar = myScrollPane.getHorizontalScrollBar(); + scrollBar.setValue(scrollBar.getMaximum()); + } + }); + + myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, KeyEvent.CTRL_MASK), new ActionListener() { + public void actionPerformed(ActionEvent e) { + JScrollBar scrollBar = myScrollPane.getVerticalScrollBar(); + scrollBar.setValue(0); + } + }); + + myKeyboardActions.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, KeyEvent.CTRL_MASK), new ActionListener() { + public void actionPerformed(ActionEvent e) { + JScrollBar scrollBar = myScrollPane.getVerticalScrollBar(); + scrollBar.setValue(scrollBar.getMaximum()); + } + }); + } + + public String getText() { + return myText; + } + + public void dispose() { + myBackStack.clear(); + myForwardStack.clear(); + myKeyboardActions.clear(); + myElement = null; + myManager = null; + myHint = null; + } + +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java index 3b1b009e3d..1dfe156296 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java @@ -25,15 +25,14 @@ import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupManager; import com.intellij.ide.BrowserUtil; import com.intellij.ide.DataManager; +import com.intellij.ide.IdeEventQueue; +import com.intellij.ide.util.PropertiesComponent; import com.intellij.ide.util.gotoByName.ChooseByNameBase; import com.intellij.lang.Language; import com.intellij.lang.LanguageDocumentation; import com.intellij.lang.documentation.CompositeDocumentationProvider; import com.intellij.lang.documentation.DocumentationProvider; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.ex.ActionManagerEx; import com.intellij.openapi.actionSystem.ex.AnActionListener; import com.intellij.openapi.application.ApplicationManager; @@ -44,26 +43,38 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.util.*; +import com.intellij.openapi.wm.*; +import com.intellij.openapi.wm.ex.ToolWindowManagerEx; import com.intellij.openapi.wm.ex.WindowManagerEx; import com.intellij.psi.*; +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.psi.presentation.java.SymbolPresentationUtil; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.ui.content.*; import com.intellij.ui.popup.AbstractPopup; import com.intellij.ui.popup.NotLookupOrSearchCondition; import com.intellij.ui.popup.PopupUpdateProcessor; import com.intellij.util.Alarm; +import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.update.Activatable; +import com.intellij.util.ui.update.UiNotifyConnector; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.lang.ref.WeakReference; import java.util.*; import java.util.List; public class DocumentationManager { + private static final String SHOW_DOCUMENTATION_IN_TOOL_WINDOW = "ShowDocumentationInToolWindow"; + private static final String DOCUMENTATION_AUTO_UPDATE_ENABLED = "DocumentationAutoUpdateEnabled"; @NonNls public static final String JAVADOC_LOCATION_AND_SIZE = "javadoc.popup"; private final Project myProject; private Editor myEditor = null; @@ -74,10 +85,13 @@ public class DocumentationManager { public static final Key ORIGINAL_ELEMENT_KEY = Key.create("Original element"); @NonNls public static final String PSI_ELEMENT_PROTOCOL = "psi_element://"; @NonNls private static final String DOC_ELEMENT_PROTOCOL = "doc_element://"; + private ToolWindow myToolWindow = null; private final ActionManagerEx myActionManagerEx; private static final int ourFlagsForTargetElements = TargetElementUtilBase.getInstance().getAllAccepted(); + private boolean myAutoUpdateDocumentation = PropertiesComponent.getInstance().isTrueValue(DOCUMENTATION_AUTO_UPDATE_ENABLED); + private Runnable myAutoUpdateRequest; public static DocumentationManager getInstance(Project project) { return ServiceManager.getService(project, DocumentationManager.class); @@ -115,7 +129,7 @@ public class DocumentationManager { myUpdateDocAlarm = new Alarm(Alarm.ThreadToUse.OWN_THREAD,myProject); } - public JBPopup showJavaDocInfo(@NotNull final PsiElement element, final PsiElement original) { + public void showJavaDocInfo(@NotNull final PsiElement element, final PsiElement original) { PopupUpdateProcessor updateProcessor = new PopupUpdateProcessor(element.getProject()) { public void updatePopup(Object lookupItemObject) { if (lookupItemObject instanceof PsiElement) { @@ -123,11 +137,11 @@ public class DocumentationManager { } } }; - return doShowJavaDocInfo(element, true, false, updateProcessor, original); + + doShowJavaDocInfo(element, true, false, updateProcessor, original); } - @Nullable - public JBPopup showJavaDocInfo(final Editor editor, @Nullable final PsiFile file, boolean requestFocus) { + public void showJavaDocInfo(final Editor editor, @Nullable final PsiFile file, boolean requestFocus) { myEditor = editor; final Project project = getProject(file); PsiDocumentManager.getInstance(project).commitAllDocuments(); @@ -151,13 +165,13 @@ public class DocumentationManager { } } - if (element == null && file == null) return null; //file == null for text field editor + if (element == null && file == null) return; //file == null for text field editor if (element == null) { // look if we are within a javadoc comment element = originalElement; - if (element == null) return null; + if (element == null) return; PsiComment comment = PsiTreeUtil.getParentOfType(element, PsiComment.class); - if (comment == null) return null; + if (comment == null) return; element = comment.getParent(); //if (!(element instanceof PsiDocCommentOwner)) return null; } @@ -198,69 +212,211 @@ public class DocumentationManager { } }; - return doShowJavaDocInfo(element, false, requestFocus, updateProcessor, originalElement); + doShowJavaDocInfo(element, false, requestFocus, updateProcessor, originalElement); } - private JBPopup doShowJavaDocInfo(PsiElement element, boolean heavyWeight, boolean requestFocus, PopupUpdateProcessor updateProcessor, PsiElement originalElement) { + private void doShowJavaDocInfo(final PsiElement element, boolean heavyWeight, boolean requestFocus, PopupUpdateProcessor updateProcessor, final PsiElement originalElement) { Project project = getProject(element); - final DocumentationComponent component = new DocumentationComponent(this); - - final JBPopup hint = JBPopupFactory.getInstance().createComponentPopupBuilder(component, component) - .setRequestFocusCondition(project, NotLookupOrSearchCondition.INSTANCE) - .setProject(project) - .addListener(updateProcessor) - .addUserData(updateProcessor) - .setForceHeavyweight(heavyWeight) - .setDimensionServiceKey(myProject, JAVADOC_LOCATION_AND_SIZE, false) - .setResizable(true) - .setMovable(true) - .setTitle(getTitle(element)) - .setCancelCallback(new Computable() { - public Boolean compute() { - if (fromQuickSearch()) { - ((ChooseByNameBase.JPanelProvider)myPreviouslyFocused.getParent()).unregisterHint(); + + if (myToolWindow == null && PropertiesComponent.getInstance().isTrueValue(SHOW_DOCUMENTATION_IN_TOOL_WINDOW)) { + createToolWindow(element, originalElement); + } else if (myToolWindow != null) { + final Content content = myToolWindow.getContentManager().getSelectedContent(); + if (content != null) { + final DocumentationComponent component = (DocumentationComponent) content.getComponent(); + if (component.getElement() != element) { + content.setDisplayName(getTitle(element, true)); + fetchDocInfo(getDefaultCollector(element, originalElement), component, true); + } + + if (!myToolWindow.isVisible()) myToolWindow.show(null); + } + } else { + final DocumentationComponent component = new DocumentationComponent(this); + Processor pinCallback = new Processor() { + public boolean process(JBPopup popup) { + createToolWindow(element, originalElement); + popup.cancel(); + return false; + } + }; + + final List> actions = Collections.singletonList(Pair.create(new ActionListener() { + public void actionPerformed(ActionEvent e) { + createToolWindow(element, originalElement); + final JBPopup hint = getDocInfoHint(); + if (hint != null && hint.isVisible()) hint.cancel(); + } + }, ActionManagerEx.getInstanceEx().getKeyboardShortcut("QuickJavaDoc").getFirstKeyStroke())); + + final JBPopup hint = JBPopupFactory.getInstance().createComponentPopupBuilder(component, component) + .setRequestFocusCondition(project, NotLookupOrSearchCondition.INSTANCE) + .setProject(project) + .addListener(updateProcessor) + .addUserData(updateProcessor) + .setKeyboardActions(actions) + .setForceHeavyweight(heavyWeight) + .setDimensionServiceKey(myProject, JAVADOC_LOCATION_AND_SIZE, false) + .setResizable(true) + .setMovable(true) + .setTitle(getTitle(element, false)) + .setCouldPin(pinCallback) + .setCancelCallback(new Computable() { + public Boolean compute() { + if (fromQuickSearch()) { + ((ChooseByNameBase.JPanelProvider)myPreviouslyFocused.getParent()).unregisterHint(); + } + + Disposer.dispose(component); + myEditor = null; + myPreviouslyFocused = null; + myParameterInfoController = null; + return Boolean.TRUE; } + }) + .createPopup(); + - Disposer.dispose(component); - myEditor = null; - myPreviouslyFocused = null; - myParameterInfoController = null; - return Boolean.TRUE; + AbstractPopup oldHint = (AbstractPopup)getDocInfoHint(); + if (oldHint != null) { + DocumentationComponent oldComponent = (DocumentationComponent)oldHint.getComponent(); + PsiElement element1 = oldComponent.getElement(); + if (Comparing.equal(element, element1)) { + if (requestFocus) { + component.getComponent().requestFocus(); } - }) - .createPopup(); + return; + } + oldHint.cancel(); + } + component.setHint(hint); - AbstractPopup oldHint = (AbstractPopup)getDocInfoHint(); - if (oldHint != null) { - DocumentationComponent oldComponent = (DocumentationComponent)oldHint.getComponent(); - PsiElement element1 = oldComponent.getElement(); - if (Comparing.equal(element, element1)) { - if (requestFocus) { - component.getComponent().requestFocus(); - } - return oldHint; + fetchDocInfo(getDefaultCollector(element, originalElement), component); + + myDocInfoHintRef = new WeakReference(hint); + myPreviouslyFocused = WindowManagerEx.getInstanceEx().getFocusedComponent(project); + + if (fromQuickSearch()) { + ((ChooseByNameBase.JPanelProvider)myPreviouslyFocused.getParent()).registerHint(hint); } - oldHint.cancel(); } + } + + private void createToolWindow(final PsiElement element, PsiElement originalElement) { + assert myToolWindow == null; + + final DocumentationComponent component = new DocumentationComponent(this, new AnAction[]{ + new ToggleAction("Auto show documentation for selected element", "Show documentation for current element automatically", + IconLoader.getIcon("/general/autoscrollFromSource.png")) { + @Override + public boolean isSelected(AnActionEvent e) { + return myAutoUpdateDocumentation; + } + + @Override + public void setSelected(AnActionEvent e, boolean state) { + PropertiesComponent.getInstance().setValue(DOCUMENTATION_AUTO_UPDATE_ENABLED, Boolean.TRUE.toString()); + myAutoUpdateDocumentation = state; + restartAutoUpdate(state); + } + }, + new AnAction("Restore popup behavior", "Restore documentation popup behavior", IconLoader.getIcon("/actions/cancel.png")) { + @Override + public void actionPerformed(AnActionEvent e) { + restorePopupBehavior(); + } + }}); - component.setHint(hint); + final ToolWindowManagerEx toolWindowManagerEx = ToolWindowManagerEx.getInstanceEx(myProject); + myToolWindow = toolWindowManagerEx.registerToolWindow(ToolWindowId.DOCUMENTATION, true, ToolWindowAnchor.RIGHT, myProject); + myToolWindow.setIcon(IconLoader.getIcon("/general/documentation.png")); + myToolWindow.setAvailable(true, null); + myToolWindow.setToHideOnEmptyContent(false); + myToolWindow.setAutoHide(false); + + final Rectangle rectangle = WindowManager.getInstance().getIdeFrame(myProject).suggestChildFrameBounds(); + myToolWindow.setDefaultState(ToolWindowAnchor.RIGHT, ToolWindowType.FLOATING, rectangle); + + final ContentManager contentManager = myToolWindow.getContentManager(); + final ContentFactory contentFactory = ContentFactory.SERVICE.getInstance(); + final Content content = contentFactory.createContent(component, getTitle(element, true), false); + contentManager.addContent(content); + + contentManager.addContentManagerListener(new ContentManagerAdapter() { + @Override + public void contentRemoved(ContentManagerEvent event) { + if (contentManager.getContentCount() == 0) { + restorePopupBehavior(); + } + } + }); + + new UiNotifyConnector(component, new Activatable() { + public void showNotify() { + restartAutoUpdate(myAutoUpdateDocumentation); + } + + public void hideNotify() { + restartAutoUpdate(false); + } + }); + + myToolWindow.show(null); + PropertiesComponent.getInstance().setValue(SHOW_DOCUMENTATION_IN_TOOL_WINDOW, Boolean.TRUE.toString()); + restartAutoUpdate(PropertiesComponent.getInstance().isTrueValue(DOCUMENTATION_AUTO_UPDATE_ENABLED)); fetchDocInfo(getDefaultCollector(element, originalElement), component); + } + + private void restartAutoUpdate(final boolean state) { + if (state && myToolWindow != null) { + if (myAutoUpdateRequest == null) { + myAutoUpdateRequest = new Runnable() { + public void run() { + final DataContext dataContext = DataManager.getInstance().getDataContext(); + final Editor editor = PlatformDataKeys.EDITOR.getData(dataContext); + if (editor != null) { + final PsiFile file = PsiUtilBase.getPsiFileInEditor(editor, myProject); + + final Editor injectedEditor = InjectedLanguageUtil.getEditorForInjectedLanguageNoCommit(editor, file); + if (injectedEditor != null) { + final PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(injectedEditor, myProject); + if (psiFile != null) { + showJavaDocInfo(injectedEditor, psiFile, false); + return; + } + } - myDocInfoHintRef = new WeakReference(hint); - myPreviouslyFocused = WindowManagerEx.getInstanceEx().getFocusedComponent(project); + if (file != null) { + showJavaDocInfo(editor, file, false); + } + } + } + }; - if (fromQuickSearch()) { - ((ChooseByNameBase.JPanelProvider)myPreviouslyFocused.getParent()).registerHint(hint); + IdeEventQueue.getInstance().addIdleListener(myAutoUpdateRequest, 500); + } + } else { + if (myAutoUpdateRequest != null) { + IdeEventQueue.getInstance().removeIdleListener(myAutoUpdateRequest); + myAutoUpdateRequest = null; + } } + } - return hint; + private void restorePopupBehavior() { + if (myToolWindow != null) { + PropertiesComponent.getInstance().setValue(SHOW_DOCUMENTATION_IN_TOOL_WINDOW, Boolean.FALSE.toString()); + ToolWindowManagerEx.getInstanceEx(myProject).unregisterToolWindow(ToolWindowId.DOCUMENTATION); + myToolWindow = null; + restartAutoUpdate(false); + } } - private static String getTitle(@NotNull final PsiElement element) { + private static String getTitle(@NotNull final PsiElement element, final boolean _short) { final String title = SymbolPresentationUtil.getSymbolPresentableText(element); - return CodeInsightBundle.message("javadoc.info.title", title != null ? title : element.getText()); + return _short ? title != null ? title : element.getText() : CodeInsightBundle.message("javadoc.info.title", title != null ? title : element.getText()); } public static void storeOriginalElement(final Project project, final PsiElement originalElement, final PsiElement element) { @@ -394,29 +550,33 @@ public class DocumentationManager { } public void fetchDocInfo(final DocumentationCollector provider, final DocumentationComponent component) { - doFetchDocInfo(component, provider, true); + doFetchDocInfo(component, provider, true, false); + } + + public void fetchDocInfo(final DocumentationCollector provider, final DocumentationComponent component, final boolean clearHistory) { + doFetchDocInfo(component, provider, true, clearHistory); } public void fetchDocInfo(final PsiElement element, final DocumentationComponent component) { - doFetchDocInfo(component, getDefaultCollector(element, null), true); + doFetchDocInfo(component, getDefaultCollector(element, null), true, false); } - public ActionCallback queueFetchDocInfo(final DocumentationCollector provider, final DocumentationComponent component) { - return doFetchDocInfo(component, provider, false); + public ActionCallback queueFetchDocInfo(final DocumentationCollector provider, final DocumentationComponent component, final boolean clearHistory) { + return doFetchDocInfo(component, provider, false, clearHistory); } public ActionCallback queueFetchDocInfo(final PsiElement element, final DocumentationComponent component) { - return queueFetchDocInfo(getDefaultCollector(element, null), component); + return queueFetchDocInfo(getDefaultCollector(element, null), component, false); } - private ActionCallback doFetchDocInfo(final DocumentationComponent component, final DocumentationCollector provider, final boolean cancelRequests) { + private ActionCallback doFetchDocInfo(final DocumentationComponent component, final DocumentationCollector provider, final boolean cancelRequests, final boolean clearHistory) { final ActionCallback callback = new ActionCallback(); component.startWait(); if (cancelRequests) { myUpdateDocAlarm.cancelAllRequests(); } if (component.isEmpty()) { - component.setText(CodeInsightBundle.message("javadoc.fetching.progress")); + component.setText(CodeInsightBundle.message("javadoc.fetching.progress"), null, clearHistory); } myUpdateDocAlarm.addRequest(new Runnable() { @@ -441,7 +601,7 @@ public class DocumentationManager { String message = ex[0] instanceof IndexNotReadyException ? "Documentation is not available until indices are built." : CodeInsightBundle.message("javadoc.external.fetch.error.message", ex[0].getLocalizedMessage()); - component.setText(message, true); + component.setText(message, null, true); callback.setDone(); } }); @@ -472,13 +632,13 @@ public class DocumentationManager { } if (text == null) { - component.setText(CodeInsightBundle.message("no.documentation.found"), true); + component.setText(CodeInsightBundle.message("no.documentation.found"), element, true); } else if (text.length() == 0) { - component.setText(component.getText(), true); + component.setText(component.getText(), element, true, clearHistory); } else { - component.setData(element, text); + component.setData(element, text, clearHistory); } final AbstractPopup jbPopup = (AbstractPopup)getDocInfoHint(); @@ -486,7 +646,7 @@ public class DocumentationManager { callback.setDone(); return; } - jbPopup.setCaption(getTitle(element)); + jbPopup.setCaption(getTitle(element, false)); final String dimensionServiceKey = jbPopup.getDimensionServiceKey(); Dimension dimension = component.getPreferredSize(); final Dimension storedSize = dimensionServiceKey != null ? DimensionService.getInstance().getSize(dimensionServiceKey, getProject(element)) : null; diff --git a/platform/lang-impl/src/com/intellij/find/actions/ShowUsagesAction.java b/platform/lang-impl/src/com/intellij/find/actions/ShowUsagesAction.java index fe232519c6..69d744506b 100644 --- a/platform/lang-impl/src/com/intellij/find/actions/ShowUsagesAction.java +++ b/platform/lang-impl/src/com/intellij/find/actions/ShowUsagesAction.java @@ -26,6 +26,7 @@ import com.intellij.find.findUsages.FindUsagesManager; import com.intellij.find.impl.FindManagerImpl; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.actionSystem.ex.ActionManagerEx; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorLocation; @@ -574,20 +575,9 @@ public class ShowUsagesAction extends AnAction { } private static KeyboardShortcut getSettingsShortcut() { - AnAction action = ActionManager.getInstance().getAction("ShowUsagesSettings"); - final ShortcutSet shortcutSet = action.getShortcutSet(); - final Shortcut[] shortcuts = shortcutSet.getShortcuts(); - for (final Shortcut shortcut : shortcuts) { - KeyboardShortcut kb = (KeyboardShortcut)shortcut; - if (kb.getSecondKeyStroke() == null) { - return (KeyboardShortcut)shortcut; - } - } - - return null; + return ActionManagerEx.getInstanceEx().getKeyboardShortcut("ShowUsagesSettings"); } - private static void addUsageNodes(GroupNode root, final UsageViewImpl usageView, List outNodes) { for (UsageNode node : root.getUsageNodes()) { Usage usage = node.getUsage(); diff --git a/platform/platform-api/src/com/intellij/openapi/ui/popup/ComponentPopupBuilder.java b/platform/platform-api/src/com/intellij/openapi/ui/popup/ComponentPopupBuilder.java index f6a8bf5dd4..bc93253a1a 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/popup/ComponentPopupBuilder.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/popup/ComponentPopupBuilder.java @@ -21,6 +21,7 @@ import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Pair; import com.intellij.ui.InplaceButton; +import com.intellij.util.Processor; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -110,6 +111,9 @@ public interface ComponentPopupBuilder { ComponentPopupBuilder setCommandButton(@NotNull InplaceButton commandButton); @NotNull + ComponentPopupBuilder setCouldPin(@Nullable Processor callback); + + @NotNull ComponentPopupBuilder setKeyboardActions(@NotNull List> keyboardActions); @NotNull diff --git a/platform/platform-api/src/com/intellij/openapi/wm/ToolWindowId.java b/platform/platform-api/src/com/intellij/openapi/wm/ToolWindowId.java index 0aabb763db..08ebcaa9e9 100644 --- a/platform/platform-api/src/com/intellij/openapi/wm/ToolWindowId.java +++ b/platform/platform-api/src/com/intellij/openapi/wm/ToolWindowId.java @@ -35,4 +35,5 @@ public interface ToolWindowId { String VCS = UIBundle.message("tool.window.name.version.control"); String MODULES_DEPENDENCIES = UIBundle.message("tool.window.name.module.dependencies"); String DUPLICATES = UIBundle.message("tool.window.name.module.duplicates"); + String DOCUMENTATION = UIBundle.message("tool.window.name.documentation"); } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/notification/impl/ui/NotificationsListPanel.java b/platform/platform-impl/src/com/intellij/notification/impl/ui/NotificationsListPanel.java index f7fa5cc529..3f6cfa46f5 100644 --- a/platform/platform-impl/src/com/intellij/notification/impl/ui/NotificationsListPanel.java +++ b/platform/platform-impl/src/com/intellij/notification/impl/ui/NotificationsListPanel.java @@ -53,8 +53,6 @@ public class NotificationsListPanel extends JPanel implements NotificationModelL private static final Logger LOG = Logger.getInstance("#com.intellij.notification.impl.ui.NotificationsListPanel"); private static final String REMOVE_KEY = "REMOVE"; - private WeakReference myPopupRef; - private Project myProject; private Wrapper myWrapper; private JComponent myActiveComponent; @@ -134,17 +132,6 @@ public class NotificationsListPanel extends JPanel implements NotificationModelL return size; } - public void clear() { - if (myPopupRef != null) { - final JBPopup jbPopup = myPopupRef.get(); - if (jbPopup != null) { - jbPopup.cancel(); - } - - myPopupRef = null; - } - } - public JComponent getPreferredFocusedComponent() { return myWrapper.getTargetComponent(); } diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/ex/ActionManagerEx.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/ex/ActionManagerEx.java index 7d963bcab6..a562efabba 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/ex/ActionManagerEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/ex/ActionManagerEx.java @@ -18,11 +18,9 @@ package com.intellij.openapi.actionSystem.ex; import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.extensions.PluginId; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; @@ -54,6 +52,8 @@ public abstract class ActionManagerEx extends ActionManager{ public abstract void fireAfterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event); + @Nullable + public abstract KeyboardShortcut getKeyboardShortcut(@NotNull String actionId); public abstract void fireBeforeEditorTyping(char c, DataContext dataContext); diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java index 013fb4ef82..520db075c1 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java @@ -1030,6 +1030,21 @@ public final class ActionManagerImpl extends ActionManagerEx implements Applicat } } + @Override + public KeyboardShortcut getKeyboardShortcut(@NotNull String actionId) { + AnAction action = ActionManager.getInstance().getAction(actionId); + final ShortcutSet shortcutSet = action.getShortcutSet(); + final Shortcut[] shortcuts = shortcutSet.getShortcuts(); + for (final Shortcut shortcut : shortcuts) { + KeyboardShortcut kb = (KeyboardShortcut)shortcut; + if (kb.getSecondKeyStroke() == null) { + return (KeyboardShortcut)shortcut; + } + } + + return null; + } + public void fireBeforeEditorTyping(char c, DataContext dataContext) { myLastTimeEditorWasTypedIn = System.currentTimeMillis(); AnActionListener[] listeners = getActionListeners(); diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/ex/WindowManagerEx.java b/platform/platform-impl/src/com/intellij/openapi/wm/ex/WindowManagerEx.java index 2caa33a09f..c040a4f17e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/ex/WindowManagerEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/ex/WindowManagerEx.java @@ -89,6 +89,11 @@ public abstract class WindowManagerEx extends WindowManager { public abstract Rectangle getScreenBounds(); /** + * @return bounds for the screen device for the given project frame + */ + public abstract Rectangle getScreenBounds(@NotNull final Project project); + + /** * @return true is and only if current OS supports alpha mode for windows and * all native libraries were successfully loaded. */ diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/TestWindowManager.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/TestWindowManager.java index 113c19f572..e2966ec8ee 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/TestWindowManager.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/TestWindowManager.java @@ -59,6 +59,11 @@ public final class TestWindowManager extends WindowManagerEx implements Applicat return null; } + @Override + public Rectangle getScreenBounds(@NotNull Project project) { + return null; + } + public void setWindowMask(final Window window, final Shape mask) { } diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/WindowManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/WindowManagerImpl.java index 81b7a07aca..c5f0c2ad74 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/WindowManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/WindowManagerImpl.java @@ -206,6 +206,22 @@ public final class WindowManagerImpl extends WindowManagerEx implements Applicat return myScreenBounds; } + @Override + public Rectangle getScreenBounds(@NotNull Project project) { + final GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment(); + final Point onScreen = getFrame(project).getLocationOnScreen(); + final GraphicsDevice[] devices = environment.getScreenDevices(); + for (final GraphicsDevice device : devices) { + final Rectangle bounds = device.getDefaultConfiguration().getBounds(); + if (bounds.contains(onScreen)) { + return bounds; + } + } + + return null; + + } + public final boolean isInsideScreenBounds(final int x, final int y, final int width) { return x >= myScreenBounds.x + 50 - width && diff --git a/platform/platform-impl/src/com/intellij/ui/popup/AbstractPopup.java b/platform/platform-impl/src/com/intellij/ui/popup/AbstractPopup.java index 846d59dde3..57bb88b57f 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/AbstractPopup.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/AbstractPopup.java @@ -40,6 +40,7 @@ import com.intellij.ui.*; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.speedSearch.SpeedSearch; import com.intellij.util.ImageLoader; +import com.intellij.util.Processor; import com.intellij.util.ui.ChildFocusWatcher; import com.intellij.util.ui.EmptyIcon; import com.intellij.util.ui.UIUtil; @@ -177,7 +178,8 @@ public class AbstractPopup implements JBPopup { @Nullable String adText, final boolean headerAlwaysFocusable, @NotNull List> keyboardActions, - Component settingsButtons) { + Component settingsButtons, + @Nullable final Processor pinCallback) { if (requestFocus && !focusable) { assert false : "Incorrect argument combination: requestFocus=" + requestFocus + " focusable=" + focusable; @@ -217,7 +219,16 @@ public class AbstractPopup implements JBPopup { else { myCaption = new CaptionPanel(); } - if (cancelButton != null) { + + if (pinCallback != null) { + myCaption.setButtonComponent(new InplaceButton(new IconButton("Pin", IconLoader.getIcon("/general/autohideOff.png"), + IconLoader.getIcon("/general/autohideOff.png"), + IconLoader.getIcon("/general/autohideOffInactive.png")), new ActionListener() { + public void actionPerformed(final ActionEvent e) { + pinCallback.process(AbstractPopup.this); + } + })); + } else if (cancelButton != null) { myCaption.setButtonComponent(new InplaceButton(cancelButton, new ActionListener() { public void actionPerformed(final ActionEvent e) { cancel(); diff --git a/platform/platform-impl/src/com/intellij/ui/popup/ComponentPopupBuilderImpl.java b/platform/platform-impl/src/com/intellij/ui/popup/ComponentPopupBuilderImpl.java index a65c331bf3..a7cac6f3b1 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/ComponentPopupBuilderImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/ComponentPopupBuilderImpl.java @@ -23,6 +23,7 @@ import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Pair; +import com.intellij.util.Processor; import com.intellij.util.ui.EmptyIcon; import com.intellij.ui.InplaceButton; import org.jetbrains.annotations.NotNull; @@ -60,6 +61,7 @@ public class ComponentPopupBuilderImpl implements ComponentPopupBuilder { private boolean myCancelKeyEnabled = true; private boolean myLocateByContent = false; private boolean myPlacewithinScreen = true; + private Processor myPinCallback = null; private Dimension myMinSize; private MaskProvider myMaskProvider; private float myAlpha; @@ -162,6 +164,12 @@ public class ComponentPopupBuilderImpl implements ComponentPopupBuilder { } @NotNull + public ComponentPopupBuilder setCouldPin(@Nullable final Processor callback) { + myPinCallback = callback; + return this; + } + + @NotNull public ComponentPopupBuilder setKeyboardActions(@NotNull List> keyboardActions) { myKeyboardActions = keyboardActions; return this; @@ -193,7 +201,7 @@ public class ComponentPopupBuilderImpl implements ComponentPopupBuilder { myCancelButton, myCancelOnMouseOutCallback, myCancelOnWindow, myTitleIcon, myCancelKeyEnabled, myLocateByContent, myPlacewithinScreen, myMinSize, myAlpha, myMaskProvider, myInStack, myModalContext, myFocusOwners, myAd, - myHeaderAlwaysFocusable, myKeyboardActions, mySettingsButtons); + myHeaderAlwaysFocusable, myKeyboardActions, mySettingsButtons, myPinCallback); if (myUserData != null) { popup.setUserData(myUserData); } diff --git a/platform/platform-impl/src/com/intellij/ui/popup/WizardPopup.java b/platform/platform-impl/src/com/intellij/ui/popup/WizardPopup.java index ea45e3b566..a699e43ae6 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/WizardPopup.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/WizardPopup.java @@ -88,7 +88,7 @@ public abstract class WizardPopup extends AbstractPopup implements ActionListene final Project project = PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext()); init(project, scrollPane, getPreferredFocusableComponent(), true, true, true, true, null, false, aStep.getTitle(), null, true, null, false, null, null, null, false, null, true, false, true, null, 0f, - null, true, false, new Component[0], null, true, Collections.>emptyList(), null); + null, true, false, new Component[0], null, true, Collections.>emptyList(), null, null); registerAction("disposeAll", KeyEvent.VK_ESCAPE, InputEvent.SHIFT_MASK, new AbstractAction() { public void actionPerformed(ActionEvent e) { diff --git a/platform/platform-resources-en/src/messages/UIBundle.properties b/platform/platform-resources-en/src/messages/UIBundle.properties index af1d0635c1..6fcceb24c5 100644 --- a/platform/platform-resources-en/src/messages/UIBundle.properties +++ b/platform/platform-resources-en/src/messages/UIBundle.properties @@ -158,4 +158,5 @@ row.move.up.without.mnemonic=Move Up row.move.down.without.mnemonic=Move Down move.up.action.name=Move Up move.down.action.name=Move Down -file.chooser.save.dialog.file.name=File name: \ No newline at end of file +file.chooser.save.dialog.file.name=File name: +tool.window.name.documentation=Documentation \ No newline at end of file -- 2.11.4.GIT