/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.netbeans.modules.java.navigation; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import javax.swing.Action; import javax.swing.JComponent; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyVetoException; import java.io.IOException; import java.net.URI; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JPanel; import javax.swing.JToolBar; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import javax.swing.text.JTextComponent; import javax.swing.tree.TreePath; import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.api.annotations.common.NonNull; import org.netbeans.api.annotations.common.NullAllowed; import org.netbeans.api.java.source.Task; import org.netbeans.api.java.source.CompilationController; import org.netbeans.api.java.source.ElementHandle; import org.netbeans.api.java.source.JavaSource; import org.netbeans.api.java.source.TreePathHandle; import org.netbeans.api.java.source.ui.ElementJavadoc; import org.netbeans.modules.java.navigation.ElementNode.Description; import org.netbeans.modules.java.navigation.actions.FilterSubmenuAction; import org.netbeans.modules.java.navigation.actions.SortActions; import org.netbeans.modules.java.navigation.base.FiltersManager; import org.netbeans.modules.java.navigation.base.HistorySupport; import org.netbeans.modules.java.navigation.base.Resolvers; import org.netbeans.modules.java.navigation.base.SelectJavadocTask; import org.openide.nodes.Node; import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.netbeans.modules.java.navigation.base.TapPanel; import org.netbeans.modules.java.navigation.base.Utils; import org.openide.awt.StatusDisplayer; import org.openide.explorer.ExplorerManager; import org.openide.explorer.view.BeanTreeView; import org.openide.explorer.view.Visualizer; import org.openide.filesystems.FileObject; import org.openide.filesystems.URLMapper; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.util.Exceptions; import org.openide.util.ImageUtilities; import org.openide.util.Lookup; import org.openide.util.Mutex; import org.openide.util.NbPreferences; import org.openide.util.Pair; import org.openide.util.RequestProcessor; import org.openide.util.lookup.AbstractLookup; import org.openide.util.lookup.InstanceContent; import org.openide.windows.TopComponent; /** * * @author phrebejk */ @SuppressWarnings("ClassWithMultipleLoggers") public class ClassMemberPanelUI extends javax.swing.JPanel implements ExplorerManager.Provider, Lookup.Provider, FiltersManager.FilterChangeListener, PropertyChangeListener { private static final String JDOC_ICON = "org/netbeans/modules/java/navigation/resources/javadoc_open.png"; //NOI18N private static final String CMD_JDOC = "jdoc"; //NOI18N private static final String CMD_HISTORY = "history"; //NOI18N private static final int MIN_HISTORY_WIDTH = 50; private static final int HISTORY_HEIGHT = 20; private static final ThreadLocal<Boolean> ignoreJavaDoc = new ThreadLocal<>(); private final ExplorerManager manager = new ExplorerManager(); private final MyBeanTreeView elementView; private final TapPanel filtersPanel; private final InstanceContent selectedNodes = new InstanceContent(); private final Lookup lookup = new AbstractLookup(selectedNodes); private final ClassMemberFilters filters; private final AtomicReference<State> state = new AtomicReference<State>(); private final Action[] actions; // General actions for the panel private final SelectJavadocTask jdocFinder; private final RequestProcessor.Task watcherTask = WATCHER_RP.create(new Runnable() { @Override public void run() { final State current = state.get(); if (current != State.DONE) { LOG.log( Level.WARNING, "No scheduled navigator update in {0}ms, current state: {1}", //NOI18N new Object[]{ WATCHER_TIME, state.get() }); } } }); private final RequestProcessor.Task jdocTask; private final HistorySupport history; private long lastShowWaitNodeTime = -1; //@GuardedBy this private Toolbar toolbar; private volatile boolean auto; private static final int JDOC_TIME = 500; private static final Logger LOG = Logger.getLogger(ClassMemberPanelUI.class.getName()); //NOI18N private static final Logger PERF_LOG = Logger.getLogger(ClassMemberPanelUI.class.getName() + ".perf"); //NOI18N private static final RequestProcessor RP = new RequestProcessor(ClassMemberPanelUI.class.getName(), 1); private static final RequestProcessor WATCHER_RP = new RequestProcessor(ClassMemberPanelUI.class.getName() + ".watcher", 1, false, false); //NOI18N private static final int WATCHER_TIME = 30000; private static final String INHERITED_COLOR_KEY = "nb.navigator.inherited.color"; //NOI18N private static final String TYPE_COLOR_KEY = "nb.navigator.type.color"; //NOI18N private static final Color DEFAULT_TYPE_COLOR = new Color(0x70,0x70,0x70); //NOI18N private static final Color DEFAULT_INHERITED_COLOR = new Color(0x7D,0x69, 0x4A); //NOI18N private final Color inheritedColor; private final Color typeColor; /** Creates new form ClassMemberPanelUi */ public ClassMemberPanelUI() { inheritedColor = UIManager.getColor(INHERITED_COLOR_KEY); typeColor = UIManager.getColor(TYPE_COLOR_KEY); history = HistorySupport.getInstnace(this.getClass()); jdocFinder = SelectJavadocTask.create(this); jdocTask = RP.create(jdocFinder); initComponents(); manager.addPropertyChangeListener(this); // Tree view of the elements elementView = createBeanTreeView(); add(elementView, BorderLayout.CENTER); // filters filtersPanel = new TapPanel(); filtersPanel.setOrientation(TapPanel.DOWN); // tooltip KeyStroke toggleKey = KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); String keyText = Utilities.keyToString(toggleKey); filtersPanel.setToolTipText(NbBundle.getMessage(ClassMemberPanelUI.class, "TIP_TapPanel", keyText)); //NOI18N filters = new ClassMemberFilters( this ); filters.getFiltersManager().hookChangeListener(this); JComponent buttons = filters.getComponent(); buttons.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 0)); filtersPanel.add(buttons); Utils.updateBackground(filtersPanel); actions = new Action[] { SortActions.createSortByNameAction(filters), SortActions.createSortBySourceAction(filters), null, new FilterSubmenuAction(filters.getFiltersManager()) }; add(filtersPanel, BorderLayout.SOUTH); boolean expanded = NbPreferences.forModule(ClassMemberPanelUI.class).getBoolean("filtersPanelTap.expanded", true); //NOI18N filtersPanel.setExpanded(expanded); filtersPanel.addPropertyChangeListener(this); } @Override public boolean requestFocusInWindow() { boolean result = super.requestFocusInWindow(); elementView.requestFocusInWindow(); return result; } @Override public void requestFocus() { super.requestFocus(); elementView.requestFocus(); } @Override public org.openide.util.Lookup getLookup() { // XXX Check for chenge of FileObject return lookup; } public org.netbeans.modules.java.navigation.ElementScanningTask getTask() { return new ElementScanningTask(this); } @NonNull String getInheritedColor() { return getHtmlColor( inheritedColor == null ? DEFAULT_INHERITED_COLOR : inheritedColor); } @NonNull String getTypeColor() { return getHtmlColor( typeColor == null ? DEFAULT_TYPE_COLOR : typeColor); } @NonNull private static String getHtmlColor(@NonNull final Color c) { final int r = c.getRed(); final int g = c.getGreen(); final int b = c.getBlue(); final StringBuilder result = new StringBuilder(); result.append ("#"); //NOI18N final String rs = Integer.toHexString (r); final String gs = Integer.toHexString (g); final String bs = Integer.toHexString (b); if (r < 0x10) result.append('0'); result.append(rs); if (g < 0x10) result.append ('0'); result.append(gs); if (b < 0x10) result.append ('0'); result.append(bs); return result.toString(); } void showWaitNode() { Mutex.EVENT.readAccess(new Runnable() { @Override public void run() { elementView.setRootVisible(true); manager.setRootContext(ElementNode.getWaitNode()); lastShowWaitNodeTime = System.currentTimeMillis(); scheduled(); } }); } void clearNodes(final boolean resetAutoRefresh) { ClassMemberPanel.compareAndSetLastUsedFile(null); if (resetAutoRefresh) { auto = false; } Mutex.EVENT.readAccess(new Runnable() { @Override public void run() { elementView.setRootVisible(false); manager.setRootContext(new AbstractNode(Children.LEAF)); } }); } private void scheduled() { state.set(State.SCHEDULED); boolean ae = false; assert ae = true; if (ae) { watcherTask.schedule(WATCHER_TIME); } } void start() { state.set(State.INVOKED); } private void done() { state.set(State.DONE); boolean ae = false; assert ae = true; if (ae) { watcherTask.cancel(); } } public void selectNode(final Pair<ElementHandle<Element>,TreePathHandle> pattern ) { final ElementNode root = getRootNode(); if ( root == null ) { return; } final ElementNode node = root.stream() .filter((n)-> { final Description d = n.getDescritption(); boolean match = true; if (pattern.first() != null) { match &= pattern.first().equals(d.getElementHandle()); } if (pattern.second() != null) { match &= pattern.second().equals(d.getTreePathHandle()); } return match; }) .findFirst() .orElse(null); ignoreJavaDoc.set(true); try { manager.setSelectedNodes(new Node[]{ node == null ? root : node }); } catch (PropertyVetoException propertyVetoException) { Exceptions.printStackTrace(propertyVetoException); } finally { ignoreJavaDoc.remove(); } } public void setContext( @NonNull final JavaSource js, @NullAllowed JTextComponent target) { final Callable<Pair<URI,ElementHandle<TypeElement>>> resolver = target == null ? Resolvers.createFileResolver(js) : Resolvers.createEditorResolver( js, target.getCaret().getDot()); schedule(resolver); } synchronized JComponent getToolbar() { if (toolbar == null) { toolbar = new Toolbar(); } return toolbar; } void refresh() { RP.execute(new Runnable() { @Override public void run() { ElementNode rootNode = getRootNode(); if (rootNode != null) { final FileObject fo = rootNode.getDescritption().fileObject; if (fo != null) { final JavaSource js = JavaSource.forFileObject(fo); if (js != null) { try { js.runUserActionTask(new Task<CompilationController>() { @Override public void run(CompilationController parameter) throws Exception { parameter.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED); getTask().runImpl(parameter, true); } }, true); } catch (IOException ex) { Exceptions.printStackTrace(ex); } } } } } }); } void refresh( @NonNull final Description description, final boolean userAction) { auto = !userAction; final ElementNode rootNode = getRootNode(); if ( rootNode != null && rootNode.getDescritption().fileObject.equals( description.fileObject) ) { // update //System.out.println("UPDATE ======" + description.fileObject.getName() ); jdocTask.cancel(); jdocFinder.cancel(); RP.post(new Runnable() { public void run() { rootNode.updateRecursively( description ); if (!userAction) { toolbar.setAuto(); } done(); } } ); } else { Mutex.EVENT.readAccess(new Runnable() { @Override public void run() { elementView.setRootVisible(false); manager.setRootContext(new ElementNode( description ) ); if (!userAction) { toolbar.setAuto(); } done(); boolean scrollOnExpand = getScrollOnExpand(); setScrollOnExpand( false ); elementView.setAutoWaitCursor(false); elementView.expandAll(); elementView.setAutoWaitCursor(true); setScrollOnExpand( scrollOnExpand ); if (PERF_LOG.isLoggable(Level.FINE)) { final long tm2 = System.currentTimeMillis(); final long tm1 = lastShowWaitNodeTime; if (tm1 != -1) { lastShowWaitNodeTime = -1; PERF_LOG.log(Level.FINE, String.format("ClassMemberPanelUI refresh took: %d ms", (tm2 - tm1)), new Object[] { description.getFileObject().getName(), (tm2 - tm1) }); } } } } ); } } boolean isAutomaticRefresh() { return auto; } public void sort() { ElementNode root = getRootNode(); if( null != root ) root.refreshRecursively(); } public ClassMemberFilters getFilters() { return filters; } public void expandNode( Node n ) { elementView.expandNode(n); } public Action[] getActions() { return actions; } public FileObject getFileObject() { final ElementNode root = getRootNode(); if (root != null) { return root.getDescritption().fileObject; } else { return null; } } // FilterChangeListener ---------------------------------------------------- public void filterStateChanged(ChangeEvent e) { ElementNode root = getRootNode(); if ( root != null ) { root.refreshRecursively(); } } boolean getScrollOnExpand() { return null == elementView ? true : elementView.getScrollOnExpand(); } void setScrollOnExpand( boolean scroll ) { if( null != elementView ) elementView.setScrollOnExpand( scroll ); } /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { setLayout(new java.awt.BorderLayout()); }// </editor-fold>//GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables // End of variables declaration//GEN-END:variables // Private methods --------------------------------------------------------- private ElementNode getRootNode() { Node n = manager.getRootContext(); if ( n instanceof ElementNode ) { return (ElementNode)n; } else { return null; } } private MyBeanTreeView createBeanTreeView() { return new MyBeanTreeView(); } private void scheduleJavadocRefresh(final int time) { jdocFinder.cancel(); jdocTask.schedule(time); } private void schedule(@NonNull final Callable<Pair<URI, ElementHandle<TypeElement>>> resolver) { showWaitNode(); final Future<Pair<URI, ElementHandle<TypeElement>>> becomesHandle = RP.submit(resolver); final RefreshTask refresh = new RefreshTask(becomesHandle); RP.execute(refresh); } // ExplorerManager.Provider imlementation ---------------------------------- public ExplorerManager getExplorerManager() { return manager; } private ElementJavadoc getJavaDocFor( @NonNull final ElementNode node, @NullAllowed final Callable<Boolean> cancel) { ElementNode root = getRootNode(); if ( root == null ) { return null; } final ElementHandle<? extends Element> eh = node.getDescritption().getElementHandle(); if (eh == null) { return null; } final JavaSource js = JavaSource.forFileObject( root.getDescritption().fileObject ); if (js == null) { return null; } final JavaDocCalculator calculator = new JavaDocCalculator(eh, cancel); try { js.runUserActionTask( calculator, true ); } catch( IOException ioE ) { Exceptions.printStackTrace( ioE ); return null; } return calculator.doc; } private static class JavaDocCalculator implements Task<CompilationController> { private final ElementHandle<? extends Element> handle; private final Callable<Boolean> cancel; private ElementJavadoc doc; public JavaDocCalculator( @NonNull final ElementHandle<? extends Element> handle, @NullAllowed final Callable<Boolean> cancel) { this.handle = handle; this.cancel = cancel; } @Override public void run(CompilationController cc) throws Exception { cc.toPhase( JavaSource.Phase.UP_TO_DATE ); Element e = handle.resolve( cc ); doc = ElementJavadoc.create(cc, e, cancel ); } }; private class MyBeanTreeView extends BeanTreeView implements ToolTipManagerEx.ToolTipProvider { private final ToolTipManagerEx toolTipManager; public MyBeanTreeView() { toolTipManager = new ToolTipManagerEx( this ); } public boolean getScrollOnExpand() { return tree.getScrollsOnExpand(); } public void setScrollOnExpand( boolean scroll ) { this.tree.setScrollsOnExpand( scroll ); } @Override public JComponent getComponent() { return tree; } @Override public String getToolTipText(@NonNull Node node) { ElementJavadoc doc = getDocumentation(node); return null == doc ? null : doc.getText(); } @CheckForNull @Override public Node findNode(@NonNull final Point loc) { final TreePath path = tree.getPathForLocation( loc.x, loc.y ); if( null == path ) { return null; } final Node node = Visualizer.findNode( path.getLastPathComponent()); if (!(node instanceof ElementNode)) { return null; } final ElementNode enode = (ElementNode) node; final ElementNode.Description desc = enode.getDescritption(); //Other and module do not have javadoc return desc.kind != ElementKind.OTHER && desc.kind != ElementKind.MODULE ? node : null; } @CheckForNull private ElementJavadoc getDocumentation(@NullAllowed final Node node) { if( node instanceof ElementNode ) { return getJavaDocFor((ElementNode)node, toolTipManager); } return null; } @Override public Rectangle getToolTipSourceBounds(Point loc) { ElementNode root = getRootNode(); if ( root == null ) { return null; } TreePath path = tree.getPathForLocation( loc.x, loc.y ); return null == path ? null : tree.getPathBounds( path ); } @Override public Point getToolTipLocation( Point mouseLocation, Dimension tipSize ) { Point screenLocation = getLocationOnScreen(); Rectangle sBounds = getGraphicsConfiguration().getBounds(); Dimension compSize = getSize(); Point res = new Point(); Rectangle tooltipSrcRect = getToolTipSourceBounds( mouseLocation ); //May be null, prevent the NPE, nothing will be shown anyway. if (tooltipSrcRect == null) { tooltipSrcRect = new Rectangle(); } Point viewPosition = getViewport().getViewPosition(); screenLocation.x -= viewPosition.x; screenLocation.y -= viewPosition.y; //first try bottom right res.x = screenLocation.x + compSize.width; res.y = screenLocation.y + tooltipSrcRect.y+tooltipSrcRect.height; if( res.x + tipSize.width <= sBounds.x+sBounds.width && res.y + tipSize.height <= sBounds.y+sBounds.height ) { return res; } //upper right res.x = screenLocation.x + compSize.width; res.y = screenLocation.y + tooltipSrcRect.y - tipSize.height; if( res.x + tipSize.width <= sBounds.x+sBounds.width && res.y >= sBounds.y ) { return res; } //lower left res.x = screenLocation.x - tipSize.width; res.y = screenLocation.y + tooltipSrcRect.y; if( res.x >= sBounds.x && res.y + tipSize.height <= sBounds.y+sBounds.height ) { return res; } //upper left res.x = screenLocation.x - tipSize.width; res.y = screenLocation.y + tooltipSrcRect.y + tooltipSrcRect.height - tipSize.height; if( res.x >= sBounds.x && res.y >= sBounds.y ) { return res; } //give up (who's got such a small display anyway?) res.x = screenLocation.x + tooltipSrcRect.x; if( sBounds.y + sBounds.height - (screenLocation.y + tooltipSrcRect.y + tooltipSrcRect.height) > screenLocation.y + tooltipSrcRect.y - sBounds.y ) { res.y = screenLocation.y + tooltipSrcRect.y + tooltipSrcRect.height; } else { res.y = screenLocation.y + tooltipSrcRect.y - tipSize.height; } return res; } @Override public void invokeUserAction(final MouseEvent me) { Mutex.EVENT.readAccess( new Runnable() { @Override public void run() { if( null != me ) { final Node node = findNode(me.getPoint()); if (node != null) { final ElementJavadoc doc = getDocumentation(node); final ElementNode root = getRootNode(); final FileObject owner = root == null ? null : root.getDescritption().fileObject; JavadocTopComponent tc = JavadocTopComponent.findInstance(); if( null != tc ) { tc.open(); tc.setJavadoc(owner, doc); tc.requestActive(); } } } } }); } //#123940 start private boolean inHierarchy; private boolean doExpandAll; @Override public void addNotify() { super.addNotify(); inHierarchy = true; if (doExpandAll) { super.expandAll(); doExpandAll = false; } } @Override public void removeNotify() { super.removeNotify(); inHierarchy = false; this.toolTipManager.hideTipWindow(); } @Override public void expandAll() { super.expandAll(); if (!inHierarchy) { doExpandAll = true; } } //#123940 end } @Override public void propertyChange(final PropertyChangeEvent evt) { if (ExplorerManager.PROP_SELECTED_NODES.equals(evt.getPropertyName())) { final boolean javadocDone = ignoreJavaDoc.get() == Boolean.TRUE; RP.execute(new Runnable() { @Override public void run() { final Node[] oldNodes = (Node[]) evt.getOldValue(); final Node[] newNodes = (Node[]) evt.getNewValue(); for (Node n : oldNodes) { selectedNodes.remove(n); } for (Node n : newNodes) { selectedNodes.add(n); } if (newNodes.length > 0 && !javadocDone && JavadocTopComponent.shouldUpdate()) { scheduleJavadocRefresh(JDOC_TIME); } } }); } else if (TapPanel.EXPANDED_PROPERTY.equals(evt.getPropertyName())) { NbPreferences.forModule(ClassMemberPanelUI.class) .putBoolean("filtersPanelTap.expanded", filtersPanel.isExpanded()); } } private enum State { SCHEDULED, INVOKED, DONE } private class RefreshTask implements Runnable, Task<CompilationController> { private final Future<Pair<URI,ElementHandle<TypeElement>>> becomesHandle; RefreshTask(@NonNull final Future<Pair<URI,ElementHandle<TypeElement>>> becomesHandle) { assert becomesHandle != null; this.becomesHandle = becomesHandle; } @Override @NbBundle.Messages({ "ERR_Cannot_Resolve_File=Cannot resolve type: {0}.", "ERR_Not_Declared_Type=Not a declared type."}) public void run() { try { final Pair<URI,ElementHandle<TypeElement>> handlePair = becomesHandle.get(); if (handlePair != null) { final FileObject target = URLMapper.findFileObject(handlePair.first().toURL()); if (target != null) { final JavaSource targetJs = JavaSource.forFileObject(target); if (targetJs != null) { history.addToHistory(handlePair); targetJs.runUserActionTask(this, true); ((Toolbar)getToolbar()).select(handlePair); } else { clearNodes(true); StatusDisplayer.getDefault().setStatusText(Bundle.ERR_Cannot_Resolve_File( handlePair.second().getQualifiedName())); } } else { clearNodes(true); StatusDisplayer.getDefault().setStatusText(Bundle.ERR_Cannot_Resolve_File( handlePair.second().getQualifiedName())); } } else { clearNodes(true); StatusDisplayer.getDefault().setStatusText(Bundle.ERR_Not_Declared_Type()); } } catch (InterruptedException ex) { Exceptions.printStackTrace(ex); } catch (ExecutionException ex) { Exceptions.printStackTrace(ex); } catch (IOException ex) { Exceptions.printStackTrace(ex); } } @Override public void run(@NonNull final CompilationController cc) throws Exception { cc.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED); getTask().runImpl(cc, true); } } private class Toolbar extends JPanel implements ActionListener, ListDataListener { private final JComboBox historyCombo; private boolean ignoreEvents; @NbBundle.Messages({ "TXT_InspectMembersHistoryEmpty=<empty>", "TXT_InspectMembersHistoryAuto=<auto>", "TOOLTIP_OpenJDoc=Open Javadoc Window", "TOOLTIP_InspectMembersHistory=Inspect Members History" }) Toolbar() { setLayout(new GridBagLayout()); setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5)); final JToolBar toolbar = new NoBorderToolBar(JToolBar.HORIZONTAL); toolbar.setFloatable(false); toolbar.setRollover(true); toolbar.setBorderPainted(false); toolbar.setBorder(BorderFactory.createEmptyBorder()); toolbar.setOpaque(false); toolbar.setFocusable(false); historyCombo = new JComboBox(HistorySupport.createModel(history, Bundle.TXT_InspectMembersHistoryEmpty())); historyCombo.setMinimumSize(new Dimension(MIN_HISTORY_WIDTH,HISTORY_HEIGHT)); historyCombo.setRenderer(HistorySupport.createRenderer(history)); historyCombo.setActionCommand(CMD_HISTORY); historyCombo.addActionListener(this); historyCombo.getModel().addListDataListener(this); historyCombo.setEnabled(false); historyCombo.setToolTipText(Bundle.TOOLTIP_InspectMembersHistory()); final JButton jdocButton = new JButton(ImageUtilities.loadImageIcon(JDOC_ICON, true)); jdocButton.setActionCommand(CMD_JDOC); jdocButton.addActionListener(this); jdocButton.setFocusable(false); jdocButton.setToolTipText(Bundle.TOOLTIP_OpenJDoc()); toolbar.setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.gridx = 0; c.gridy = 0; c.gridwidth = 1; c.gridheight = 1; c.insets = new Insets(0, 0, 0, 0); c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.BOTH; c.weightx = 1; c.weighty = 0; toolbar.add(historyCombo, c); c = new GridBagConstraints(); c.gridx = GridBagConstraints.RELATIVE; c.gridy = 0; c.gridwidth = GridBagConstraints.REMAINDER; c.gridheight = 1; c.insets = new Insets(0, 0, 0, 0); c.anchor = GridBagConstraints.EAST; c.fill = GridBagConstraints.NONE; c.weightx = 0; c.weighty = 0; toolbar.add(jdocButton, c); c = new GridBagConstraints(); c.gridx = 0; c.gridy = 0; c.gridwidth = 1; c.gridheight = 1; c.insets = new Insets(0, 0, 0, 0); c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1; c.weighty = 0; add(toolbar,c); Utils.updateBackground(this); } @Override public void actionPerformed(ActionEvent e) { assert SwingUtilities.isEventDispatchThread(); if (CMD_JDOC.equals(e.getActionCommand())) { final TopComponent win = JavadocTopComponent.findInstance(); if (win != null && !win.isShowing()) { win.open(); win.requestVisible(); scheduleJavadocRefresh(0); } } else if (!ignoreEvents && CMD_HISTORY.equals(e.getActionCommand())) { final Object selItem = historyCombo.getSelectedItem(); if (selItem instanceof Pair) { final Pair<URI,ElementHandle<TypeElement>> pair = (Pair<URI,ElementHandle<TypeElement>>)selItem; schedule(new Callable<Pair<URI, ElementHandle<TypeElement>>>() { @Override public Pair<URI, ElementHandle<TypeElement>> call() throws Exception { return pair; } }); } } } @Override public void intervalAdded(ListDataEvent e) { showHistory(); } @Override public void intervalRemoved(ListDataEvent e) { showHistory(); } @Override public void contentsChanged(ListDataEvent e) { showHistory(); } void select(@NonNull final Pair<URI,ElementHandle<TypeElement>> pair) { Mutex.EVENT.readAccess(new Runnable() { @Override public void run() { ignoreEvents = true; try { historyCombo.getModel().setSelectedItem(pair); } finally { ignoreEvents = false; } } }); } void setAuto() { Mutex.EVENT.readAccess(new Runnable() { @Override public void run() { if (historyCombo.isEnabled()) { historyCombo.getModel().setSelectedItem(Bundle.TXT_InspectMembersHistoryAuto()); } } }); } private void showHistory() { if (!history.getHistory().isEmpty()) { historyCombo.setEnabled(true); } } } }