Index: core/settings/src/org/netbeans/modules/settings/convertors/SerialDataNode.java =================================================================== RCS file: /cvs/core/settings/src/org/netbeans/modules/settings/convertors/SerialDataNode.java,v --- core/settings/src/org/netbeans/modules/settings/convertors/SerialDataNode.java 21 Mar 2004 18:28:58 -0000 1.13 +++ core/settings/src/org/netbeans/modules/settings/convertors/SerialDataNode.java 2 May 2004 11:01:40 -0000 @@ -159,6 +159,10 @@ return getIcon (type); } + public String getHtmlDisplayName() { + return null; + } + /** here should be decided if some change was fired by the setting object * or the node should notify convertor about the change. This is just * workaround ensuring backward compatibility for archaic settings from Index: core/src/org/netbeans/core/projects/SettingChildren.java =================================================================== RCS file: /cvs/core/src/org/netbeans/core/projects/SettingChildren.java,v --- core/src/org/netbeans/core/projects/SettingChildren.java 25 Mar 2004 16:05:41 -0000 1.23 +++ core/src/org/netbeans/core/projects/SettingChildren.java 2 May 2004 11:01:41 -0000 @@ -106,7 +106,10 @@ } public SystemAction[] getActions() { return removeActions(super.getActions(), new SystemAction[] {SystemAction.get(ToolsAction.class)}); - } + } + public String getHtmlDisplayName() { + return null; + } } /** Property allowing display/manipulation of setting status for one specific layer. */ Index: core/src/org/netbeans/core/ui/UINodes.java =================================================================== RCS file: /cvs/core/src/org/netbeans/core/ui/UINodes.java,v --- core/src/org/netbeans/core/ui/UINodes.java 16 Apr 2004 00:13:20 -0000 1.11 +++ core/src/org/netbeans/core/ui/UINodes.java 2 May 2004 11:01:42 -0000 @@ -137,6 +137,10 @@ public Image getOpenedIcon (int type) { return getIcon(type); } + + public String getHtmlDisplayName() { + return null; + } public SystemAction[] getActions () { if (staticActions == null) { Index: core/swing/tabcontrol/nbproject/project.xml =================================================================== RCS file: /cvs/core/swing/tabcontrol/nbproject/project.xml,v --- core/swing/tabcontrol/nbproject/project.xml 30 Apr 2004 04:10:41 -0000 1.3 +++ core/swing/tabcontrol/nbproject/project.xml 2 May 2004 11:01:42 -0000 @@ -17,7 +17,17 @@ core/swing/tabcontrol - + + + org.openide + + + + 1 + 4.31 + + + org.netbeans.swing.tabcontrol org.netbeans.swing.tabcontrol.plaf Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabData.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabData.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabData.java 22 Apr 2004 13:09:06 -0000 1.2 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabData.java 2 May 2004 11:01:42 -0000 @@ -117,7 +117,7 @@ * @return */ public String toString() { - return "[" + txt + "]"; + return txt; } /** Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractTabCellRenderer.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractTabCellRenderer.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractTabCellRenderer.java 22 Apr 2004 13:09:09 -0000 1.2 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractTabCellRenderer.java 2 May 2004 11:01:43 -0000 @@ -21,6 +21,8 @@ import org.netbeans.swing.tabcontrol.TabData; import org.netbeans.swing.tabcontrol.TabDisplayer; +import org.openide.awt.HtmlRenderer; + import javax.swing.*; import javax.swing.border.Border; import java.awt.*; @@ -430,8 +432,8 @@ if (isClipLeft()) { //fiddle with the string to get "...blah" String s = preTruncateString(getText(), g, txtW - 4); //subtract 4 so it's not flush w/ tab edge - Html.renderString(s, g, txtX, txtY, txtW, txtH, getFont(), - getForeground(), Html.STYLE_CLIP, true); + HtmlRenderer.renderString(s, g, txtX, txtY, txtW, txtH, getFont(), + getForeground(), HtmlRenderer.STYLE_CLIP, true); } else { String s; if (isClipRight()) { @@ -441,8 +443,8 @@ } else { s = getText(); } - Html.renderString(s, g, txtX, txtY, txtW, txtH, getFont(), - getForeground(), Html.STYLE_TRUNCATE, true); + HtmlRenderer.renderString(s, g, txtX, txtY, txtW, txtH, getFont(), + getForeground(), HtmlRenderer.STYLE_TRUNCATE, true); } } Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java 22 Apr 2004 13:09:11 -0000 1.2 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java 2 May 2004 11:01:43 -0000 @@ -14,6 +14,7 @@ package org.netbeans.swing.tabcontrol.plaf; import org.netbeans.swing.tabcontrol.TabDisplayer; +import org.openide.awt.HtmlRenderer; import javax.swing.*; import javax.swing.plaf.ComponentUI; @@ -153,9 +154,9 @@ textY = (height / 2) - (textHeight / 2) + fm.getAscent() - 1; } - Html.renderString(text, g, textX, textY, textW, height, getTxtFont(), + HtmlRenderer.renderString(text, g, textX, textY, textW, height, getTxtFont(), UIManager.getColor("textText"), - Html.STYLE_TRUNCATE, true); + HtmlRenderer.STYLE_TRUNCATE, true); } //private static final JButton jb = new JButton(); Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BaseTabLayoutModel.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BaseTabLayoutModel.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BaseTabLayoutModel.java 22 Apr 2004 13:09:11 -0000 1.2 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BaseTabLayoutModel.java 2 May 2004 11:01:43 -0000 @@ -20,6 +20,7 @@ package org.netbeans.swing.tabcontrol.plaf; import org.netbeans.swing.tabcontrol.TabDataModel; +import org.openide.awt.HtmlRenderer; import javax.swing.*; import java.awt.*; @@ -105,10 +106,10 @@ //dump it if the font changes. Integer result = (Integer) widthMap.get(text); if (result == null) { - double wid = Html.renderString(text, TabListPopup.getOffscreenGraphics(), 0, 0, + double wid = HtmlRenderer.renderString(text, TabListPopup.getOffscreenGraphics(), 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE, getFont(), - Color.BLACK, Html.STYLE_TRUNCATE, + Color.BLACK, HtmlRenderer.STYLE_TRUNCATE, false); result = new Integer(Math.round(Math.round(wid))); widthMap.put(text, result); Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/GtkViewTabDisplayerUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/GtkViewTabDisplayerUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/GtkViewTabDisplayerUI.java 22 Apr 2004 13:09:14 -0000 1.2 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/GtkViewTabDisplayerUI.java 2 May 2004 11:01:44 -0000 @@ -14,6 +14,7 @@ package org.netbeans.swing.tabcontrol.plaf; import org.netbeans.swing.tabcontrol.TabDisplayer; +import org.openide.awt.HtmlRenderer; import javax.swing.*; import javax.swing.plaf.ComponentUI; @@ -166,9 +167,9 @@ textY = (height / 2) - (textHeight / 2) + fm.getAscent() - 1; } - Html.renderString(text, g, textX, textY, textW, height, getTxtFont(), + HtmlRenderer.renderString(text, g, textX, textY, textW, height, getTxtFont(), chiclet.getTextForeground(), //XXX - Html.STYLE_TRUNCATE, true); + HtmlRenderer.STYLE_TRUNCATE, true); } //private static final JButton jb = new JButton(); Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/SlidingTabDisplayerButtonUI.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/SlidingTabDisplayerButtonUI.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/SlidingTabDisplayerButtonUI.java 22 Apr 2004 13:09:15 -0000 1.2 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/SlidingTabDisplayerButtonUI.java 2 May 2004 11:01:44 -0000 @@ -20,6 +20,8 @@ import org.netbeans.swing.tabcontrol.TabDisplayer; +import org.openide.awt.HtmlRenderer; + import javax.swing.*; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicToggleButtonUI; @@ -163,8 +165,8 @@ txtW -= iconH + b.getIconTextGap(); } - Html.renderString(b.getText(), g, txtX, txtY, txtW, txtH, b.getFont(), - b.getForeground(), Html.STYLE_TRUNCATE, true); + HtmlRenderer.renderString(b.getText(), g, txtX, txtY, txtW, txtH, b.getFont(), + b.getForeground(), HtmlRenderer.STYLE_TRUNCATE, true); } private static SlidingTabDisplayerButtonUI AQUA_INSTANCE = null; Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabListPopup.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabListPopup.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabListPopup.java 22 Apr 2004 13:09:16 -0000 1.2 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabListPopup.java 2 May 2004 11:01:45 -0000 @@ -17,7 +17,10 @@ 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; @@ -39,56 +42,51 @@ 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; - private static TabListPopup defaultInstance = null; - - /** - * Reference to displayer for which we display quicklist - */ + /** Reference to container for which we display quicklist */ private TabDisplayer displayer = null; - /** - * Creates a new instance of TabListPanel - */ - private TabListPopup() { - super(new TabListPopupTableModel()); - setBorder(BorderFactory.createLineBorder(getForeground())); - setColors(); - } + /** 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; - /** - * Sets right colors of the control. Colors should imitate colors of - * drop-down list of combo box. - */ - private void setColors() { - Color color = UIManager.getColor("ComboBox.background"); - setBackground(color); - //setGridColor(color); - color = UIManager.getColor("ComboBox.foreground"); - setForeground(color); - color = UIManager.getColor("ComboBox.selectionBackground"); - setSelectionBackground(color); - color = UIManager.getColor("ComboBox.selectionForeground"); - setSelectionForeground(color); - Font font = (Font) UIManager.get("ComboBox.font"); - setFont(font); - // only vertical lines visible - setShowHorizontalLines(false); - } + private static final Border rendererBorder = + BorderFactory.createEmptyBorder (2, 3, 0, 3); - public void processKeyEvent(KeyEvent ke) { - //Apparently the code that changes selection on enter is - //not an action in JTable's action map. Override it entirely. - if (ke.getKeyCode() == ke.VK_ENTER) { - int row = getSelectedRow(); - int col = getSelectedColumn(); - setSelectedTab(row, col); - hideCurrentPopup(); - } else if (ke.getKeyCode() == ke.VK_ESCAPE) { - hideCurrentPopup(); - } else { - super.processKeyEvent(ke); + 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); } /** @@ -131,34 +129,63 @@ super.setFont(f); } - public Component prepareRenderer(TableCellRenderer renderer, int row, - int column) { - Component result = super.prepareRenderer(renderer, row, column); - TabData value = (TabData) getValueAt(row, column); - + public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { + //Will always use the default instance of HtmlRenderer + JComponent result = (JComponent) + super.prepareRenderer (renderer, row, column); + + //Find our TabData object + Object value = getTTModel().getValueAt(row, column); + if (value == null) { + //it's a filler space, we're done + return result; + } + + //Set up font, selection, icon, colors, borders int selIdx = displayer.getSelectionModel().getSelectedIndex(); - boolean isSelTab = false; - if (selIdx != -1) { - isSelTab = value == displayer.getModel().getTab (selIdx); + boolean isSelTab = selIdx != -1 ? + value == displayer.getModel().getTab(selIdx) + : false; + + if (isSelTab) { + result.setFont (getFont().deriveFont (Font.BOLD)); } - result.setFont( - isSelTab ? getFont().deriveFont(Font.BOLD) : getFont()); - boolean sel = row == getSelectedRow() - && column == getSelectedColumn() && value != null; - if (sel) { - result.setBackground(getSelectionBackground()); - result.setForeground(getSelectionForeground()); + 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); + ((JComponent)result).setOpaque(true); + if (row == getSelectedRow() && column == getSelectedColumn()) { + result.setBackground (getSelectionBackground()); } else { - result.setBackground(getBackground()); - result.setForeground(getForeground()); + result.setBackground (getBackground()); } + + + return result; } - boolean needCalcRowHeight = true; - /** * 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 @@ -213,8 +240,8 @@ int currCt = mdl.getColumnCount(); if (currCt < count) { for (int i = currCt; i < count; i++) { - mdl.addColumn(new TableColumn(i, 75, getTTModel(), null)); - } + 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)); @@ -222,35 +249,37 @@ } } - private Dimension prefSize = null; - + /** 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) { - Dimension d = new Dimension(); + Insets ins = getInsets(); + + prefSize = new Dimension(ins.left + ins.top, ins.right + ins.bottom); int cols = getColumnCount(); int rows = getRowCount(); - for (int i = 0; i < cols; i++) { - int currWidth = 0; - for (int j = 0; j < rows; j++) { - TableCellRenderer ren = getCellRenderer(j, i); - Component c = ren.getTableCellRendererComponent(this, - getModel() - .getValueAt( - j, - i), - false, - false, j, - i); - prepareRenderer(ren, j, i); - Dimension curr = c.getPreferredSize(); - currWidth = Math.max(curr.width, currWidth); + + //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); } - d.width += currWidth; - getColumnModel().getColumn(i).setPreferredWidth(currWidth); + //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); } - d.height = rows * getRowHeight(); - - prefSize = d; + //Rows will be fixed height, so just multiply it out + prefSize.height += rows * getRowHeight(); } return prefSize; } @@ -290,9 +319,6 @@ invocationTime = System.currentTimeMillis(); } - Component invokingComponent = null; - long invocationTime = -1; - public void removeNotify() { super.removeNotify(); removeMouseListener(this); @@ -465,9 +491,6 @@ } - private static Popup currentPopup = null; - private static BackupListener blistener = null; - public synchronized static void hideCurrentPopup() { if (currentPopup != null) { //Issue 41121 - use invokeLater to allow any pending @@ -497,8 +520,6 @@ toHide.hide(); } } - - private static Reference instance = null; private static TabListPopup sharedInstance() { TabListPopup result = null; Index: core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabListPopupTableModel.java =================================================================== RCS file: /cvs/core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabListPopupTableModel.java,v --- core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabListPopupTableModel.java 22 Apr 2004 13:09:16 -0000 1.2 +++ core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabListPopupTableModel.java 2 May 2004 11:01:48 -0000 @@ -31,7 +31,7 @@ * * @author Tim Boudreau */ -class TabListPopupTableModel implements TableModel, TableCellRenderer { +class TabListPopupTableModel implements TableModel { private transient TabDisplayer displayer = null; private transient ArrayList tableModelListenerList; /** @@ -207,117 +207,4 @@ return cols; } - public Component getTableCellRendererComponent(JTable table, Object value, - boolean isSelected, - boolean hasFocus, int row, - int column) { - if (value == null) { - return dummyRenderer; - } - TabData td = (TabData) value; - - Icon i = td.getIcon(); - String txt = td.getText(); - renderer.setText(txt); - renderer.setIcon(i); - renderer.setSelected(table.isCellSelected(row, column)); - renderer.setIconTextGap(29 - i.getIconWidth()); - if (isSelected) { - renderer.setForeground(table.getSelectionForeground()); - } else { - renderer.setForeground(table.getForeground()); - } - - return renderer; - } - - private static DefaultTableCellRenderer dummyRenderer = new DefaultTableCellRenderer(); - private static Renderer renderer = new Renderer(); - - private static class Renderer extends DefaultListCellRenderer { - private String text; - private Dimension prefSize; - private boolean selected = false; - - void setSelected(boolean val) { - selected = val; - } - - public Dimension getPreferredSize() { - if (text == null) { - return new Dimension(0, 0); - } - if (prefSize == null) { - Graphics g = TabListPopup.getOffscreenGraphics(); - - prefSize = new Dimension(); - FontMetrics fm = g.getFontMetrics(getFont()); - prefSize.height = fm.getHeight(); - double wid = Html.renderString(text, g, 0, 0, - Integer.MAX_VALUE, - Integer.MAX_VALUE, getFont(), - getForeground(), - Html.STYLE_TRUNCATE, false); - prefSize.width = new Double(wid).intValue() + 10; - Icon ic = getIcon(); - if (ic != null) { - prefSize.width += ic.getIconWidth() + getIconTextGap(); - prefSize.height = Math.max(prefSize.height, - ic.getIconHeight() + 2); - } - //Increase vertical spacing - prefSize.height += 4; - } - return prefSize; - } - - public void setText(String txt) { - text = txt; - prefSize = null; - } - - public void setFont(Font f) { - if (f != getFont()) { - prefSize = null; - super.setFont(f); - } - } - - public String getText() { - return text; - } - - private void paintBackground(Graphics g) { - g.setColor(getBackground()); - g.fillRect(0, 0, getWidth(), getHeight()); - } - - public void paint(Graphics g) { - g.setFont(getFont()); - Dimension d = getSize(); - if (d.width == 0) { - d = getPreferredSize(); - } - - paintBackground(g); - - getIcon().paintIcon(this, g, 2, 2); - int x = getIconTextGap() + getIcon().getIconWidth(); - - g.setColor(getForeground()); - Html.renderString(getText(), g, x, 2 + getTextBase(g), d.width, - d.height, getFont(), getForeground(), - Html.STYLE_TRUNCATE, true); - } - - int fh = -1; - - private int getTextBase(Graphics g) { - if (fh == -1) { - FontMetrics fm = g.getFontMetrics(getFont()); - fh = fm.getAscent(); - } - return fh; - } - } } Index: core/windows/src/org/netbeans/core/windows/WindowManagerImpl.java =================================================================== RCS file: /cvs/core/windows/src/org/netbeans/core/windows/WindowManagerImpl.java,v --- core/windows/src/org/netbeans/core/windows/WindowManagerImpl.java 22 Apr 2004 13:09:31 -0000 1.21 +++ core/windows/src/org/netbeans/core/windows/WindowManagerImpl.java 2 May 2004 11:01:56 -0000 @@ -718,15 +718,40 @@ central.setProjectName(projectName); } + private static final boolean NAME_HACK = Boolean.getBoolean("nb.tabnames.html"); //NOI18N /** Helper method to retrieve the display name of TopComponent. */ public String getTopComponentDisplayName(TopComponent tc) { if(tc == null) { return null; } - String displayName = tc.getDisplayName(); - return displayName == null ? tc.getName() : displayName; + if (displayName == null) { + displayName = tc.getName(); + } + if (NAME_HACK) { + //THIS IS FOR DEMO PURPOSES ONLY! A PROPER API IS NEEDED + //(TopComponent.getHtmlDisplayName()), OR + //HTML SHOULD BE PRE-SUPPLIED + if (displayName.endsWith("*")) { + + displayName = "" + + displayName.substring(0, displayName.length()-2); + + } else { + + int i = displayName.indexOf ("[read only]"); + if (i > 0) { + String nuName = "" + + displayName.substring (0, i-1); + if (i + 11 < nuName.length()) { + nuName += displayName.substring(i+11); + } + displayName = nuName; + } + } + } + return displayName; } // PENDING for ModeImpl only. Index: form/src/org/netbeans/modules/form/FormProperty.java =================================================================== RCS file: /cvs/form/src/org/netbeans/modules/form/FormProperty.java,v --- form/src/org/netbeans/modules/form/FormProperty.java 21 Jan 2004 17:36:07 -0000 1.27 +++ form/src/org/netbeans/modules/form/FormProperty.java 2 May 2004 11:02:18 -0000 @@ -163,13 +163,14 @@ // ---------------------------------------- // getter, setter & related methods -// public String getDisplayName() { -// String displayName = super.getDisplayName(); -// if (isChanged()) -// displayName = "" + displayName + ""; // NOI18N -// return displayName; -// } -// + public String getHtmlDisplayName() { + if (isChanged()) { + return "" + getDisplayName(); + } else { + return null; + } + } + /** Gets the real value of this property directly from the target object. */ public abstract Object getTargetValue() throws IllegalAccessException, Index: openide/openide-spec-vers.properties =================================================================== RCS file: /cvs/openide/openide-spec-vers.properties,v --- openide/openide-spec-vers.properties 30 Apr 2004 13:56:33 -0000 1.138 +++ openide/openide-spec-vers.properties 2 May 2004 11:03:04 -0000 @@ -4,4 +4,4 @@ # Must always be numeric (numbers separated by '.', e.g. 4.11). # See http://openide.netbeans.org/versioning-policy.html for more. -openide.specification.version=4.30 +openide.specification.version=4.31 Index: openide/api/doc/changes/apichanges.xml =================================================================== RCS file: /cvs/openide/api/doc/changes/apichanges.xml,v --- openide/api/doc/changes/apichanges.xml 30 Apr 2004 13:56:33 -0000 1.193 +++ openide/api/doc/changes/apichanges.xml 2 May 2004 11:03:12 -0000 @@ -113,6 +113,39 @@ + + + + Lightweight HTML rendering methods + + + + + + A lightweight HTML renderer which can render a limited subset of + HTML has been added to the APIs, and will be used in Explorer. + Nodes wishing to provide text rendered in HTML may do so by + returning subset-compliant HTML formatted text from the new + method getHtmlDisplayName. An interface, + HTMLStatus has been created which extends + FileSystem.Status, has been created, which allows + filesystems to supply HTML formatted status information, by + implementing it on their FileSystem.Status implementation. + Filesystems which delegate to other filesystems my implement + FileSystem.HtmlStatus and simply return null for filesystems which + do not support it. + If one is present, DataNode will use it to supply HTML formatted + text to Explorer. The renderer itself can be found in + org.openide.awt.HtmlRenderer. + + + + + + + + + Index: openide/loaders/manifest.mf =================================================================== RCS file: /cvs/openide/loaders/manifest.mf,v --- openide/loaders/manifest.mf 25 Mar 2004 17:46:06 -0000 1.10 +++ openide/loaders/manifest.mf 2 May 2004 11:03:16 -0000 @@ -1,5 +1,5 @@ Manifest-Version: 1.0 OpenIDE-Module: org.openide.loaders -OpenIDE-Module-Specification-Version: 4.12 +OpenIDE-Module-Specification-Version: 4.13 OpenIDE-Module-Localizing-Bundle: org/openide/loaders/Bundle.properties Index: openide/loaders/api/apichanges.xml =================================================================== RCS file: /cvs/openide/loaders/api/apichanges.xml,v --- openide/loaders/api/apichanges.xml 24 Mar 2004 16:32:39 -0000 1.4 +++ openide/loaders/api/apichanges.xml 2 May 2004 11:03:16 -0000 @@ -78,6 +78,24 @@ + + + DataNode.getHtmlDisplayName + + + + + + DataNode now implements getHtmlDisplayName() as follows: If the + underlying filesystem implements the new FileSystem.HtmlStatus, it will + call FileSystem.HtmlStatus.annotateNameHtml() and return the result. + This enables, for example, CVS annotations to be shown with a different + font color. + + + + + Two new folder sort modes Index: openide/loaders/src/org/openide/loaders/DataNode.java =================================================================== RCS file: /cvs/openide/loaders/src/org/openide/loaders/DataNode.java,v --- openide/loaders/src/org/openide/loaders/DataNode.java 26 Apr 2004 22:06:33 -0000 1.12 +++ openide/loaders/src/org/openide/loaders/DataNode.java 2 May 2004 11:03:18 -0000 @@ -22,6 +22,7 @@ import org.openide.ErrorManager; import org.openide.filesystems.*; +import org.openide.filesystems.FileSystem.HtmlStatus; import org.openide.util.datatransfer.*; import org.openide.util.HelpCtx; import org.openide.util.RequestProcessor; @@ -174,6 +175,38 @@ return s; } + + + /** Get a display name formatted using the limited HTML subset supported + * by HtmlRenderer. If the underlying + * FileSystem.Status is an instance of HmlStatus, + * this method will return non-null if status information is added. + * + * @return a string containing compliant HTML markup or null + * @see org.openide.awt.HtmlRenderer + * @see org.openide.nodes.Node#getHtmlDisplayName + * @since 4.13 + */ + public String getHtmlDisplayName() { + try { + FileSystem.Status stat = + obj.getPrimaryFile().getFileSystem().getStatus(); + if (stat instanceof HtmlStatus) { + HtmlStatus hstat = (HtmlStatus) stat; + + String result = hstat.annotateNameHtml ( + super.getDisplayName(), new LazyFilesSet()); + + //Make sure the super string was really modified + if (!super.getDisplayName().equals(result)) { + return result; + } + } + } catch (FileStateInvalidException e) { + //do nothing and fall through + } + return super.getHtmlDisplayName(); + } /** Get the displayed icon for this node. * A filesystem may {@link org.openide.filesystems.FileSystem#getStatus specially alter} this. Index: openide/masterfs/src/org/netbeans/modules/masterfs/MasterFileSystem.java =================================================================== RCS file: /cvs/openide/masterfs/src/org/netbeans/modules/masterfs/MasterFileSystem.java,v --- openide/masterfs/src/org/netbeans/modules/masterfs/MasterFileSystem.java 2 May 2004 08:03:38 -0000 1.9 +++ openide/masterfs/src/org/netbeans/modules/masterfs/MasterFileSystem.java 2 May 2004 11:03:18 -0000 @@ -242,7 +242,7 @@ } - private static final class StatusImpl implements FileSystem.Status { + private static final class StatusImpl implements FileSystem.HtmlStatus { public Image annotateIcon(Image icon, int iconType, Set files) { //int size = files.size(); Set transformedSet = new HashSet(); @@ -273,6 +273,18 @@ name = (fs != null) ? fs.getStatus().annotateName(name, transformedSet) : name; } return name; + } + + public String annotateNameHtml(String name, Set files) { + Set transformedSet = new HashSet(); + MasterFileObject hfo = Utils.transformSet(files, transformedSet); + if (hfo != null) { + FileSystem fs = hfo.getDelegateFileSystem(); + if (fs != null && fs.getStatus() instanceof FileSystem.HtmlStatus) { + return ((FileSystem.HtmlStatus) fs.getStatus()).annotateNameHtml(name, transformedSet); + } + } + return null; } } Index: openide/src/org/openide/awt/HtmlLabelUI.java =================================================================== RCS file: openide/src/org/openide/awt/HtmlLabelUI.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/src/org/openide/awt/HtmlLabelUI.java 2 May 2004 11:03:19 -0000 @@ -0,0 +1,367 @@ +/* + * 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-2003 Sun + * Microsystems, Inc. All Rights Reserved. + */ +package org.openide.awt; + +import org.openide.ErrorManager; + +import javax.swing.*; +import javax.swing.plaf.LabelUI; +import javax.swing.plaf.ComponentUI; +import java.awt.*; +import java.util.Map; +import java.util.HashMap; + +/** + * A LabelUI which uses the lightweight HTML renderer. Stateless - only one instance should ever exist. + */ +public class HtmlLabelUI extends LabelUI { + /** System property to automatically turn on antialiasing for html strings */ + private static final boolean antialias = Boolean.getBoolean( + "nb.cellrenderer.antialiasing"); //NOI18N + + private static HtmlLabelUI uiInstance = null; + + public static ComponentUI createUI (JComponent c) { + assert c instanceof HtmlRendererImpl; + if (uiInstance == null) { + uiInstance = new HtmlLabelUI(); + } + return uiInstance; + } + + public Dimension getPreferredSize(JComponent c) { + return calcPreferredSize ((HtmlRendererImpl) c); + } + + /** Get the width of the text */ + private static int textWidth(String text, Graphics g, Font f, boolean html) { + if (text != null) { + if (html) { + return Math.round(Math.round(Math.ceil(HtmlRenderer.renderHTML(text, g, 0, 0, + Integer.MAX_VALUE, Integer.MAX_VALUE, f, + Color.BLACK, HtmlRenderer.STYLE_CLIP, false)))); + } else { + return Math.round(Math.round(Math.ceil(HtmlRenderer.renderPlainString(text, g, 0, 0, + Integer.MAX_VALUE, Integer.MAX_VALUE, f, + Color.BLACK, HtmlRenderer.STYLE_CLIP, false)))); + } + } else { + return 0; + } + } + + private Dimension calcPreferredSize(HtmlRendererImpl r) { + Insets ins = r.getInsets(); + Dimension prefSize = new java.awt.Dimension(ins.left + ins.right, ins.top + ins.bottom); + String text = r.getText(); + + Graphics g = r.getGraphics(); + Icon icon = r.getIcon(); + + if (text != null) { + FontMetrics fm = g.getFontMetrics (r.getFont()); + prefSize.height += fm.getMaxAscent() + fm.getMaxDescent() + 2; + } + + if (icon != null) { + if (r.isCentered()) { + prefSize.height += icon.getIconHeight() + r.getIconTextGap(); + prefSize.width += icon.getIconWidth(); + } else { + prefSize.height = Math.max (icon.getIconHeight(), prefSize.height); + prefSize.width += icon.getIconWidth() + r.getIconTextGap(); + } + } + + //Antialiasing affects the text metrics, so use it if needed when + //calculating preferred size or the result here will be narrower + //than the space actually needed + if (antialias) { + //For L&Fs such as Aqua and SmoothMetal, we will need to manually apply + //rendering hints to get antialiasing, since we're doing our + //own painting logic - they don't do this for things they don't + //know about + ((Graphics2D)g).addRenderingHints(getHints()); + } + + int textwidth = textWidth(text, g, r.getFont(), r.isHtml()) + 4; + if (r.isCentered()) { + prefSize.width = Math.max (prefSize.width, textwidth + ins.right + ins.left); + } else { + prefSize.width += textwidth + r.getIndent(); + } +// System.err.println ("PrefSize: " + prefSize + " text " + r.getText()); + return prefSize; + } + + private static Map hintsMap = null; + static final Map getHints() { + if (hintsMap == null) { + hintsMap = new HashMap(); + hintsMap.put(RenderingHints.KEY_FRACTIONALMETRICS, + RenderingHints.VALUE_FRACTIONALMETRICS_ON); + hintsMap.put(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + hintsMap.put(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + } + return hintsMap; + } + + + public void update(Graphics g, JComponent c) { + Color bg = getBackgroundFor ((HtmlRendererImpl) c); + if (bg != null) { + g.setColor(bg); + g.fillRect(0, 0, c.getWidth(),c.getHeight()); + if (((HtmlRendererImpl) c).isLeadSelection()) { + Color focus = UIManager.getColor("textHighlight"); + if (focus == null) { + focus = Color.BLUE; + } + g.setColor(focus); + g.drawRect (0, 0, c.getWidth()-1, c.getHeight()-1); + } + } + paint(g, c); + } + + public void paint(Graphics g, JComponent c) { + if (antialias) { + //For L&Fs such as Aqua and SmoothMetal, we will need to manually apply + //rendering hints to get antialiasing, since we're doing our + //own painting logic - they don't do this for things they don't + //know about + ((Graphics2D)g).addRenderingHints(getHints()); + } + HtmlRendererImpl r = (HtmlRendererImpl) c; + if (r.isCentered()) { + paintIconAndTextCentered (g, r); + } else { + paintIconAndText (g, r); + } + } + + /** Actually paint the icon and text using our own html rendering engine. */ + private void paintIconAndText(Graphics g, HtmlRendererImpl r) { + Font f = r.getFont(); + g.setFont(f); + FontMetrics fm = g.getFontMetrics(); + + //Find out what height we need + int txtH = fm.getHeight(); + Insets ins = r.getInsets(); + + //find out the available height less the insets + int availH = r.getHeight() - (ins.top + ins.bottom); + + int txtY; + if (availH >= txtH) { + //Center the text if we have space + txtY = txtH + ins.top + ((availH / 2) - (txtH / 2)) - fm.getMaxDescent(); + } else { + //Okay, it's not going to fit, punt. + txtY = fm.getMaxAscent(); + } + int txtX = r.getIndent(); + + Icon icon = r.getIcon(); + //Check the icon non-null and height (see TabData.NO_ICON for why) + if (icon != null && icon.getIconWidth() > 0 && icon.getIconHeight() > 0) { + int iconY; + if (availH > icon.getIconHeight()) { + //add 2 to make sure icon top pixels are not cut off by outline + iconY = ins.top + ((availH / 2) - (icon.getIconHeight() / 2));// + 2; + } else if (availH == icon.getIconHeight()){ + //They're an exact match, make it 0 + iconY = 0; + } else { + //Won't fit; make the top visible and cut the rest off (option: + //center it and clip it on top and bottom - probably even harder + //to recognize that way, though) + iconY = ins.top; + } + //add in the insets + int iconX = ins.left + r.getIndent() + 1; //+1 to get it out of the way of the focus border + try { + //Diagnostic - the CPP module currently is constructing + //some ImageIcon from a null image in Options. So, catch it and at + //least give a meaningful message that indicates what node + //is the culprit + icon.paintIcon(r, g, iconX, iconY); + + } catch (NullPointerException npe) { + ErrorManager.getDefault().annotate(npe, ErrorManager.EXCEPTION, + "Probably an ImageIcon with a null source image: " + icon + " - " + //NOI18N + r.getText(), null, null, null); //NOI18N + ErrorManager.getDefault().notify(npe); + } + txtX = iconX + icon.getIconWidth() + r.getIconTextGap(); + } else { + //If there's no icon, paint the text where the icon would start + txtX += ins.left; + } + + String text = r.getText(); + if (text == null) { + //No text, we're done + return; + } + + //Get the available horizontal pixels for text + int txtW = icon != null ? r.getWidth() - (ins.left + ins.right + + icon.getIconWidth() + r.getIconTextGap() + r.getIndent()) : r.getWidth() - + (ins.left + ins.right + r.getIndent()); + + Color foreground = getForegroundFor (r); + + if (r.isHtml()) { + HtmlRenderer.renderHTML(text, g, txtX, txtY, txtW, txtH, f, + foreground, r.getRenderStyle(), true); + } else { + HtmlRenderer.renderString(text, g, txtX, txtY, txtW, txtH, f, + foreground, r.getRenderStyle(), true); + } + } + + private void paintIconAndTextCentered (Graphics g, HtmlRendererImpl r) { + Insets ins = r.getInsets(); + Icon ic = r.getIcon(); + int w = r.getWidth() - (ins.left + ins.right); + int txtX = ins.left; + int txtY = 0; + if (ic != null && ic.getIconWidth() > 0 && ic.getIconHeight() > 0) { + int iconx = w > ic.getIconWidth() ? (w / 2) - (ic.getIconWidth() / 2) : txtX; + int icony = 0; + ic.paintIcon(r, g, iconx, icony); + txtY += ic.getIconHeight() + r.getIconTextGap(); + } + int txtW = r.getPreferredSize().width; + txtX = txtW < r.getWidth() ? (r.getWidth() / 2) - (txtW / 2) : 0; + int txtH = r.getHeight() - txtY; + + Font f = r.getFont(); + g.setFont(f); + FontMetrics fm = g.getFontMetrics(f); + txtY += fm.getMaxAscent(); + + Color foreground = getForegroundFor (r); + if (r.isHtml()) { + HtmlRenderer.renderHTML(r.getText(), g, txtX, txtY, txtW, txtH,f, + foreground, r.getRenderStyle(), true); + } else { + HtmlRenderer.renderString(r.getText(), g, txtX, txtY, txtW, txtH, r.getFont(), + foreground, r.getRenderStyle(), true); + } + } + + static Color getBackgroundFor (HtmlRendererImpl r) { + if (r.isOpaque()) { + return r.getBackground(); + } + + if (r.isSelected() && !r.isParentFocused() && !isGTK()) { + return getUnfocusedSelectionBackground(); + } + + if (isGTK()) { + //GTK does its own thing, we'll only screw it up by painting the background ourselves + return null; + } + + Color result = null; + + if (r.isSelected()) { + switch (r.getType()) { + case HtmlRendererImpl.TYPE_LIST : + result = UIManager.getColor ("List.selectionBackground"); //NOI18N + break; + case HtmlRendererImpl.TYPE_TABLE : + result = UIManager.getColor ("Table.selectionBackground"); //NOI18N + break; + case HtmlRendererImpl.TYPE_TREE : + return UIManager.getColor ("Tree.selectionBackground"); //NOI18N + } + return result == null ? r.getBackground() : result; + } + return null; + } + + static Color getForegroundFor (HtmlRendererImpl r) { + if (r.isSelected() && !r.isParentFocused()) { + return getUnfocusedSelectionForeground(); + } + + if (!r.isEnabled()) { + return UIManager.getColor ("textInactiveText"); //NOI18N + } + + Color result = null; + if (r.isSelected()) { + switch (r.getType()) { + case HtmlRendererImpl.TYPE_LIST : + result = UIManager.getColor ("List.selectionForeground"); //NOI18N + case HtmlRendererImpl.TYPE_TABLE : + result = UIManager.getColor ("Table.selectionForeground"); //NOI18N + case HtmlRendererImpl.TYPE_TREE : + result = UIManager.getColor ("Tree.selectionForeground"); //NOI18N + } + } + return result == null ? r.getForeground() : result; + } + + private static boolean isGTK() { + return "GTK".equals (UIManager.getLookAndFeel().getID()); + } + + private static Color unfocusedSelBg = null; + private static Color unfocusedSelFg = null; + /** Get the system-wide unfocused selection background color */ + private static Color getUnfocusedSelectionBackground() { + if (unfocusedSelBg == null) { + //allow theme/ui custom definition + unfocusedSelBg = + UIManager.getColor("nb.explorer.unfocusedSelBg"); //NOI18N + if (unfocusedSelBg == null) { + //try to get standard shadow color + unfocusedSelBg = UIManager.getColor("controlShadow"); //NOI18N + if (unfocusedSelBg == null) { + //Okay, the look and feel doesn't suport it, punt + unfocusedSelBg = Color.lightGray; + } + //Lighten it a bit because disabled text will use controlShadow/ + //gray + unfocusedSelBg = unfocusedSelBg.brighter(); + } + } + return unfocusedSelBg; + } + + /** Get the system-wide unfocused selection foreground color */ + private static Color getUnfocusedSelectionForeground() { + if (unfocusedSelFg == null) { + //allow theme/ui custom definition + unfocusedSelFg = + UIManager.getColor("nb.explorer.unfocusedSelFg"); //NOI18N + if (unfocusedSelFg == null) { + //try to get standard shadow color + unfocusedSelFg = UIManager.getColor("textText"); //NOI18N + if (unfocusedSelFg == null) { + //Okay, the look and feel doesn't suport it, punt + unfocusedSelFg = Color.BLACK; + } + } + } + return unfocusedSelFg; + } +} Index: openide/src/org/openide/awt/HtmlRenderer.java =================================================================== RCS file: openide/src/org/openide/awt/HtmlRenderer.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/src/org/openide/awt/HtmlRenderer.java 2 May 2004 11:03:21 -0000 @@ -0,0 +1,1236 @@ +/* + * 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-2003 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +/* + * HtmlRenderer.java + * + * Created on January 2, 2004, 12:49 AM + */ +package org.openide.awt; + +import org.openide.ErrorManager; + +import javax.swing.*; +import javax.swing.table.TableCellRenderer; +import javax.swing.tree.TreeCellRenderer; +import java.awt.*; +import java.awt.font.LineMetrics; +import java.awt.geom.Area; +import java.awt.geom.Rectangle2D; +import java.util.*; + +/** + * A lightweight html renderer supporting a minimal subset of HTML used for + * markup purposes only - basic font styles, colors, etc. Also supports + * named logical colors specified by a preceding ! character for specifying + * colors that should be looked up in the current look and feel's UIDefaults + * (e.g. <font color=&!textText&>). + *

+ * Provides a generic cell renderer implementation which can be used for trees, tables, + * lists, combo boxes, etc. + *

+ * If you only need to paint some HTML quickly, use the static methods for + * painting - renderString, renderPlainString or + * renderHtml. These methods differ as follows: + *

    + *
  • renderString will check the string for opening HTML tags + * (upper or lower but not mixed case) and call either renderPlainString + * or renderHtml as appropriate. Note this method does not tolerate + * whitespace in opening html tags - it expects exactly 6 characters to make up + * the opening tag if present.
  • + *
  • renderPlainString simply renders a string to the graphics context, + * takes the same agruments as renderHtml, but will also honor + * STYLE_TRUNCATE, so strings can be rendered with trailing + * elipsis if there is not enough space
  • + *
  • renderHtml renders whatever is passed to it as HTML, regardless + * of whether it has opening HTML tags or not. It can be used to render plain + * strings, but renderPlainString is faster for that. It is useful + * if you want to render a string you know to be compliant + * HTML markup, but which does not have opening and closing HTML tags (though + * they are harmless if present).
  • + *

    + * This parser is designed entirely for performance; there are no separate parsing + * and rendering loops. In order to acheive its performance, some trade offs + * are required. + * To reiterate: This is not a forgiving HTML parser - the HTML supplied + * must follow the guidelines documented here! + *

    + * The following tags are supported, in upper or lower (but not mixed) case: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    <B>Boldface text
    <S>Strikethrough text
    <U>Underline text
    <I>Italic text
    <EM>Emphasized text (same as italic)
    <STRONG>Strong text (same as bold)
    <font>Font color - font attributes other than color are not supported. Colors + * may be specified as hexidecimal strings, such as #FF0000 or as logical colors + * defined in the current look and feel by specifying a ! character as the first + * character of the color name. Logical colors are colors available from the + * current look and feel's UIManager. For example, <font + * color="!Tree.background"> will set the font color to the + * result of UIManager.getColor("Tree.background"). + * Font size tags are not supported. + *
    + * The lightweight html renderer supports the following named sgml character + * entities: quot, lt, amp, lsquo, rsquo, ldquo, rdquo, ndash, mdash, ne, + * le, ge, copy, reg, trade. . It also supports numeric entities + * (e.g. &8822;). + *

    Why not use the JDK's HTML support? The JDK's HTML support works + * well for stable components, but suffers from performance problems in the + * case of cell renderers - each call to set the text (which happens once per + * cell, per paint) causes a document tree to be created in memory. For small, + * markup only strings, this is overkill. For rendering short strings + * (for example, in a tree or table cell renderer) + * with limited HTML, this method is approximately 10x faster than standard + * Swing HTML rendering. + * + *

    Specifying logical colors
    + * Hardcoded text colors are undesirable, as they can be incompatible (even + * invisible) on some look and feels or themes, depending on the background + * color. + * The lightweight HTML renderer supports a non-standard syntax for specifying + * font colors via a key for a color in the UI defaults for the current look + * and feel. This is accomplished by prefixing the key name with a ! + * character. For example: <font color='!controlShadow'>. + * + *

    Modes of operation
    + * This method supports two modes of operation: + *

      + *
    1. STYLE_CLIP - as much text as will fit in the pixel width passed + * to the method should be painted, and the text should be cut off at the maximum + * width or clip rectangle maximum X boundary for the graphics object, whichever is + * smaller.
    2. + *
    3. STYLE_TRUNCATE - paint as much text as will fit in the pixel + * width passed to the method, but paint the last three characters as .'s, in the + * same manner as a JLabel truncates its text when the available space is too + * small.
    4. + *
    + *

    + * The paint methods can also be used in non-painting mode to establish the space + * necessary to paint a string. This is accomplished by passing the value of the + * paint argument as false. The return value will be the required + * width in pixels + * to display the text. Note that in order to retrieve an + * accurate value, the argument for available width should be passed + * as Integer.MAX_VALUE or an appropriate maximum size - otherwise + * the return value will either be the passed maximum width or the required + * width, whichever is smaller. Also, the clip shape for the passed graphics + * object should be null or a value larger than the maximum possible render size, + * or text size measurement will stop at the clip bounds. HtmlRenderer.getGraphics() + * will always return non-null and non-clipped, and is suitable to pass in such a + * situation. + *

    + * + * @since 4.30 + * @see org.openide.nodes.Node#getHtmlDisplayName + * @see org.openide.filesystems.FileSystem.HtmlStatus + * @author Tim Boudreau + */ +public final class HtmlRenderer { + private static HtmlRendererImpl LABEL = null; + + private HtmlRenderer() { + //do nothing + } + + /** + * Returns an instance of Renderer which may be used as a table/tree/list cell renderer. + * This method must be called on the AWT event thread. If you know you will + * be passing it legal HTML (legal as documented here), call setHtml(true) on the + * result of this call after calling getNNNCellRenderer to provide this hint. + * + * @return A cell renderer that can render HTML. + */ + public static final Renderer createRenderer () { + return new HtmlRendererImpl(); + } + + /** + * For HTML rendering jobs outside of trees/lists/tables, returns a JLabel which will paint its text using + * the lightweight HTML renderer. The result of this call will implement the Renderer interface. + * To use, simply call sharedLabel(), configure the text, etc., and paint it to a graphics context + * (SwingUtilities.paintComponent() is not needed - simply call paint (Graphics), which is much faster), or + * take its preferred size if using it to measure text dimensions. + *

    + * Do not hold a reference to the return value of this call - other code may reconfigure it unexpectedly. + * The shared instance's state is reset to default font, alignment, etc., on each call to sharedLabel. + *

    + * This method must be called only from the AWT event thread; this is enforced with assertions. + * + * @return A JLabel which can render a subset of html very quickly + * @param renderStyle The rendering style. Currently supported are STYLE_TRUNCATE and STYLE_CLIP. + * @param isHtml Null if the text might or might not be HTML and will have opening HTML tags if it is. + * Boolean.TRUE if the text is definitely HTML, and might not have opening HTML tags. + * Boolean.FALSE if the text should be rendered as plain text even if it contains + * HTML markup. + */ + public static final JLabel sharedLabel (int renderStyle, Boolean isHtml) { + assert SwingUtilities.isEventDispatchThread() : "HtmlRenderer.sharedLabel " + //NOI18N + "may only be accessed on the event dispatch thread"; //NOI18N + + switch (renderStyle) { + case STYLE_TRUNCATE : + case STYLE_CLIP : + break; + default : + throw new IllegalArgumentException ("Unknown render style " + renderStyle); //NOI18N + } + + if (LABEL == null) { + LABEL = new HtmlRendererImpl(); + } else { + LABEL.reset(); + } + + if (isHtml != null) { + LABEL.setHtml(isHtml.booleanValue()); + } + + LABEL.setRenderStyle(renderStyle); + return LABEL; + } + + /** Interface aggregating TableCellRenderer, TreeCellRenderer and ListCellRenderer. + * Return type of sharedInstance(). + */ + public interface Renderer extends TableCellRenderer, TreeCellRenderer, ListCellRenderer { + /** Indicate that the component being rendered has keyboard focus. NetBeans requires that a different + * selection color be used depending on whether the view has focus. + * + * @param parentFocused Whether or not the focused selection color should be used + */ + void setParentFocused (boolean parentFocused); + + /** + * Indicate that the text should be painted centered below the icon. This is primarily used + * by org.openide.explorer.view.IconView + * + * @param centered Whether or not centered painting should be used. + */ + void setCentered (boolean centered); + + /** + * Set a number of pixels the icon and text should be indented. Used by ChoiceView and ListView to + * fake tree-style nesting. This value has no effect if setCentered(true) has been called. + * + * @param pixels The number of pixels to indent + */ + void setIndent (int pixels); + + /** + * Explicitly tell the renderer it is going to receive HTML markup, or it is not. If the renderer should + * check the string for opening HTML tags to determine this, don't call this method. If you know + * the string will be compliant HTML, it is preferable to call this method with true; if you want to intentionally + * render HTML markup literally, call this method with false. + * + * @param val + */ + void setHtml (boolean val); + + /** + * Set the rendering style - this can be JLabel-style truncated-with-elipsis (...) text, or clipped text. + * The default is STYLE_CLIP. + * + * @param style The text style + */ + void setRenderStyle (int style); + + /** Set the icon to be used for painting + * + * @param icon An icon or null + */ + void setIcon (Icon icon); + + /** Clear any stale data from previous use by other components, + * clearing the icon, text, disabled state and other customizations, returning the component + * to its initialized state. This is done automatically when get*CellRenderer() is called, + * and to the shared instance when sharedLabel() is called.

    + * Users of the static sharedLabel() method may want to call this method if they + * use the returned instance more than once without again calling sharedLabel(). + */ + void reset(); + + /** Set the text to be displayed. Use this if the object being rendered's toString() does not + * return a real user-displayable string, after calling get**CellRenderer(). Typically after calling + * this one calls setHtml() if the text is known to either be or not be HTML markup. + * + * @param txt The text that should be displayed + */ + void setText (String txt); + + /** + * Convenience method to set the gap between the icon and text. + * + * @param gap an integer number of pixels + */ + void setIconTextGap (int gap); + } + + /** Stack object used during HTML rendering to hold previous colors in + * the case of nested color entries. */ + private static Stack colorStack = new Stack(); + /** Constant used by renderString, renderPlainString + * renderHTML, and HtmlRenderer.setRenderStyle + * if painting should simply be cut off at the boundary of the cooordinates passed. */ + public static final int STYLE_CLIP=0; + /** Constant used by renderString, renderPlainString + * renderHTML and HtmlRenderer.setRenderStyle if + * painting should produce an ellipsis (...) + * if the text would overlap the boundary of the coordinates passed */ + public static final int STYLE_TRUNCATE=1; + /** Constant used by renderString, renderPlainString + * renderHTML and HtmlRenderer.setRenderStyle + * if painting should word wrap the text. In + * this case, the return value of any of the above methods will be the + * height, rather than width painted. */ + private static final int STYLE_WORDWRAP=2; + /** System property to cause exceptions to be thrown when unparsable + * html is encountered */ + private static final boolean strictHtml = Boolean.getBoolean( + "netbeans.lwhtml.strict"); //NOI18N + /** Cache for strings which have produced errors, so we don't post an + * error message more than once */ + private static Set badStrings=null; + /** Definitions for a limited subset of sgml character entities */ + private static final Object[] entities = new Object[] { + new char[] {'g','t'}, new char[] {'l','t'}, //NOI18N + new char[] {'q','u','o','t'}, new char[] {'a','m','p'}, //NOI18N + new char[] {'l','s','q','u','o'}, //NOI18N + new char[] {'r','s','q','u','o'}, //NOI18N + new char[] {'l','d','q','u','o'}, //NOI18N + new char[] {'r','d','q','u','o'}, //NOI18N + new char[] {'n','d','a','s','h'}, //NOI18N + new char[] {'m','d','a','s','h'}, //NOI18N + new char[] {'n','e'}, //NOI18N + new char[] {'l','e'}, //NOI18N + new char[] {'g','e'}, //NOI18N + + new char[] {'c','o','p','y'}, //NOI18N + new char[] {'r','e','g'}, //NOI18N + new char[] {'t','r','a','d','e'} //NOI18N + //The rest of the SGML entities are left as an excercise for the reader + }; //NOI18N + /** Mappings for the array of sgml character entities to characters */ + private static final char[] entitySubstitutions = new char[] { + '>','<','"','&',8216, 8217, 8220, 8221, 8211, 8212, 8800, 8804, 8805, //NOI18N + 169, 174, 8482 + }; + + /**Render a string to a graphics instance, using the same API as renderHTML(). + * Can render a string using JLabel-style ellipsis (...) in the case that + * it will not fit in the passed rectangle, if the style parameter is + * STYLE_CLIP. Returns the width in pixels successfully painted. + * This method is not thread-safe and should not be called off + * the AWT thread. + * + * @see #renderHTML */ + public static double renderPlainString(String s, Graphics g, int x, int y, int w, int h, Font f, Color defaultColor, int style, boolean paint) { + assert SwingUtilities.isEventDispatchThread(); + //per Jarda's request, keep the word wrapping code but don't expose it. + if (style < 0 || style > 1) { + throw new IllegalArgumentException( + "Unknown rendering mode: " + style); //NOI18N + } + return _renderPlainString(s, g, x, y, w, h, f, defaultColor, style, + paint); + } + + private static double _renderPlainString(String s, Graphics g, int x, int y, int w, int h, Font f, Color foreground, int style, boolean paint) { + FontMetrics fm = g.getFontMetrics(f); + Rectangle2D r = fm.getStringBounds(s, g); + if (paint) { + g.setColor(foreground); + g.setFont(f); + if ((r.getWidth() <= w) || (style == STYLE_CLIP)) { + g.drawString(s, x, y); + } else { + char[] chars = new char[s.length()]; + s.getChars(0, s.length()-1, chars, 0); + if (chars.length == 0) { + return 0; + } + double chWidth = r.getWidth() / chars.length; + int estCharsOver = new Double((r.getWidth() - w) / chWidth).intValue(); + if (style == STYLE_TRUNCATE) { + int length = chars.length - estCharsOver; + if (length <=0) { + return 0; + } + if (paint) { + if (length > 3) { + Arrays.fill(chars, length-3, length, '.'); //NOI18N + g.drawChars(chars, 0, length, x, y); + } else { + Shape shape = g.getClip(); + if (s != null) { + Area area = new Area(shape); + area.intersect (new Area(new Rectangle(x,y,w,h))); + g.setClip(area); + } else { + g.setClip(new Rectangle(x,y,w,h)); + } + g.drawString("...", x,y); + g.setClip(shape); + } + } + } else { + //TODO implement plaintext word wrap if we want to support it at some point + } + } + } + return r.getWidth(); + } + + /** Render a string to a graphics context, using HTML markup if the string + * begins with html tags. Delegates to renderPlainString() + * or renderHTML() as appropriate. See the documentation for + * renderHTML() for details of the subset of HTML that is + * supported. + * @param s The string to render + * @param g A graphics object into which the string should be drawn, or which should be + * used for calculating the appropriate size + * @param x The x coordinate to paint at. + * @param y The y position at which to paint. Note that this method does not calculate font + * height/descent - this value should be the baseline for the line of text, not + * the upper corner of the rectangle to paint in. + * @param w The maximum width within which to paint. + * @param h The maximum height within which to paint. + * @param f The base font to be used for painting or calculating string width/height. + * @param defaultColor The base color to use if no font color is specified as html tags + * @param style The wrapping style to use, either STYLE_CLIP, + * or STYLE_TRUNCATE + * @param paint True if actual painting should occur. If false, this method will not actually + * paint anything, only return a value representing the width/height needed to + * paint the passed string. + * @return The width in pixels required + * to paint the complete string, or the passed parameter w if it is + * smaller than the required width. + */ + public static double renderString(String s, Graphics g, int x, int y, int w, int h, Font f, Color defaultColor, int style, boolean paint) { + if (s.startsWith(" 1) { + throw new IllegalArgumentException( + "Unknown rendering mode: " + style); //NOI18N + } + return _renderHTML(6, s, g, x, y, w, h, f, defaultColor, style, paint); + } else { + return renderPlainString(s, g, x, y, w, h, f, defaultColor, style, paint); + } + } + + /** Render a string as HTML using a fast, lightweight renderer supporting a limited + * subset of HTML. The following tags are supported, in upper or lower case: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    <B>Boldface text
    <S>Strikethrough text
    <U>Underline text
    <I>Italic text
    <EM>Emphasized text (same as italic)
    <STRONG>Strong text (same as bold)
    <font>Font color - font attributes other than color are not supported. Colors + * may be specified as hexidecimal strings, such as #FF0000 or as logical colors + * defined in the current look and feel by specifying a ! character as the first + * character of the color name. Logical colors are colors available from the + * current look and feel's UIManager. For example, <font + * color="!Tree.background"> will set the font color to the + * result of UIManager.getColor("Tree.background"). + * Font size tags are not supported. + *
    + * The lightweight html renderer supports the following named sgml character + * entities: quot, lt, amp, lsquo, rsquo, ldquo, rdquo, ndash, mdash, ne, + * le, ge, copy, reg, trade. . It also supports numeric entities + * (e.g. &#8822;). + *

    When to use this method instead of the JDK's HTML support: when + * rendering short strings (for example, in a tree or table cell renderer) + * with limited HTML, this method is approximately 10x faster than JDK HTML + * rendering (it does not build and parse a document tree). + * + *

    Specifying logical colors
    + * Hardcoded text colors are undesirable, as they can be incompatible (even + * invisible) on some look and feels or themes. + * The lightweight HTML renderer supports a non-standard syntax for specifying + * font colors via a key for a color in the UI defaults for the current look + * and feel. This is accomplished by prefixing the key name with a ! + * character. For example: <font color='!controlShadow'>. + * + *

    Modes of operation
    + * This method supports two modes of operation: + *

      + *
    1. STYLE_CLIP - as much text as will fit in the pixel width passed + * to the method should be painted, and the text should be cut off at the maximum + * width or clip rectangle maximum X boundary for the graphics object, whichever is + * smaller.
    2. + *
    3. STYLE_TRUNCATE - paint as much text as will fit in the pixel + * width passed to the method, but paint the last three characters as .'s, in the + * same manner as a JLabel truncates its text when the available space is too + * small.
    4. + *
    + *

    + * This method can also be used in non-painting mode to establish the space + * necessary to paint a string. This is accomplished by passing the value of the + * paint argument as false. The return value will be the required + * width in pixels + * to display the text. Note that in order to retrieve an + * accurate value, the argument for available width should be passed + * as Integer.MAX_VALUE or an appropriate maximum size - otherwise + * the return value will either be the passed maximum width or the required + * width, whichever is smaller. Also, the clip shape for the passed graphics + * object should be null or a value larger than the maximum possible render size. + *

    + * This method will log a warning if it encounters HTML markup it cannot + * render. To aid diagnostics, if NetBeans is run with the argument + * -J-Dnetbeans.lwhtml.strict=true an exception will be thrown + * when an attempt is made to render unsupported HTML.

    + *

    + * @param s The string to render + * @param g A graphics object into which the string should be drawn, or which should be + * used for calculating the appropriate size + * @param x The x coordinate to paint at. + * @param y The y position at which to paint. Note that this method does not calculate font + * height/descent - this value should be the baseline for the line of text, not + * the upper corner of the rectangle to paint in. + * @param w The maximum width within which to paint. + * @param h The maximum height within which to paint. + * @param f The base font to be used for painting or calculating string width/height. + * @param defaultColor The base color to use if no font color is specified as html tags + * @param style The wrapping style to use, either STYLE_CLIP, + * or STYLE_TRUNCATE + * @param paint True if actual painting should occur. If false, this method will not actually + * paint anything, only return a value representing the width/height needed to + * paint the passed string. + * @return The width in pixels required + * to paint the complete string, or the passed parameter w if it is + * smaller than the required width. + */ + public static double renderHTML(String s, Graphics g, int x, int y, + int w, int h, Font f, + Color defaultColor, int style, + boolean paint) { + + assert SwingUtilities.isEventDispatchThread(); + + //per Jarda's request, keep the word wrapping code but don't expose it. + if (style < 0 || style > 1) { + throw new IllegalArgumentException( + "Unknown rendering mode: " + style); //NOI18N + } + return _renderHTML(0, s, g, x, y, w, h, f, defaultColor, style, + paint); + } + + /** Implementation of HTML rendering */ + private static double _renderHTML(int pos, String s, Graphics g, int x, + int y, int w, int h, Font f, Color defaultColor, int style, + boolean paint) { + + //Thread safety - avoid allocating memory for the common case + Stack colorStack = SwingUtilities.isEventDispatchThread() ? + HtmlRenderer.colorStack : new Stack(); + + g.setColor(defaultColor); + g.setFont(f); + char[] chars = s.toCharArray(); + int origX = x; + boolean done = false; //flag if rendering completed, either by finishing the string or running out of space + boolean inTag = false; //flag if the current position is inside a tag, and the tag should be processed rather than rendering + boolean inClosingTag = false; //flag if the current position is inside a closing tag + boolean strikethrough = false; //flag if a strikethrough line should be painted + boolean underline = false; //flag if an underline should be painted + boolean bold = false; //flag if text is currently bold + boolean italic = false; //flag if text is currently italic + boolean truncated = false; //flag if the last possible character has been painted, and the next loop should paint "..." and return + double widthPainted = 0; //the total width painted, for calculating needed space + double heightPainted = 0; //the total height painted, for calculating needed space + boolean lastWasWhitespace = false; //flag to skip additional whitespace if one whitespace char already painted + double lastHeight=0; //the last line height, for calculating total required height + FontMetrics baseFontMetrics = g.getFontMetrics(g.getFont()); + + + double dotWidth = 0; + //Calculate the width of a . character if we may need to truncate + if (style == STYLE_TRUNCATE) { + dotWidth = g.getFontMetrics().charWidth('.'); //NOI18N + } + + /* How this all works, for anyone maintaining this code (hopefully it will + never need it): + 1. The string is converted to a char array + 2. Loop over the characters. Variable pos is the current point. + 2a. See if we're in a tag by or'ing inTag with currChar == '<' + If WE ARE IN A TAG: + 2a1: is it an opening tag? + If YES: + - Identify the tag, Configure the Graphics object with + the appropriate font, color, etc. Set pos = the first + character after the tag + If NO (it's a closing tag) + - Identify the tag. Reconfigure the Graphics object + with the state it should be in outside the tag + (reset the font if italic, pop a color off the stack, etc.) + 2b. If WE ARE NOT IN A TAG + - Locate the next < or & character or the end of the string + - Paint the characters using the Graphics object + - Check underline and strikethrough tags, and paint line if + needed + See if we're out of space, and do the right thing for the style + (paint ..., give up or skip to the next line) + */ + + //Clear any junk left behind from a previous rendering loop + colorStack.clear(); + + //Enter the painting loop + while (!done) { + if (pos == s.length()) { + return widthPainted; + } + //see if we're in a tag + try { + inTag |= chars[pos] == '<'; + } catch (ArrayIndexOutOfBoundsException e) { + //Should there be any problem, give a meaningful enough + //message to reproduce the problem + ArrayIndexOutOfBoundsException aib = + new ArrayIndexOutOfBoundsException( + "HTML rendering failed at position " + pos + " in String \"" //NOI18N + + s + "\". Please report this at http://www.netbeans.org"); //NOI18N + + if (strictHtml) { + throw aib; + } else { + ErrorManager.getDefault().notify (ErrorManager.WARNING, aib); + return renderPlainString(s, g, x, y, w, h, f, defaultColor, style, paint); + } + } + inClosingTag = inTag && (pos+1 < chars.length) && chars[pos+1] == '/'; //NOI18N + + if (truncated) { + //Then we've almost run out of space, time to print ... and quit + g.setColor(defaultColor); + g.setFont(f); + if (paint) { + g.drawString("...", x, y); //NOI18N + } + done = true; + } else if (inTag) { + //If we're in a tag, don't paint, process it + pos++; + int tagEnd = pos; + while (!done && (chars[tagEnd] != '>')) { + done = tagEnd == chars.length -1; + tagEnd++; + } + + if (inClosingTag) { + //Handle closing tags by resetting the Graphics object (font, etc.) + pos++; + switch (chars[pos]) { + case 'P' : //NOI18N + case 'p' : //NOI18N + case 'H' : //NOI18N + case 'h' : break; //ignore html opening/closing tags + case 'B' : //NOI18N + case 'b' : //NOI18N + if (chars[pos+1] == 'r' || chars[pos+1] == 'R') { + break; + } + if (!bold) { + throwBadHTML("Closing bold tag w/o " + //NOI18N + "opening bold tag", pos, chars); //NOI18N + } + if (italic) { + g.setFont(f.deriveFont(Font.ITALIC)); + } else { + g.setFont(f.deriveFont(Font.PLAIN)); + } + bold = false; + break; + case 'E' : //NOI18N + case 'e' : //em tag + case 'I' : //NOI18N + case 'i' : //NOI18N + if (bold) { + g.setFont(f.deriveFont(Font.BOLD)); + } else { + g.setFont(f.deriveFont(Font.PLAIN)); + } + if (!italic) { + throwBadHTML("Closing italics tag w/o" //NOI18N + + "opening italics tag", pos, chars); //NOI18N + } + italic = false; + break; + case 'S' : //NOI18N + case 's' : //NOI18N + switch (chars[pos+1]) { + case 'T' : //NOI18N + case 't' : if (italic) { //NOI18N + g.setFont(f.deriveFont( + Font.ITALIC)); + } else { + g.setFont(f.deriveFont( + Font.PLAIN)); + } + bold = false; + break; + case '>' : //NOI18N + strikethrough = false; + break; + } + break; + case 'U' : //NOI18N + case 'u' : underline = false; //NOI18N + break; + case 'F' : //NOI18N + case 'f' : //NOI18N + if (colorStack.isEmpty()) { + g.setColor(defaultColor); + } else { + g.setColor((Color) colorStack.pop()); + } + break; + default : + throwBadHTML( + "Malformed or unsupported HTML", //NOI18N + pos, chars); + } + } else { + //Okay, we're in an opening tag. See which one and configure the Graphics object + switch (chars[pos]) { + case 'B' : //NOI18N + case 'b' : //NOI18N + switch (chars[pos+1]) { + case 'R' : //NOI18N + case 'r' : //NOI18N + if (style == STYLE_WORDWRAP) { + x = origX; + int lineHeight = g.getFontMetrics().getHeight(); + y += lineHeight; + heightPainted += lineHeight; + widthPainted = 0; + } + break; + case '>' : + bold = true; + if (italic) { + g.setFont(f.deriveFont(Font.BOLD | Font.ITALIC)); + } else { + g.setFont(f.deriveFont(Font.BOLD)); + } + break; + } + break; + case 'e' : //NOI18N //em tag + case 'E' : //NOI18N + case 'I' : //NOI18N + case 'i' : //NOI18N + italic = true; + if (bold) { + g.setFont(f.deriveFont(Font.ITALIC | Font.BOLD)); + } else { + g.setFont(f.deriveFont(Font.ITALIC)); + } + break; + case 'S' : //NOI18N + case 's' : //NOI18N + switch (chars[pos+1]) { + case '>' : + strikethrough = true; + break; + case 'T' : + case 't' : + bold = true; + if (italic) { + g.setFont(f.deriveFont(Font.BOLD | Font.ITALIC)); + } else { + g.setFont(f.deriveFont(Font.BOLD)); + } + break; + } + break; + case 'U' : //NOI18N + case 'u' : //NOI18N + underline = true; + break; + case 'f' : //NOI18N + case 'F' : //NOI18N + Color c = findColor(chars, pos, tagEnd); + colorStack.push(g.getColor()); + g.setColor(c); + break; + case 'P' : //NOI18N + case 'p' : //NOI18N + if (style == STYLE_WORDWRAP) { + x = origX; + int lineHeight=g.getFontMetrics().getHeight(); + y += lineHeight + (lineHeight / 2); + heightPainted = y + lineHeight; + widthPainted = 0; + } + break; + case 'H' : + case 'h' : //Just an opening HTML tag + if (pos == 1) { + break; + } + default : throwBadHTML( + "Malformed or unsupported HTML", pos, chars); //NOI18N + } + } + + pos = tagEnd + (done ? 0 : 1); + inTag = false; + } else { + //Okay, we're not in a tag, we need to paint + + if (lastWasWhitespace) { + //Skip multiple whitespace characters + while (pos < s.length() && Character.isWhitespace(chars[pos])) { + pos++; + } + //Check strings terminating with multiple whitespace - + //otherwise could get an AIOOBE here + if (pos == chars.length - 1) { + return style != STYLE_WORDWRAP ? widthPainted : heightPainted; + } + } + + //Flag to indicate if an ampersand entity was processed, + //so the resulting & doesn't get treated as the beginning of + //another entity (and loop endlessly) + boolean isAmp=false; + //Flag to indicate the next found < character really should + //be painted (it came from an entity), it is not the beginning + //of a tag + boolean nextLtIsEntity=false; + int nextTag = chars.length-1; + if ((chars[pos] == '&')) { //NOI18N + boolean inEntity=pos != chars.length-1; + if (inEntity) { + int newPos = substEntity(chars, pos+1); + inEntity = newPos != -1; + if (inEntity) { + pos = newPos; + isAmp = chars[pos] == '&'; //NOI18N + //flag it so the next iteration won't think the < + //starts a tag + nextLtIsEntity = chars[pos] == '<'; + } else { + nextLtIsEntity = false; + isAmp = true; + } + } + } else { + nextLtIsEntity=false; + } + + for (int i=pos; i < chars.length; i++) { + if (((chars[i] == '<') && (!nextLtIsEntity)) || ((chars[i] == '&') && !isAmp)) { //NOI18N + nextTag = i-1; + break; + } + //Reset these flags so we don't skip all & or < chars for the rest of the string + isAmp = false; + nextLtIsEntity=false; + } + + + FontMetrics fm = g.getFont() == f ? baseFontMetrics : g.getFontMetrics(g.getFont()); + + //Get the bounds of the substring we'll paint + Rectangle2D r = fm.getStringBounds(chars, pos, nextTag + 1, g); + //Store the height, so we can add it if we're in word wrap mode, + //to return the height painted + lastHeight = r.getHeight(); + //Work out the length of this tag + int length = (nextTag + 1) - pos; + + //Flag to be set to true if we run out of space + boolean goToNextRow = false; + + //Flag that the current line is longer than the available width, + //and should be wrapped without finding a word boundary + boolean brutalWrap = false; + //Work out the per-character avg width of the string, for estimating + //when we'll be out of space and should start the ... in truncate + //mode + double chWidth; + + if (style == STYLE_TRUNCATE) { + //if we're truncating, use the width of one dot from an + //ellipsis to get an accurate result for truncation + chWidth = dotWidth; + } else { + //calculate an average character width + chWidth= r.getWidth() / (nextTag - pos); + //can return this sometimes, so handle it + if (chWidth == Double.POSITIVE_INFINITY || chWidth == Double.NEGATIVE_INFINITY) { + chWidth = fm.getMaxAdvance(); + } + } + + + if ((style != STYLE_CLIP) && + ((style == STYLE_TRUNCATE && + (widthPainted + r.getWidth() > w - (chWidth * 3)))) || + (style == STYLE_WORDWRAP && + (widthPainted + r.getWidth() > w))) { + + if (chWidth > 3) { + double pixelsOff = (widthPainted + ( + r.getWidth() + 5) + ) - w; + + double estCharsOver = pixelsOff / chWidth; + + if (style == STYLE_TRUNCATE) { + int charsToPaint = Math.round(Math.round(Math.ceil((w - widthPainted) / chWidth))); +/* System.err.println("estCharsOver = " + estCharsOver); + System.err.println("Chars to paint " + charsToPaint + " chwidth = " + chWidth + " widthPainted " + widthPainted); + System.err.println("Width painted + width of tag: " + (widthPainted + r.getWidth()) + " available: " + w); + */ + + int startPeriodsPos = pos + charsToPaint -3; + if (startPeriodsPos >= chars.length) { + startPeriodsPos = chars.length - 4; + } + length = (startPeriodsPos - pos); + if (length < 0) length = 0; + r = fm.getStringBounds(chars, pos, pos+length, g); +// System.err.println("Truncated set to true at " + pos + " (" + chars[pos] + ")"); + truncated = true; + } else { + //Word wrap mode + goToNextRow = true; + int lastChar = new Double(nextTag - + estCharsOver).intValue(); + //Unlike Swing's word wrap, which does not wrap on tag boundaries correctly, if we're out of space, + //we're out of space + brutalWrap = x == 0; + for (int i = lastChar; i > pos; i--) { + lastChar--; + if (Character.isWhitespace(chars[i])) { + length = (lastChar - pos) + 1; + brutalWrap = false; + break; + } + } + if ((lastChar <= pos) && (length > estCharsOver) + && !brutalWrap) { + x = origX; + y += r.getHeight(); + heightPainted += r.getHeight(); + boolean boundsChanged = false; + while (!done && Character.isWhitespace( + chars[pos]) && (pos < nextTag)) { + pos++; + boundsChanged = true; + done = pos == chars.length -1; + } + if (pos == nextTag) { + lastWasWhitespace = true; + } + if (boundsChanged) { + //recalculate the width we will add + r = fm.getStringBounds(chars, pos, + nextTag + 1, g); + } + goToNextRow = false; + widthPainted = 0; + if (chars[pos - 1 + length] == '<') { + length --; + } + } else if (brutalWrap) { + //wrap without checking word boundaries + length = (new Double( + (w - widthPainted) / chWidth)).intValue(); + if (pos + length > nextTag) { + length = (nextTag - pos); + } + goToNextRow = true; + } + } + } + } + if (!done) { + if (paint) { + g.drawChars(chars, pos, length, x, y); + } + + if (strikethrough || underline) { + LineMetrics lm = fm.getLineMetrics(chars, pos, + length - 1, g); + int lineWidth = new Double(x + r.getWidth()).intValue(); + if (paint) { + if (strikethrough) { + int stPos = Math.round(lm.getStrikethroughOffset()) + + g.getFont().getBaselineFor(chars[pos]) + + 1; + //PENDING - worth supporting with g.setStroke()? A one pixel line is most likely + //good enough + + //int stThick = Math.round (lm.getStrikethroughThickness()); + g.drawLine(x, y + stPos, lineWidth, y + stPos); + } + if (underline) { + int stPos = Math.round( + lm.getUnderlineOffset()) + + g.getFont().getBaselineFor(chars[pos]) + + 1; + //PENDING - worth supporting with g.setStroke()? A one pixel line is most likely + //good enough + + //int stThick = new Float (lm.getUnderlineThickness()).intValue(); + g.drawLine(x, y + stPos, lineWidth, y + stPos); + } + } + } + if (goToNextRow) { + //if we're in word wrap mode and need to go to the next + //line, reconfigure the x and y coordinates + x = origX; + y += r.getHeight(); + heightPainted += r.getHeight(); + widthPainted = 0; + pos += (length); + //skip any leading whitespace + while ((pos < chars.length) && + (Character.isWhitespace(chars[pos])) && + (chars[pos] != '<')) { + pos++; + } + lastWasWhitespace = true; + done |= pos >= chars.length; + } else { + x += r.getWidth(); + widthPainted += r.getWidth(); + lastWasWhitespace = Character.isWhitespace( + chars[nextTag]); + pos = nextTag + 1; + } + done |= nextTag == chars.length; + } + } + } + if (style != STYLE_WORDWRAP) { + return widthPainted; + } else { + return heightPainted + lastHeight; + } + } + + /** Parse a font color tag and return an appopriate java.awt.Color instance */ + private static Color findColor(final char[] ch, final int pos, + final int tagEnd) { + int colorPos = pos; + boolean useUIManager = false; + for (int i=pos; i < tagEnd; i ++) { + if (ch[i] == 'c') { + colorPos = i + 6; + if (ch[colorPos] == '\'' || ch[colorPos] == '"') { + colorPos++; + } + //skip the leading # character + if (ch[colorPos] == '#') { + colorPos++; + } else if (ch[colorPos] == '!') { + useUIManager = true; + colorPos++; + } + break; + } + } + if (colorPos == pos) { + String out = "Could not find color identifier in font declaration"; //NOI18N + throwBadHTML(out, pos, ch); + } + //Okay, we're now on the first character of the hex color definition + String s; + if (useUIManager) { + int end = ch.length-1; + for (int i=colorPos; i < ch.length; i++) { + if (ch[i] == '"' || ch[i] == '\'') { //NOI18N + end = i; + break; + } + } + s = new String(ch, colorPos, end-colorPos); + } else { + s = new String(ch, colorPos, 6); + } + Color result=null; + if (useUIManager) { + result = UIManager.getColor(s); + //Not all look and feels will provide standard colors; handle it gracefully + if (result == null) { + throwBadHTML( + "Could not resolve logical font declared in HTML: " + s, //NOI18N + pos, ch); + result = UIManager.getColor("textText"); //NOI18N + //Avoid NPE in headless situation? + if (result == null) { + result = Color.BLACK; + } + } + } else { + try { + int rgb = Integer.parseInt(s, 16); + result = new Color(rgb); + } catch (NumberFormatException nfe) { + throwBadHTML( + "Illegal hexadecimal color text: " + s + //NOI18N + " in HTML string", colorPos, ch); //NOI18N + } + } + if (result == null) { + throwBadHTML("Unresolvable html color: " + s //NOI18N + + " in HTML string \n ", pos, ch); //NOI18N + } + return result; + } + + /** Find an entity at the passed character position in the passed array. + * If an entity is found, the trailing ; character will be substituted + * with the resulting character, and the position of that character + * in the array will be returned as the new position to render from, + * causing the renderer to skip the intervening characters */ + private static final int substEntity(char[] ch, int pos) { + //There are no 1 character entities, abort + if (pos >= ch.length-2) { + return -1; + } + //if it's numeric, parse out the number + if (ch[pos] == '#') { //NOI18N + return substNumericEntity(ch, pos+1); + } + //Okay, we've potentially got a named character entity. Try to find it. + boolean match; + for (int i=0; i < entities.length; i++) { + char[] c = (char[]) entities[i]; + match = true; + if (c.length < ch.length-pos) { + for (int j=0; j < c.length; j++) { + match &= c[j] == ch[j+pos]; + } + } else { + match = false; + } + if (match) { + //if it's a match, we still need the trailing ; + if (ch[pos+c.length] == ';') { //NOI18N + //substitute the character referenced by the entity + ch[pos+c.length] = entitySubstitutions[i]; + return pos+c.length; + } + } + } + return -1; + } + + /** Finds a character defined as a numeric entity (e.g. &#8222;) + * and replaces the trailing ; with the referenced character, returning + * the position of it so the renderer can continue from there. + */ + private static final int substNumericEntity(char[] ch, int pos) { + for (int i=pos; i < ch.length; i++) { + if (ch[i] == ';') { + try { + ch[i] = (char) Integer.parseInt( + new String(ch, pos, i - pos)); + return i; + } catch (NumberFormatException nfe) { + throwBadHTML("Unparsable numeric entity: " + //NOI18N + new String(ch, pos, i - pos), pos, ch); //NOI18N + } + } + } + return -1; + } + + /** Throw an exception for unsupported or bad html, indicating where the problem is + * in the message */ + private static void throwBadHTML(String msg, int pos, char[] chars) { + char[] chh = new char[pos]; + Arrays.fill(chh, ' '); //NOI18N + chh[pos-1] = '^'; //NOI18N + String out = msg + "\n " + new String(chars) + "\n " + + new String(chh) + "\n Full HTML string:" + new String(chars); //NOI18N + if (!strictHtml) { + if (ErrorManager.getDefault().isLoggable(ErrorManager.WARNING)) { + if (badStrings == null) { + badStrings = new HashSet(); + } + if (!badStrings.contains(msg)) { + //ErrorManager bug, issue 38372 - log messages containing + //newlines are truncated - so for now we iterate the + //string we've just constructed + StringTokenizer tk = new StringTokenizer(out, "\n", false); + while (tk.hasMoreTokens()) { + ErrorManager.getDefault().log(ErrorManager.WARNING, + tk.nextToken()); + } + badStrings.add(msg.intern()); + } + } + } else { + throw new IllegalArgumentException(out); + } + } +} Index: openide/src/org/openide/awt/HtmlRendererImpl.java =================================================================== RCS file: openide/src/org/openide/awt/HtmlRendererImpl.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/src/org/openide/awt/HtmlRendererImpl.java 2 May 2004 11:03:22 -0000 @@ -0,0 +1,671 @@ +/* + * 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-2003 Sun + * Microsystems, Inc. All Rights Reserved. + */ +package org.openide.awt; +import org.openide.awt.HtmlRenderer; +import org.openide.awt.HtmlLabelUI; + +import javax.swing.*; +import javax.swing.plaf.basic.BasicLabelUI; +import javax.swing.plaf.LabelUI; +import javax.swing.event.AncestorListener; +import javax.swing.border.Border; +import java.awt.*; +import java.awt.event.*; +import java.awt.image.BufferedImage; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.beans.PropertyChangeListener; +import java.beans.VetoableChangeListener; +import java.io.*; + +/** + * Html renderer component implementation. The actual painting is done by HtmlLabelUI, which uses + * HtmlRenderer.renderString(). What this class does: Provide some methods for resetting its state + * between uses (see HtmlRenderer.sharedLabel() for why), overrides for a bunch of things for performance + * reasons, and some conversions to handle the case that the lightweight html renderer is disabled + * (-J-Dnb.useSwingHtmlRendering=true), to convert our minor extensions to html syntax to standard + * syntax for the swing renderer. + *

    + * Mainly this class provides an implementation of the various cell renderer interfaces which + * HtmlRenderer.Renderer aggregates, and the convenience methods it provides. + * + * @author Tim Boudreau + * @since 4.30 + * + */ +class HtmlRendererImpl extends JLabel implements HtmlRenderer.Renderer { + + private boolean centered = false; + private boolean parentFocused = false; + private Boolean html = null; + private int indent = 0; + private Border border = null; + private boolean selected = false; + private boolean leadSelection = false; + private Dimension prefSize = null; + private int type = TYPE_UNKNOWN; + private int renderStyle = HtmlRenderer.STYLE_CLIP; + private static final Rectangle bounds = new Rectangle(); + private boolean enabled = true; + + private static final boolean swingRendering = Boolean.getBoolean ("nb.useSwingHtmlRendering"); //NOI18N + private static final Insets EMPTY_INSETS = new Insets (0, 0, 0, 0); + + static final int TYPE_UNKNOWN = -1; + static final int TYPE_TREE = 0; + static final int TYPE_LIST = 1; + static final int TYPE_TABLE = 2; + + /** Restore the renderer to a pristine state */ + public void reset() { + parentFocused = false; + setCentered (false); + html = null; + indent = 0; + border = null; + setIcon (null); + setOpaque (false); + selected = false; + leadSelection = false; + prefSize = null; + type = TYPE_UNKNOWN; + renderStyle = HtmlRenderer.STYLE_CLIP; + setFont (UIManager.getFont("controlFont")); //NOI18N + setIconTextGap (3); + setEnabled (true); + border = null; + + //Defensively ensure the insets haven't been messed with + EMPTY_INSETS.top = 0; + EMPTY_INSETS.left = 0; + EMPTY_INSETS.right = 0; + EMPTY_INSETS.bottom = 0; + } + + public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean leadSelection, + int row, int column) { + reset(); + configureFrom (value, table, selected, leadSelection); + type = TYPE_TABLE; + if (swingRendering && selected) { + setBackground (table.getSelectionBackground()); + setBackground (table.getSelectionForeground()); + setOpaque (true); + } + return this; + } + + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, + boolean leaf, int row, boolean leadSelection) { + reset(); + configureFrom (value, tree, selected, leadSelection); + type = TYPE_TREE; + if (swingRendering && selected) { + setBackground (HtmlLabelUI.getBackgroundFor(this)); + setForeground (HtmlLabelUI.getForegroundFor(this)); + setOpaque (true); + } + return this; + } + + public Component getListCellRendererComponent(JList list, Object value, int index, boolean selected, + boolean leadSelection) { + + reset(); + configureFrom (value, list, selected, leadSelection); + type = TYPE_LIST; + if (swingRendering && selected) { + setBackground (list.getSelectionBackground()); + setForeground (list.getSelectionForeground()); + setOpaque (true); + } + return this; + } + + /** Generic code to set properties appropriately from any of the renderer + * fetching methods */ + private void configureFrom (Object value, JComponent target, + boolean selected, boolean leadSelection) { + + if (value == null) { + value = ""; + } + + setText (value instanceof String ? (String) value : value.toString()); + + setSelected(selected); + if (selected) { + setParentFocused(checkFocused(target)); + } else { + setParentFocused(false); + } + + setEnabled (target.isEnabled()); + + setLeadSelection (leadSelection); + + setFont (target.getFont()); + } + + private boolean checkFocused(JComponent c) { + Component focused = + KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner(); + boolean result = c == focused; + if (!result) { + result = c.isAncestorOf(focused); + } + return result; + } + + public void addNotify() { + if (swingRendering) { + super.addNotify(); + } + } + + public void removeNotify() { + if (swingRendering) { + super.removeNotify(); + } + } + + public void setSelected (boolean val) { + selected = val; + } + + public void setParentFocused (boolean val) { + parentFocused = val; + } + + public void setLeadSelection (boolean val) { + leadSelection = val; + } + + public void setCentered(boolean val) { + centered = val; + if (val) { + setIconTextGap (5); + } + if (swingRendering) { + if (val) { + setVerticalTextPosition(JLabel.BOTTOM); + setHorizontalAlignment(JLabel.CENTER); + setHorizontalTextPosition(JLabel.CENTER); + } else { + setVerticalTextPosition(JLabel.CENTER); + setHorizontalAlignment(JLabel.LEADING); + setHorizontalTextPosition(JLabel.TRAILING); + } + } + } + + public void setIndent(int pixels) { + this.indent = pixels; + } + + public void setHtml (boolean val) { + Boolean wasHtml = html; + String txt = getText(); + html = val ? Boolean.TRUE : Boolean.FALSE; + if (swingRendering && html != wasHtml) { + + //Ensure label UI gets updated and builds its little document tree... + firePropertyChange ("text", txt, getText()); //NOI18N + } + } + + public void setRenderStyle(int style) { + renderStyle = style; + } + + int getRenderStyle () { + return renderStyle; + } + + boolean isLeadSelection () { + return leadSelection; + } + + boolean isCentered() { + return centered; + } + + boolean isParentFocused() { + return parentFocused; + } + + boolean isHtml() { + if (html == null) { + String s = getText(); + html = checkHtml (s); + } + return html.booleanValue(); + } + + private Boolean checkHtml(String s) { + Boolean result; + if (s == null) { + result = Boolean.FALSE; + } else if (s.startsWith (""; //NOI18N + } + return s; + } + + /** + * Converts extended UI manager color tags into legal html in the case that we're using swing rendering + * + * @param s string to convert if it has questionable font tags + * @return The converted string + */ + private static String ensureLegalFontColorTags(String s) { + String check = s.toUpperCase(); + int start = 0; + int fidx = check.indexOf("', start); //NOI18N + start = tagEnd+1; + if (tagEnd == -1) { + break; + } + if (cidx != -1) { + if (cidx < tagEnd) { + //we have a font color tag + int eidx = check.indexOf('=', cidx); //NOI18N + if (eidx != -1) { + int bangIdx = check.indexOf('!', eidx); //NOI18N + if (bangIdx != -1 && bangIdx < tagEnd) { + int colorStart = bangIdx + 1; + int colorEnd = tagEnd; + for (int i=colorStart; i < tagEnd; i++) { + char c = s.charAt(i); + if (!Character.isLetter(c)) { + colorEnd = i; + break; + } + } + if (sb == null) { + sb = new StringBuffer (s); + } + String colorString = s.substring(colorStart, colorEnd); + String converted = convertToStandardColor (colorString); + sb.replace(bangIdx, colorEnd, converted); + s = sb.toString(); + check = s.toUpperCase(); + } + } + } + } + fidx = check.indexOf("not use the + * internal HTML renderer is in effect, this will fire changes normally */ + protected final void firePropertyChange(String name, Object old, Object nue) { + if (swingRendering) { + if ("text".equals(name) && isHtml()) { + //Force in the HTML tags so the UI will set up swing HTML rendering appropriately + nue = getText(); + } + super.firePropertyChange(name, old, nue); + } + } + + public Border getBorder() { + Border result; + if (indent != 0 && swingRendering) { + result = BorderFactory.createEmptyBorder (0, indent, 0, 0); + } else { + result = border; + } + return result; + } + + public void setBorder (Border b) { + Border old = border; + border = b; + if (swingRendering) { + firePropertyChange ("border", old, b); + } + } + + public Insets getInsets() { + Insets result; + + //Call getBorder(), not just read the field - if swingRendering, the border will be constructed, and the + //insets are what will make the indent property work; HtmlLabelUI doesn't need this, it just reads the + //insets property, but BasicLabelUI and its ilk do + Border b = getBorder(); + if (b == null) { + result = EMPTY_INSETS; + } else { + result = b.getBorderInsets(this); + } + return result; + } + + public void setEnabled (boolean b) { + //OptimizeIt shows about 12Ms overhead calling back to Component.enable(), so avoid it if possible + enabled = b; + if (swingRendering) { + super.setEnabled(b); + } + } + + public boolean isEnabled() { + return enabled; + } + + public void updateUI() { + if (swingRendering) { + super.updateUI(); + } else { + setUI (HtmlLabelUI.createUI(this)); + } + } + + /** Overridden to produce a graphics object even when isDisplayable() is + * false, so that calls to getPreferredSize() will return accurate + * dimensions (presuming the font and text are set correctly) even when + * not onscreen. */ + public Graphics getGraphics() { + Graphics result = null; + if (isDisplayable()) { + result = super.getGraphics(); + } + if (result == null) { + result = scratchGraphics(); + } + return result; + } + + //For experimentation - holding the graphics object may be the source of some + //strange painting problems on Apple + private static boolean noCacheGraphics = Boolean.getBoolean("nb.renderer.nocache"); //NOI18N + + private static Reference scratchGraphics = null; + /** Fetch a scratch graphics object for calculating preferred sizes while + * offscreen */ + private static final Graphics scratchGraphics() { + Graphics result = null; + if (scratchGraphics != null) { + result = (Graphics) scratchGraphics.get(); + if (result != null) { + result.setClip(null); //just in case somebody did something nasty + } + } + if (result == null) { + result = GraphicsEnvironment. + getLocalGraphicsEnvironment(). + getDefaultScreenDevice().getDefaultConfiguration(). + createCompatibleImage(1, 1).getGraphics(); + + if (!noCacheGraphics){ + scratchGraphics = new SoftReference (result); + } + } + return result; + } + + public void setBounds (int x, int y, int w, int h) { + if (swingRendering) { + super.setBounds (x, y, w, h); + } + bounds.setBounds(x, y, w, h); + } + + public void reshape (int x, int y, int w, int h) { + if (swingRendering) { + super.reshape(x, y, w, h); + } + } + + public int getWidth() { + return bounds.width; + } + + public int getHeight() { + return bounds.height; + } + + public Point getLocation() { + return bounds.getLocation(); + } + + /** Overridden to do nothing for performance reasons */ + public void validate() { + //do nothing + } + + /** Overridden to do nothing for performance reasons */ + public void repaint (long tm, int x, int y, int w, int h) { + //do nothing + } + + /** Overridden to do nothing for performance reasons */ + public void repaint() { + //do nothing + } + + /** Overridden to do nothing for performance reasons */ + public void invalidate() { + //do nothing + } + + /** Overridden to do nothing for performance reasons */ + public void revalidate() { + //do nothing + } + + /** Overridden to do nothing for performance reasons */ + public void addAncestorListener (AncestorListener l) { + if (swingRendering) { + super.addAncestorListener (l); + } + } + + /** Overridden to do nothing for performance reasons */ + public void addComponentListener (ComponentListener l) { + if (swingRendering) { + super.addComponentListener (l); + } + } + + /** Overridden to do nothing for performance reasons */ + public void addContainerListener (ContainerListener l) { + if (swingRendering) { + super.addContainerListener (l); + } + } + + /** Overridden to do nothing for performance reasons */ + public void addHierarchyListener (HierarchyListener l) { + if (swingRendering) { + super.addHierarchyListener (l); + } + } + + /** Overridden to do nothing for performance reasons */ + public void addHierarchyBoundsListener (HierarchyBoundsListener l) { + if (swingRendering) { + super.addHierarchyBoundsListener (l); + } + } + + /** Overridden to do nothing for performance reasons */ + public void addInputMethodListener (InputMethodListener l) { + if (swingRendering) { + super.addInputMethodListener (l); + } + } + + /** Overridden to do nothing for performance reasons */ + public void addFocusListener (FocusListener fl) { + if (swingRendering) { + super.addFocusListener (fl); + } + } + + /** Overridden to do nothing for performance reasons */ + public void addMouseListener (MouseListener ml) { + if (swingRendering) { + super.addMouseListener (ml); + } + } + + /** Overridden to do nothing for performance reasons */ + public void addMouseWheelListener (MouseWheelListener ml) { + if (swingRendering) { + super.addMouseWheelListener (ml); + } + } + + /** Overridden to do nothing for performance reasons */ + public void addMouseMotionListener (MouseMotionListener ml) { + if (swingRendering) { + super.addMouseMotionListener (ml); + } + } + + /** Overridden to do nothing for performance reasons */ + public void addVetoableChangeListener (VetoableChangeListener vl) { + if (swingRendering) { + super.addVetoableChangeListener (vl); + } + } + + /** Overridden to do nothing for performance reasons, unless using standard swing rendering */ + public void addPropertyChangeListener (String s, PropertyChangeListener l) { + if (swingRendering) { + super.addPropertyChangeListener (s, l); + } + } + + public void addPropertyChangeListener (PropertyChangeListener l) { + if (swingRendering) { + super.addPropertyChangeListener (l); + } + } +} Index: openide/src/org/openide/explorer/propertysheet/BaseTable.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/propertysheet/BaseTable.java,v --- openide/src/org/openide/explorer/propertysheet/BaseTable.java 1 Mar 2004 01:26:19 -0000 1.15 +++ openide/src/org/openide/explorer/propertysheet/BaseTable.java 2 May 2004 11:03:25 -0000 @@ -552,6 +552,8 @@ Component result = renderer.getTableCellRendererComponent(this, value, isSelected, false, row, col); + if (result != null) { + /* if (isSelected) { result.setBackground(getSelectionBackground()); result.setForeground(getSelectionForeground()); @@ -565,6 +567,8 @@ result.setForeground(getForeground()); } result.setFont(getFont()); + */ + } return result; } Index: openide/src/org/openide/explorer/propertysheet/ButtonPanel.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/propertysheet/ButtonPanel.java,v --- openide/src/org/openide/explorer/propertysheet/ButtonPanel.java 4 Feb 2004 11:21:25 -0000 1.16 +++ openide/src/org/openide/explorer/propertysheet/ButtonPanel.java 2 May 2004 11:03:25 -0000 @@ -149,6 +149,12 @@ button.setRolloverIcon(PropUtils.getCustomButtonIcon()); } + public void setOpaque(boolean b) { + if (getInplaceEditor() != null) { + getInplaceEditor().getComponent().setOpaque(true); + } + } + public void setFont(Font f) { if (comp != null) { comp.setFont(f); Index: openide/src/org/openide/explorer/propertysheet/ComboInplaceEditor.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/propertysheet/ComboInplaceEditor.java,v --- openide/src/org/openide/explorer/propertysheet/ComboInplaceEditor.java 26 Apr 2004 18:10:44 -0000 1.23 +++ openide/src/org/openide/explorer/propertysheet/ComboInplaceEditor.java 2 May 2004 11:03:26 -0000 @@ -60,7 +60,7 @@ * less borders & such */ public ComboInplaceEditor(boolean tableUI) { if (tableUI) { - putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE); + putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE); //NOI18N } if (Boolean.getBoolean("netbeans.ps.combohack")) { //NOI18N setLightWeightPopupEnabled(false); @@ -72,7 +72,7 @@ if (tableUI) { updateUI(); } - } + } /** Overridden to add a listener to the editor if necessary, since the * UI won't do that for us without a focus listener */ @@ -233,9 +233,10 @@ /** Overridden to use CleanComboUI on Metal L&F to avoid extra borders */ public void updateUI() { LookAndFeel lf = UIManager.getLookAndFeel(); + String id = lf.getID(); boolean useClean = tableUI && (lf instanceof MetalLookAndFeel || - "GTK".equals(lf.getID())); //NOI18N + "GTK".equals(id) || "Kunststoff".equals(id)); //NOI18N if (useClean) { super.setUI (PropUtils.createComboUI(this, tableUI)); Index: openide/src/org/openide/explorer/propertysheet/DescriptionPanel.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/propertysheet/DescriptionPanel.java,v --- openide/src/org/openide/explorer/propertysheet/DescriptionPanel.java 22 Apr 2004 13:10:24 -0000 1.12 +++ openide/src/org/openide/explorer/propertysheet/DescriptionPanel.java 2 May 2004 11:03:26 -0000 @@ -98,16 +98,13 @@ } descLabel.setFocusable(false); - Color bg = UIManager.getColor("window"); - if (bg != null) { - descLabel.setBackground(bg); //NOI18N - } descLabel.setEditable(false); descLabel.setFont (titleLabel.getFont()); descLabel.setForeground(titleLabel.getForeground()); descLabel.setWrapStyleWord(true); descLabel.setLineWrap(true); descLabel.setRows (2); + descLabel.setOpaque(false); titleLabel.setFont (getFont().deriveFont (Font.BOLD)); Index: openide/src/org/openide/explorer/propertysheet/IconPanel.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/propertysheet/IconPanel.java,v --- openide/src/org/openide/explorer/propertysheet/IconPanel.java 30 Dec 2003 16:58:40 -0000 1.4 +++ openide/src/org/openide/explorer/propertysheet/IconPanel.java 2 May 2004 11:03:27 -0000 @@ -174,6 +174,12 @@ } } + public void setOpaque (boolean val) { + if (getInplaceEditor() != null) { + getInplaceEditor().getComponent().setOpaque(true); + } + } + /** Proxies the embedded inplace editor */ public javax.swing.JComponent getComponent() { return this; Index: openide/src/org/openide/explorer/propertysheet/PropUtils.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/propertysheet/PropUtils.java,v --- openide/src/org/openide/explorer/propertysheet/PropUtils.java 29 Feb 2004 03:56:21 -0000 1.33 +++ openide/src/org/openide/explorer/propertysheet/PropUtils.java 2 May 2004 11:03:29 -0000 @@ -25,7 +25,6 @@ import java.io.StringWriter; import java.lang.reflect.*; import java.util.*; -import javax.accessibility.*; import javax.swing.*; import javax.swing.plaf.metal.*; import java.text.MessageFormat; @@ -323,27 +322,15 @@ return true; } + private static Graphics scratchGraphics = null; + /** Get a scratch graphics object which can be used to calculate string * widths offscreen */ static Graphics getScratchGraphics(Component c) { - //OS-X 1.4.1 calling getGraphics() can cause cyclic repaints - //if called while painting. Safer to use an offscreen, cached - //resource, probably everywhere. - Graphics result = null; //c.getGraphics(); - if (result == null) { - //xxx this grabs the AWT tree lock - /* - result = - GraphicsEnvironment.getLocalGraphicsEnvironment - ().getDefaultScreenDevice().getDefaultConfiguration - ().createCompatibleImage(1,1).getGraphics(); - */ - if (scratch == null) { - scratch = new BufferedImage(1,1,BufferedImage.TYPE_INT_ARGB); - } - result = scratch.getGraphics(); + if (scratchGraphics == null) { + scratchGraphics = new BufferedImage (1, 1, BufferedImage.TYPE_INT_RGB).getGraphics(); } - return result; + return scratchGraphics; } Index: openide/src/org/openide/explorer/propertysheet/RendererFactory.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/propertysheet/RendererFactory.java,v --- openide/src/org/openide/explorer/propertysheet/RendererFactory.java 25 Feb 2004 00:45:00 -0000 1.10 +++ openide/src/org/openide/explorer/propertysheet/RendererFactory.java 2 May 2004 11:03:30 -0000 @@ -21,10 +21,8 @@ import java.awt.Color; import java.awt.Component; import java.awt.Dimension; -import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Image; -import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -47,8 +45,8 @@ import javax.swing.border.BevelBorder; import javax.swing.border.Border; import javax.swing.event.ChangeListener; -import javax.swing.table.DefaultTableCellRenderer; import org.openide.ErrorManager; +import org.openide.awt.HtmlRenderer; import org.openide.nodes.Node.Property; import org.openide.util.Utilities; @@ -236,11 +234,11 @@ return lbl; } - public JLabel getStringRenderer() { + public JComponent getStringRenderer() { StringRenderer result = stringRenderer(); result.clear(); result.setEnabled(true); - return (JLabel) result; + return result; } private JComponent prepareRadioButtons(PropertyEditor editor, PropertyEnv env) { @@ -426,14 +424,7 @@ /** Overridden only fire those properties needed */ protected void firePropertyChange(String name, Object old, Object nue) { - //gtk L&F needs these, although bg and fg don't work on it in 1.4.2 - /* - if ("foreground".equals(name) || - "background".equals(name) || "font".equals(name) || - "editable".equals(name) || "enabled".equals(name)) { //NOI18N - super.firePropertyChange(name, old, nue); - } - */ + //firing all changes for now - breaks text painting on OS-X super.firePropertyChange(name,old,nue); } @@ -495,7 +486,7 @@ /** A renderer for string properties, which can also delegate to the * property editor's paint()method if possible. */ - private static final class StringRenderer extends DefaultTableCellRenderer implements InplaceEditor { + private static final class StringRenderer extends JLabel implements InplaceEditor { private PropertyEditor editor=null; private PropertyEnv env=null; private boolean tableUI=false; @@ -517,29 +508,39 @@ return enabled; } - public Dimension getPreferredSize() { - Graphics g = PropUtils.getScratchGraphics(this); - FontMetrics fm = g.getFontMetrics(getFont()); - int w; - if (getText() != null) { - //avoid NPE in sun.awt.font.FontDesignMetrics.stringWidth():281 - w = fm.stringWidth(getText()) + 4; - } else { - w = PropUtils.getMinimumPropPanelWidth(); + /** Overridden to do nothing */ + protected void firePropertyChange(String name, Object old, Object nue) { + //do nothing } - int h = fm.getHeight() + 2; - w = Math.max(w,PropUtils.getMinimumPropPanelWidth()); - h = Math.max(h,PropUtils.getMinimumPropPanelHeight()); - Dimension result = new Dimension(w,h); - if (getIcon() != null) { - result.height = Math.max (result.height, getIcon().getIconHeight()); - result.width += getIcon().getIconHeight(); - } - if (getBorder() != null) { - Insets i = getBorder().getBorderInsets(this); - result.width += i.right+i.left; - result.height += i.top+i.bottom; + + public void validate() { + //do nothing } + + public void invalidate() { + //do nothing + } + + public void revalidate() { + //do nothing + } + + public void repaint() { + //do nothing + } + + public void repaint (long tm, int x, int y, int w, int h) { + //do nothing + } + + public Dimension getPreferredSize() { + Dimension result = super.getPreferredSize(); + result.width = Math.max(result.width, + PropUtils.getMinimumPropPanelWidth()); + + result.height = Math.max(result.height, + PropUtils.getMinimumPropPanelHeight()); + return result; } @@ -553,7 +554,16 @@ if (editor != null && editor.isPaintable()) { delegatedPaint (g); } else { - super.paint (g); + JLabel lbl = (JLabel) HtmlRenderer.sharedLabel(HtmlRenderer.STYLE_TRUNCATE, null); + lbl.setEnabled (isEnabled()); + lbl.setText (getText()); + lbl.setIcon (getIcon()); + lbl.setIconTextGap (getIconTextGap()); + lbl.setBounds (getBounds()); + lbl.setOpaque (true); + lbl.setBackground (getBackground()); + lbl.setForeground (getForeground()); + lbl.paint (g); } clear(); } @@ -572,6 +582,8 @@ b.paintBorder(this, g, 0, 0, getWidth(), getHeight()); } Rectangle r = getBounds(); + //XXX May be the source of Rochelle's multiple rows of error + //marking misalignment problem...(I do not jest) r.x = getWidth() > 16 ? editor instanceof Boolean3WayEditor ? 0 : 3 : 0; //align text with other renderers r.width -= getWidth() > 16 ? editor instanceof Boolean3WayEditor ? 0 : 3 : 0; //align text with other renderers r.y = 0; @@ -584,19 +596,14 @@ public void clear() { editor = null; env = null; - setText(""); //NOI18N setIcon(null); setOpaque(true); } + private Object value = null; public void setValue(Object o) { - super.setValue(o); - } - - public void setText(String s) { - //XXX hotfix for the form editor until the HTML renderer can - //be put into trunk - per Trung's request - super.setText(stripHTML(s)); + value = o; + setText (value instanceof String ? (String) value : value != null ? value.toString() : null); } public void connect(PropertyEditor p, PropertyEnv env) { @@ -664,7 +671,7 @@ } public boolean supportsTextEntry() { - return true; + return false; } /** Overridden to do nothing */ @@ -675,38 +682,6 @@ protected void fireStateChanged() { } - /** Overridden to do nothing */ - protected void firePropertyChange(String name, Object old, Object nue) { - } - - /** Overridden to do nothing */ - public void firePropertyChange(String name, boolean old, boolean nue) { - } - - /** Overridden to do nothing */ - public void firePropertyChange(String name, int old, int nue) { - } - - /** Overridden to do nothing */ - public void firePropertyChange(String name, byte old, byte nue) { - } - - /** Overridden to do nothing */ - public void firePropertyChange(String name, char old, char nue) { - } - - /** Overridden to do nothing */ - public void firePropertyChange(String name, double old, double nue) { - } - - /** Overridden to do nothing */ - public void firePropertyChange(String name, float old, float nue) { - } - - /** Overridden to do nothing */ - public void firePropertyChange(String name, short old, short nue) { - } - public void addActionListener(ActionListener al) { //do nothing } @@ -739,34 +714,6 @@ super.firePropertyChange(name, old, nue); } } - - /** Overridden to do nothing */ - public void firePropertyChange(String name, boolean old, boolean nue) { - } - - /** Overridden to do nothing */ - public void firePropertyChange(String name, int old, int nue) { - } - - /** Overridden to do nothing */ - public void firePropertyChange(String name, byte old, byte nue) { - } - - /** Overridden to do nothing */ - public void firePropertyChange(String name, char old, char nue) { - } - - /** Overridden to do nothing */ - public void firePropertyChange(String name, double old, double nue) { - } - - /** Overridden to do nothing */ - public void firePropertyChange(String name, float old, float nue) { - } - - /** Overridden to do nothing */ - public void firePropertyChange(String name, short old, short nue) { - } } private static final class RadioRenderer extends RadioInplaceEditor { @@ -1051,68 +998,6 @@ public boolean supportsTextEntry() { return false; - } - - } - - public static boolean requiresSwingPainting (Component c) { - if (!(c instanceof InplaceEditor)) { - return true; - } - InplaceEditor ine = (InplaceEditor) c; - if (ine instanceof ButtonPanel) { - //ine = ((ButtonPanel) ine).getInplaceEditor(); - return ((ButtonPanel) ine).getInplaceEditor() instanceof JComboBox; - } - if (ine instanceof IconPanel) { - ine = ((IconPanel) ine).getInplaceEditor(); - } - - if (ine.getComponent() instanceof JComboBox) { - return true; - } - - if (ine instanceof CheckboxInplaceEditor || ine instanceof RadioInplaceEditor || - ine instanceof StringRenderer || ine instanceof ExceptionRenderer || - ine instanceof Boolean3WayEditor.Boolean3Inplace || ine instanceof StringInplaceEditor) { - return false; - } - return true; - } - - private static boolean isHTML(String s) { - if (s == null) return false; - boolean result = s.startsWith("") || s.startsWith(""); //NOI18N - return result; - } - - private static String stripHTML(String s) { - if (s == null) { - return s; - } - if (isHTML(s)) { - StringBuffer result = new StringBuffer(s.length()); - char[] c = s.toCharArray(); - boolean inTag = false; - for (int i=0; i < c.length; i++) { - //XXX need to handle entity includes - boolean wasInTag = inTag; - if (!inTag) { - if (c[i] == '<') { - inTag = true; - } - } else { - if (c[i] == '>') { - inTag = false; - } - } - if (!inTag && wasInTag == inTag) { - result.append(c[i]); - } - } - return result.toString(); - } else { - return s; } } } Index: openide/src/org/openide/explorer/propertysheet/SheetCellRenderer.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/propertysheet/SheetCellRenderer.java,v --- openide/src/org/openide/explorer/propertysheet/SheetCellRenderer.java 21 Jan 2004 20:36:53 -0000 1.8 +++ openide/src/org/openide/explorer/propertysheet/SheetCellRenderer.java 2 May 2004 11:03:30 -0000 @@ -17,19 +17,18 @@ package org.openide.explorer.propertysheet; import java.awt.*; -import java.awt.event.ActionEvent; import java.beans.FeatureDescriptor; import javax.swing.*; import javax.swing.table.TableCellRenderer; -import javax.swing.table.DefaultTableCellRenderer; +import org.openide.awt.HtmlRenderer; import org.openide.nodes.Node.*; + /** An implementation of SheetCellRenderer that wraps custom InplaceEditors * to efficiently render properties. * * @author Tim Boudreau */ final class SheetCellRenderer implements TableCellRenderer { - static SetRenderer setRenderer = null; private RendererFactory factory=null; private boolean tableUI; boolean includeMargin=false; @@ -52,14 +51,6 @@ rbMax = i; } - private SetRenderer getSetRenderer() { - if (setRenderer == null) { - setRenderer = new SetRenderer(); - } - setRenderer.dontPaint = true; - return setRenderer; - } - public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) { FeatureDescriptor fd = (FeatureDescriptor) value; Component result; @@ -68,21 +59,30 @@ selected |= hasFocus && table.getSelectedRow() == row; if (fd instanceof PropertySet) { - SetRenderer sr = getSetRenderer(); - sr.setText(fd.getDisplayName()); - sr.setExpanded(((SheetTable) table).getPropertySetModel().isExpanded(fd)); - result = sr; + return null; } else { if (column == 0) { - JLabel lbl = factory().getStringRenderer(); - lbl.setText(fd.getDisplayName()); + String txt = ((Property) fd).getHtmlDisplayName(); + boolean isHtml = txt != null; + if (!isHtml) { + txt = fd.getDisplayName(); + } + JLabel lbl = HtmlRenderer.sharedLabel(HtmlRenderer.STYLE_TRUNCATE, isHtml ? Boolean.TRUE : + Boolean.FALSE); + HtmlRenderer.Renderer ren = (HtmlRenderer.Renderer) lbl; + + lbl.setText (txt); + lbl.setOpaque (selected); + if (selected) { + lbl.setBackground (table.getSelectionBackground()); + lbl.setForeground (table.getSelectionForeground()); + lbl.setOpaque (true); + } + if (includeMargin) { - lbl.setBorder(BorderFactory.createEmptyBorder( - 0, PropUtils.getMarginWidth() + 2, 0, 1)); + ren.setIndent (PropUtils.getMarginWidth() + 2); } else { - //Use a 2 pixel margin so it's not flush - lbl.setBorder(BorderFactory.createEmptyBorder(0, - PropUtils.getTextMargin(), 0, 0)); + ren.setIndent (PropUtils.getTextMargin()); } //Support for name marking with icon requested by form editor @@ -91,21 +91,22 @@ lbl.setIcon((Icon) o); } else if (o instanceof Image) { lbl.setIcon(new ImageIcon((Image) o)); - } else { - lbl.setIcon(null); } result = lbl; } else { result = factory().getRenderer((Property) fd); - //Use a 2 pixel margin so it's not flush - /* - ((JComponent)result).setBorder(BorderFactory.createEmptyBorder(0, - PropUtils.getTextMargin(), 0, 0)); - */ + if (selected) { + result.setBackground (table.getSelectionBackground()); + result.setForeground (table.getSelectionForeground()); + ((JComponent)result).setOpaque(true); + } else { + result.setBackground (table.getBackground()); + result.setForeground (table.getForeground()); + ((JComponent)result).setOpaque(false); + } } } -// result.setFont(table.getFont()); return result; } @@ -114,86 +115,5 @@ factory = new RendererFactory(true); } return factory; - } - - /** A renderer for property sets, which should be rendered double-width. - * This renderer intentionally does nothing when called in the normal - * paint loop; if the boolean field dontPaint is set - * to true (calling getTableCellRendererComponent will set this to true) - * its paint method is a no-op. The table will retrieve this component - * and paint it across two columns after the rest of the paint cycle - * is completed. - */ - static class SetRenderer extends DefaultTableCellRenderer { - - public boolean dontPaint = true; - int textY = -1; - int iconY = -1; - - /** Discard/recalc UI dependent values */ - public void updateUI() { - super.updateUI(); - iconY=-1; - textY=-1; - } - - private boolean expanded; - public void setExpanded (boolean val) { - expanded = val; - } - - public Icon getIcon() { - return expanded ? PropUtils.getExpandedIcon() : - PropUtils.getCollapsedIcon(); - } - - /** Calculate y position to center text and icon vertically */ - private void calcYPos(FontMetrics fm) { - int h = getHeight(); - int ih = getIcon().getIconHeight(); - int fh = fm.getHeight(); - if (fh >= h) { - textY = 0 + fm.getAscent(); - } else { - textY = ((h - fh) / 2) + fm.getAscent(); - } - - if (ih >= h) { - iconY = 0; - } else { - iconY = (h - ih) / 2; - } - } - - /** Paint the component, if the dontPaint field is - * false. In the standard rendering pass of JTable, this will - * be true; the table will iterate the available sets and paint - * them across the entire width of the table after the rest has - * been painted. - */ - public void paint(Graphics g) { - if (!dontPaint) { - if (iconY == -1) calcYPos(g.getFontMetrics(getFont())); - g.setFont(getFont()); - g.setColor(getBackground()); - g.fillRect(0,0,getWidth(),getHeight()); - Icon ic = getIcon(); - ic.paintIcon(this, g, PropUtils.getIconMargin(), iconY); - g.setColor(getForeground()); - g.drawString(getText(), PropUtils.getIconMargin()+ PropUtils.getMarginWidth() + 2, //XXX text icon gap - textY); - } - } - /** Overridden to do nothing */ - protected void fireActionPerformed(ActionEvent ae) { - } - - /** Overridden to do nothing */ - protected void fireStateChanged() { - } - - /** Overridden to do nothing */ - protected void firePropertyChange(String name, Object old, Object nue) { - } } } Index: openide/src/org/openide/explorer/propertysheet/SheetTable.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/propertysheet/SheetTable.java,v --- openide/src/org/openide/explorer/propertysheet/SheetTable.java 22 Apr 2004 13:10:25 -0000 1.38 +++ openide/src/org/openide/explorer/propertysheet/SheetTable.java 2 May 2004 11:03:32 -0000 @@ -20,6 +20,7 @@ import javax.swing.table.*; import javax.swing.event.*; import org.openide.ErrorManager; +import org.openide.awt.HtmlRenderer; import org.openide.util.NbBundle; import org.openide.nodes.Node.Property; import org.openide.nodes.Node.PropertySet; @@ -369,7 +370,7 @@ return; } - int max = min + getVisibleRowCount(); //psm.getCount(); + int max = min + getVisibleRowCount(); for (int i=min; i < max; i++) { FeatureDescriptor fd = psm.getFeatureDescriptor(i); @@ -405,11 +406,12 @@ } public Component prepareRenderer(TableCellRenderer renderer, int row, int col) { + Component result = super.prepareRenderer(renderer, row, col); if (row < 0 || row >= getRowCount()) { - return super.prepareRenderer(renderer, row, col); + return result; } Object value = getValueAt(row, col); - Component result = super.prepareRenderer(renderer, row, col); + /* if (value instanceof PropertySet) { boolean selected = row == getSelectedRow(); result.setBackground (selected ? @@ -426,49 +428,71 @@ result.setEnabled(writable); } } + */ + if (result != null && value instanceof Property && col == 1) { + result.setEnabled (((Property) value).canWrite()); + } return result; - } - + } + + private HtmlRenderer.Renderer htmlrenderer = null; + /** Paint the expandable sets. These are painted double width, * across the entire width of the table. */ private void paintExpandableSets (Graphics g) { - //special paint handling for double-wide expando sets - //XXX build an array of appropriate component indices in getCellRenderer and don't iterate - //the entire set of rows! - int max = this.getRowCount(); - for (int i = 0; i < max; i++) { - //find the items that are property sets - if (!(getSheetModel().getPropertySetModel().isProperty(i))) { - //get the rectangle and double its width - Rectangle r = getCellRect (i, 0, false); - if (r.y > getHeight()) { - //Don't paint unnecessarily - return; - } - r.width = getWidth(); - if (g.hitClip (r.x, r.y, r.width, r.height)) { - //get the appropriate renderer - TableCellRenderer tcr = getCellRenderer (i, 0); - boolean selected = getSelectedRow()==i && isKnownComponent( + int start = getFirstVisibleRow(); + int end = getVisibleRowCount(); + + Insets ins = getInsets(); + + boolean canBeSelected = isKnownComponent( KeyboardFocusManager.getCurrentKeyboardFocusManager(). getPermanentFocusOwner()); - Component c = tcr.getTableCellRendererComponent(this, - this.getValueAt (i,0), selected, false, i, 0); - //A minor optimization - when the standard table painting - //happens, the set renderer will simply do nothing. This - //field controls whether its paint method is a no-op. - ((SheetCellRenderer.SetRenderer) c).dontPaint = false; - c.setBackground (selected ? - PropUtils.getSelectedSetRendererColor() : - PropUtils.getSetRendererColor()); - - c.setForeground (selected ? - PropUtils.getSelectedSetForegroundColor() : - PropUtils.getSetForegroundColor()); + for (int i=0; i < end; i++) { + int idx = start + i; + Object value = getValueAt (idx, 0); + + if (value instanceof PropertySet) { - paintComponent(g, c, r.x, r.y, r.width, r.height); + Rectangle r = getCellRect (idx, 0, false); + r.x = ins.left; + r.width = getWidth() - (ins.left + ins.right); + if (g.hitClip (r.x, r.y, r.width, r.height)) { + PropertySet ps = (PropertySet) value; + + String txt = ps.getHtmlDisplayName(); + boolean isHtml = txt != null; + if (!isHtml) { + txt = ps.getDisplayName(); + } + boolean selected = canBeSelected && getSelectedRow() == idx; + if (htmlrenderer == null) { + htmlrenderer = HtmlRenderer.createRenderer(); + } + + JComponent painter = (JComponent) htmlrenderer.getTableCellRendererComponent(this, txt, selected, selected, idx, 0); + + htmlrenderer.setHtml (isHtml); + + htmlrenderer.setIconTextGap (2); + + htmlrenderer.setIcon( + getPropertySetModel().isExpanded(ps) ? + PropUtils.getExpandedIcon() : PropUtils.getCollapsedIcon() + ); + + if (!selected) { + painter.setBackground (PropUtils.getSetRendererColor()); + painter.setForeground (PropUtils.getSetForegroundColor()); + } else { + painter.setBackground (PropUtils.getSelectedSetRendererColor()); + painter.setForeground (PropUtils.getSelectedSetForegroundColor()); + } + painter.setOpaque (true); + + paintComponent (g, painter, r.x, r.y, r.width, r.height); } } } Index: openide/src/org/openide/explorer/view/ChoiceView.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/view/ChoiceView.java,v --- openide/src/org/openide/explorer/view/ChoiceView.java 3 Dec 2002 14:11:38 -0000 1.14 +++ openide/src/org/openide/explorer/view/ChoiceView.java 2 May 2004 11:03:32 -0000 @@ -51,7 +51,7 @@ /** Initialize view. */ private void initializeChoice () { - setRenderer (new NodeRenderer ()); + setRenderer (NodeRenderer.sharedInstance()); setModel (model = createModel ()); Index: openide/src/org/openide/explorer/view/IconView.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/view/IconView.java,v --- openide/src/org/openide/explorer/view/IconView.java 27 Feb 2003 23:40:39 -0000 1.19 +++ openide/src/org/openide/explorer/view/IconView.java 2 May 2004 11:03:32 -0000 @@ -27,6 +27,10 @@ - improve cell renderer (two lines of text or hints) - better behaviour during scrolling (ListPane) - external selection bug (BUG ID: 01110034) + - + - XXX if doing anything with this class other than deleting it, rewrite it to use a JTable - that would be + - much more sensible and scalable. -Tim + - */ /** A view displaying icons. @@ -67,7 +71,7 @@ return null; } }; - list.setCellRenderer (new NodeRenderer (true)); + list.setCellRenderer (NodeRenderer.sharedInstance()); return list; } Index: openide/src/org/openide/explorer/view/ListViewDropSupport.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/view/ListViewDropSupport.java,v --- openide/src/org/openide/explorer/view/ListViewDropSupport.java 21 Nov 2003 09:05:25 -0000 1.16 +++ openide/src/org/openide/explorer/view/ListViewDropSupport.java 2 May 2004 11:03:32 -0000 @@ -53,9 +53,6 @@ /** The component we are supporting with drop support */ protected JList list; - /** For managing visual appearance of JList cells. */ - protected NodeRenderer.List cellRenderer; - // Operations public ListViewDropSupport (ListView view, JList list) { this( view, list, true ); @@ -66,7 +63,6 @@ { this.view = view; this.list = list; - //cellRenderer = (NodeListCellRenderer)list.getCellRenderer(); this.dropTargetPopupAllowed = dropTargetPopupAllowed; } @@ -265,13 +261,4 @@ } return dropTarget; } - - /** Safe getter for the cell renderer of asociated list */ - NodeRenderer.List getCellRenderer () { - if (cellRenderer == null) - cellRenderer = (NodeRenderer.List)list.getCellRenderer(); - return cellRenderer; - } - - } Index: openide/src/org/openide/explorer/view/NodeRenderer.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/view/NodeRenderer.java,v --- openide/src/org/openide/explorer/view/NodeRenderer.java 22 Apr 2004 13:10:26 -0000 1.30 +++ openide/src/org/openide/explorer/view/NodeRenderer.java 2 May 2004 11:03:33 -0000 @@ -20,29 +20,13 @@ import java.awt.Image; import java.awt.KeyboardFocusManager; -import javax.swing.BorderFactory; -import javax.swing.ImageIcon; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JTree; -import javax.swing.ListCellRenderer; -import javax.swing.UIManager; -import javax.swing.border.Border; -import javax.swing.border.LineBorder; -import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.TreeCellRenderer; -import java.beans.BeanInfo; -import java.lang.ref.Reference; -import java.lang.ref.WeakReference; -import java.util.WeakHashMap; -import javax.swing.JRootPane; -import javax.swing.SwingUtilities; -import javax.swing.plaf.ColorUIResource; +import javax.swing.*; -import org.openide.ErrorManager; +import org.openide.awt.HtmlRenderer; +import org.openide.awt.ListPane; import org.openide.nodes.Node; -import org.openide.util.Utilities; /** Default renderer for nodes. Can paint either Nodes directly or @@ -50,44 +34,41 @@ * * @see org.openide.nodes.Node * - * @author Jaroslav Tulach + * @author Jaroslav Tulach, Tim Boudreau */ public class NodeRenderer extends Object implements TreeCellRenderer, ListCellRenderer { + /** Shared instance of NodeRenderer. */ - private static NodeRenderer sharedInstance; + //no point in lazy initialization, it's the only thing this class + //is used for + private static NodeRenderer sharedInstance = new NodeRenderer(); /** Flag indicating if to use big icons. */ - private boolean bigIcons; + private boolean bigIcons = false; + + private HtmlRenderer.Renderer renderer = HtmlRenderer.createRenderer(); - static Border emptyBorder = BorderFactory.createEmptyBorder (1, 1, 1, 1); - /** Creates default renderer. */ public NodeRenderer () { } - /** Creates renderer. * @param bigIcons use big icons if possible + * @deprecated bigIcons was only used by IconView, and not used by that anymore. Use sharedInstance() + * to get an instance of NodeRenderer. */ public NodeRenderer (boolean bigIcons) { this.bigIcons = bigIcons; } - /** Gets for one singleton sharedInstance. */ + /** Get the singleton instance used by all explorer views. */ public static NodeRenderer sharedInstance () { - if (sharedInstance == null) { - sharedInstance = new NodeRenderer (); - } return sharedInstance; } - // - // Rendering methods - // - /** Finds the component that is capable of drawing the cell in a tree. * @param value value can be either Node * or a VisualizerNode. @@ -98,446 +79,135 @@ boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus ) { - return getTree().getTreeCellRendererComponent ( - tree, value, sel, expanded, leaf, row, hasFocus - ); - } - - - /** This is the only method defined by ListCellRenderer. We just - * reconfigure the Jlabel each time we're called. - */ - public Component getListCellRendererComponent ( - JList list, - Object value, // value to display - int index, // cell index - boolean isSelected, // is the cell selected - boolean cellHasFocus // the list and the cell have the focus - ) { - // accepting either Node or Visualizers - VisualizerNode vis = (value instanceof Node) ? - VisualizerNode.getVisualizer (null, (Node)value) - : - (VisualizerNode)value; + VisualizerNode vis = findVisualizerNode (value); - if (vis == null) { - vis = VisualizerNode.EMPTY; + String text = vis.getHtmlDisplayName(); + boolean isHtml = text != null; + if (!isHtml) { + text = vis.getDisplayName(); } - ListCellRenderer r = bigIcons ? (ListCellRenderer)getPane() : getList(); - - Component result = r.getListCellRendererComponent ( - list, vis, index, isSelected, cellHasFocus - ); - result.setFont(list.getFont()); - return result; - } - - // ******************** - // Support for dragging - // ******************** - - /** Value of the cell with 'drag under' visual feedback */ - private static VisualizerNode draggedOver; - - - /** DnD operation enters. Update look and feel to the 'drag under' state. - * @param value the value of cell which should have 'drag under' visual feedback - */ - static void dragEnter (Object dragged) { - draggedOver = (VisualizerNode)dragged; - } - - /** DnD operation exits. Revert to the normal look and feel. */ - static void dragExit () { - draggedOver = null; - } - - - // ******************** - // Cache for ImageIcons - // ******************** + //Get our result value - really it is ren, but this call causes + //it to configure itself with the passed values + Component result = renderer.getTreeCellRendererComponent( + tree, text, sel, expanded, leaf, row, hasFocus); - /** default icon to use when none is present */ - private static final String DEFAULT_ICON = "org/openide/resources/defaultNode.gif"; // NOI18N + renderer.setHtml(isHtml); - /** loaded default icon */ - private static ImageIcon defaultIcon; + //Do our additional configuration - set up the icon and possibly + //do some hacks to make it look focused for TreeTableView + configureFrom (renderer, tree, expanded, sel, vis); - /** of icons used (Image, IconImage)*/ - private static final WeakHashMap map = new WeakHashMap (); - - /** Loades default icon if not loaded. */ - static ImageIcon getDefaultIcon () { - if (defaultIcon == null) { - defaultIcon = new ImageIcon(Utilities.loadImage(DEFAULT_ICON)); - } - - return defaultIcon; + return result; } - /** Finds imager for given resource. - * @param image image to get - * @return icon for the image + /** This is the only method defined by ListCellRenderer. We just + * reconfigure the Jlabel each time we're called. */ - static ImageIcon getIcon (Image image) { - Reference ref = (Reference)map.get (image); - - ImageIcon icon = ref == null ? null : (ImageIcon)ref.get (); - if (icon != null) { - return icon; - } - - icon = new ImageIcon (image); - map.put (image, new WeakReference (icon)); - - return icon; - } - - // - // Renderers - // - - - private static NodeRenderer.Tree tree = null; - - private synchronized static NodeRenderer.Tree getTree () { - if (tree == null) - tree = new NodeRenderer.Tree (); - return tree; - } - - private static NodeRenderer.Pane pane = null; - - private synchronized static NodeRenderer.Pane getPane() { - if (pane == null) - pane = new NodeRenderer.Pane (); - return pane; - } - - private static NodeRenderer.List list = null; - - private synchronized static NodeRenderer.List getList() { - if (list == null) - list = new NodeRenderer.List (); - return list; - } + public Component getListCellRendererComponent ( + JList list, Object value, int index, boolean sel, + boolean cellHasFocus) { + VisualizerNode vis = findVisualizerNode(value); - /** Tree cell renderer. Accepts only VisualizerNode values. */ - final static class Tree extends JLabel implements TreeCellRenderer { - /** generated Serialized Version UID */ - static final long serialVersionUID = -183570483117501696L; - - private boolean treeHasFocus = false; - - private boolean hasFocus = false; - private boolean selected = false; - - public Tree() { - } - - public boolean isOpaque() { - return false; - } - - public void setBackground (Color c) { - bg = c; - } + String text = vis.getHtmlDisplayName(); + boolean isHtml = text != null; + if (!isHtml) { + text = vis.getDisplayName(); + } + + //Get our result value - really it is ren, but this call causes + //it to configure itself with the passed values + Component result = renderer.getListCellRendererComponent( + list, text, index, sel, cellHasFocus || value == draggedOver); + renderer.setHtml(isHtml); + + //Do our additional configuration - set up the icon and possibly + //do some hacks to make it look focused for TreeTableView + int iconWidth = configureFrom (renderer, list, false, sel, vis); + + boolean bigIcons = this.bigIcons || list instanceof ListPane; + + if (bigIcons) { + renderer.setCentered(true); + } else { + //Indent elements in a ListView/ChoiceView relative to their position + //in the node tree. Only does anything if you've subclassed and + //overridden createModel(). Does anybody do that? + if (list.getModel() instanceof NodeListModel && ((NodeListModel) list.getModel()).getDepth() > 1) { + int indent = iconWidth * + NodeListModel.findVisualizerDepth (list.getModel (), vis); - private Color bg = Color.WHITE; - - public void paint (java.awt.Graphics g) { - if (!isSynth && selected) { - Color c = g.getColor(); - g.setColor (bg); - g.fillRect (0,0,getWidth(), getHeight()); - } - super.paint(g); - } - - /** @return Rendered cell component */ - public Component getTreeCellRendererComponent( - JTree tree, Object value, - boolean sel, boolean expanded, - boolean leaf, int row, boolean hasFocus - ) { - setEnabled(tree.isEnabled()); - // accepts only VisualizerNode - VisualizerNode vis = (VisualizerNode)value; - - Image iconImg; - if (expanded) { - iconImg = vis.node.getOpenedIcon(BeanInfo.ICON_COLOR_16x16); - } else { - iconImg = vis.node.getIcon(BeanInfo.ICON_COLOR_16x16); - } - - // bugfix #28515, check if getIcon contract isn't broken - if (iconImg == null) { - String method = expanded ? "getOpenedIcon" : "getIcon"; // NOI18N - ErrorManager.getDefault ().log (ErrorManager.WARNING, "Node \"" + vis.node.getName () + // NOI18N - "\" [" +vis.node.getClass().getName()+ "] cannot return null from " + method + "(). See Node." + method + " contract."); // NOI18N - } else { - ImageIcon nodeicon = NodeRenderer.getIcon(iconImg); - - setIconTextGap (4 - nodeicon.getIconWidth() - + ( nodeicon.getIconWidth() > 24 ? nodeicon.getIconWidth() : 24 ) ); - setIcon(nodeicon); - } - - setText(vis.getDisplayName ()); - - // provide "drag under" feedback if DnD operation is active // NOI18N - if (vis == draggedOver) { - sel = true; + renderer.setIndent (indent); } - - this.hasFocus = hasFocus; - selected = sel; - - if (sel) { - //Find out who has focus - Component focusOwner = - KeyboardFocusManager.getCurrentKeyboardFocusManager(). - getPermanentFocusOwner(); - - treeHasFocus = focusOwner == tree || - tree.isAncestorOf(focusOwner) || focusOwner instanceof TreeTable; - - if (!treeHasFocus) { - TreeTable tt = (TreeTable)SwingUtilities.getAncestorOfClass(TreeTable.class, focusOwner); - if (tt != null) { - treeHasFocus = focusOwner != null && - tt.getDefaultRenderer(TreeTableModelAdapter.class) - == tree; - } - } - setForeground(treeHasFocus || isSynth ? - UIManager.getColor("Tree.selectionForeground") : - getNoFocusSelectionForeground()); //NOI18N - - setBackground(treeHasFocus || isSynth ? - UIManager.getColor("Tree.selectionBackground") : - getNoFocusSelectionBackground()); - - } else { - setForeground(tree.getForeground()); - setBackground(tree.getBackground()); - } - - return this; - } - - protected void firePropertyChange(String name, Object old, Object nw) { - // do really nothing! - } - - public void validate() { - - } - - public void doLayout() { - - } - - public void revalidate() { - - } - - public void repaint(long l, int x, int y, int w, int h) { - } + return result; + } - public void repaint() { - - } - - } // End of class Tree. - - /** Hack for broken backgrounds on synth look and feel */ - private static boolean isSynth = UIManager.getLookAndFeel().getClass().getName().indexOf ("com.sun.java.swing.plaf.gtk") != -1; - - - /** Implements a ListCellRenderer for rendering items - * of a List containing Nodes. - * It displays the node's 16x16 icon and its display name. + /** Utility method which performs configuration which is common to all of the renderer + * implementations - sets the icon and focus properties on the renderer + * from the VisualizerNode. * - * @author Ian Formanek */ - static final class List extends JLabel implements ListCellRenderer { - /** generated Serialized Version UID */ - static final long serialVersionUID = -8387317362588264203L; - - /** Focused Node border. */ - protected static Border focusBorder = UIManager.getColor ("List.focusCellHighlight") != null ? // NOI18N - BorderFactory.createLineBorder (UIManager.getColor ("List.focusCellHighlight")) : // NOI18N - // recommended by issue 37335 - LineBorder.createGrayLineBorder (); - - public List() { - setOpaque(true); - } - - /** This is the only method defined by ListCellRenderer. We just - * reconfigure the Jlabel each time we're called. - */ - public Component getListCellRendererComponent ( - JList list, - Object value, // value to display - int index, // cell index - boolean isSelected, // is the cell selected - boolean cellHasFocus) // the list and the cell have the focus - { - VisualizerNode vis = (VisualizerNode)value; - ImageIcon nodeicon = NodeRenderer.getIcon(vis.node.getIcon(BeanInfo.ICON_COLOR_16x16)); - setIcon(nodeicon); - setText(vis.getDisplayName ()); - if (isSelected) { - Component focusOwner = - KeyboardFocusManager.getCurrentKeyboardFocusManager(). - getPermanentFocusOwner(); - - boolean hasFocus = focusOwner == list || list.isAncestorOf(focusOwner); - - setBackground(hasFocus ? list.getSelectionBackground() : - getNoFocusSelectionBackground()); - - setForeground(hasFocus ? list.getSelectionForeground() : - getNoFocusSelectionForeground()); - } else { - setBackground(list.getBackground()); - setForeground(list.getForeground()); - } - - setIconTextGap (4 - nodeicon.getIconWidth() - + ( nodeicon.getIconWidth() > 24 ? nodeicon.getIconWidth() : 24 ) ); - - - int delta = NodeListModel.findVisualizerDepth (list.getModel (), vis); - - Border border = (cellHasFocus || value == draggedOver) ? focusBorder : emptyBorder; - if (delta > 0) { - border = BorderFactory.createCompoundBorder ( - BorderFactory.createEmptyBorder (0, nodeicon.getIconWidth() * delta, 0, 0), - border - ); - } - setBorder(border); - - return this; - } - - protected void firePropertyChange(String name, Object old, Object nw) { - // do really nothing! + private int configureFrom (HtmlRenderer.Renderer ren, Container + target, boolean useOpenedIcon, boolean sel, VisualizerNode vis) { + + Icon icon = vis.getIcon(useOpenedIcon, bigIcons); + + 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 (24 - 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); } - } // End of class List. + ren.setIcon (icon); - - /** List cell renderer which renders icon and display name from VisualizerNode. */ - final static class Pane extends JLabel implements ListCellRenderer { - /** generated Serialized Version UID */ - static final long serialVersionUID = -5100925551665387243L; - - /** Focused Node border. */ - static Border focusBorder = UIManager.getColor ("List.focusCellHighlight") != null ? // NOI18N - BorderFactory.createLineBorder (UIManager.getColor ("List.focusCellHighlight")) : // NOI18N - // recommended by issue 37335 - LineBorder.createGrayLineBorder (); - - /** Creates a new NetbeansListCellRenderer */ - public Pane () { - setOpaque(true); - setVerticalTextPosition(JLabel.BOTTOM); - setHorizontalAlignment(JLabel.CENTER); - setHorizontalTextPosition(JLabel.CENTER); + if (target instanceof TreeTable.TreeTableCellRenderer) { + TreeTable tt = ((TreeTable.TreeTableCellRenderer) target).getTreeTable(); + ren.setParentFocused (tt.hasFocus() || tt.isEditing()); } + return icon.getIconWidth() == 0 ? 24 : icon.getIconWidth(); + } - /** This is the only method defined by ListCellRenderer. We just - * reconfigure the Jlabel each time we're called. - * @param list the JList - * @param value the value returned by list.getModel().getElementAt(index) - * @param index the cells index - * @param isSelected true if the specified cell was selected - * @param cellHasFocus true if the specified cell has the focus - * @return a component whose paint() method will render the specified value - */ - public Component getListCellRendererComponent ( - JList list, Object value, int index, - boolean isSelected, boolean cellHasFocus - ) { - VisualizerNode vis = (VisualizerNode)value; - - setIcon(NodeRenderer.getIcon(vis.node.getIcon(BeanInfo.ICON_COLOR_32x32))); - setText(vis.getDisplayName ()); - Component focusOwner = - KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); - - boolean hasFocus = focusOwner == list || - list.isAncestorOf(focusOwner); + /** Utility method to find a visualizer node for the object passed to + * any of the cell renderer methods as the value */ + private static final VisualizerNode findVisualizerNode(Object value) { + VisualizerNode vis = (value instanceof Node) ? + VisualizerNode.getVisualizer (null, (Node) value) : + (VisualizerNode)value; - if (isSelected){ - setBackground(hasFocus ? list.getSelectionBackground() - : getNoFocusSelectionBackground()); - - setForeground(hasFocus ? list.getSelectionForeground() : - getNoFocusSelectionForeground()); - } - else { - setBackground(list.getBackground()); - setForeground(list.getForeground()); + if (vis == null) { + vis = VisualizerNode.EMPTY; } - setBorder(cellHasFocus ? focusBorder : emptyBorder); - return this; + return vis; } - protected void firePropertyChange(String name, Object old, Object nw) { - // do really nothing! - } + // ******************** + // Support for dragging + // ******************** - } // End of class Pane. + /** Value of the cell with 'drag under' visual feedback */ + private static VisualizerNode draggedOver; - private static Color noFocusSelectionBackground=null; - static Color getNoFocusSelectionBackground() { - if (noFocusSelectionBackground == null) { - //allow theme/ui custom definition - noFocusSelectionBackground = - UIManager.getColor("nb.explorer.noFocusSelectionBackground"); //NOI18N - if (noFocusSelectionBackground == null) { - //try to get standard shadow color - noFocusSelectionBackground = UIManager.getColor("controlShadow"); //NOI18N - if (noFocusSelectionBackground == null) { - //Okay, the look and feel doesn't suport it, punt - noFocusSelectionBackground = new ColorUIResource(Color.lightGray); - } - //Lighten it a bit because disabled text will use controlShadow/ - //gray - noFocusSelectionBackground = noFocusSelectionBackground.brighter(); - } - } - return noFocusSelectionBackground; + /** DnD operation enters. Update look and feel to the 'drag under' state. + * @param dragged the value of cell which should have 'drag under' visual feedback + */ + static void dragEnter (Object dragged) { + draggedOver = (VisualizerNode)dragged; } - private static Color noFocusSelectionForeground=null; - static Color getNoFocusSelectionForeground() { - if (noFocusSelectionForeground == null) { - //allow theme/ui custom definition - noFocusSelectionForeground = - UIManager.getColor("nb.explorer.noFocusSelectionForeground"); //NOI18N - if (noFocusSelectionForeground == null) { - //try to get standard shadow color - noFocusSelectionForeground = UIManager.getColor("textText"); //NOI18N - if (noFocusSelectionForeground == null) { - //Okay, the look and feel doesn't suport it, punt - noFocusSelectionForeground = Color.BLACK; - } - } - } - return noFocusSelectionForeground; + /** DnD operation exits. Revert to the normal look and feel. */ + static void dragExit () { + draggedOver = null; } - } Index: openide/src/org/openide/explorer/view/TableSheetCell.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/view/TableSheetCell.java,v --- openide/src/org/openide/explorer/view/TableSheetCell.java 17 Feb 2004 16:47:49 -0000 1.18 +++ openide/src/org/openide/explorer/view/TableSheetCell.java 2 May 2004 11:03:33 -0000 @@ -259,11 +259,11 @@ propPanel.setBackground(tableHasFocus ? table.getSelectionBackground() : - NodeRenderer.getNoFocusSelectionBackground()); + TreeTable.getUnfocusedSelectedBackground()); propPanel.setForeground(tableHasFocus ? table.getSelectionForeground() : - NodeRenderer.getNoFocusSelectionForeground()); + TreeTable.getUnfocusedSelectedForeground()); } else { propPanel.setBackground(table.getBackground()); @@ -290,7 +290,7 @@ nullPanel.setBackground(tableHasFocus ? table.getSelectionBackground() : - NodeRenderer.getNoFocusSelectionBackground()); + TreeTable.getUnfocusedSelectedBackground()); //XXX may want to handle inverse theme here and use brighter if //below a threshold. Deferred to centralized color management //being implemented. Index: openide/src/org/openide/explorer/view/TreeTable.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/view/TreeTable.java,v --- openide/src/org/openide/explorer/view/TreeTable.java 2 Mar 2004 19:06:13 -0000 1.46 +++ openide/src/org/openide/explorer/view/TreeTable.java 2 May 2004 11:03:35 -0000 @@ -73,8 +73,7 @@ this.tree = new TreeTableCellRenderer(treeModel); this.tableModel = new TreeTableModelAdapter(tree, tableModel); - NodeRenderer rend = NodeRenderer.sharedInstance (); - tree.setCellRenderer(rend); + tree.setCellRenderer(NodeRenderer.sharedInstance ()); // Install a tableModel representing the visible rows in the tree. setModel(this.tableModel); @@ -276,19 +275,9 @@ int rowHeight = fm.getHeight() + 4; needCalcRowHeight = false; rowHeight = Math.min(20, rowHeight); - setRowHeight (rowHeight); + tree.setRowHeight (rowHeight); } - /* - * Overridden to pass the new rowHeight to the tree. - */ - public void setRowHeight(int rowHeight) { - super.setRowHeight(rowHeight); - if (tree != null && tree.getRowHeight() != rowHeight) { - tree.setRowHeight(getRowHeight()); - } - } - /** * Returns the tree that is being shared between the model. */ @@ -578,7 +567,6 @@ public TreeTableCellRenderer(TreeModel model) { super(model); - setRowHeight(getRowHeight()); setToggleClickCount(0); putClientProperty("JTree.lineStyle", "None"); // NOI18N } @@ -599,20 +587,26 @@ //do nothing } + /** + * Accessor so NodeRenderer can check if the tree table or its child has + * focus and paint with the appropriate color. + * + * @see NodeRenderer#configureFrom + * @return The tree table + */ + TreeTable getTreeTable() { + return TreeTable.this; + } + /** * Sets the row height of the tree, and forwards the row height to * the table. */ - public void setRowHeight(int rowHeight) { - if (rowHeight > 0) { - synchronized (getTreeLock()) { - super.setRowHeight(rowHeight); - if (TreeTable.this != null && - TreeTable.this.getRowHeight() != rowHeight) { - TreeTable.this.setRowHeight(getRowHeight()); - } - } - } + public void setRowHeight(int rowHeight) { + if (rowHeight > 0) { + super.setRowHeight(rowHeight); + TreeTable.this.setRowHeight(rowHeight); + } } /** @@ -703,12 +697,16 @@ focusOwner == TreeTable.this || TreeTable.this.isAncestorOf(focusOwner); + //TODO - it should be possible to simply set the correct + //color in prepareRenderer for the tree's cell renderer, + //rather than set it for the whole tree. Might fix a + //couple problems. -Tim setBackground(tableHasFocus ? table.getSelectionBackground() : - NodeRenderer.getNoFocusSelectionBackground()); + getUnfocusedSelectedBackground()); setForeground(tableHasFocus ? table.getSelectionForeground() : - NodeRenderer.getNoFocusSelectionForeground()); + getUnfocusedSelectedForeground()); } else { setBackground(table.getBackground()); setForeground(table.getForeground()); @@ -818,10 +816,10 @@ if (isEditing() && editorComp != null) { editorComp.setBackground(focused ? getSelectionBackground() : - NodeRenderer.getNoFocusSelectionBackground()); + getUnfocusedSelectedBackground()); editorComp.setForeground(focused ? getSelectionForeground() : - NodeRenderer.getNoFocusSelectionForeground()); + getUnfocusedSelectedForeground()); } } @@ -1611,4 +1609,46 @@ } } } + + private static Color unfocusedSelBg = null; + private static Color unfocusedSelFg = null; + + /** Get the system-wide unfocused selection background color */ + static Color getUnfocusedSelectedBackground() { + if (unfocusedSelBg == null) { + //allow theme/ui custom definition + unfocusedSelBg = + UIManager.getColor("nb.explorer.unfocusedSelBg"); //NOI18N + if (unfocusedSelBg == null) { + //try to get standard shadow color + unfocusedSelBg = UIManager.getColor("controlShadow"); //NOI18N + if (unfocusedSelBg == null) { + //Okay, the look and feel doesn't suport it, punt + unfocusedSelBg = Color.lightGray; + } + //Lighten it a bit because disabled text will use controlShadow/ + //gray + unfocusedSelBg = unfocusedSelBg.brighter(); + } + } + return unfocusedSelBg; + } + + /** Get the system-wide unfocused selection foreground color */ + static Color getUnfocusedSelectedForeground() { + if (unfocusedSelFg == null) { + //allow theme/ui custom definition + unfocusedSelFg = + UIManager.getColor("nb.explorer.unfocusedSelFg"); //NOI18N + if (unfocusedSelFg == null) { + //try to get standard shadow color + unfocusedSelFg = UIManager.getColor("textText"); //NOI18N + if (unfocusedSelFg == null) { + //Okay, the look and feel doesn't suport it, punt + unfocusedSelFg = Color.BLACK; + } + } + } + return unfocusedSelFg; + } } Index: openide/src/org/openide/explorer/view/TreeTableView.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/view/TreeTableView.java,v --- openide/src/org/openide/explorer/view/TreeTableView.java 29 Apr 2004 15:11:54 -0000 1.59 +++ openide/src/org/openide/explorer/view/TreeTableView.java 2 May 2004 11:03:37 -0000 @@ -361,7 +361,8 @@ hScrollBar = new JScrollBar(JScrollBar.HORIZONTAL); hScrollBar.putClientProperty(MetalScrollBarUI.FREE_STANDING_PROP, Boolean.FALSE); - + hScrollBar.setVisible(false); + listener = new ScrollListener(); treeTable.addPropertyChangeListener(listener); @@ -511,14 +512,18 @@ public void addNotify() { // to allow displaying popup also in blank area if ( treeTable.getParent() != null ) { - treeTable.getParent().addMouseListener( tableMouseListener ); + treeTableParent = treeTable.getParent(); + treeTableParent.addMouseListener( tableMouseListener ); } - super.addNotify(); + listener.revalidateScrollBar(); } + private Component treeTableParent = null; public void removeNotify() { super.removeNotify(); + treeTableParent.removeMouseListener ( tableMouseListener ); + treeTableParent = null; // clear node listeners tableModel.setNodes(new Node[] {}); @@ -799,10 +804,10 @@ } private void revalidateScrollBar() { - if (!isShowing()) { + if (!isDisplayable()) { return; } - if (treeTable.getColumnModel().getColumnCount() > 0 && ((TreeTable)treeTable).getTreeColumnIndex() > 0) { + if (treeTable.getColumnModel().getColumnCount() > 0 && ((TreeTable)treeTable).getTreeColumnIndex() >= 0) { int extentWidth = treeTable.getColumnModel().getColumn(((TreeTable)treeTable).getTreeColumnIndex()).getWidth(); int maxWidth = tree.getPreferredSize().width; int extentHeight = scrollPane.getViewport().getSize().height; Index: openide/src/org/openide/explorer/view/TreeView.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/view/TreeView.java,v --- openide/src/org/openide/explorer/view/TreeView.java 22 Apr 2004 13:10:26 -0000 1.159 +++ openide/src/org/openide/explorer/view/TreeView.java 2 May 2004 11:03:39 -0000 @@ -350,6 +350,8 @@ dropSupport.activate(dropActive); } + //XXX does anybody know what the XXX comments below mean? - Tim + /** Actions constants comes from DnDConstants.XXX constants. * All actions (copy, move, link) are allowed by default. * @return Set of actions which are allowed when dragging from @@ -1106,7 +1108,7 @@ // note: dropTarget is activated in constructor } // lazy cell editor init - tree.setCellEditor(new TreeViewCellEditor(tree, new NodeRenderer.Tree ())); + tree.setCellEditor(new TreeViewCellEditor(tree)); tree.setEditable(true); } @@ -1186,7 +1188,34 @@ setupSearch(); } - + + private boolean firstPaint = true; + + private void calcRowHeight (Graphics g) { + int height = g.getFontMetrics(getFont()).getHeight(); + setRowHeight(Math.max (18, height + 2)); + firstPaint = false; + } + + public void paint (Graphics g) { + if (firstPaint) { + calcRowHeight(g); + //This will generate a repaint, so don't bother continuing with super.paint() + //but do paint the background color so it doesn't paint gray the first time + g.setColor (getBackground()); + g.fillRect (0, 0, getWidth(), getHeight()); + return; + } + super.paint (g); + } + + public void setFont (Font f) { + if (f != getFont()) { + firstPaint = true; + super.setFont (f); + } + } + protected void processFocusEvent (FocusEvent fe) { super.processFocusEvent(fe); //Since the selected when focused is different, we need to force a Index: openide/src/org/openide/explorer/view/TreeViewCellEditor.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/view/TreeViewCellEditor.java,v --- openide/src/org/openide/explorer/view/TreeViewCellEditor.java 22 Apr 2004 13:10:26 -0000 1.38 +++ openide/src/org/openide/explorer/view/TreeViewCellEditor.java 2 May 2004 11:03:39 -0000 @@ -44,11 +44,13 @@ /** Construct a cell editor. * @param tree the tree - * @param renderer the renderer to use for the cell */ - public TreeViewCellEditor(JTree tree, TreeCellRenderer renderer) { - super(tree, renderer instanceof DefaultTreeCellRenderer ? - (DefaultTreeCellRenderer) renderer : new DefaultTreeCellRenderer()); //XXX for testing GTK hacks - will be deleted with the HTML mini renderer integration + public TreeViewCellEditor(JTree tree) { + //Use a dummy DefaultTreeCellEditor - we'll set up the correct + //icon when we fetch the editor component (see EOF). Not sure + //it's wildly vaulable to subclass DefaultTreeCellEditor here - + //we override most everything + super(tree, new DefaultTreeCellRenderer()); // deal with selection if already exists if (tree.getSelectionCount() == 1) { lastPath = tree.getSelectionPath(); @@ -155,7 +157,7 @@ } }; - tf.registerKeyboardAction( + tf.registerKeyboardAction( //TODO update to use inputMap/actionMap this, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true), JComponent.WHEN_FOCUSED @@ -264,6 +266,7 @@ protected void prepareForEditing () { tree.removeMouseMotionListener (this); + super.prepareForEditing (); } @@ -274,7 +277,7 @@ } /** Redefined default cell editor to convert nodes to name */ - static class Ed extends DefaultCellEditor { + class Ed extends DefaultCellEditor { /** generated Serialized Version UID */ static final long serialVersionUID = -6373058702842751408L; @@ -294,6 +297,8 @@ else delegate.setValue(""); // NOI18N + editingIcon = ((VisualizerNode) value).getIcon(expanded, false); + ((JTextField) editorComponent).selectAll(); return editorComponent; } Index: openide/src/org/openide/explorer/view/VisualizerNode.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/view/VisualizerNode.java,v --- openide/src/org/openide/explorer/view/VisualizerNode.java 20 Jan 2004 15:31:59 -0000 1.39 +++ openide/src/org/openide/explorer/view/VisualizerNode.java 2 May 2004 11:03:40 -0000 @@ -13,9 +13,13 @@ package org.openide.explorer.view; +import java.awt.Image; +import java.beans.BeanInfo; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.util.*; +import javax.swing.Icon; +import javax.swing.ImageIcon; import javax.swing.SwingUtilities; import javax.swing.event.EventListenerList; import javax.swing.tree.TreeNode; @@ -23,14 +27,16 @@ import org.openide.ErrorManager; import org.openide.nodes.*; import org.openide.util.Mutex; +import org.openide.util.Utilities; import org.openide.util.enum.QueueEnumeration; /** Visual representation of one node. Holds necessary information about nodes * like icon, name, description and also list of its children. *

    -* There is at most one VisualizerNode for one node. All of them are hold in a cache. +* There is at most one VisualizerNode for one node. All of them are held in a cache. *

    -* The VisualizerNode level provides secure layer between Nodes and Swing AWT dispatch +* VisualizerNode provides a thread-safe layer between Nodes, which may fire +* property changes on any thread, and the AWT dispatch thread * thread. * * @author Jaroslav Tulach @@ -54,6 +60,12 @@ private static final ErrorManager err = ErrorManager.getDefault().getInstance("org.openide.explorer.view.VisualizerNode"); // NOI18N + /** Cached icon - pre-html, there was a separate cache in NodeRenderer, but + * if we're keeping a weak cache of VisualizerNodes, there's no reason not + * to keep it here */ + private Icon icon = null; + + // bugfix #29435, getVisualizer is synchronized in place of be called only from EventQueue /** Finds VisualizerNode for given node. * @param ch the children this visualizer should belong to @@ -274,12 +286,20 @@ */ public void propertyChange(final java.beans.PropertyChangeEvent evt) { String name = evt.getPropertyName (); + boolean isIconChange = Node.PROP_ICON.equals(name) || Node.PROP_OPENED_ICON.equals(name); if ( Node.PROP_NAME.equals (name) || Node.PROP_DISPLAY_NAME.equals (name) || - Node.PROP_ICON.equals (name) || - Node.PROP_OPENED_ICON.equals (name) + isIconChange ) { + if (isIconChange) { + //Ditch the cached icon type so the next call to getIcon() will + //recreate the ImageIcon + cachedIconType = -1; + } + if (Node.PROP_DISPLAY_NAME.equals(name)) { + htmlDisplayName = null; + } SwingUtilities.invokeLater (this); return; } @@ -305,13 +325,6 @@ } } ); } - /* - if ( - "lookAndFeel".equals (name) // NOI18N - ) { - SwingUtilities.invokeLater (this); - } - */ } /** Update the state of this class by retrieving new name, etc. @@ -391,6 +404,68 @@ return getDisplayName (); } + private static final String NO_HTML_DISPLAYNAME="noHtmlDisplayName"; + private String htmlDisplayName = null; + public String getHtmlDisplayName() { + if (htmlDisplayName == null) { + htmlDisplayName = node.getHtmlDisplayName(); + if (htmlDisplayName == null) { + htmlDisplayName = NO_HTML_DISPLAYNAME; + } + } + return htmlDisplayName == NO_HTML_DISPLAYNAME ? null : htmlDisplayName; + } + + Icon getIcon(boolean opened, boolean large) { + int newCacheType = getCacheType(opened, large); + + if (cachedIconType != newCacheType) { + int iconType = large ? BeanInfo.ICON_COLOR_32x32 : + BeanInfo.ICON_COLOR_16x16; + + Image image = opened ? node.getOpenedIcon( + iconType) : node.getIcon( + iconType); + + // bugfix #28515, check if getIcon contract isn't broken + if (image == null) { + String method = opened ? "getOpenedIcon" : "getIcon"; // NOI18N + ErrorManager.getDefault ().log (ErrorManager.WARNING, "Node \"" + + node.getName () + + "\" [" + node.getClass().getName()+ + "] cannot return null from " + method + "(). See Node." + + method + " contract."); // NOI18N + + icon = defaultIcon; + } else { + icon = new ImageIcon (image); + } + } + cachedIconType = newCacheType; + return icon; + } + + /** Some simple bitmasking to determine the type of the cached icon. + * Generally, it's worth caching one, but not a bunch - generally one will + * be used repeatedly. */ + private static final int getCacheType (boolean opened, boolean large) { + return (opened ? 2 : 0) | (large ? 1 : 0); + } + + /** loaded default icon */ + private static Icon defaultIcon; + private int cachedIconType = -1; + + /** default icon to use when none is present */ + private static final String DEFAULT_ICON = "org/openide/resources/defaultNode.gif"; // NOI18N + /** Loads default icon if not loaded. */ + private static Icon getDefaultIcon () { + if (defaultIcon == null) { + defaultIcon = new ImageIcon(Utilities.loadImage(DEFAULT_ICON)); + } + return defaultIcon; + } + /** Strong reference. */ private static final class StrongReference extends WeakReference { Index: openide/src/org/openide/filesystems/FileSystem.java =================================================================== RCS file: /cvs/openide/src/org/openide/filesystems/FileSystem.java,v --- openide/src/org/openide/filesystems/FileSystem.java 16 Apr 2004 00:17:22 -0000 1.78 +++ openide/src/org/openide/filesystems/FileSystem.java 2 May 2004 11:03:41 -0000 @@ -700,6 +700,40 @@ public java.awt.Image annotateIcon (java.awt.Image icon, int iconType, java.util.Set files); } + /** Extension interface for Status provides HTML-formatted annotations. + * Principally this is used to deëmphasize status text by presenting + * it in a lighter color, by placing it inside + * <font color=!controlShadow> tags. Note that it is preferable to + * use logical colors (such as controlShadow) which are resolved by calling + * UIManager.getColor(key) — this way they will always fit with the + * look and feel. To use a logical color, prefix the color name with a + * ! character. + *

    + * Please use only the limited markup subset of HTML supported by the + * lightweight HTML renderer. + * @see org.openide.awt.HtmlRenderer + * @since 4.30 + */ + public static interface HtmlStatus extends Status { + /** Annotate a name such that the returned value contains HTML markup. + * The return value less the html content should typically be the same + * as the return value from annotateName(). This is used, + * for example, by VCS filesystems to deëphasize the status information + * included in the file name by using a light grey font color. + *

    + * For consistency with Node.getHtmlDisplayName(), + * filesystems that proxy other filesystems (and so must implement + * this interface to supply HTML annotations) should return null if + * the filesystem they proxy does not provide an implementation of + * HTMLStatus. + * @since 4.30 + * @see org.openide.awt.HtmlRenderer + * @see org.openide.loaders.DataNode#getHtmlDisplayName + * @see org.openide.nodes.Node#getHtmlDisplayName + **/ + public String annotateNameHtml (String name, java.util.Set files); + } + /** Empty status */ private static final Status STATUS_NONE = new Status () { public String annotateName (String name, java.util.Set files) { Index: openide/src/org/openide/nodes/FilterNode.java =================================================================== RCS file: /cvs/openide/src/org/openide/nodes/FilterNode.java,v --- openide/src/org/openide/nodes/FilterNode.java 24 Feb 2004 13:34:06 -0000 1.88 +++ openide/src/org/openide/nodes/FilterNode.java 2 May 2004 11:03:43 -0000 @@ -22,6 +22,7 @@ import java.util.*; import java.lang.ref.WeakReference; +import java.lang.reflect.Method; import org.openide.ErrorManager; import org.openide.util.datatransfer.NewType; @@ -643,6 +644,47 @@ } return retValue; + } + + /** Get a display name containing HTML markup. Note: If you subclass + * FilterNode and override getDisplayName(), this method will + * always return null unless you override it as well (assuming that if you're + * changing the display name, you don't want an HTML display name constructed + * from the original node's display name to be what shows up in views of + * this node). If getDisplayName() is not overridden, + * this method will return whatever the original node returns from this + * method. + *

    + * Note that if you do override getDisplayName, you should also override + * this method to return null. + * + * + * + * @see org.openide.nodes.Node#getHtmlDisplayName + * @return An HTML display name, if available, or null if no display name + * is available */ + public String getHtmlDisplayName() { + if (overridesGetDisplayName()) { + return null; + } else { + return delegating (DELEGATE_GET_DISPLAY_NAME) ? + original.getHtmlDisplayName() : super.getHtmlDisplayName(); + } + } + + private boolean overridesGetDisplayName() { + if (getClass() != FilterNode.class) { + try { + Method m = getClass().getMethod("getDisplayName", null); //NOI18N + return m.getDeclaringClass() != FilterNode.class; + } catch (NoSuchMethodException nsme) { + //can't happen + ErrorManager.getDefault().notify(nsme); + return true; + } + } else { + return false; + } } /* Index: openide/src/org/openide/nodes/Node.java =================================================================== RCS file: /cvs/openide/src/org/openide/nodes/Node.java,v --- openide/src/org/openide/nodes/Node.java 9 Apr 2004 12:32:12 -0000 1.81 +++ openide/src/org/openide/nodes/Node.java 2 May 2004 11:03:45 -0000 @@ -655,6 +655,32 @@ } } + /** Return a variant of the display name containing HTML markup + * conforming to the limited subset of font-markup HTML supported by + * the lightweight HTML renderer org.openide.awt.HtmlRenderer + * (font color, bold, italic and strikethrough supported; font + * colors can be UIManager color keys if they are prefixed with + * a ! character, i.e. <font color=&'controlShadow'>). + * Enclosing html tags are not needed. If returning non-null, HTML + * markup characters that should be literally renderered must be + * escaped ( > becomes &gt; and so forth). + *

    This method should return either an HTML display name + * or null; it should not return the non-html display name. + *

    + * Note there is no property corresponding to the html display name - + * if it should change, a change in the display name should be fired; this + * should not be a mechanism for returning anything other than a marked + * up version of the return value of getDisplayName. + * + * @see org.openide.awt.HtmlRenderer + * @since 4.30 + * @return a String containing conformant HTML markup which + * represents the display name, or null. The default implementation + * returns null. */ + public String getHtmlDisplayName() { + return null; + } + /** Register delegating lookup so it can always be found. */ final void registerDelegatingLookup (NodeLookup l) { @@ -1051,6 +1077,26 @@ public int hashCode () { return getName().hashCode (); } + + /** Return a variant of the display name containing HTML markup + * conforming to the limited subset of font-markup HTML supported by + * the lightweight HTML renderer org.openide.awt.HtmlRenderer + * (font color, bold, italic and strikethrough supported; font + * colors can be UIManager color keys if they are prefixed with + * a ! character, i.e. <font color=&'controlShadow'>). + * Enclosing html tags are not needed. + *

    This method should return either an HTML display name + * or null; it should not return the non-html display name if no + * markup is needed. + * + * @see org.openide.awt.HtmlRenderer + * @since 4.30 + * @return a String containing conformant, legal HTML markup which + * represents the display name, or null. The default implementation + * returns null. */ + public String getHtmlDisplayName() { + return null; + } } /** Description of a Bean property on a node, and operations on it. @@ -1197,6 +1243,25 @@ return getName ().hashCode () * ( valueType == null ? 1 : valueType.hashCode () ); + } + + /** Return a variant of the display name containing HTML markup + * conforming to the limited subset of font-markup HTML supported by + * the lightweight HTML renderer org.openide.awt.HtmlRenderer + * (font color, bold, italic and strikethrough supported; font + * colors can be UIManager color keys if they are prefixed with + * a ! character, i.e. <font color=&'controlShadow'>). + * Enclosing html tags are not needed. + *

    This method should return either an HTML display name + * or null; it should not return the non-html display name. + * + * @see org.openide.awt.HtmlRenderer + * @since 4.30 + * @return a String containing conformant, legal HTML markup which + * represents the display name, or null. The default implementation + * returns null. */ + public String getHtmlDisplayName() { + return null; } } Index: openide/test/unit/src/org/openide/nodes/NodeHtmlTest.java =================================================================== RCS file: openide/test/unit/src/org/openide/nodes/NodeHtmlTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/test/unit/src/org/openide/nodes/NodeHtmlTest.java 2 May 2004 11:03:49 -0000 @@ -0,0 +1,102 @@ +/* + * 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.openide.nodes; + +import java.util.*; +import junit.framework.*; +import junit.textui.TestRunner; +import org.openide.nodes.*; + +import org.netbeans.junit.*; + +/** Tests HTML display name contracts for filter nodes and regular nodes + * + * @author Tim Boudreau + */ +public class NodeHtmlTest extends NbTestCase { + + public NodeHtmlTest(String name) { + super(name); + } + + public static void main(String[] args) { + TestRunner.run(new NbTestSuite(FilterNodeTest.class)); + } + + public void testDefaultHtmlDisplayNameIsNull() { + AbstractNode a = new AbstractNode (Children.LEAF); + a.setDisplayName("Finch"); + assertEquals("AbstractNode.getDisplayName is broken", "Finch", + a.getDisplayName()); + + assertNull("Unless overridden, getHtmlDisplayName should return null, " + + "not " + a.getHtmlDisplayName(), a.getHtmlDisplayName()); + + FilterNode fn = new FilterNode (a); + assertNull ("Filternode should have no default html display name unless" + + " its original overrides getHtmlDisplayName", fn.getHtmlDisplayName()); + } + + public void testFilteredHtmlNameIsPropagated() { + Node n = new HtmlNode(); + n.setDisplayName ("Whipporwill"); + FilterNode fn = new FilterNode (n); + + assertNotNull("This test is broken", n.getHtmlDisplayName()); + + assertNotNull("If a filter node's original supplies an html display " + + "name, the filter node's html display name should be non-null", + fn.getHtmlDisplayName()); + + assertEquals("FilterNode should propagate the html name of the original", + fn.getHtmlDisplayName(), n.getHtmlDisplayName()); + } + + public void testFilteredHtmlNameNotPropagatedIfGetDisplayNameOverridden() { + Node n = new HtmlNode(); + n.setDisplayName ("Lark"); + FilterNode fn = new HtmlDisplayNameNode (n); + + assertNotNull("This test is broken", n.getHtmlDisplayName()); + + assertNotNull("This test is broken", n.getHtmlDisplayName()); + + assertNull ("A filternode whose getDisplayName() method is overridden" + + " should return null from getHtmlDisplayName() even though its " + + " original returns non-null - got " + fn.getHtmlDisplayName(), + fn.getHtmlDisplayName()); + } + + + private static final String HTML_STRING = "this is html"; + private static class HtmlNode extends AbstractNode { + public HtmlNode() { + super (Children.LEAF); + } + + public String getHtmlDisplayName() { + return HTML_STRING; + } + } + + private static class HtmlDisplayNameNode extends FilterNode { + public HtmlDisplayNameNode (Node orig) { + super (orig); + } + public String getDisplayName() { + return "Not the same name!"; + } + } +} + Index: openide/test/unit/src/org/openide/windows/TopComponentActivatedNodesTest.java =================================================================== RCS file: /cvs/openide/test/unit/src/org/openide/windows/TopComponentActivatedNodesTest.java,v --- openide/test/unit/src/org/openide/windows/TopComponentActivatedNodesTest.java 30 Jan 2004 16:45:00 -0000 1.4 +++ openide/test/unit/src/org/openide/windows/TopComponentActivatedNodesTest.java 2 May 2004 11:03:50 -0000 @@ -62,6 +62,10 @@ return suite; } + protected boolean runInEQ() { + return true; + } + private ExplorerPanel p; private ExplorerManager em; private Node[] nodes; Index: projects/projectui/src/org/netbeans/modules/project/ui/ProjectsRootNode.java =================================================================== RCS file: /cvs/projects/projectui/src/org/netbeans/modules/project/ui/ProjectsRootNode.java,v --- projects/projectui/src/org/netbeans/modules/project/ui/ProjectsRootNode.java 1 May 2004 15:12:26 -0000 1.7 +++ projects/projectui/src/org/netbeans/modules/project/ui/ProjectsRootNode.java 2 May 2004 11:03:55 -0000 @@ -212,6 +212,10 @@ return isMain() ? MessageFormat.format( badgedNamePattern, new Object[] { original } ) : original; } + public String getHtmlDisplayName() { + return isMain() ? "" + super.getDisplayName() + "" : null; //NOI18N + } + public Image getIcon( int type ) { Image original = super.getIcon( type ); return isMain() ? Utilities.mergeImages( original, mainProjectBadge, 5, 10 ) : original; Index: vcscore/src/org/netbeans/modules/vcscore/VcsFileSystem.java =================================================================== RCS file: /cvs/vcscore/src/org/netbeans/modules/vcscore/VcsFileSystem.java,v --- vcscore/src/org/netbeans/modules/vcscore/VcsFileSystem.java 27 Apr 2004 14:42:24 -0000 1.258 +++ vcscore/src/org/netbeans/modules/vcscore/VcsFileSystem.java 2 May 2004 11:04:38 -0000 @@ -105,7 +105,8 @@ AbstractFileSystem.List, AbstractFileSystem.Info, AbstractFileSystem.Change, FileSystem.Status, CommandExecutionContext, CacheHandlerListener, - FileObjectExistence, VcsOISActivator, Serializable { + FileObjectExistence, VcsOISActivator, Serializable, + FileSystem.HtmlStatus { public static interface IgnoreListSupport { @@ -2591,6 +2592,13 @@ } else { result = displayName; } + return result; + } + + public String annotateNameHtml (String name, java.util.Set files) { + String result = annotateName (name, files); + result = Utilities.replaceString(result, name, + name + "") + ""; //NOI18N return result; }