|
What this is
Other links
The source code/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun * Microsystems, Inc. All Rights Reserved. */ package org.netbeans.swing.tabcontrol.plaf; import org.netbeans.swing.tabcontrol.TabData; import org.netbeans.swing.tabcontrol.TabbedContainer; import org.netbeans.swing.tabcontrol.TabDisplayer; import org.openide.awt.HtmlRenderer; import javax.swing.*; import javax.swing.border.Border; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.lang.reflect.Field; import java.util.EventObject; import java.util.List; /** * A panel that displays a drop down list of items, in several columns if * needed, associated with a JTabbedPane * * @author Tim Boudreau */ final class TabListPopup extends JTable implements MouseMotionListener, MouseListener { /** Reference to the focus owner when addNotify was called. This is the * component that received the mouse event, so it's what we need to listen * on to update the selected cell as the user drags the mouse */ private Component invokingComponent = null; /** Cached preferred size value */ private Dimension prefSize = null; /** Flag indicating that the fixed row height has not yet been calculated - * this is for fontsize support */ boolean needCalcRowHeight = true; /** Reference to container for which we display quicklist */ private TabDisplayer displayer = null; /** Reference to the popup object currently showing the default instance, * if it is visible */ private static Popup currentPopup=null; /** AWTEventListener which is attached when the popup is shown to ensure * that it is closed when it should be. It is removed after the popup * has been hidden. */ private static AWTEventListener blistener = null; /** Reference to the default shared instance */ private static Reference instance=null; /** Time of invocation, used to determine if a mouse release is * delayed long enough from a mouse press that it should close * the popup, instead of assuming the user wants move-and-click * behavior instead of drag-and-click behavior */ long invocationTime = -1; private static final Border rendererBorder = BorderFactory.createEmptyBorder (2, 3, 0, 3); private static HtmlRenderer.Renderer renderer = null; /** Creates a new instance of TabListPanel */ private TabListPopup() { super (new TabListPopupTableModel()); //Set up a line border around the edges setBorder ( BorderFactory.createLineBorder(getForeground())); setShowHorizontalLines(false); setBackground (UIManager.getColor("ComboBox.background")); //NOI18N if (renderer == null) { renderer = HtmlRenderer.createRenderer(); } setDefaultRenderer(Object.class, renderer); } /** * Maps tab selected in quicklist to tab index in displayer to select * correct tab */ private void setSelectedTab(int row, int col) { //Find corresponding index in displayer Object o = getTTModel().getValueAt(row, col); if (o instanceof TabData) { TabData td = (TabData) o; List l = displayer.getModel().getTabs(); int ind = -1; for (int i = 0; i < l.size(); i++) { if (td.equals(l.get(i))) { ind = i; break; } } if (ind != -1) { int old = displayer.getSelectionModel().getSelectedIndex(); displayer.getSelectionModel().setSelectedIndex(ind); //#40665 fix start if (displayer.getType() == TabbedContainer.TYPE_EDITOR && ind >= 0 && ind == old) { displayer.getUI().makeTabVisible(ind); } //#40665 fix end } } } public void updateUI() { needCalcRowHeight = true; super.updateUI(); } public void setFont(Font f) { needCalcRowHeight = true; super.setFont(f); } public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { //Find our TabData object Object value = getTTModel().getValueAt(row, column); //Set up font, selection, icon, colors, borders //Under very peculiar circumstances, displayer can be null - this happens on the //mac if the popup has been displayed, and then focus is shifted to another //application before it has had a chance to paint. You need swapping or a big //hefty garbage collection after the popup is displayed but before it paints //to make it happen int selIdx = displayer != null ? displayer.getSelectionModel().getSelectedIndex() : -1; boolean isSelTab = selIdx != -1 ? value == displayer.getModel().getTab(selIdx) : false; boolean isMouseOver = row == getSelectedRow() && column == getSelectedColumn() && value != null; JComponent result = /*(JComponent) super.prepareRenderer (renderer, row, column); */ (JComponent) renderer.getTableCellRendererComponent(this, value, isMouseOver, isMouseOver, row, column); if (value == null) { //it's a filler space, we're done result.setOpaque(false); return result; } if (isSelTab) { result.setFont (getFont().deriveFont (Font.BOLD)); } Icon icon = ((TabData) value).getIcon(); HtmlRenderer.Renderer ren = (HtmlRenderer.Renderer) result; ren.setIcon(icon); if (icon.getIconWidth() > 0) { //Max annotated icon width is 24, so to have all the text and all //the icons come out aligned, set the icon text gap to the difference //plus a two pixel margin ren.setIconTextGap (26 - icon.getIconWidth()); } else { //If the icon width is 0, fill the space and add in //the extra two pixels so the node names are aligned (btw, this //does seem to waste a frightful amount of horizontal space in //a tree that can use all it can get) ren.setIndent (26); } //The table may not really have focus, but it should always use the focus //color for the selection, not controlShadow ((HtmlRenderer.Renderer) result).setParentFocused(true); result.setBorder (rendererBorder); result.setOpaque(true); if (isMouseOver) { result.setBackground (getSelectionBackground()); result.setForeground (getSelectionForeground()); } else { result.setBackground (getBackground()); result.setForeground (getForeground()); } return result; } /** * Calculate the height of rows based on the current font. This is done * when the first paint occurs, to ensure that a valid Graphics object is * available. * * @since 1.25 */ private void calcRowHeight(Graphics g) { Font f = getFont(); FontMetrics fm = g.getFontMetrics(f); //As icons are displayed use maximum from font and icon height int rowHeight = Math.max(fm.getHeight(), 16) + 4; needCalcRowHeight = false; setRowHeight(rowHeight); } public void attach(TabDisplayer cont) { prefSize = null; displayer = cont; //Calc row height here so that TableModel can adjust number of columns. calcRowHeight(getOffscreenGraphics()); getTTModel().setRowHeight(getRowHeight()); getTTModel().attach(cont); synchronizeColumns(getTTModel().getColumnCount()); getSelectionModel().clearSelection(); getSelectionModel().setAnchorSelectionIndex(-1); getSelectionModel().setLeadSelectionIndex(-1); } static SoftReference ctx = null; /** * Provides an offscreen graphics context so that widths based on character * size can be calculated correctly before the component is shown */ public static Graphics2D getOffscreenGraphics() { BufferedImage result = null; //XXX multi-monitors w/ different resolution may have problems; //Better to call Toolkit to create a screen graphics if (ctx != null) { result = (BufferedImage) ctx.get(); } if (result == null) { result = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); ctx = new SoftReference(result); } return (Graphics2D) result.getGraphics(); } private void synchronizeColumns(int count) { TableColumnModel mdl = getColumnModel(); int currCt = mdl.getColumnCount(); if (currCt < count) { for (int i = currCt; i < count; i++) { mdl.addColumn(new TableColumn(i, 75, renderer, null)); } } else if (currCt > count) { for (int i = currCt - 1; i >= count; i--) { mdl.removeColumn(mdl.getColumn(i)); } } } /** Overridden to calculate a preferred size based on the current optimal * number of columns, and set up the preferred width for each column based * on the maximum width tab name & icon displayed in it */ public Dimension getPreferredSize() { if (prefSize == null) { Insets ins = getInsets(); prefSize = new Dimension(ins.left + ins.top, ins.right + ins.bottom); int cols = getColumnCount(); int rows = getRowCount(); //Iterate the columns for (int i=0; i < cols; i++) { int columnWidth = 0; //For each column, iterate the rows for (int j=0; j < rows; j++) { TableCellRenderer ren = getCellRenderer(j,i); Component c = prepareRenderer (ren, j, i); //find the widest cell columnWidth = Math.max (c.getPreferredSize().width, columnWidth); } //Add in the max width needed for this column to the total //width prefSize.width += columnWidth; //Store it in the column model so it will be displayed with //the right width getColumnModel().getColumn(i).setPreferredWidth(columnWidth); } //Rows will be fixed height, so just multiply it out prefSize.height += rows * getRowHeight(); } return prefSize; } private final TabListPopupTableModel getTTModel() { return (TabListPopupTableModel) getModel(); } public boolean isAttached() { return displayer != null; } public void detach() { displayer = null; getTTModel().detach(); } public void addNotify() { super.addNotify(); addMouseListener(this); addMouseMotionListener(this); //Set initial selection if there is any field in table if ((getRowCount() > 0) && (getColumnCount() > 0)) { changeSelection(-1, -1, false, false); } EventObject eo = EventQueue.getCurrentEvent(); if (eo != null) { if (eo.getSource() instanceof Component) { invokingComponent = (Component) eo.getSource(); } } if (invokingComponent != null) { invokingComponent.addMouseListener(this); invokingComponent.addMouseMotionListener(this); } invocationTime = System.currentTimeMillis(); } public void removeNotify() { super.removeNotify(); removeMouseListener(this); removeMouseMotionListener(this); if (invokingComponent != null) { invokingComponent.removeMouseListener(this); invokingComponent.removeMouseMotionListener(this); invokingComponent = null; } detach(); } public void paint(Graphics g) { if (needCalcRowHeight) { calcRowHeight(g); } super.paint(g); } int convertIndex(int row, int column) { return column * getRowCount() + row; } public void mouseClicked(MouseEvent e) { e.consume(); } public void mousePressed(MouseEvent e) { Point p = e.getPoint(); p = SwingUtilities.convertPoint((Component) e.getSource(), p, this); if (contains(p)) { //Otherwise the AWT listener will handle hiding the popup int row = getSelectedRow(); int col = getSelectedColumn(); setSelectedTab(row, col); //Hide window hideCurrentPopup(); } e.consume(); } public void mouseReleased(MouseEvent e) { if (e.getSource() == invokingComponent) { long time = System.currentTimeMillis(); if (time - invocationTime > 500) { mousePressed(e); } } e.consume(); } public void mouseEntered(MouseEvent e) { e.consume(); } public void mouseExited(MouseEvent e) { clearSelection(); e.consume(); } //MouseMotionListener public void mouseDragged(MouseEvent e) { mouseMoved(e); e.consume(); } public void mouseMoved(MouseEvent e) { Point p = e.getPoint(); //It may have occured on the button that invoked the tabtable if (e.getSource() != this) { p = SwingUtilities.convertPoint((Component) e.getSource(), p, this); } if (this.contains(p)) { int row = rowAtPoint(p); int col = columnAtPoint(p); changeSelection(row, col, false, false); } else { clearSelection(); } e.consume(); } public static synchronized void invoke(final TabDisplayer c, int screenX, int screenY) { if (currentPopup != null) { hideCurrentPopup(); return; } if (c.getModel().size() == 0) { return; } //Get our singleton soft-cached instance final TabListPopup tt = sharedInstance(); //Aim it at the tabbed displayer in question tt.attach(c); //Get a popup object for the right coordinates. Offset it to the //left so it appears with its upper right corner under the button maybeHackPopupForAqua(); currentPopup = PopupFactory.getSharedInstance().getPopup(c, tt, screenX - tt.getPreferredSize() .width, screenY); //show it currentPopup.show(); //Use an AWT listener to hide it in certain circumstances blistener = new BackupListener(tt); } /** * Focus changes are occasionally missed if the editor has focus while the * tab table is active. This listener ensures that any mouse event on it * will indeed close the component. */ private static class BackupListener implements AWTEventListener { //XXX could consolidate the property change listener above into this //and just have one listener class. private TabListPopup tt; public BackupListener(TabListPopup tt) { this.tt = tt; Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_EVENT_MASK | AWTEvent.KEY_EVENT_MASK); } private boolean onTabTable(MouseEvent e) { Point p = e.getPoint(); p = SwingUtilities.convertPoint((Component) e.getSource(), p, tt); return tt.contains(p); } public void eventDispatched(AWTEvent event) { if (event.getSource() == tt) { return; } if (event instanceof MouseEvent) { if (event.getID() == MouseEvent.MOUSE_RELEASED) { long time = System.currentTimeMillis(); if (time - tt.invocationTime > 500) { if (!onTabTable((MouseEvent) event)) { //Don't take any chances Toolkit.getDefaultToolkit() .removeAWTEventListener(this); hideCurrentPopup(); } } } else if (event.getID() == MouseEvent.MOUSE_PRESSED) { if (!onTabTable((MouseEvent) event)) { //Don't take any chances if (event.getSource() != tt.invokingComponent) { //If it's the invoker, don't do anything - it //will generate another call to invoke(), which will //do the hiding - if we do it here, it will get //shown again when the button processes the event Toolkit.getDefaultToolkit() .removeAWTEventListener(this); hideCurrentPopup(); } } } } else if (event instanceof KeyEvent) { if (event.getID() == KeyEvent.KEY_PRESSED) { Toolkit.getDefaultToolkit().removeAWTEventListener(this); hideCurrentPopup(); } } } } public synchronized static void hideCurrentPopup() { if (currentPopup != null) { //Issue 41121 - use invokeLater to allow any pending //event processing against the popup contents to run //before the popup is hidden SwingUtilities.invokeLater (new PopupHider(currentPopup)); currentPopup = null; } if (blistener != null) { Toolkit.getDefaultToolkit().removeAWTEventListener(blistener); } } /** Runnable which hides the popup in a subsequent event queue * loop. This is to avoid problems with BasicToolbarUI, which * will try to process events on the component after it has been * hidden and throw exceptions. * @see http://www.netbeans.org/issues/show_bug.cgi?id=41121 */ private static class PopupHider implements Runnable { private Popup toHide; public PopupHider (Popup popup) { toHide = popup; } public void run() { toHide.hide(); } } private static TabListPopup sharedInstance() { TabListPopup result = null; if (instance != null) { result = (TabListPopup) instance.get(); } if (result == null) { result = new TabListPopup(); instance = new SoftReference(result); } return result; } private static void maybeHackPopupForAqua() { //Workaround for JDK bug NNN - a note in the 1.4.2 source for //PopupFactory says: // MacOSX we want to change the default for pupus to be heavyweight! // was: private int popupType = LIGHT_WEIGHT_POPUP; // reverting out the change because we need better support in AWT // for heavyweights! // Steve will put this back in when AWT handles it better (after DP6) //private int popupType = HEAVY_WEIGHT_POPUP; //but the fix for Apple is actually still commented out, so lightweight //popups do not work correctly. We correct this here via reflection: try { String osName = System.getProperty ("os.name"); if ("Mac OS X".equals (osName) || osName.startsWith ("Darwin")) { Field toSet = PopupFactory.class.getDeclaredField("popupType"); toSet.setAccessible(true); toSet.set(PopupFactory.getSharedInstance(), new Integer(2)); } } catch (Exception e) { e.printStackTrace(); } } } |
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.