/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. * * Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun * Microsystems, Inc. All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ package com.tvl.modules.editor.completion; import com.tvl.spi.editor.completion.CompletionDocumentation; import java.awt.Color; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JEditorPane; import javax.swing.JScrollPane; import javax.swing.JToolBar; import javax.swing.KeyStroke; import javax.swing.UIManager; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import javax.swing.plaf.TextUI; import javax.swing.text.Document; import javax.swing.text.EditorKit; import javax.swing.text.JTextComponent; import javax.swing.text.Keymap; import javax.swing.text.html.HTMLDocument; import org.netbeans.api.annotations.common.StaticResource; import org.netbeans.editor.BaseKit; import org.netbeans.editor.ext.ExtKit; import org.openide.awt.HtmlBrowser; import org.openide.awt.StatusDisplayer; import org.openide.util.ImageUtilities; import org.openide.util.NbBundle; import org.openide.util.RequestProcessor; /** * * @author Martin Roskanin, Dusan Balek */ @NbBundle.Messages({ "HINT_doc_browser_back_button=Go to previous page", "HINT_doc_browser_forward_button=Go to next page", "HINT_doc_browser_show_web_button=Show documentation in external web browser", "HINT_doc_browser_goto_source_button=Open source in editor", }) public class DocumentationScrollPane extends JScrollPane { @StaticResource private static final String BACK = "com/tvl/modules/editor/completion/resources/back.png"; //NOI18N @StaticResource private static final String FORWARD = "com/tvl/modules/editor/completion/resources/forward.png"; //NOI18N @StaticResource private static final String GOTO_SOURCE = "com/tvl/modules/editor/completion/resources/open_source_in_editor.png"; //NOI18N @StaticResource private static final String SHOW_WEB = "com/tvl/modules/editor/completion/resources/open_in_external_browser.png"; //NOI18N private static final String JAVADOC_ESCAPE = "javadoc-escape"; //NOI18N private static final String JAVADOC_BACK = "javadoc-back"; //NOI18N private static final String JAVADOC_FORWARD = "javadoc-forward"; //NOI18N private static final String JAVADOC_OPEN_IN_BROWSER = "javadoc-open-in-browser"; //NOI18N private static final String JAVADOC_OPEN_SOURCE = "javadoc-open-source"; //NOI18N private static final String COPY_TO_CLIPBOARD = "copy-to-clipboard"; private static final int ACTION_JAVADOC_ESCAPE = 0; private static final int ACTION_JAVADOC_BACK = 1; private static final int ACTION_JAVADOC_FORWARD = 2; private static final int ACTION_JAVADOC_OPEN_IN_BROWSER = 3; private static final int ACTION_JAVADOC_OPEN_SOURCE = 4; private static final int ACTION_JAVADOC_COPY = 5; private static final RequestProcessor RP = new RequestProcessor(DocumentationScrollPane.class); private JButton bBack, bForward, bGoToSource, bShowWeb; private HTMLDocView view; // doc browser history private List<CompletionDocumentation> history = new ArrayList<>(5); private int currentHistoryIndex = -1; protected CompletionDocumentation currentDocumentation = null; private Dimension documentationPreferredSize; /** Creates a new instance of ScrollJavaDocPane */ @SuppressWarnings("OverridableMethodCallInConstructor") public DocumentationScrollPane(JTextComponent editorComponent) { super(); // Determine and use fixed preferred size documentationPreferredSize = CompletionSettings.getInstance(editorComponent).documentationPopupPreferredSize(); setPreferredSize(null); // Use the documentationPopupPreferredSize Color bgColor = new JEditorPane().getBackground(); bgColor = new Color( Math.max(bgColor.getRed() - 8, 0 ), Math.max(bgColor.getGreen() - 8, 0 ), bgColor.getBlue()); // Add the completion doc view view = new HTMLDocView(bgColor); view.addHyperlinkListener(new HyperlinkAction()); setViewportView(view); installTitleComponent(); installKeybindings(editorComponent); setFocusable(true); } public @Override void setPreferredSize(Dimension preferredSize) { if (preferredSize == null) { preferredSize = documentationPreferredSize; } super.setPreferredSize(preferredSize); } public void setData(CompletionDocumentation doc) { setDocumentation(doc); if (doc != null) { addToHistory(doc); } } private ImageIcon resolveIcon(String res){ return ImageUtilities.loadImageIcon(res, false); } private void installTitleComponent() { JToolBar toolbar = new JToolBar(); toolbar.setFloatable(false); toolbar.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, UIManager.getColor("controlDkShadow"))); //NOI18N toolbar.setLayout(new GridBagLayout()); GridBagConstraints gdc = new GridBagConstraints(); gdc.gridx = 0; gdc.gridy = 0; gdc.anchor = GridBagConstraints.WEST; ImageIcon icon = resolveIcon(BACK); if (icon != null) { bBack = new BrowserButton(icon); bBack.addMouseListener(new MouseEventListener(bBack)); bBack.setEnabled(false); bBack.setFocusable(false); bBack.setContentAreaFilled(false); bBack.setMargin(new Insets(0, 0, 0, 0)); bBack.setToolTipText(Bundle.HINT_doc_browser_back_button()); //NOI18N toolbar.add(bBack, gdc); } gdc.gridx = 1; gdc.gridy = 0; gdc.anchor = GridBagConstraints.WEST; icon = resolveIcon(FORWARD); if (icon != null) { bForward = new BrowserButton(icon); bForward.addMouseListener(new MouseEventListener(bForward)); bForward.setEnabled(false); bForward.setFocusable(false); bForward.setContentAreaFilled(false); bForward.setToolTipText(Bundle.HINT_doc_browser_forward_button()); //NOI18N bForward.setMargin(new Insets(0, 0, 0, 0)); toolbar.add(bForward, gdc); } gdc.gridx = 2; gdc.gridy = 0; gdc.anchor = GridBagConstraints.WEST; icon = resolveIcon(SHOW_WEB); if (icon != null) { bShowWeb = new BrowserButton(icon); bShowWeb.addMouseListener(new MouseEventListener(bShowWeb)); bShowWeb.setEnabled(false); bShowWeb.setFocusable(false); bShowWeb.setContentAreaFilled(false); bShowWeb.setMargin(new Insets(0, 0, 0, 0)); bShowWeb.setToolTipText(Bundle.HINT_doc_browser_show_web_button()); //NOI18N toolbar.add(bShowWeb, gdc); } gdc.gridx = 3; gdc.gridy = 0; gdc.weightx = 1.0; gdc.anchor = GridBagConstraints.WEST; icon = resolveIcon(GOTO_SOURCE); if (icon != null) { bGoToSource = new BrowserButton(icon); bGoToSource.addMouseListener(new MouseEventListener(bGoToSource)); bGoToSource.setEnabled(false); bGoToSource.setFocusable(false); bGoToSource.setContentAreaFilled(false); bGoToSource.setMargin(new Insets(0, 0, 0, 0)); bGoToSource.setToolTipText(Bundle.HINT_doc_browser_goto_source_button()); //NOI18N toolbar.add(bGoToSource, gdc); } setColumnHeaderView(toolbar); } private synchronized void setDocumentation(CompletionDocumentation doc) { currentDocumentation = doc; if (currentDocumentation != null) { String text = currentDocumentation.getText(); URL url = currentDocumentation.getURL(); if (text != null){ Document document = view.getDocument(); document.putProperty(Document.StreamDescriptionProperty, null); if (url!=null){ // fix of issue #58658 if (document instanceof HTMLDocument){ ((HTMLDocument)document).setBase(url); } } view.setContent(text, null); } else if (url != null){ try{ view.setContent("", null); //NOI18N view.getDocument().putProperty("javax.swing.JEditorPane.postdata", ""); //NOI18N view.setPage(url); }catch(IOException ioe){ StatusDisplayer.getDefault().setStatusText(ioe.toString()); } } bShowWeb.setEnabled(url != null); bGoToSource.setEnabled(currentDocumentation.getGotoSourceAction() != null); } } private synchronized void addToHistory(CompletionDocumentation doc) { int histSize = history.size(); for (int i = currentHistoryIndex + 1; i < histSize; i++){ history.remove(history.size() - 1); } history.add(doc); currentHistoryIndex = history.size() - 1; if (currentHistoryIndex > 0) bBack.setEnabled(true); bForward.setEnabled(false); } private synchronized void backHistory() { if (currentHistoryIndex > 0) { currentHistoryIndex--; setDocumentation(history.get(currentHistoryIndex)); if (currentHistoryIndex == 0) bBack.setEnabled(false); bForward.setEnabled(true); } } private synchronized void forwardHistory(){ if (currentHistoryIndex <history.size()-1){ currentHistoryIndex++; setDocumentation(history.get(currentHistoryIndex)); if (currentHistoryIndex == history.size() - 1) bForward.setEnabled(false); bBack.setEnabled(true); } } synchronized void clearHistory(){ currentHistoryIndex = -1; history.clear(); bBack.setEnabled(false); bForward.setEnabled(false); } private void openInExternalBrowser(){ CompletionDocumentation cd = currentDocumentation; if (cd != null) { URL url = cd.getURL(); if (url != null) HtmlBrowser.URLDisplayer.getDefault().showURL(url); } } private void goToSource() { CompletionDocumentation cd = currentDocumentation; if (cd != null) { Action action = cd.getGotoSourceAction(); if (action != null) action.actionPerformed(new ActionEvent(cd, 0, null)); } } private void copy() { view.copy(); } /** Attempt to find the editor keystroke for the given editor action. */ private KeyStroke[] findEditorKeys(String editorActionName, KeyStroke defaultKey, JTextComponent component) { // This method is implemented due to the issue // #25715 - Attempt to search keymap for the keybinding that logically corresponds to the action KeyStroke[] ret = new KeyStroke[] { defaultKey }; if (component != null) { TextUI componentUI = component.getUI(); Keymap km = component.getKeymap(); if (componentUI != null && km != null) { EditorKit kit = componentUI.getEditorKit(component); if (kit instanceof BaseKit) { Action a = ((BaseKit)kit).getActionByName(editorActionName); if (a != null) { KeyStroke[] keys = km.getKeyStrokesForAction(a); if (keys != null && keys.length > 0) { ret = keys; } } } } } return ret; } private void registerKeybinding(int action, String actionName, KeyStroke stroke, String editorActionName, JTextComponent component){ KeyStroke[] keys = findEditorKeys(editorActionName, stroke, component); for (int i = 0; i < keys.length; i++) { getInputMap().put(keys[i], actionName); } getActionMap().put(actionName, new DocPaneAction(action)); } @SuppressWarnings("deprecation") private void installKeybindings(JTextComponent component) { // Register Escape key registerKeybinding(ACTION_JAVADOC_ESCAPE, JAVADOC_ESCAPE, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), ExtKit.escapeAction, component); // Register javadoc back key registerKeybinding(ACTION_JAVADOC_BACK, JAVADOC_BACK, KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.ALT_MASK), null, component); // Register javadoc forward key registerKeybinding(ACTION_JAVADOC_FORWARD, JAVADOC_FORWARD, KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.ALT_MASK), null, component); // Register open in external browser key registerKeybinding(ACTION_JAVADOC_OPEN_IN_BROWSER, JAVADOC_OPEN_IN_BROWSER, KeyStroke.getKeyStroke(KeyEvent.VK_F1, KeyEvent.ALT_MASK | KeyEvent.SHIFT_MASK), null, component); // Register open the source in editor key registerKeybinding(ACTION_JAVADOC_OPEN_SOURCE, JAVADOC_OPEN_SOURCE, KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.ALT_MASK | KeyEvent.CTRL_MASK), null, component); //register copy action registerKeybinding(ACTION_JAVADOC_COPY, COPY_TO_CLIPBOARD, KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_MASK), COPY_TO_CLIPBOARD, component); // Register movement keystrokes to be reachable through Ctrl+<orig-keystroke> mapWithCtrl(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0)); mapWithCtrl(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)); mapWithCtrl(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0)); mapWithCtrl(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0)); mapWithCtrl(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0)); mapWithCtrl(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)); } private void mapWithCtrl(KeyStroke key) { InputMap inputMap = getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); Object actionKey = inputMap.get(key); if (actionKey != null) { key = KeyStroke.getKeyStroke(key.getKeyCode(), key.getModifiers() | InputEvent.CTRL_MASK); getInputMap().put(key, actionKey); } } private class BrowserButton extends JButton { public BrowserButton() { setBorderPainted(false); setFocusPainted(false); } public BrowserButton(String text){ super(text); setBorderPainted(false); setFocusPainted(false); } public BrowserButton(Icon icon){ super(icon); setBorderPainted(false); setFocusPainted(false); } public @Override void setEnabled(boolean b) { super.setEnabled(b); } } private class MouseEventListener extends MouseAdapter { private final JButton button; MouseEventListener(JButton button) { this.button = button; } public @Override void mouseEntered(MouseEvent ev) { if (button.isEnabled()){ button.setContentAreaFilled(true); button.setBorderPainted(true); } } public @Override void mouseExited(MouseEvent ev) { button.setContentAreaFilled(false); button.setBorderPainted(false); } public @Override void mouseClicked(MouseEvent evt) { if (button.equals(bBack)){ backHistory(); }else if(button.equals(bForward)){ forwardHistory(); }else if(button.equals(bGoToSource)){ goToSource(); }else if (button.equals(bShowWeb)){ openInExternalBrowser(); } } } private class HyperlinkAction implements HyperlinkListener { @Override public void hyperlinkUpdate(HyperlinkEvent e) { if (e != null && HyperlinkEvent.EventType.ACTIVATED.equals(e.getEventType())) { final String desc = e.getDescription(); if (desc != null) { RP.post(new Runnable() { public @Override void run() { CompletionDocumentation cd = currentDocumentation; if (cd != null) { final CompletionDocumentation doc = cd.resolveLink(desc); if (doc != null) { EventQueue.invokeLater(new Runnable() { public @Override void run() { setData(doc); } }); } } } }); } } } } private class DocPaneAction extends AbstractAction { private final int action; private DocPaneAction(int action) { this.action = action; } @Override public void actionPerformed(ActionEvent actionEvent) { switch (action) { case ACTION_JAVADOC_ESCAPE: CompletionImpl.get().hideDocumentation(false); break; case ACTION_JAVADOC_BACK: backHistory(); break; case ACTION_JAVADOC_FORWARD: forwardHistory(); break; case ACTION_JAVADOC_OPEN_IN_BROWSER: openInExternalBrowser(); break; case ACTION_JAVADOC_OPEN_SOURCE: goToSource(); break; case ACTION_JAVADOC_COPY: copy(); break; } } } }