This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 29466
Collapse All | Expand All

(-)core/settings/src/org/netbeans/modules/settings/convertors/SerialDataNode.java (+4 lines)
Lines 159-164 Link Here
159
        return getIcon (type);
159
        return getIcon (type);
160
    }
160
    }
161
    
161
    
162
    public String getHtmlDisplayName() {
163
        return null;
164
    }
165
    
162
    /** here should be decided if some change was fired by the setting object
166
    /** here should be decided if some change was fired by the setting object
163
     * or the node should notify convertor about the change. This is just
167
     * or the node should notify convertor about the change. This is just
164
     * workaround ensuring backward compatibility for archaic settings from
168
     * workaround ensuring backward compatibility for archaic settings from
(-)core/src/org/netbeans/core/projects/SettingChildren.java (-1 / +4 lines)
Lines 106-112 Link Here
106
        }        
106
        }        
107
        public SystemAction[] getActions() {            
107
        public SystemAction[] getActions() {            
108
            return removeActions(super.getActions(), new SystemAction[] {SystemAction.get(ToolsAction.class)});
108
            return removeActions(super.getActions(), new SystemAction[] {SystemAction.get(ToolsAction.class)});
109
        }        
109
        } 
110
        public String getHtmlDisplayName() {
111
            return null;
112
        }
110
    }
113
    }
111
114
112
    /** Property allowing display/manipulation of setting status for one specific layer. */
115
    /** Property allowing display/manipulation of setting status for one specific layer. */
(-)core/src/org/netbeans/core/ui/UINodes.java (+4 lines)
Lines 137-142 Link Here
137
        public Image getOpenedIcon (int type) {
137
        public Image getOpenedIcon (int type) {
138
            return getIcon(type);
138
            return getIcon(type);
139
        }
139
        }
140
        
141
        public String getHtmlDisplayName() {
142
            return null;
143
        }
140
144
141
        public SystemAction[] getActions () {
145
        public SystemAction[] getActions () {
142
            if (staticActions == null) {
146
            if (staticActions == null) {
(-)core/swing/tabcontrol/nbproject/project.xml (-1 / +11 lines)
Lines 17-23 Link Here
17
    <configuration>
17
    <configuration>
18
        <data xmlns="http://www.netbeans.org/ns/nb-module-project/1">
18
        <data xmlns="http://www.netbeans.org/ns/nb-module-project/1">
19
            <path>core/swing/tabcontrol</path>
19
            <path>core/swing/tabcontrol</path>
20
            <module-dependencies/>
20
            <module-dependencies>
21
                <dependency>
22
                    <code-name-base>org.openide</code-name-base>
23
                    <build-prerequisite/>
24
                    <compile-dependency/>
25
                    <run-dependency>
26
                        <release-version>1</release-version>
27
                        <specification-version>4.31</specification-version> 
28
                    </run-dependency>
29
                </dependency>
30
            </module-dependencies>
21
            <public-packages>
31
            <public-packages>
22
                <package>org.netbeans.swing.tabcontrol</package>
32
                <package>org.netbeans.swing.tabcontrol</package>
23
                <package>org.netbeans.swing.tabcontrol.plaf</package>
33
                <package>org.netbeans.swing.tabcontrol.plaf</package>
(-)core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/TabData.java (-1 / +1 lines)
Lines 117-123 Link Here
117
     * @return
117
     * @return
118
     */
118
     */
119
    public String toString() {
119
    public String toString() {
120
        return "[" + txt + "]";
120
        return txt;
121
    }
121
    }
122
122
123
    /**
123
    /**
(-)core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractTabCellRenderer.java (-4 / +6 lines)
Lines 21-26 Link Here
21
import org.netbeans.swing.tabcontrol.TabData;
21
import org.netbeans.swing.tabcontrol.TabData;
22
import org.netbeans.swing.tabcontrol.TabDisplayer;
22
import org.netbeans.swing.tabcontrol.TabDisplayer;
23
23
24
import org.openide.awt.HtmlRenderer;
25
24
import javax.swing.*;
26
import javax.swing.*;
25
import javax.swing.border.Border;
27
import javax.swing.border.Border;
26
import java.awt.*;
28
import java.awt.*;
Lines 430-437 Link Here
430
        if (isClipLeft()) {
432
        if (isClipLeft()) {
431
            //fiddle with the string to get "...blah"
433
            //fiddle with the string to get "...blah"
432
            String s = preTruncateString(getText(), g, txtW - 4); //subtract 4 so it's not flush w/ tab edge
434
            String s = preTruncateString(getText(), g, txtW - 4); //subtract 4 so it's not flush w/ tab edge
433
            Html.renderString(s, g, txtX, txtY, txtW, txtH, getFont(),
435
            HtmlRenderer.renderString(s, g, txtX, txtY, txtW, txtH, getFont(),
434
                              getForeground(), Html.STYLE_CLIP, true);
436
                              getForeground(), HtmlRenderer.STYLE_CLIP, true);
435
        } else {
437
        } else {
436
            String s;
438
            String s;
437
            if (isClipRight()) {
439
            if (isClipRight()) {
Lines 441-448 Link Here
441
            } else {
443
            } else {
442
                s = getText();
444
                s = getText();
443
            }
445
            }
444
            Html.renderString(s, g, txtX, txtY, txtW, txtH, getFont(),
446
            HtmlRenderer.renderString(s, g, txtX, txtY, txtW, txtH, getFont(),
445
                              getForeground(), Html.STYLE_TRUNCATE, true);
447
                              getForeground(), HtmlRenderer.STYLE_TRUNCATE, true);
446
        }
448
        }
447
    }
449
    }
448
450
(-)core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java (-2 / +3 lines)
Lines 14-19 Link Here
14
package org.netbeans.swing.tabcontrol.plaf;
14
package org.netbeans.swing.tabcontrol.plaf;
15
15
16
import org.netbeans.swing.tabcontrol.TabDisplayer;
16
import org.netbeans.swing.tabcontrol.TabDisplayer;
17
import org.openide.awt.HtmlRenderer;
17
18
18
import javax.swing.*;
19
import javax.swing.*;
19
import javax.swing.plaf.ComponentUI;
20
import javax.swing.plaf.ComponentUI;
Lines 153-161 Link Here
153
            textY = (height / 2) - (textHeight / 2) + fm.getAscent() - 1;
154
            textY = (height / 2) - (textHeight / 2) + fm.getAscent() - 1;
154
        }
155
        }
155
156
156
        Html.renderString(text, g, textX, textY, textW, height, getTxtFont(),
157
        HtmlRenderer.renderString(text, g, textX, textY, textW, height, getTxtFont(),
157
                          UIManager.getColor("textText"),
158
                          UIManager.getColor("textText"),
158
                          Html.STYLE_TRUNCATE, true);
159
                          HtmlRenderer.STYLE_TRUNCATE, true);
159
    }
160
    }
160
    
161
    
161
    //private static final JButton jb = new JButton();
162
    //private static final JButton jb = new JButton();
(-)core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BaseTabLayoutModel.java (-2 / +3 lines)
Lines 20-25 Link Here
20
package org.netbeans.swing.tabcontrol.plaf;
20
package org.netbeans.swing.tabcontrol.plaf;
21
21
22
import org.netbeans.swing.tabcontrol.TabDataModel;
22
import org.netbeans.swing.tabcontrol.TabDataModel;
23
import org.openide.awt.HtmlRenderer;
23
24
24
import javax.swing.*;
25
import javax.swing.*;
25
import java.awt.*;
26
import java.awt.*;
Lines 105-114 Link Here
105
        //dump it if the font changes.
106
        //dump it if the font changes.
106
        Integer result = (Integer) widthMap.get(text);
107
        Integer result = (Integer) widthMap.get(text);
107
        if (result == null) {
108
        if (result == null) {
108
            double wid = Html.renderString(text, TabListPopup.getOffscreenGraphics(), 0, 0,
109
            double wid = HtmlRenderer.renderString(text, TabListPopup.getOffscreenGraphics(), 0, 0,
109
                                           Integer.MAX_VALUE,
110
                                           Integer.MAX_VALUE,
110
                                           Integer.MAX_VALUE, getFont(),
111
                                           Integer.MAX_VALUE, getFont(),
111
                                           Color.BLACK, Html.STYLE_TRUNCATE,
112
                                           Color.BLACK, HtmlRenderer.STYLE_TRUNCATE,
112
                                           false);
113
                                           false);
113
            result = new Integer(Math.round(Math.round(wid)));
114
            result = new Integer(Math.round(Math.round(wid)));
114
            widthMap.put(text, result);
115
            widthMap.put(text, result);
(-)core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/GtkViewTabDisplayerUI.java (-2 / +3 lines)
Lines 14-19 Link Here
14
package org.netbeans.swing.tabcontrol.plaf;
14
package org.netbeans.swing.tabcontrol.plaf;
15
15
16
import org.netbeans.swing.tabcontrol.TabDisplayer;
16
import org.netbeans.swing.tabcontrol.TabDisplayer;
17
import org.openide.awt.HtmlRenderer;
17
18
18
import javax.swing.*;
19
import javax.swing.*;
19
import javax.swing.plaf.ComponentUI;
20
import javax.swing.plaf.ComponentUI;
Lines 166-174 Link Here
166
            textY = (height / 2) - (textHeight / 2) + fm.getAscent() - 1;
167
            textY = (height / 2) - (textHeight / 2) + fm.getAscent() - 1;
167
        }
168
        }
168
169
169
        Html.renderString(text, g, textX, textY, textW, height, getTxtFont(),
170
        HtmlRenderer.renderString(text, g, textX, textY, textW, height, getTxtFont(),
170
                          chiclet.getTextForeground(), //XXX
171
                          chiclet.getTextForeground(), //XXX
171
                          Html.STYLE_TRUNCATE, true);
172
                          HtmlRenderer.STYLE_TRUNCATE, true);
172
    }
173
    }
173
    
174
    
174
    //private static final JButton jb = new JButton();
175
    //private static final JButton jb = new JButton();
(-)core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/SlidingTabDisplayerButtonUI.java (-2 / +4 lines)
Lines 20-25 Link Here
20
20
21
import org.netbeans.swing.tabcontrol.TabDisplayer;
21
import org.netbeans.swing.tabcontrol.TabDisplayer;
22
22
23
import org.openide.awt.HtmlRenderer;
24
23
import javax.swing.*;
25
import javax.swing.*;
24
import javax.swing.plaf.ComponentUI;
26
import javax.swing.plaf.ComponentUI;
25
import javax.swing.plaf.basic.BasicToggleButtonUI;
27
import javax.swing.plaf.basic.BasicToggleButtonUI;
Lines 163-170 Link Here
163
            txtW -= iconH + b.getIconTextGap();
165
            txtW -= iconH + b.getIconTextGap();
164
        }
166
        }
165
        
167
        
166
        Html.renderString(b.getText(), g, txtX, txtY, txtW, txtH, b.getFont(),
168
        HtmlRenderer.renderString(b.getText(), g, txtX, txtY, txtW, txtH, b.getFont(),
167
              b.getForeground(), Html.STYLE_TRUNCATE, true);
169
              b.getForeground(), HtmlRenderer.STYLE_TRUNCATE, true);
168
    }
170
    }
169
171
170
    private static SlidingTabDisplayerButtonUI AQUA_INSTANCE = null;
172
    private static SlidingTabDisplayerButtonUI AQUA_INSTANCE = null;
(-)core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabListPopup.java (-96 / +117 lines)
Lines 17-23 Link Here
17
import org.netbeans.swing.tabcontrol.TabbedContainer;
17
import org.netbeans.swing.tabcontrol.TabbedContainer;
18
import org.netbeans.swing.tabcontrol.TabDisplayer;
18
import org.netbeans.swing.tabcontrol.TabDisplayer;
19
19
20
import org.openide.awt.HtmlRenderer;
21
20
import javax.swing.*;
22
import javax.swing.*;
23
import javax.swing.border.Border;
21
import javax.swing.table.TableCellRenderer;
24
import javax.swing.table.TableCellRenderer;
22
import javax.swing.table.TableColumn;
25
import javax.swing.table.TableColumn;
23
import javax.swing.table.TableColumnModel;
26
import javax.swing.table.TableColumnModel;
Lines 39-94 Link Here
39
42
40
final class TabListPopup extends JTable implements MouseMotionListener,
43
final class TabListPopup extends JTable implements MouseMotionListener,
41
        MouseListener {
44
        MouseListener {
45
    /** Reference to the focus owner when addNotify was called.  This is the
46
     * component that received the mouse event, so it's what we need to listen
47
     * on to update the selected cell as the user drags the mouse */
48
    private Component invokingComponent = null;
49
    /** Cached preferred size value */
50
    private Dimension prefSize = null;
51
    /** Flag indicating that the fixed row height has not yet been calculated - 
52
     * this is for fontsize support */
53
    boolean needCalcRowHeight = true;
42
54
43
    private static TabListPopup defaultInstance = null;
55
    /** Reference to container for which we display quicklist */
44
45
    /**
46
     * Reference to displayer for which we display quicklist
47
     */
48
    private TabDisplayer displayer = null;
56
    private TabDisplayer displayer = null;
49
57
50
    /**
58
    /** Reference to the popup object currently showing the default instance,
51
     * Creates a new instance of TabListPanel
59
     * if it is visible */
52
     */
60
    private static Popup currentPopup=null;
53
    private TabListPopup() {
61
    /** AWTEventListener which is attached when the popup is shown to ensure
54
        super(new TabListPopupTableModel());
62
     * that it is closed when it should be.  It is removed after the popup
55
        setBorder(BorderFactory.createLineBorder(getForeground()));
63
     * has been hidden. */
56
        setColors();
64
    private static AWTEventListener blistener = null;
57
    }
65
    /** Reference to the default shared instance */
66
    private static Reference instance=null;
67
    /** Time of invocation, used to determine if a mouse release is
68
     * delayed long enough from a mouse press that it should close
69
     * the popup, instead of assuming the user wants move-and-click
70
     * behavior instead of drag-and-click behavior */
71
    long invocationTime = -1;
58
72
59
    /**
73
    private static final Border rendererBorder = 
60
     * Sets right colors of the control. Colors should imitate colors of
74
        BorderFactory.createEmptyBorder (2, 3, 0, 3);
61
     * drop-down list of combo box.
62
     */
63
    private void setColors() {
64
        Color color = UIManager.getColor("ComboBox.background");
65
        setBackground(color);
66
        //setGridColor(color);
67
        color = UIManager.getColor("ComboBox.foreground");
68
        setForeground(color);
69
        color = UIManager.getColor("ComboBox.selectionBackground");
70
        setSelectionBackground(color);
71
        color = UIManager.getColor("ComboBox.selectionForeground");
72
        setSelectionForeground(color);
73
        Font font = (Font) UIManager.get("ComboBox.font");
74
        setFont(font);
75
        // only vertical lines visible
76
        setShowHorizontalLines(false);
77
    }
78
75
79
    public void processKeyEvent(KeyEvent ke) {
76
    private static HtmlRenderer.Renderer renderer = null;
80
        //Apparently the code that changes selection on enter is
77
81
        //not an action in JTable's action map.  Override it entirely.
78
    /** Creates a new instance of TabListPanel */
82
        if (ke.getKeyCode() == ke.VK_ENTER) {
79
    private TabListPopup() {
83
            int row = getSelectedRow();
80
        super (new TabListPopupTableModel());
84
            int col = getSelectedColumn();
81
        //Set up a line border around the edges
85
            setSelectedTab(row, col);
82
        setBorder (
86
            hideCurrentPopup();
83
            BorderFactory.createLineBorder(getForeground()));        
87
        } else if (ke.getKeyCode() == ke.VK_ESCAPE) {
84
        setShowHorizontalLines(false);
88
            hideCurrentPopup();
85
        setBackground (UIManager.getColor("ComboBox.background")); //NOI18N
89
        } else {
86
        if (renderer == null) {
90
            super.processKeyEvent(ke);
87
            renderer = HtmlRenderer.createRenderer();
91
        }
88
        }
89
        setDefaultRenderer(Object.class, renderer);
92
    }
90
    }
93
91
94
    /**
92
    /**
Lines 131-164 Link Here
131
        super.setFont(f);
129
        super.setFont(f);
132
    }
130
    }
133
131
134
    public Component prepareRenderer(TableCellRenderer renderer, int row,
132
    public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
135
                                     int column) {
133
        //Will always use the default instance of HtmlRenderer
136
        Component result = super.prepareRenderer(renderer, row, column);
134
        JComponent result = (JComponent)
137
        TabData value = (TabData) getValueAt(row, column);
135
            super.prepareRenderer (renderer, row, column);
138
136
137
        //Find our TabData object
138
        Object value = getTTModel().getValueAt(row, column);
139
        if (value == null) {
140
            //it's a filler space, we're done
141
            return result;
142
        }
143
        
144
        //Set up font, selection, icon, colors, borders
139
        int selIdx = displayer.getSelectionModel().getSelectedIndex();
145
        int selIdx = displayer.getSelectionModel().getSelectedIndex();
140
        boolean isSelTab = false;
146
        boolean isSelTab = selIdx != -1 ? 
141
        if (selIdx != -1) {
147
            value == displayer.getModel().getTab(selIdx) 
142
            isSelTab = value == displayer.getModel().getTab (selIdx);
148
            : false;
149
150
        if (isSelTab) {
151
            result.setFont (getFont().deriveFont (Font.BOLD));
143
        }
152
        }
144
153
145
        result.setFont(
154
        Icon icon = ((TabData) value).getIcon();
146
                isSelTab ? getFont().deriveFont(Font.BOLD) : getFont());
155
147
        boolean sel = row == getSelectedRow()
156
        HtmlRenderer.Renderer ren = (HtmlRenderer.Renderer) result;
148
                && column == getSelectedColumn() && value != null;
157
        ren.setIcon(icon);
149
        if (sel) {
158
        
150
            result.setBackground(getSelectionBackground());
159
        if (icon.getIconWidth() > 0) {
151
            result.setForeground(getSelectionForeground());
160
            //Max annotated icon width is 24, so to have all the text and all
161
            //the icons come out aligned, set the icon text gap to the difference
162
            //plus a two pixel margin
163
            ren.setIconTextGap (26 - icon.getIconWidth());
164
        } else {
165
            //If the icon width is 0, fill the space and add in
166
            //the extra two pixels so the node names are aligned (btw, this
167
            //does seem to waste a frightful amount of horizontal space in
168
            //a tree that can use all it can get)
169
            ren.setIndent (26);
170
        }
171
        
172
        //The table may not really have focus, but it should always use the focus
173
        //color for the selection, not controlShadow
174
        ((HtmlRenderer.Renderer) result).setParentFocused(true);
175
        result.setBorder (rendererBorder);
176
        ((JComponent)result).setOpaque(true);
177
        if (row == getSelectedRow() && column == getSelectedColumn()) {
178
            result.setBackground (getSelectionBackground());
152
        } else {
179
        } else {
153
            result.setBackground(getBackground());
180
            result.setBackground (getBackground());
154
            result.setForeground(getForeground());
155
        }
181
        }
182
 
183
 
184
        
156
        return result;
185
        return result;
157
    }
186
    }
158
187
159
188
160
    boolean needCalcRowHeight = true;
161
162
    /**
189
    /**
163
     * Calculate the height of rows based on the current font.  This is done
190
     * Calculate the height of rows based on the current font.  This is done
164
     * when the first paint occurs, to ensure that a valid Graphics object is
191
     * when the first paint occurs, to ensure that a valid Graphics object is
Lines 213-220 Link Here
213
        int currCt = mdl.getColumnCount();
240
        int currCt = mdl.getColumnCount();
214
        if (currCt < count) {
241
        if (currCt < count) {
215
            for (int i = currCt; i < count; i++) {
242
            for (int i = currCt; i < count; i++) {
216
                mdl.addColumn(new TableColumn(i, 75, getTTModel(), null));
243
                mdl.addColumn(new TableColumn(i, 75, 
217
            }
244
                    renderer, null));            }
218
        } else if (currCt > count) {
245
        } else if (currCt > count) {
219
            for (int i = currCt - 1; i >= count; i--) {
246
            for (int i = currCt - 1; i >= count; i--) {
220
                mdl.removeColumn(mdl.getColumn(i));
247
                mdl.removeColumn(mdl.getColumn(i));
Lines 222-256 Link Here
222
        }
249
        }
223
    }
250
    }
224
251
225
    private Dimension prefSize = null;
252
    /** Overridden to calculate a preferred size based on the current optimal
226
253
     * number of columns, and set up the preferred width for each column based
254
     * on the maximum width tab name & icon displayed in it */
227
    public Dimension getPreferredSize() {
255
    public Dimension getPreferredSize() {
228
        if (prefSize == null) {
256
        if (prefSize == null) {
229
            Dimension d = new Dimension();
257
            Insets ins = getInsets();
258
            
259
            prefSize = new Dimension(ins.left + ins.top, ins.right + ins.bottom);
230
            int cols = getColumnCount();
260
            int cols = getColumnCount();
231
            int rows = getRowCount();
261
            int rows = getRowCount();
232
            for (int i = 0; i < cols; i++) {
262
            
233
                int currWidth = 0;
263
            //Iterate the columns
234
                for (int j = 0; j < rows; j++) {
264
            for (int i=0; i < cols; i++) {
235
                    TableCellRenderer ren = getCellRenderer(j, i);
265
                int columnWidth = 0;
236
                    Component c = ren.getTableCellRendererComponent(this,
266
                //For each column, iterate the rows
237
                                                                    getModel()
267
                for (int j=0; j < rows; j++) {
238
                                                                    .getValueAt(
268
                    TableCellRenderer ren = getCellRenderer(j,i);
239
                                                                            j,
269
                    Component c = prepareRenderer (ren, j, i);
240
                                                                            i),
270
                    //find the widest cell
241
                                                                    false,
271
                    columnWidth = Math.max (c.getPreferredSize().width, 
242
                                                                    false, j,
272
                        columnWidth);
243
                                                                    i);
244
                    prepareRenderer(ren, j, i);
245
                    Dimension curr = c.getPreferredSize();
246
                    currWidth = Math.max(curr.width, currWidth);
247
                }
273
                }
248
                d.width += currWidth;
274
                //Add in the max width needed for this column to the total
249
                getColumnModel().getColumn(i).setPreferredWidth(currWidth);
275
                //width
276
                prefSize.width += columnWidth;
277
                //Store it in the column model so it will be displayed with
278
                //the right width
279
                getColumnModel().getColumn(i).setPreferredWidth(columnWidth);
250
            }
280
            }
251
            d.height = rows * getRowHeight();
281
            //Rows will be fixed height, so just multiply it out
252
282
            prefSize.height += rows * getRowHeight();
253
            prefSize = d;
254
        }
283
        }
255
        return prefSize;
284
        return prefSize;
256
    }
285
    }
Lines 290-298 Link Here
290
        invocationTime = System.currentTimeMillis();
319
        invocationTime = System.currentTimeMillis();
291
    }
320
    }
292
321
293
    Component invokingComponent = null;
294
    long invocationTime = -1;
295
296
    public void removeNotify() {
322
    public void removeNotify() {
297
        super.removeNotify();
323
        super.removeNotify();
298
        removeMouseListener(this);
324
        removeMouseListener(this);
Lines 465-473 Link Here
465
491
466
    }
492
    }
467
493
468
    private static Popup currentPopup = null;
469
    private static BackupListener blistener = null;
470
471
    public synchronized static void hideCurrentPopup() {
494
    public synchronized static void hideCurrentPopup() {
472
        if (currentPopup != null) {
495
        if (currentPopup != null) {
473
            //Issue 41121 - use invokeLater to allow any pending
496
            //Issue 41121 - use invokeLater to allow any pending
Lines 497-504 Link Here
497
            toHide.hide();
520
            toHide.hide();
498
         }
521
         }
499
     }     
522
     }     
500
501
    private static Reference instance = null;
502
523
503
    private static TabListPopup sharedInstance() {
524
    private static TabListPopup sharedInstance() {
504
        TabListPopup result = null;
525
        TabListPopup result = null;
(-)core/swing/tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabListPopupTableModel.java (-114 / +1 lines)
Lines 31-37 Link Here
31
 *
31
 *
32
 * @author Tim Boudreau
32
 * @author Tim Boudreau
33
 */
33
 */
34
class TabListPopupTableModel implements TableModel, TableCellRenderer {
34
class TabListPopupTableModel implements TableModel {
35
    private transient TabDisplayer displayer = null;
35
    private transient TabDisplayer displayer = null;
36
    private transient ArrayList tableModelListenerList;
36
    private transient ArrayList tableModelListenerList;
37
    /**
37
    /**
Lines 207-323 Link Here
207
        return cols;
207
        return cols;
208
    }
208
    }
209
209
210
    public Component getTableCellRendererComponent(JTable table, Object value,
211
                                                   boolean isSelected,
212
                                                   boolean hasFocus, int row,
213
                                                   int column) {
214
        if (value == null) {
215
            return dummyRenderer;
216
        }
217
        TabData td = (TabData) value;
218
219
        Icon i = td.getIcon();
220
        String txt = td.getText();
221
        renderer.setText(txt);
222
        renderer.setIcon(i);
223
        renderer.setSelected(table.isCellSelected(row, column));
224
        renderer.setIconTextGap(29 - i.getIconWidth());
225
        if (isSelected) {
226
            renderer.setForeground(table.getSelectionForeground());
227
        } else {
228
            renderer.setForeground(table.getForeground());
229
        }
230
231
        return renderer;
232
    }
233
234
    private static DefaultTableCellRenderer dummyRenderer = new DefaultTableCellRenderer();
235
    private static Renderer renderer = new Renderer();
236
237
    private static class Renderer extends DefaultListCellRenderer {
238
        private String text;
239
        private Dimension prefSize;
240
        private boolean selected = false;
241
242
        void setSelected(boolean val) {
243
            selected = val;
244
        }
245
246
        public Dimension getPreferredSize() {
247
            if (text == null) {
248
                return new Dimension(0, 0);
249
            }
250
            if (prefSize == null) {
251
                Graphics g = TabListPopup.getOffscreenGraphics();
252
253
                prefSize = new Dimension();
254
                FontMetrics fm = g.getFontMetrics(getFont());
255
                prefSize.height = fm.getHeight();
256
                double wid = Html.renderString(text, g, 0, 0,
257
                                               Integer.MAX_VALUE,
258
                                               Integer.MAX_VALUE, getFont(),
259
                                               getForeground(),
260
                                               Html.STYLE_TRUNCATE, false);
261
                prefSize.width = new Double(wid).intValue() + 10;
262
                Icon ic = getIcon();
263
                if (ic != null) {
264
                    prefSize.width += ic.getIconWidth() + getIconTextGap();
265
                    prefSize.height = Math.max(prefSize.height,
266
                                               ic.getIconHeight() + 2);
267
                }
268
                //Increase vertical spacing
269
                prefSize.height += 4;
270
            }
271
            return prefSize;
272
        }
273
274
        public void setText(String txt) {
275
            text = txt;
276
            prefSize = null;
277
        }
278
279
        public void setFont(Font f) {
280
            if (f != getFont()) {
281
                prefSize = null;
282
                super.setFont(f);
283
            }
284
        }
285
286
        public String getText() {
287
            return text;
288
        }
289
290
        private void paintBackground(Graphics g) {
291
            g.setColor(getBackground());
292
            g.fillRect(0, 0, getWidth(), getHeight());
293
        }
294
295
        public void paint(Graphics g) {
296
            g.setFont(getFont());
297
            Dimension d = getSize();
298
            if (d.width == 0) {
299
                d = getPreferredSize();
300
            }
301
302
            paintBackground(g);
303
304
            getIcon().paintIcon(this, g, 2, 2);
305
            int x = getIconTextGap() + getIcon().getIconWidth();
306
307
            g.setColor(getForeground());
308
            Html.renderString(getText(), g, x, 2 + getTextBase(g), d.width,
309
                              d.height, getFont(), getForeground(),
310
                              Html.STYLE_TRUNCATE, true);
311
        }
312
313
        int fh = -1;
314
315
        private int getTextBase(Graphics g) {
316
            if (fh == -1) {
317
                FontMetrics fm = g.getFontMetrics(getFont());
318
                fh = fm.getAscent();
319
            }
320
            return fh;
321
        }
322
    }
323
}
210
}
(-)core/windows/src/org/netbeans/core/windows/WindowManagerImpl.java (-2 / +27 lines)
Lines 718-732 Link Here
718
        central.setProjectName(projectName);
718
        central.setProjectName(projectName);
719
    }
719
    }
720
720
721
    private static final boolean NAME_HACK = Boolean.getBoolean("nb.tabnames.html"); //NOI18N
721
    
722
    
722
    /** Helper method to retrieve the display name of TopComponent. */
723
    /** Helper method to retrieve the display name of TopComponent. */
723
    public String getTopComponentDisplayName(TopComponent tc) {
724
    public String getTopComponentDisplayName(TopComponent tc) {
724
        if(tc == null) {
725
        if(tc == null) {
725
            return null;
726
            return null;
726
        }
727
        }
727
        
728
        String displayName = tc.getDisplayName();
728
        String displayName = tc.getDisplayName();
729
        return displayName == null ? tc.getName() : displayName;
729
        if (displayName == null) {
730
            displayName = tc.getName();
731
        }
732
        if (NAME_HACK) {
733
            //THIS IS FOR DEMO PURPOSES ONLY!  A PROPER API IS NEEDED
734
            //(TopComponent.getHtmlDisplayName()), OR
735
            //HTML SHOULD BE PRE-SUPPLIED
736
            if (displayName.endsWith("*")) { 
737
                
738
                displayName = "<html><b>" + 
739
                    displayName.substring(0, displayName.length()-2);
740
                
741
            } else {
742
                
743
                int i = displayName.indexOf ("[read only]"); 
744
                if (i > 0) {
745
                    String nuName = "<html><font color='#555555'><i>" +
746
                        displayName.substring (0, i-1);
747
                    if (i + 11 < nuName.length()) {
748
                        nuName += displayName.substring(i+11);
749
                    }
750
                    displayName = nuName;
751
                }
752
            }
753
        }
754
        return displayName;
730
    }
755
    }
731
    
756
    
732
    // PENDING for ModeImpl only.
757
    // PENDING for ModeImpl only.
(-)form/src/org/netbeans/modules/form/FormProperty.java (-7 / +8 lines)
Lines 163-175 Link Here
163
    // ----------------------------------------
163
    // ----------------------------------------
164
    // getter, setter & related methods
164
    // getter, setter & related methods
165
165
166
//    public String getDisplayName() {
166
    public String getHtmlDisplayName() {
167
//        String displayName = super.getDisplayName();
167
        if (isChanged()) {
168
//        if (isChanged())
168
            return "<b>" + getDisplayName();
169
//            displayName = "<html><b>" + displayName + "</b>"; // NOI18N
169
        } else {
170
//        return displayName;
170
            return null;
171
//    }
171
        }
172
//
172
    }
173
173
    /** Gets the real value of this property directly from the target object.
174
    /** Gets the real value of this property directly from the target object.
174
     */
175
     */
175
    public abstract Object getTargetValue() throws IllegalAccessException,
176
    public abstract Object getTargetValue() throws IllegalAccessException,
(-)openide/openide-spec-vers.properties (-1 / +1 lines)
Lines 4-7 Link Here
4
# Must always be numeric (numbers separated by '.', e.g. 4.11).
4
# Must always be numeric (numbers separated by '.', e.g. 4.11).
5
# See http://openide.netbeans.org/versioning-policy.html for more.
5
# See http://openide.netbeans.org/versioning-policy.html for more.
6
6
7
openide.specification.version=4.30
7
openide.specification.version=4.31
(-)openide/api/doc/changes/apichanges.xml (+33 lines)
Lines 113-118 Link Here
113
  </apidefs>
113
  </apidefs>
114
114
115
<!-- ACTUAL CHANGES BEGIN HERE: -->
115
<!-- ACTUAL CHANGES BEGIN HERE: -->
116
<changes>
117
     <change>
118
      <api name="general"/>
119
      <summary>Lightweight HTML rendering methods</summary>
120
      <version major="4" minor="31"/>
121
      <date day="3" month="5" year="2004"/>
122
      <author login="tboudreau"/>
123
      <compatibility addition="yes" deprecation="no" />
124
      <description>
125
      A lightweight HTML renderer which can render a limited subset of
126
      HTML has been added to the APIs, and will be used in Explorer.
127
      Nodes wishing to provide text rendered in HTML may do so by
128
      returning subset-compliant HTML formatted text from the new
129
      method <code>getHtmlDisplayName</code>.  An interface,
130
      <code>HTMLStatus</code> has been created which extends 
131
      <code>FileSystem.Status</code>, has been created, which allows
132
      filesystems to supply HTML formatted status information, by
133
      implementing it on their <code>FileSystem.Status</code> implementation.
134
      Filesystems which delegate to other filesystems my implement 
135
      FileSystem.HtmlStatus and simply return null for filesystems which
136
      do not support it.
137
      If one is present, DataNode will use it to supply HTML formatted
138
      text to Explorer.  The renderer itself can be found in 
139
      org.openide.awt.HtmlRenderer.
140
      </description>
141
      <class package="org.openide.filesystems.FileSystem" name="HtmlStatus"/>
142
      <class package="org.openide.nodes" name="Node"/>
143
      <class package="org.openide.awt" name="HtmlRenderer"/>
144
      <class package="org.openide.loaders" name="DataNode"/>
145
      <issue number="29466"/>
146
     </change>
147
 
148
   <change>
116
149
117
  <changes>
150
  <changes>
118
    <change>
151
    <change>
(-)openide/loaders/manifest.mf (-1 / +1 lines)
Lines 1-5 Link Here
1
Manifest-Version: 1.0
1
Manifest-Version: 1.0
2
OpenIDE-Module: org.openide.loaders
2
OpenIDE-Module: org.openide.loaders
3
OpenIDE-Module-Specification-Version: 4.12
3
OpenIDE-Module-Specification-Version: 4.13
4
OpenIDE-Module-Localizing-Bundle: org/openide/loaders/Bundle.properties
4
OpenIDE-Module-Localizing-Bundle: org/openide/loaders/Bundle.properties
5
5
(-)openide/loaders/api/apichanges.xml (+18 lines)
Lines 78-83 Link Here
78
78
79
  <changes>
79
  <changes>
80
80
81
    <change>
82
      <api name="loaders"/>
83
      <summary>DataNode.getHtmlDisplayName</summary>
84
      <version major="4" minor="13"/>
85
      <date day="3" month="5" year="2004"/>
86
      <author login="tboudreau"/>
87
      <compatibility addition="yes"/>
88
      <description>
89
      DataNode now implements getHtmlDisplayName() as follows:  If the
90
      underlying filesystem implements the new FileSystem.HtmlStatus, it will
91
      call FileSystem.HtmlStatus.annotateNameHtml() and return the result.
92
      This enables, for example, CVS annotations to be shown with a different
93
      font color.
94
      </description>
95
      <class package="org.openide.loaders" name="DataNode"/>
96
      <issue number="29466"/>
97
    </change>
98
81
    <change id="SortMode.LAST_MODIFIED-SIZE">
99
    <change id="SortMode.LAST_MODIFIED-SIZE">
82
      <api name="loaders"/>
100
      <api name="loaders"/>
83
      <summary>Two new folder sort modes</summary>
101
      <summary>Two new folder sort modes</summary>
(-)openide/loaders/src/org/openide/loaders/DataNode.java (+33 lines)
Lines 22-27 Link Here
22
22
23
import org.openide.ErrorManager;
23
import org.openide.ErrorManager;
24
import org.openide.filesystems.*;
24
import org.openide.filesystems.*;
25
import org.openide.filesystems.FileSystem.HtmlStatus;
25
import org.openide.util.datatransfer.*;
26
import org.openide.util.datatransfer.*;
26
import org.openide.util.HelpCtx;
27
import org.openide.util.HelpCtx;
27
import org.openide.util.RequestProcessor;
28
import org.openide.util.RequestProcessor;
Lines 174-179 Link Here
174
175
175
        return s;
176
        return s;
176
    }
177
    }
178
179
     
180
     /** Get a display name formatted using the limited HTML subset supported
181
      * by <code>HtmlRenderer</code>.  If the underlying 
182
      * <code>FileSystem.Status</code> is an instance of HmlStatus,
183
      * this method will return non-null if status information is added.
184
      *
185
      * @return a string containing compliant HTML markup or null
186
      * @see org.openide.awt.HtmlRenderer
187
      * @see org.openide.nodes.Node#getHtmlDisplayName
188
      * @since 4.13 
189
      */
190
     public String getHtmlDisplayName() {
191
         try {
192
             FileSystem.Status stat = 
193
                 obj.getPrimaryFile().getFileSystem().getStatus();
194
             if (stat instanceof HtmlStatus) {
195
                 HtmlStatus hstat = (HtmlStatus) stat;
196
                 
197
                 String result = hstat.annotateNameHtml (
198
                     super.getDisplayName(), new LazyFilesSet());
199
                 
200
                 //Make sure the super string was really modified
201
                 if (!super.getDisplayName().equals(result)) {
202
                     return result;
203
                 }
204
             }
205
         } catch (FileStateInvalidException e) {
206
             //do nothing and fall through
207
         }
208
         return super.getHtmlDisplayName();
209
     }    
177
210
178
    /** Get the displayed icon for this node.
211
    /** Get the displayed icon for this node.
179
     * A filesystem may {@link org.openide.filesystems.FileSystem#getStatus specially alter} this.
212
     * A filesystem may {@link org.openide.filesystems.FileSystem#getStatus specially alter} this.
(-)openide/masterfs/src/org/netbeans/modules/masterfs/MasterFileSystem.java (-1 / +13 lines)
Lines 242-248 Link Here
242
    }
242
    }
243
243
244
    
244
    
245
    private static final class StatusImpl implements FileSystem.Status {
245
    private static final class StatusImpl implements FileSystem.HtmlStatus {
246
        public Image annotateIcon(Image icon, int iconType, Set files) {
246
        public Image annotateIcon(Image icon, int iconType, Set files) {
247
            //int size = files.size();
247
            //int size = files.size();
248
            Set transformedSet = new HashSet();
248
            Set transformedSet = new HashSet();
Lines 273-278 Link Here
273
                name = (fs != null) ? fs.getStatus().annotateName(name, transformedSet) : name;
273
                name = (fs != null) ? fs.getStatus().annotateName(name, transformedSet) : name;
274
            }
274
            }
275
            return name;
275
            return name;
276
        }
277
278
        public String annotateNameHtml(String name, Set files) {
279
            Set transformedSet = new HashSet();
280
            MasterFileObject hfo = Utils.transformSet(files, transformedSet);
281
            if (hfo != null) {
282
                FileSystem fs = hfo.getDelegateFileSystem();
283
                if (fs != null && fs.getStatus() instanceof FileSystem.HtmlStatus) {
284
                    return ((FileSystem.HtmlStatus) fs.getStatus()).annotateNameHtml(name, transformedSet);
285
                }
286
            }
287
            return null;
276
        }
288
        }
277
    }
289
    }
278
    
290
    
(-)openide/src/org/openide/awt/HtmlLabelUI.java (+367 lines)
Added Link Here
1
/*
2
 *                 Sun Public License Notice
3
 *
4
 * The contents of this file are subject to the Sun Public License
5
 * Version 1.0 (the "License"). You may not use this file except in
6
 * compliance with the License. A copy of the License is available at
7
 * http://www.sun.com/
8
 *
9
 * The Original Code is NetBeans. The Initial Developer of the Original
10
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
11
 * Microsystems, Inc. All Rights Reserved.
12
 */
13
package org.openide.awt;
14
15
import org.openide.ErrorManager;
16
17
import javax.swing.*;
18
import javax.swing.plaf.LabelUI;
19
import javax.swing.plaf.ComponentUI;
20
import java.awt.*;
21
import java.util.Map;
22
import java.util.HashMap;
23
24
/**
25
 * A LabelUI which uses the lightweight HTML renderer.  Stateless - only one instance should ever exist.
26
 */
27
public class HtmlLabelUI extends LabelUI {
28
    /** System property to automatically turn on antialiasing for html strings */
29
    private static final boolean antialias = Boolean.getBoolean(
30
        "nb.cellrenderer.antialiasing"); //NOI18N
31
32
    private static HtmlLabelUI uiInstance = null;
33
34
    public static ComponentUI createUI (JComponent c) {
35
        assert c instanceof HtmlRendererImpl;
36
        if (uiInstance == null) {
37
            uiInstance = new HtmlLabelUI();
38
        }
39
        return uiInstance;
40
    }
41
42
    public Dimension getPreferredSize(JComponent c) {
43
        return calcPreferredSize ((HtmlRendererImpl) c);
44
    }
45
46
    /** Get the width of the text */
47
    private static int textWidth(String text, Graphics g, Font f, boolean html) {
48
        if (text != null) {
49
            if (html) {
50
                return Math.round(Math.round(Math.ceil(HtmlRenderer.renderHTML(text, g, 0, 0,
51
                    Integer.MAX_VALUE, Integer.MAX_VALUE, f,
52
                    Color.BLACK, HtmlRenderer.STYLE_CLIP, false))));
53
            } else {
54
                return Math.round(Math.round(Math.ceil(HtmlRenderer.renderPlainString(text, g, 0, 0,
55
                    Integer.MAX_VALUE, Integer.MAX_VALUE, f,
56
                    Color.BLACK, HtmlRenderer.STYLE_CLIP, false))));
57
            }
58
        } else {
59
            return 0;
60
        }
61
    }
62
63
    private Dimension calcPreferredSize(HtmlRendererImpl r) {
64
        Insets ins = r.getInsets();
65
        Dimension prefSize = new java.awt.Dimension(ins.left + ins.right, ins.top + ins.bottom);
66
        String text = r.getText();
67
68
        Graphics g = r.getGraphics();
69
        Icon icon = r.getIcon();
70
71
        if (text != null) {
72
            FontMetrics fm = g.getFontMetrics (r.getFont());
73
            prefSize.height += fm.getMaxAscent() + fm.getMaxDescent() + 2;
74
        }
75
76
        if (icon != null) {
77
            if (r.isCentered()) {
78
                prefSize.height += icon.getIconHeight() + r.getIconTextGap();
79
                prefSize.width += icon.getIconWidth();
80
            } else {
81
                prefSize.height = Math.max (icon.getIconHeight(), prefSize.height);
82
                prefSize.width += icon.getIconWidth() + r.getIconTextGap();
83
            }
84
        }
85
86
        //Antialiasing affects the text metrics, so use it if needed when
87
        //calculating preferred size or the result here will be narrower
88
        //than the space actually needed
89
        if (antialias) {
90
            //For L&Fs such as Aqua and SmoothMetal, we will need to manually apply
91
            //rendering hints to get antialiasing, since we're doing our
92
            //own painting logic - they don't do this for things they don't
93
            //know about
94
            ((Graphics2D)g).addRenderingHints(getHints());
95
        }
96
97
        int textwidth = textWidth(text, g, r.getFont(), r.isHtml()) + 4;
98
        if (r.isCentered()) {
99
            prefSize.width = Math.max (prefSize.width, textwidth + ins.right + ins.left);
100
        } else {
101
            prefSize.width += textwidth + r.getIndent();
102
        }
103
//        System.err.println ("PrefSize: " + prefSize + " text " + r.getText());
104
        return prefSize;
105
    }
106
107
    private static Map hintsMap = null;
108
    static final Map getHints() {
109
        if (hintsMap == null) {
110
            hintsMap = new HashMap();
111
            hintsMap.put(RenderingHints.KEY_FRACTIONALMETRICS,
112
                RenderingHints.VALUE_FRACTIONALMETRICS_ON);
113
            hintsMap.put(RenderingHints.KEY_TEXT_ANTIALIASING,
114
                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
115
            hintsMap.put(RenderingHints.KEY_ANTIALIASING,
116
                RenderingHints.VALUE_ANTIALIAS_ON);
117
        }
118
        return hintsMap;
119
    }
120
121
122
    public void update(Graphics g, JComponent c) {
123
        Color bg = getBackgroundFor ((HtmlRendererImpl) c);
124
        if (bg != null) {
125
            g.setColor(bg);
126
            g.fillRect(0, 0, c.getWidth(),c.getHeight());
127
            if (((HtmlRendererImpl) c).isLeadSelection()) {
128
                Color focus = UIManager.getColor("textHighlight");
129
                if (focus == null) {
130
                    focus = Color.BLUE;
131
                }
132
                g.setColor(focus);
133
                g.drawRect (0, 0, c.getWidth()-1, c.getHeight()-1);
134
            }
135
        }
136
        paint(g, c);
137
    }
138
139
    public void paint(Graphics g, JComponent c) {
140
        if (antialias) {
141
            //For L&Fs such as Aqua and SmoothMetal, we will need to manually apply
142
            //rendering hints to get antialiasing, since we're doing our
143
            //own painting logic - they don't do this for things they don't
144
            //know about
145
            ((Graphics2D)g).addRenderingHints(getHints());
146
        }
147
        HtmlRendererImpl r = (HtmlRendererImpl) c;
148
        if (r.isCentered()) {
149
            paintIconAndTextCentered (g, r);
150
        } else {
151
            paintIconAndText (g, r);
152
        }
153
    }
154
155
    /** Actually paint the icon and text using our own html rendering engine. */
156
    private void paintIconAndText(Graphics g, HtmlRendererImpl r) {
157
        Font f = r.getFont();
158
        g.setFont(f);
159
        FontMetrics fm = g.getFontMetrics();
160
161
        //Find out what height we need
162
        int txtH = fm.getHeight();
163
        Insets ins = r.getInsets();
164
165
        //find out the available height less the insets
166
        int availH = r.getHeight() - (ins.top + ins.bottom);
167
168
        int txtY;
169
        if (availH >= txtH) {
170
            //Center the text if we have space
171
            txtY = txtH + ins.top + ((availH / 2) - (txtH / 2)) - fm.getMaxDescent();
172
        } else {
173
            //Okay, it's not going to fit, punt.
174
            txtY = fm.getMaxAscent();
175
        }
176
        int txtX = r.getIndent();
177
178
        Icon icon = r.getIcon();
179
        //Check the icon non-null and height (see TabData.NO_ICON for why)
180
        if (icon != null && icon.getIconWidth() > 0 && icon.getIconHeight() > 0) {
181
            int iconY;
182
            if (availH > icon.getIconHeight()) {
183
                //add 2 to make sure icon top pixels are not cut off by outline
184
                iconY = ins.top + ((availH / 2) - (icon.getIconHeight() / 2));// + 2;
185
            } else if (availH == icon.getIconHeight()){
186
                //They're an exact match, make it 0
187
                iconY = 0;
188
            } else {
189
                //Won't fit; make the top visible and cut the rest off (option:
190
                //center it and clip it on top and bottom - probably even harder
191
                //to recognize that way, though)
192
                iconY = ins.top;
193
            }
194
            //add in the insets
195
            int iconX = ins.left + r.getIndent() + 1; //+1 to get it out of the way of the focus border
196
            try {
197
                //Diagnostic - the CPP module currently is constructing
198
                //some ImageIcon from a null image in Options.  So, catch it and at
199
                //least give a meaningful message that indicates what node
200
                //is the culprit
201
                icon.paintIcon(r, g, iconX, iconY);
202
203
            } catch (NullPointerException npe) {
204
                ErrorManager.getDefault().annotate(npe, ErrorManager.EXCEPTION,
205
                    "Probably an ImageIcon with a null source image: " + icon + " - " + //NOI18N
206
                    r.getText(), null, null, null); //NOI18N
207
                ErrorManager.getDefault().notify(npe);
208
            }
209
            txtX = iconX + icon.getIconWidth() + r.getIconTextGap();
210
        } else {
211
            //If there's no icon, paint the text where the icon would start
212
            txtX += ins.left;
213
        }
214
215
        String text = r.getText();
216
        if (text == null) {
217
            //No text, we're done
218
            return;
219
        }
220
221
        //Get the available horizontal pixels for text
222
        int txtW = icon != null ? r.getWidth() - (ins.left + ins.right +
223
            icon.getIconWidth() + r.getIconTextGap() + r.getIndent()) : r.getWidth() -
224
            (ins.left + ins.right + r.getIndent());
225
226
        Color foreground = getForegroundFor (r);
227
228
        if (r.isHtml()) {
229
            HtmlRenderer.renderHTML(text, g, txtX, txtY, txtW, txtH, f,
230
                foreground, r.getRenderStyle(), true);
231
        } else {
232
            HtmlRenderer.renderString(text, g, txtX, txtY, txtW, txtH, f,
233
                foreground, r.getRenderStyle(), true);
234
        }
235
    }
236
237
    private void paintIconAndTextCentered (Graphics g, HtmlRendererImpl r) {
238
        Insets ins = r.getInsets();
239
        Icon ic = r.getIcon();
240
        int w = r.getWidth() - (ins.left + ins.right);
241
        int txtX = ins.left;
242
        int txtY = 0;
243
        if (ic != null && ic.getIconWidth() > 0 && ic.getIconHeight() > 0) {
244
            int iconx = w > ic.getIconWidth() ? (w / 2) - (ic.getIconWidth() / 2) : txtX;
245
            int icony = 0;
246
            ic.paintIcon(r, g, iconx, icony);
247
            txtY += ic.getIconHeight() + r.getIconTextGap();
248
        }
249
        int txtW = r.getPreferredSize().width;
250
        txtX = txtW < r.getWidth() ? (r.getWidth() / 2) - (txtW / 2) : 0;
251
        int txtH = r.getHeight() - txtY;
252
253
        Font f = r.getFont();
254
        g.setFont(f);
255
        FontMetrics fm = g.getFontMetrics(f);
256
        txtY += fm.getMaxAscent();
257
258
        Color foreground = getForegroundFor (r);
259
        if (r.isHtml()) {
260
            HtmlRenderer.renderHTML(r.getText(), g, txtX, txtY, txtW, txtH,f,
261
                foreground, r.getRenderStyle(), true);
262
        } else {
263
            HtmlRenderer.renderString(r.getText(), g, txtX, txtY, txtW, txtH, r.getFont(),
264
                foreground, r.getRenderStyle(), true);
265
        }
266
    }
267
268
    static Color getBackgroundFor (HtmlRendererImpl r) {
269
        if (r.isOpaque()) {
270
            return r.getBackground();
271
        }
272
273
        if (r.isSelected() && !r.isParentFocused() && !isGTK()) {
274
            return getUnfocusedSelectionBackground();
275
        }
276
277
        if (isGTK()) {
278
            //GTK does its own thing, we'll only screw it up by painting the background ourselves
279
            return null;
280
        }
281
282
        Color result = null;
283
284
        if (r.isSelected()) {
285
            switch (r.getType()) {
286
                case HtmlRendererImpl.TYPE_LIST :
287
                    result = UIManager.getColor ("List.selectionBackground"); //NOI18N
288
                    break;
289
                case HtmlRendererImpl.TYPE_TABLE :
290
                    result = UIManager.getColor ("Table.selectionBackground"); //NOI18N
291
                    break;
292
                case HtmlRendererImpl.TYPE_TREE :
293
                    return UIManager.getColor ("Tree.selectionBackground"); //NOI18N
294
            }
295
            return result == null ? r.getBackground() : result;
296
        }
297
        return null;
298
    }
299
300
    static Color getForegroundFor (HtmlRendererImpl r) {
301
        if (r.isSelected() && !r.isParentFocused()) {
302
            return getUnfocusedSelectionForeground();
303
        }
304
305
        if (!r.isEnabled()) {
306
            return UIManager.getColor ("textInactiveText"); //NOI18N
307
        }
308
309
        Color result = null;
310
        if (r.isSelected()) {
311
            switch (r.getType()) {
312
                case HtmlRendererImpl.TYPE_LIST :
313
                    result = UIManager.getColor ("List.selectionForeground"); //NOI18N
314
                case HtmlRendererImpl.TYPE_TABLE :
315
                    result = UIManager.getColor ("Table.selectionForeground"); //NOI18N
316
                case HtmlRendererImpl.TYPE_TREE :
317
                    result = UIManager.getColor ("Tree.selectionForeground"); //NOI18N
318
            }
319
        }
320
        return result == null ? r.getForeground() : result;
321
    }
322
323
    private static boolean isGTK() {
324
        return "GTK".equals (UIManager.getLookAndFeel().getID());
325
    }
326
327
    private static Color unfocusedSelBg = null;
328
    private static Color unfocusedSelFg = null;
329
    /** Get the system-wide unfocused selection background color */
330
    private static Color getUnfocusedSelectionBackground() {
331
        if (unfocusedSelBg == null) {
332
            //allow theme/ui custom definition
333
            unfocusedSelBg =
334
            UIManager.getColor("nb.explorer.unfocusedSelBg"); //NOI18N
335
            if (unfocusedSelBg == null) {
336
                //try to get standard shadow color
337
                unfocusedSelBg = UIManager.getColor("controlShadow"); //NOI18N
338
                if (unfocusedSelBg == null) {
339
                    //Okay, the look and feel doesn't suport it, punt
340
                    unfocusedSelBg = Color.lightGray;
341
                }
342
                //Lighten it a bit because disabled text will use controlShadow/
343
                //gray
344
                unfocusedSelBg = unfocusedSelBg.brighter();
345
            }
346
        }
347
        return unfocusedSelBg;
348
    }
349
350
    /** Get the system-wide unfocused selection foreground color */
351
    private static Color getUnfocusedSelectionForeground() {
352
        if (unfocusedSelFg == null) {
353
            //allow theme/ui custom definition
354
            unfocusedSelFg =
355
            UIManager.getColor("nb.explorer.unfocusedSelFg"); //NOI18N
356
            if (unfocusedSelFg == null) {
357
                //try to get standard shadow color
358
                unfocusedSelFg = UIManager.getColor("textText"); //NOI18N
359
                if (unfocusedSelFg == null) {
360
                    //Okay, the look and feel doesn't suport it, punt
361
                    unfocusedSelFg = Color.BLACK;
362
                }
363
            }
364
        }
365
        return unfocusedSelFg;
366
    }
367
}
(-)openide/src/org/openide/awt/HtmlRenderer.java (+1236 lines)
Added Link Here
1
/*
2
 *                 Sun Public License Notice
3
 *
4
 * The contents of this file are subject to the Sun Public License
5
 * Version 1.0 (the "License"). You may not use this file except in
6
 * compliance with the License. A copy of the License is available at
7
 * http://www.sun.com/
8
 *
9
 * The Original Code is NetBeans. The Initial Developer of the Original
10
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
11
 * Microsystems, Inc. All Rights Reserved.
12
 */
13
14
/*
15
 * HtmlRenderer.java
16
 *
17
 * Created on January 2, 2004, 12:49 AM
18
 */
19
package org.openide.awt;
20
21
import org.openide.ErrorManager;
22
23
import javax.swing.*;
24
import javax.swing.table.TableCellRenderer;
25
import javax.swing.tree.TreeCellRenderer;
26
import java.awt.*;
27
import java.awt.font.LineMetrics;
28
import java.awt.geom.Area;
29
import java.awt.geom.Rectangle2D;
30
import java.util.*;
31
32
/**
33
 * A lightweight html renderer supporting a minimal subset of HTML used for
34
 * markup purposes only - basic font styles, colors, etc.  Also supports
35
 * named logical colors specified by a preceding ! character for specifying
36
 * colors that should be looked up in the current look and feel's UIDefaults
37
 * (e.g. <code>&lt;font color=&amp;!textText&amp&gt;</code>).
38
 * <p>
39
 * Provides a generic cell renderer implementation which can be used for trees, tables,
40
 * lists, combo boxes, etc.
41
 * <p>
42
 * If you only need to paint some HTML quickly, use the static methods for
43
 * painting - <code>renderString</code>, <code>renderPlainString</code> or
44
 * <code>renderHtml</code>.  These methods differ as follows:
45
 * <ul>
46
 * <li><b>renderString</b> will check the string for opening HTML tags
47
 * (upper or lower but not mixed case) and call either <code>renderPlainString</code>
48
 * or <code>renderHtml</code> as appropriate.  Note this method does not tolerate
49
 * whitespace in opening html tags - it expects exactly 6 characters to make up
50
 * the opening tag if present.</li>
51
 * <li><b>renderPlainString</b> simply renders a string to the graphics context,
52
 * takes the same agruments as <code>renderHtml</code>, but will also honor
53
 * <code>STYLE_TRUNCATE</code>, so strings can be rendered with trailing
54
 * elipsis if there is not enough space</li>
55
 * <li><b>renderHtml</b> renders whatever is passed to it as HTML, regardless
56
 * of whether it has opening HTML tags or not.  It can be used to render plain
57
 * strings, but <code>renderPlainString</code> is faster for that. It is useful
58
 * if you want to render a string you <strong>know</strong> to be compliant
59
 * HTML markup, but which does not have opening and closing HTML tags (though
60
 * they are harmless if present). </li>
61
 * <p>
62
 * This parser is designed entirely for performance; there are no separate parsing
63
 * and rendering loops.  In order to acheive its performance, some trade offs
64
 * are required.
65
 * <strong>To reiterate: This is not a forgiving HTML parser - the HTML supplied
66
 * must follow the guidelines documented here!</strong>
67
 * <p>
68
 * The following tags are supported, in upper or lower (but not mixed) case:
69
 *
70
 * <table>
71
 * <tr>
72
 *  <td>&lt;B&gt;</td>
73
 *  <td>Boldface text</td>
74
 * </tr>
75
 * <tr>
76
 *  <td>&lt;S&gt;</td>
77
 *  <td>Strikethrough text</td>
78
 * </tr>
79
 * <tr>
80
 *  <td>&lt;U&gt;</td>
81
 *  <td>Underline text</td>
82
 * </tr>
83
 * <tr>
84
 *  <td>&lt;I&gt;</td>
85
 *  <td>Italic text</td>
86
 * </tr>
87
 * <tr>
88
 *  <td>&lt;EM&gt;</td>
89
 *  <td>Emphasized text (same as italic)</td>
90
 * </tr>
91
 * <tr>
92
 *  <td>&lt;STRONG&gt;</td>
93
 *  <td>Strong text (same as bold)</td>
94
 * </tr>
95
 * <tr>
96
 *  <td>&lt;font&gt;</td>
97
 *  <td>Font color - font attributes other than color are not supported.  Colors
98
 *  may be specified as hexidecimal strings, such as #FF0000 or as logical colors
99
 *  defined in the current look and feel by specifying a ! character as the first
100
 *  character of the color name.  Logical colors are colors available from the
101
 *  current look and feel's UIManager.  For example, <code>&lt;font
102
 *  color=&quot;!Tree.background&quot;&gt;</code> will set the font color to the
103
 *  result of <code>UIManager.getColor(&quot;Tree.background&quot;)</code>.
104
 * <strong>Font size tags are not supported.</strong>
105
 * </td>
106
 * </tr>
107
 * </table>
108
 * The lightweight html renderer supports the following named sgml character
109
 * entities: <code>quot, lt, amp, lsquo, rsquo, ldquo, rdquo, ndash, mdash, ne,
110
 * le, ge, copy, reg, trade.  </code>.  It also supports numeric entities
111
 * (e.g. <code>&amp;8822;</code>).
112
 * <p><b>Why not use the JDK's HTML support?</b> The JDK's HTML support works
113
 * well for stable components, but suffers from performance problems in the
114
 * case of cell renderers - each call to set the text (which happens once per
115
 * cell, per paint) causes a document tree to be created in memory.  For small,
116
 * markup only strings, this is overkill.   For rendering short strings
117
 * (for example, in a tree or table cell renderer)
118
 * with limited HTML, this method is approximately 10x faster than standard
119
 * Swing HTML rendering.
120
 *
121
 * <P><B><U>Specifying logical colors</U></B><BR>
122
 * Hardcoded text colors are undesirable, as they can be incompatible (even
123
 * invisible) on some look and feels or themes, depending on the background
124
 * color.
125
 * The lightweight HTML renderer supports a non-standard syntax for specifying
126
 * font colors via a key for a color in the UI defaults for the current look
127
 * and feel.  This is accomplished by prefixing the key name with a <code>!</code>
128
 * character.  For example: <code>&lt;font color='!controlShadow'&gt;</code>.
129
 *
130
 * <P><B><U>Modes of operation</U></B><BR>
131
 * This method supports two modes of operation:
132
 * <OL>
133
 * <LI><CODE>STYLE_CLIP</CODE> - as much text as will fit in the pixel width passed
134
 * to the method should be painted, and the text should be cut off at the maximum
135
 * width or clip rectangle maximum X boundary for the graphics object, whichever is
136
 * smaller.</LI>
137
 * <LI><CODE>STYLE_TRUNCATE</CODE> - paint as much text as will fit in the pixel
138
 * width passed to the method, but paint the last three characters as .'s, in the
139
 * same manner as a JLabel truncates its text when the available space is too
140
 * small.</LI>
141
 * </OL>
142
 * <P>
143
 * The paint methods can also be used in non-painting mode to establish the space
144
 * necessary to paint a string.  This is accomplished by passing the value of the
145
 * <code>paint</code> argument as false.  The return value will be the required
146
 * width in pixels
147
 * to display the text.  Note that in order to retrieve an
148
 * accurate value, the argument for available width should be passed
149
 * as <code>Integer.MAX_VALUE</code> or an appropriate maximum size - otherwise
150
 * the return value will either be the passed maximum width or the required
151
 * width, whichever is smaller.  Also, the clip shape for the passed graphics
152
 * object should be null or a value larger than the maximum possible render size,
153
 * or text size measurement will stop at the clip bounds.  <code>HtmlRenderer.getGraphics()</code>
154
 * will always return non-null and non-clipped, and is suitable to pass in such a
155
 * situation.
156
 * <P>
157
 *
158
 * @since 4.30
159
 * @see org.openide.nodes.Node#getHtmlDisplayName
160
 * @see org.openide.filesystems.FileSystem.HtmlStatus
161
 * @author  Tim Boudreau
162
 */
163
public final class HtmlRenderer {
164
    private static HtmlRendererImpl LABEL = null;
165
166
    private HtmlRenderer() {
167
        //do nothing
168
    }
169
170
    /**
171
     * Returns an instance of Renderer which may be used as a table/tree/list cell renderer.
172
     * This method must be called on the AWT event thread.  If you <strong>know</strong> you will
173
     * be passing it legal HTML (legal as documented here), call <code>setHtml(true)</code> on the
174
     * result of this call <strong>after calling getNNNCellRenderer</code> to provide this hint.
175
     *
176
     * @return A cell renderer that can render HTML.
177
     */
178
    public static final Renderer createRenderer () {
179
        return new HtmlRendererImpl();
180
    }
181
182
    /**
183
     * For HTML rendering jobs outside of trees/lists/tables, returns a JLabel which will paint its text using
184
     * the lightweight HTML renderer.  The result of this call will implement the <code>Renderer</code> interface.
185
     * To use, simply call <code>sharedLabel()</code>, configure the text, etc., and paint it to a graphics context
186
     * (SwingUtilities.paintComponent() is not needed - simply call paint (Graphics), which is much faster), or
187
     * take its preferred size if using it to measure text dimensions.
188
     * <p>
189
     * Do not hold a reference to the return value of this call - other code may reconfigure it unexpectedly.
190
     * The shared instance's state is reset to default font, alignment, etc., on each call to <code>sharedLabel</code>.
191
     * <p>
192
     * This method must be called only from the AWT event thread; this is enforced with assertions.
193
     *
194
     * @return A JLabel which can render a subset of html very quickly
195
     * @param renderStyle The rendering style.  Currently supported are STYLE_TRUNCATE and STYLE_CLIP.
196
     * @param isHtml Null if the text might or might not be HTML and will have opening HTML tags if it is.
197
     *        Boolean.TRUE if the text is definitely HTML, and might not have opening HTML tags.
198
     *        Boolean.FALSE if the text should be rendered as plain text even <i>if</i> it contains
199
     *        HTML markup.
200
     */
201
    public static final JLabel sharedLabel (int renderStyle, Boolean isHtml) {
202
        assert SwingUtilities.isEventDispatchThread() : "HtmlRenderer.sharedLabel " + //NOI18N
203
            "may only be accessed on the event dispatch thread"; //NOI18N
204
205
        switch (renderStyle) {
206
            case STYLE_TRUNCATE :
207
            case STYLE_CLIP :
208
                break;
209
            default :
210
                throw new IllegalArgumentException ("Unknown render style " + renderStyle); //NOI18N
211
        }
212
213
        if (LABEL == null) {
214
            LABEL = new HtmlRendererImpl();
215
        } else {
216
            LABEL.reset();
217
        }
218
        
219
        if (isHtml != null) {
220
            LABEL.setHtml(isHtml.booleanValue());
221
        }
222
        
223
        LABEL.setRenderStyle(renderStyle);
224
        return LABEL;
225
    }
226
227
    /** Interface aggregating TableCellRenderer, TreeCellRenderer and ListCellRenderer.
228
     * Return type of <code>sharedInstance()</code>.
229
     */
230
    public interface Renderer extends TableCellRenderer, TreeCellRenderer, ListCellRenderer {
231
        /** Indicate that the component being rendered has keyboard focus.  NetBeans requires that a different
232
         * selection color be used depending on whether the view has focus.
233
         *
234
         * @param parentFocused Whether or not the focused selection color should be used
235
         */
236
        void setParentFocused (boolean parentFocused);
237
238
        /**
239
         * Indicate that the text should be painted centered below the icon.  This is primarily used
240
         * by org.openide.explorer.view.IconView
241
         *
242
         * @param centered Whether or not centered painting should be used.
243
         */
244
        void setCentered (boolean centered);
245
246
        /**
247
         * Set a number of pixels the icon and text should be indented.  Used by ChoiceView and ListView to
248
         * fake tree-style nesting.  This value has no effect if <code>setCentered(true)</code> has been called.
249
         *
250
         * @param pixels The number of pixels to indent
251
         */
252
        void setIndent (int pixels);
253
254
        /**
255
         * Explicitly tell the renderer it is going to receive HTML markup, or it is not.  If the renderer should
256
         * check the string for opening HTML tags to determine this, don't call this method.  If you <strong>know</strong>
257
         * the string will be compliant HTML, it is preferable to call this method with true; if you want to intentionally
258
         * render HTML markup literally, call this method with false.
259
         *
260
         * @param val
261
         */
262
        void setHtml (boolean val);
263
264
        /**
265
         * Set the rendering style - this can be JLabel-style truncated-with-elipsis (...) text, or clipped text.
266
         * The default is STYLE_CLIP.
267
         *
268
         * @param style The text style
269
         */
270
        void setRenderStyle (int style);
271
272
        /** Set the icon to be used for painting
273
         *
274
         * @param icon An icon or null
275
         */
276
        void setIcon (Icon icon);
277
278
        /** Clear any stale data from previous use by other components,
279
         * clearing the icon, text, disabled state and other customizations, returning the component
280
         * to its initialized state.  This is done automatically when get*CellRenderer() is called,
281
         * and to the shared instance when <code>sharedLabel()</code> is called.<p>
282
         * Users of the static <code>sharedLabel()</code> method may want to call this method if they
283
         * use the returned instance more than once without again calling <code>sharedLabel()</code>.
284
         */
285
        void reset();
286
287
        /** Set the text to be displayed.  Use this if the object being rendered's toString() does not
288
         * return a real user-displayable string, after calling get**CellRenderer().  Typically after calling
289
         * this one calls setHtml() if the text is known to either be or not be HTML markup.
290
         *
291
         * @param txt The text that should be displayed
292
         */
293
        void setText (String txt);
294
295
        /**
296
         * Convenience method to set the gap between the icon and text.
297
         *
298
         * @param gap an integer number of pixels
299
         */
300
        void setIconTextGap (int gap);
301
    }
302
303
    /** Stack object used during HTML rendering to hold previous colors in
304
     * the case of nested color entries. */
305
    private static Stack colorStack = new Stack();
306
    /** Constant used by <code>renderString</code>, <code>renderPlainString</code>
307
     * <code>renderHTML</code>, and <code>HtmlRenderer.setRenderStyle</code>
308
     * if painting should simply be cut off at the boundary of the cooordinates passed.     */
309
    public static final int STYLE_CLIP=0;
310
    /** Constant used by <code>renderString</code>, <code>renderPlainString</code>
311
     * <code>renderHTML</code>  and <code>HtmlRenderer.setRenderStyle</code> if
312
     * painting should produce an ellipsis (...)
313
     * if the text would overlap the boundary of the coordinates passed */
314
    public static final int STYLE_TRUNCATE=1;
315
    /** Constant used by <code>renderString</code>, <code>renderPlainString</code>
316
     * <code>renderHTML</code> and <code>HtmlRenderer.setRenderStyle</code>
317
     * if painting should word wrap the text.  In
318
     * this case, the return value of any of the above methods will be the
319
     * height, rather than width painted. */
320
    private static final int STYLE_WORDWRAP=2;
321
    /** System property to cause exceptions to be thrown when unparsable
322
     * html is encountered */
323
    private static final boolean strictHtml = Boolean.getBoolean(
324
        "netbeans.lwhtml.strict"); //NOI18N
325
    /** Cache for strings which have produced errors, so we don't post an
326
     * error message more than once */
327
    private static Set badStrings=null;
328
    /** Definitions for a limited subset of sgml character entities */
329
    private static final Object[] entities = new Object[] {
330
        new char[] {'g','t'}, new char[] {'l','t'}, //NOI18N
331
        new char[] {'q','u','o','t'}, new char[] {'a','m','p'}, //NOI18N
332
        new char[] {'l','s','q','u','o'}, //NOI18N
333
        new char[] {'r','s','q','u','o'}, //NOI18N
334
        new char[] {'l','d','q','u','o'}, //NOI18N
335
        new char[] {'r','d','q','u','o'}, //NOI18N
336
        new char[] {'n','d','a','s','h'}, //NOI18N
337
        new char[] {'m','d','a','s','h'}, //NOI18N
338
        new char[] {'n','e'}, //NOI18N
339
        new char[] {'l','e'}, //NOI18N
340
        new char[] {'g','e'}, //NOI18N
341
342
        new char[] {'c','o','p','y'}, //NOI18N
343
        new char[] {'r','e','g'},  //NOI18N
344
        new char[] {'t','r','a','d','e'} //NOI18N
345
        //The rest of the SGML entities are left as an excercise for the reader
346
    }; //NOI18N
347
    /** Mappings for the array of sgml character entities to characters */
348
    private static final char[] entitySubstitutions = new char[] {
349
        '>','<','"','&',8216, 8217, 8220, 8221, 8211, 8212, 8800, 8804, 8805, //NOI18N
350
        169, 174, 8482
351
    };
352
353
    /**Render a string to a graphics instance, using the same API as renderHTML().
354
     * Can render a string using JLabel-style ellipsis (...) in the case that
355
     * it will not fit in the passed rectangle, if the style parameter is
356
     * STYLE_CLIP. Returns the width in pixels successfully painted.
357
     * <strong>This method is not thread-safe and should not be called off
358
     * the AWT thread.</strong>
359
     *
360
     * @see #renderHTML */
361
    public static double renderPlainString(String s, Graphics g, int x, int y, int w, int h, Font f, Color defaultColor, int style, boolean paint) {
362
        assert SwingUtilities.isEventDispatchThread();
363
        //per Jarda's request, keep the word wrapping code but don't expose it.
364
        if (style < 0 || style > 1) {
365
            throw new IllegalArgumentException(
366
            "Unknown rendering mode: " + style); //NOI18N
367
        }
368
        return _renderPlainString(s, g, x, y, w, h, f, defaultColor, style,
369
        paint);
370
    }
371
372
    private static double _renderPlainString(String s, Graphics g, int x, int y, int w, int h, Font f, Color foreground, int style, boolean paint) {
373
        FontMetrics fm = g.getFontMetrics(f);
374
        Rectangle2D r = fm.getStringBounds(s, g);
375
        if (paint) {
376
            g.setColor(foreground);
377
            g.setFont(f);
378
            if ((r.getWidth() <= w) || (style == STYLE_CLIP)) {
379
                g.drawString(s, x, y);
380
            } else {
381
                char[] chars = new char[s.length()];
382
                s.getChars(0, s.length()-1, chars, 0);
383
                if (chars.length == 0) {
384
                    return 0;
385
                }
386
                double chWidth = r.getWidth() / chars.length;
387
                int estCharsOver = new Double((r.getWidth() - w) / chWidth).intValue();
388
                if (style == STYLE_TRUNCATE) {
389
                    int length = chars.length - estCharsOver;
390
                    if (length <=0) {
391
                        return 0;
392
                    }
393
                    if (paint) {
394
                        if (length > 3) {
395
                            Arrays.fill(chars, length-3, length, '.'); //NOI18N
396
                            g.drawChars(chars, 0, length, x, y);
397
                        } else {
398
                            Shape shape = g.getClip();
399
                            if (s != null) {
400
                                Area area = new Area(shape);
401
                                area.intersect (new Area(new Rectangle(x,y,w,h)));
402
                                g.setClip(area);
403
                            } else {
404
                                g.setClip(new Rectangle(x,y,w,h));
405
                            }
406
                            g.drawString("...", x,y);
407
                            g.setClip(shape);
408
                        }
409
                    }
410
                } else {
411
                    //TODO implement plaintext word wrap if we want to support it at some point
412
                }
413
            }
414
        }
415
        return r.getWidth();
416
    }
417
418
    /** Render a string to a graphics context, using HTML markup if the string
419
     * begins with html tags.  Delegates to <code>renderPlainString()</code>
420
     * or <code>renderHTML()</code> as appropriate.  See the documentation for
421
     * <code>renderHTML()</code> for details of the subset of HTML that is
422
     * supported.
423
     * @param s The string to render
424
     * @param g A graphics object into which the string should be drawn, or which should be
425
     * used for calculating the appropriate size
426
     * @param x The x coordinate to paint at.
427
     * @param y The y position at which to paint.  Note that this method does not calculate font
428
     * height/descent - this value should be the baseline for the line of text, not
429
     * the upper corner of the rectangle to paint in.
430
     * @param w The maximum width within which to paint.
431
     * @param h The maximum height within which to paint.
432
     * @param f The base font to be used for painting or calculating string width/height.
433
     * @param defaultColor The base color to use if no font color is specified as html tags
434
     * @param style The wrapping style to use, either <code>STYLE_CLIP</CODE>,
435
     * or <CODE>STYLE_TRUNCATE</CODE>
436
     * @param paint True if actual painting should occur.  If false, this method will not actually
437
     * paint anything, only return a value representing the width/height needed to
438
     * paint the passed string.
439
     * @return The width in pixels required
440
     * to paint the complete string, or the passed parameter <code>w</code> if it is
441
     * smaller than the required width.
442
     */
443
    public static double renderString(String s, Graphics g, int x, int y, int w, int h, Font f, Color defaultColor, int style, boolean paint) {
444
        if (s.startsWith("<html") || s.startsWith("<HTML")) { //NOI18N
445
            if (style < 0 || style > 1) {
446
                throw new IllegalArgumentException(
447
                "Unknown rendering mode: " + style); //NOI18N
448
            }
449
            return _renderHTML(6, s, g, x, y, w, h, f, defaultColor, style, paint);
450
        } else {
451
            return renderPlainString(s, g, x, y, w, h, f, defaultColor, style, paint);
452
        }
453
    }
454
455
    /** Render a string as HTML using a fast, lightweight renderer supporting a limited
456
     * subset of HTML.  The following tags are supported, in upper or lower case:
457
     *
458
     * <table>
459
     * <tr>
460
     *  <td>&lt;B&gt;</td>
461
     *  <td>Boldface text</td>
462
     * </tr>
463
     * <tr>
464
     *  <td>&lt;S&gt;</td>
465
     *  <td>Strikethrough text</td>
466
     * </tr>
467
     * <tr>
468
     *  <td>&lt;U&gt;</td>
469
     *  <td>Underline text</td>
470
     * </tr>
471
     * <tr>
472
     *  <td>&lt;I&gt;</td>
473
     *  <td>Italic text</td>
474
     * </tr>
475
     * <tr>
476
     *  <td>&lt;EM&gt;</td>
477
     *  <td>Emphasized text (same as italic)</td>
478
     * </tr>
479
     * <tr>
480
     *  <td>&lt;STRONG&gt;</td>
481
     *  <td>Strong text (same as bold)</td>
482
     * </tr>
483
     * <tr>
484
     *  <td>&lt;font&gt;</td>
485
     *  <td>Font color - font attributes other than color are not supported.  Colors
486
     *  may be specified as hexidecimal strings, such as #FF0000 or as logical colors
487
     *  defined in the current look and feel by specifying a ! character as the first
488
     *  character of the color name.  Logical colors are colors available from the
489
     *  current look and feel's UIManager.  For example, <code>&lt;font
490
     *  color=&quot;!Tree.background&quot;&gt;</code> will set the font color to the
491
     *  result of <code>UIManager.getColor(&quot;Tree.background&quot)</code>.
492
     * <strong>Font size tags are not supported.</strong>
493
     * </td>
494
     * </tr>
495
     * </table>
496
     * The lightweight html renderer supports the following named sgml character
497
     * entities: <code>quot, lt, amp, lsquo, rsquo, ldquo, rdquo, ndash, mdash, ne,
498
     * le, ge, copy, reg, trade.  </code>.  It also supports numeric entities
499
     * (e.g. <code>&amp;#8822;</code>).
500
     * <p><b>When to use this method instead of the JDK's HTML support: </b> when
501
     * rendering short strings (for example, in a tree or table cell renderer)
502
     * with limited HTML, this method is approximately 10x faster than JDK HTML
503
     * rendering (it does not build and parse a document tree).
504
     *
505
     * <P><B><U>Specifying logical colors</U></B><BR>
506
     * Hardcoded text colors are undesirable, as they can be incompatible (even
507
     * invisible) on some look and feels or themes.
508
     * The lightweight HTML renderer supports a non-standard syntax for specifying
509
     * font colors via a key for a color in the UI defaults for the current look
510
     * and feel.  This is accomplished by prefixing the key name with a <code>!</code>
511
     * character.  For example: <code>&lt;font color='!controlShadow'&gt;</code>.
512
     *
513
     * <P><B><U>Modes of operation</U></B><BR>
514
     * This method supports two modes of operation:
515
     * <OL>
516
     * <LI><CODE>STYLE_CLIP</CODE> - as much text as will fit in the pixel width passed
517
     * to the method should be painted, and the text should be cut off at the maximum
518
     * width or clip rectangle maximum X boundary for the graphics object, whichever is
519
     * smaller.</LI>
520
     * <LI><CODE>STYLE_TRUNCATE</CODE> - paint as much text as will fit in the pixel
521
     * width passed to the method, but paint the last three characters as .'s, in the
522
     * same manner as a JLabel truncates its text when the available space is too
523
     * small.</LI>
524
     * </OL>
525
     * <P>
526
     * This method can also be used in non-painting mode to establish the space
527
     * necessary to paint a string.  This is accomplished by passing the value of the
528
     * <code>paint</code> argument as false.  The return value will be the required
529
     * width in pixels
530
     * to display the text.  Note that in order to retrieve an
531
     * accurate value, the argument for available width should be passed
532
     * as <code>Integer.MAX_VALUE</code> or an appropriate maximum size - otherwise
533
     * the return value will either be the passed maximum width or the required
534
     * width, whichever is smaller.  Also, the clip shape for the passed graphics
535
     * object should be null or a value larger than the maximum possible render size.
536
     * <P>
537
     * This method will log a warning if it encounters HTML markup it cannot
538
     * render.  To aid diagnostics, if NetBeans is run with the argument
539
     * <code>-J-Dnetbeans.lwhtml.strict=true</code> an exception will be thrown
540
     * when an attempt is made to render unsupported HTML.</code><p>
541
     * <p>
542
     * @param s The string to render
543
     * @param g A graphics object into which the string should be drawn, or which should be
544
     * used for calculating the appropriate size
545
     * @param x The x coordinate to paint at.
546
     * @param y The y position at which to paint.  Note that this method does not calculate font
547
     * height/descent - this value should be the baseline for the line of text, not
548
     * the upper corner of the rectangle to paint in.
549
     * @param w The maximum width within which to paint.
550
     * @param h The maximum height within which to paint.
551
     * @param f The base font to be used for painting or calculating string width/height.
552
     * @param defaultColor The base color to use if no font color is specified as html tags
553
     * @param style The wrapping style to use, either <code>STYLE_CLIP</CODE>,
554
     * or <CODE>STYLE_TRUNCATE</CODE>
555
     * @param paint True if actual painting should occur.  If false, this method will not actually
556
     * paint anything, only return a value representing the width/height needed to
557
     * paint the passed string.
558
     * @return The width in pixels required
559
     * to paint the complete string, or the passed parameter <code>w</code> if it is
560
     * smaller than the required width.
561
     */
562
    public static double renderHTML(String s, Graphics g, int x, int y,
563
        int w, int h, Font f,
564
        Color defaultColor, int style,
565
        boolean paint) {
566
567
        assert SwingUtilities.isEventDispatchThread();
568
569
        //per Jarda's request, keep the word wrapping code but don't expose it.
570
        if (style < 0 || style > 1) {
571
            throw new IllegalArgumentException(
572
            "Unknown rendering mode: " + style); //NOI18N
573
        }
574
        return _renderHTML(0, s, g, x, y, w, h, f, defaultColor, style,
575
            paint);
576
    }
577
578
    /** Implementation of HTML rendering */
579
    private static double _renderHTML(int pos, String s, Graphics g, int x,
580
        int y, int w, int h, Font f, Color defaultColor, int style,
581
        boolean paint) {
582
583
        //Thread safety - avoid allocating memory for the common case
584
        Stack colorStack = SwingUtilities.isEventDispatchThread() ? 
585
            HtmlRenderer.colorStack : new Stack();
586
587
        g.setColor(defaultColor);
588
        g.setFont(f);
589
        char[] chars = s.toCharArray();
590
        int origX = x;
591
        boolean done = false;  //flag if rendering completed, either by finishing the string or running out of space
592
        boolean inTag = false; //flag if the current position is inside a tag, and the tag should be processed rather than rendering
593
        boolean inClosingTag = false; //flag if the current position is inside a closing tag
594
        boolean strikethrough = false; //flag if a strikethrough line should be painted
595
        boolean underline = false; //flag if an underline should be painted
596
        boolean bold = false; //flag if text is currently bold
597
        boolean italic = false; //flag if text is currently italic
598
        boolean truncated = false; //flag if the last possible character has been painted, and the next loop should paint "..." and return
599
        double widthPainted = 0; //the total width painted, for calculating needed space
600
        double heightPainted = 0; //the total height painted, for calculating needed space
601
        boolean lastWasWhitespace = false; //flag to skip additional whitespace if one whitespace char already painted
602
        double lastHeight=0; //the last line height, for calculating total required height
603
        FontMetrics baseFontMetrics = g.getFontMetrics(g.getFont());
604
605
606
        double dotWidth = 0;
607
        //Calculate the width of a . character if we may need to truncate
608
        if (style == STYLE_TRUNCATE) {
609
            dotWidth = g.getFontMetrics().charWidth('.'); //NOI18N
610
        }
611
612
        /* How this all works, for anyone maintaining this code (hopefully it will
613
          never need it):
614
          1. The string is converted to a char array
615
          2. Loop over the characters.  Variable pos is the current point.
616
            2a. See if we're in a tag by or'ing inTag with currChar == '<'
617
              If WE ARE IN A TAG:
618
               2a1: is it an opening tag?
619
                 If YES:
620
                   - Identify the tag, Configure the Graphics object with
621
                     the appropriate font, color, etc.  Set pos = the first
622
                     character after the tag
623
                 If NO (it's a closing tag)
624
                   - Identify the tag.  Reconfigure the Graphics object
625
                     with the state it should be in outside the tag
626
                     (reset the font if italic, pop a color off the stack, etc.)
627
            2b. If WE ARE NOT IN A TAG
628
               - Locate the next < or & character or the end of the string
629
               - Paint the characters using the Graphics object
630
               - Check underline and strikethrough tags, and paint line if
631
                 needed
632
            See if we're out of space, and do the right thing for the style
633
            (paint ..., give up or skip to the next line)
634
         */
635
636
        //Clear any junk left behind from a previous rendering loop
637
        colorStack.clear();
638
639
        //Enter the painting loop
640
        while (!done) {
641
            if (pos == s.length()) {
642
                return widthPainted;
643
            }
644
            //see if we're in a tag
645
            try {
646
                inTag |= chars[pos] == '<';
647
            } catch (ArrayIndexOutOfBoundsException e) {
648
                //Should there be any problem, give a meaningful enough
649
                //message to reproduce the problem
650
                ArrayIndexOutOfBoundsException aib =
651
                new ArrayIndexOutOfBoundsException(
652
                "HTML rendering failed at position " + pos + " in String \"" //NOI18N
653
                + s + "\".  Please report this at http://www.netbeans.org"); //NOI18N
654
655
                if (strictHtml) {
656
                    throw aib;
657
                } else {
658
                    ErrorManager.getDefault().notify (ErrorManager.WARNING, aib);
659
                    return renderPlainString(s, g, x, y, w, h, f, defaultColor, style, paint);
660
                }
661
            }
662
            inClosingTag = inTag && (pos+1 < chars.length) && chars[pos+1]  == '/'; //NOI18N
663
664
            if (truncated) {
665
                //Then we've almost run out of space, time to print ... and quit
666
                g.setColor(defaultColor);
667
                g.setFont(f);
668
                if (paint) {
669
                    g.drawString("...", x, y); //NOI18N
670
                }
671
                done = true;
672
            } else if (inTag) {
673
                //If we're in a tag, don't paint, process it
674
                pos++;
675
                int tagEnd = pos;
676
                while (!done && (chars[tagEnd] != '>')) {
677
                    done = tagEnd == chars.length -1;
678
                    tagEnd++;
679
                }
680
681
                if (inClosingTag) {
682
                    //Handle closing tags by resetting the Graphics object (font, etc.)
683
                    pos++;
684
                    switch (chars[pos]) {
685
                        case 'P' : //NOI18N
686
                        case 'p' : //NOI18N
687
                        case 'H' : //NOI18N
688
                        case 'h' : break; //ignore html opening/closing tags
689
                        case 'B' : //NOI18N
690
                        case 'b' : //NOI18N
691
                            if (chars[pos+1] == 'r' || chars[pos+1] == 'R') {
692
                                break;
693
                            }
694
                            if (!bold) {
695
                                throwBadHTML("Closing bold tag w/o " + //NOI18N
696
                                "opening bold tag", pos, chars); //NOI18N
697
                            }
698
                            if (italic) {
699
                                g.setFont(f.deriveFont(Font.ITALIC));
700
                            } else {
701
                                g.setFont(f.deriveFont(Font.PLAIN));
702
                            }
703
                            bold = false;
704
                            break;
705
                        case 'E' : //NOI18N
706
                        case 'e' : //em tag
707
                        case 'I' : //NOI18N
708
                        case 'i' : //NOI18N
709
                            if (bold) {
710
                                g.setFont(f.deriveFont(Font.BOLD));
711
                            } else {
712
                                g.setFont(f.deriveFont(Font.PLAIN));
713
                            }
714
                            if (!italic) {
715
                                throwBadHTML("Closing italics tag w/o" //NOI18N
716
                                + "opening italics tag", pos, chars); //NOI18N
717
                            }
718
                            italic = false;
719
                            break;
720
                        case 'S' : //NOI18N
721
                        case 's' : //NOI18N
722
                            switch (chars[pos+1]) {
723
                                case 'T' : //NOI18N
724
                                case 't' : if (italic) { //NOI18N
725
                                    g.setFont(f.deriveFont(
726
                                    Font.ITALIC));
727
                                } else {
728
                                    g.setFont(f.deriveFont(
729
                                    Font.PLAIN));
730
                                }
731
                                bold = false;
732
                                break;
733
                                case '>' : //NOI18N
734
                                    strikethrough = false;
735
                                    break;
736
                            }
737
                            break;
738
                        case 'U' : //NOI18N
739
                        case 'u' : underline = false; //NOI18N
740
                        break;
741
                        case 'F' : //NOI18N
742
                        case 'f' : //NOI18N
743
                            if (colorStack.isEmpty()) {
744
                                g.setColor(defaultColor);
745
                            } else {
746
                                g.setColor((Color) colorStack.pop());
747
                            }
748
                            break;
749
                        default  :
750
                            throwBadHTML(
751
                            "Malformed or unsupported HTML", //NOI18N
752
                            pos,  chars);
753
                    }
754
                } else {
755
                    //Okay, we're in an opening tag.  See which one and configure the Graphics object
756
                    switch (chars[pos]) {
757
                        case 'B' : //NOI18N
758
                        case 'b' : //NOI18N
759
                            switch (chars[pos+1]) {
760
                                case 'R' :  //NOI18N
761
                                case 'r' : //NOI18N
762
                                    if (style == STYLE_WORDWRAP) {
763
                                        x = origX;
764
                                        int lineHeight = g.getFontMetrics().getHeight();
765
                                        y += lineHeight;
766
                                        heightPainted += lineHeight;
767
                                        widthPainted = 0;
768
                                    }
769
                                    break;
770
                                case '>' :
771
                                    bold = true;
772
                                    if (italic) {
773
                                        g.setFont(f.deriveFont(Font.BOLD | Font.ITALIC));
774
                                    } else {
775
                                        g.setFont(f.deriveFont(Font.BOLD));
776
                                    }
777
                                    break;
778
                            }
779
                            break;
780
                        case 'e' : //NOI18N  //em tag
781
                        case 'E' : //NOI18N
782
                        case 'I' : //NOI18N
783
                        case 'i' : //NOI18N
784
                            italic = true;
785
                            if (bold) {
786
                                g.setFont(f.deriveFont(Font.ITALIC | Font.BOLD));
787
                            } else {
788
                                g.setFont(f.deriveFont(Font.ITALIC));
789
                            }
790
                            break;
791
                        case 'S' : //NOI18N
792
                        case 's' : //NOI18N
793
                            switch (chars[pos+1]) {
794
                                case '>' :
795
                                    strikethrough = true;
796
                                    break;
797
                                case 'T' :
798
                                case 't' :
799
                                    bold = true;
800
                                    if (italic) {
801
                                        g.setFont(f.deriveFont(Font.BOLD | Font.ITALIC));
802
                                    } else {
803
                                        g.setFont(f.deriveFont(Font.BOLD));
804
                                    }
805
                                    break;
806
                            }
807
                            break;
808
                        case 'U' : //NOI18N
809
                        case 'u' : //NOI18N
810
                            underline = true;
811
                            break;
812
                        case 'f' : //NOI18N
813
                        case 'F' : //NOI18N
814
                            Color c = findColor(chars, pos, tagEnd);
815
                            colorStack.push(g.getColor());
816
                            g.setColor(c);
817
                            break;
818
                        case 'P' : //NOI18N
819
                        case 'p' : //NOI18N
820
                            if (style == STYLE_WORDWRAP) {
821
                                x = origX;
822
                                int lineHeight=g.getFontMetrics().getHeight();
823
                                y +=  lineHeight + (lineHeight / 2);
824
                                heightPainted = y + lineHeight;
825
                                widthPainted = 0;
826
                            }
827
                            break;
828
                        case 'H' :
829
                        case 'h' : //Just an opening HTML tag
830
                            if (pos == 1) {
831
                                break;
832
                            }
833
                        default  : throwBadHTML(
834
                            "Malformed or unsupported HTML", pos, chars); //NOI18N
835
                    }
836
                }
837
838
                pos = tagEnd + (done ? 0 : 1);
839
                inTag = false;
840
            } else {
841
                //Okay, we're not in a tag, we need to paint
842
843
                if (lastWasWhitespace) {
844
                    //Skip multiple whitespace characters
845
                    while (pos < s.length() && Character.isWhitespace(chars[pos])) {
846
                        pos++;
847
                    }
848
                    //Check strings terminating with multiple whitespace -
849
                    //otherwise could get an AIOOBE here
850
                    if (pos == chars.length - 1) {
851
                        return style != STYLE_WORDWRAP ? widthPainted : heightPainted;
852
                    }
853
                }
854
855
                //Flag to indicate if an ampersand entity was processed,
856
                //so the resulting & doesn't get treated as the beginning of
857
                //another entity (and loop endlessly)
858
                boolean isAmp=false;
859
                //Flag to indicate the next found < character really should
860
                //be painted (it came from an entity), it is not the beginning
861
                //of a tag
862
                boolean nextLtIsEntity=false;
863
                int nextTag = chars.length-1;
864
                if ((chars[pos] == '&')) { //NOI18N
865
                    boolean inEntity=pos != chars.length-1;
866
                    if (inEntity) {
867
                        int newPos = substEntity(chars, pos+1);
868
                        inEntity = newPos != -1;
869
                        if (inEntity) {
870
                            pos = newPos;
871
                            isAmp = chars[pos] == '&'; //NOI18N
872
                            //flag it so the next iteration won't think the <
873
                            //starts a tag
874
                            nextLtIsEntity = chars[pos] == '<';
875
                        } else {
876
                            nextLtIsEntity = false;
877
                            isAmp = true;
878
                        }
879
                    }
880
                } else {
881
                    nextLtIsEntity=false;
882
                }
883
884
                for (int i=pos; i < chars.length; i++) {
885
                    if (((chars[i] == '<') && (!nextLtIsEntity)) || ((chars[i] == '&') && !isAmp)) { //NOI18N
886
                        nextTag = i-1;
887
                        break;
888
                    }
889
                    //Reset these flags so we don't skip all & or < chars for the rest of the string
890
                    isAmp = false;
891
                    nextLtIsEntity=false;
892
                }
893
894
895
                FontMetrics fm = g.getFont() == f ? baseFontMetrics : g.getFontMetrics(g.getFont());
896
897
                //Get the bounds of the substring we'll paint
898
                Rectangle2D r = fm.getStringBounds(chars, pos, nextTag + 1, g);
899
                //Store the height, so we can add it if we're in word wrap mode,
900
                //to return the height painted
901
                lastHeight = r.getHeight();
902
                //Work out the length of this tag
903
                int length = (nextTag + 1) - pos;
904
905
                //Flag to be set to true if we run out of space
906
                boolean goToNextRow = false;
907
908
                //Flag that the current line is longer than the available width,
909
                //and should be wrapped without finding a word boundary
910
                boolean brutalWrap = false;
911
                //Work out the per-character avg width of the string, for estimating
912
                //when we'll be out of space and should start the ... in truncate
913
                //mode
914
                double chWidth;
915
916
                if (style == STYLE_TRUNCATE) {
917
                    //if we're truncating, use the width of one dot from an
918
                    //ellipsis to get an accurate result for truncation
919
                    chWidth = dotWidth;
920
                } else {
921
                    //calculate an average character width
922
                    chWidth= r.getWidth() / (nextTag - pos);
923
                    //can return this sometimes, so handle it
924
                    if (chWidth == Double.POSITIVE_INFINITY || chWidth == Double.NEGATIVE_INFINITY) {
925
                        chWidth = fm.getMaxAdvance();
926
                    }
927
                }
928
929
930
                if ((style != STYLE_CLIP) &&
931
                    ((style == STYLE_TRUNCATE &&
932
                    (widthPainted + r.getWidth() > w - (chWidth * 3)))) ||
933
                    (style == STYLE_WORDWRAP &&
934
                    (widthPainted + r.getWidth() > w))) {
935
936
                    if (chWidth > 3) {
937
                        double pixelsOff = (widthPainted + (
938
                            r.getWidth() + 5)
939
                            ) - w;
940
941
                        double estCharsOver = pixelsOff / chWidth;
942
943
                        if (style == STYLE_TRUNCATE) {
944
                            int charsToPaint = Math.round(Math.round(Math.ceil((w - widthPainted) / chWidth)));
945
/*                            System.err.println("estCharsOver = " + estCharsOver);
946
                            System.err.println("Chars to paint " + charsToPaint + " chwidth = " + chWidth + " widthPainted " + widthPainted);
947
                            System.err.println("Width painted + width of tag: " + (widthPainted + r.getWidth()) + " available: " + w);
948
 */
949
950
                            int startPeriodsPos = pos + charsToPaint -3;
951
                            if (startPeriodsPos >= chars.length) {
952
                                startPeriodsPos = chars.length - 4;
953
                            }
954
                            length = (startPeriodsPos - pos);
955
                            if (length < 0) length = 0;
956
                            r = fm.getStringBounds(chars, pos, pos+length, g);
957
//                            System.err.println("Truncated set to true at " + pos + " (" + chars[pos] + ")");
958
                            truncated = true;
959
                        } else {
960
                            //Word wrap mode
961
                            goToNextRow = true;
962
                            int lastChar = new Double(nextTag -
963
                                estCharsOver).intValue();
964
                            //Unlike Swing's word wrap, which does not wrap on tag boundaries correctly, if we're out of space,
965
                            //we're out of space
966
                            brutalWrap = x == 0;
967
                            for (int i = lastChar; i > pos; i--) {
968
                                lastChar--;
969
                                if (Character.isWhitespace(chars[i])) {
970
                                    length = (lastChar - pos) + 1;
971
                                    brutalWrap = false;
972
                                    break;
973
                                }
974
                            }
975
                            if ((lastChar <= pos) && (length > estCharsOver)
976
                            && !brutalWrap) {
977
                                x = origX;
978
                                y += r.getHeight();
979
                                heightPainted += r.getHeight();
980
                                boolean boundsChanged = false;
981
                                while (!done && Character.isWhitespace(
982
                                chars[pos]) && (pos < nextTag)) {
983
                                    pos++;
984
                                    boundsChanged = true;
985
                                    done = pos == chars.length -1;
986
                                }
987
                                if (pos == nextTag) {
988
                                    lastWasWhitespace = true;
989
                                }
990
                                if (boundsChanged) {
991
                                    //recalculate the width we will add
992
                                    r = fm.getStringBounds(chars, pos,
993
                                    nextTag + 1, g);
994
                                }
995
                                goToNextRow = false;
996
                                widthPainted = 0;
997
                                if (chars[pos - 1 + length] == '<') {
998
                                    length --;
999
                                }
1000
                            } else if (brutalWrap) {
1001
                                //wrap without checking word boundaries
1002
                                length = (new Double(
1003
                                    (w - widthPainted) / chWidth)).intValue();
1004
                                if (pos + length > nextTag) {
1005
                                    length = (nextTag - pos);
1006
                                }
1007
                                goToNextRow = true;
1008
                            }
1009
                        }
1010
                    }
1011
                }
1012
                if (!done) {
1013
                    if (paint) {
1014
                        g.drawChars(chars, pos, length, x, y);
1015
                    }
1016
1017
                    if (strikethrough || underline) {
1018
                        LineMetrics lm = fm.getLineMetrics(chars, pos,
1019
                            length - 1, g);
1020
                        int lineWidth = new Double(x + r.getWidth()).intValue();
1021
                        if (paint) {
1022
                            if (strikethrough) {
1023
                                int stPos = Math.round(lm.getStrikethroughOffset()) +
1024
                                        g.getFont().getBaselineFor(chars[pos])
1025
                                        + 1;
1026
                                //PENDING - worth supporting with g.setStroke()? A one pixel line is most likely
1027
                                //good enough
1028
1029
                                //int stThick = Math.round (lm.getStrikethroughThickness());
1030
                                g.drawLine(x, y + stPos, lineWidth, y + stPos);
1031
                            }
1032
                            if (underline) {
1033
                                int stPos = Math.round(
1034
                                    lm.getUnderlineOffset()) +
1035
                                    g.getFont().getBaselineFor(chars[pos])
1036
                                    + 1;
1037
                                //PENDING - worth supporting with g.setStroke()? A one pixel line is most likely
1038
                                //good enough
1039
1040
                                //int stThick = new Float (lm.getUnderlineThickness()).intValue();
1041
                                g.drawLine(x, y + stPos, lineWidth, y + stPos);
1042
                            }
1043
                        }
1044
                    }
1045
                    if (goToNextRow) {
1046
                        //if we're in word wrap mode and need to go to the next
1047
                        //line, reconfigure the x and y coordinates
1048
                        x = origX;
1049
                        y += r.getHeight();
1050
                        heightPainted += r.getHeight();
1051
                        widthPainted = 0;
1052
                        pos += (length);
1053
                        //skip any leading whitespace
1054
                        while ((pos < chars.length) &&
1055
                        (Character.isWhitespace(chars[pos])) &&
1056
                            (chars[pos] != '<')) {
1057
                                pos++;
1058
                            }
1059
                        lastWasWhitespace = true;
1060
                        done |= pos >= chars.length;
1061
                    } else {
1062
                        x += r.getWidth();
1063
                        widthPainted += r.getWidth();
1064
                        lastWasWhitespace = Character.isWhitespace(
1065
                            chars[nextTag]);
1066
                        pos = nextTag + 1;
1067
                    }
1068
                    done |= nextTag == chars.length;
1069
                }
1070
            }
1071
        }
1072
        if (style != STYLE_WORDWRAP) {
1073
            return widthPainted;
1074
        } else {
1075
            return heightPainted + lastHeight;
1076
        }
1077
    }
1078
1079
    /** Parse a font color tag and return an appopriate java.awt.Color instance */
1080
    private static Color findColor(final char[] ch, final int pos,
1081
    final int tagEnd) {
1082
        int colorPos = pos;
1083
        boolean useUIManager = false;
1084
        for (int i=pos; i < tagEnd; i ++) {
1085
            if (ch[i] == 'c') {
1086
                colorPos = i + 6;
1087
                if (ch[colorPos] == '\'' || ch[colorPos] == '"') {
1088
                    colorPos++;
1089
                }
1090
                //skip the leading # character
1091
                if (ch[colorPos] == '#') {
1092
                    colorPos++;
1093
                } else if (ch[colorPos] == '!') {
1094
                    useUIManager = true;
1095
                    colorPos++;
1096
                }
1097
                break;
1098
            }
1099
        }
1100
        if (colorPos == pos) {
1101
            String out = "Could not find color identifier in font declaration"; //NOI18N
1102
            throwBadHTML(out, pos, ch);
1103
        }
1104
        //Okay, we're now on the first character of the hex color definition
1105
        String s;
1106
        if (useUIManager) {
1107
            int end = ch.length-1;
1108
            for (int i=colorPos; i < ch.length; i++) {
1109
                if (ch[i] == '"' || ch[i] == '\'') { //NOI18N
1110
                    end = i;
1111
                    break;
1112
                }
1113
            }
1114
            s = new String(ch, colorPos, end-colorPos);
1115
        } else {
1116
            s = new String(ch, colorPos, 6);
1117
        }
1118
        Color result=null;
1119
        if (useUIManager) {
1120
            result = UIManager.getColor(s);
1121
            //Not all look and feels will provide standard colors; handle it gracefully
1122
            if (result == null) {
1123
                throwBadHTML(
1124
                "Could not resolve logical font declared in HTML: " + s, //NOI18N
1125
                pos, ch);
1126
                result = UIManager.getColor("textText"); //NOI18N
1127
                //Avoid NPE in headless situation?
1128
                if (result == null) {
1129
                    result = Color.BLACK;
1130
                }
1131
            }
1132
        } else {
1133
            try {
1134
                int rgb = Integer.parseInt(s, 16);
1135
                result = new Color(rgb);
1136
            } catch (NumberFormatException nfe) {
1137
                throwBadHTML(
1138
                "Illegal hexadecimal color text: " + s + //NOI18N
1139
                " in HTML string", colorPos, ch);   //NOI18N
1140
            }
1141
        }
1142
        if (result == null) {
1143
            throwBadHTML("Unresolvable html color: " + s //NOI18N
1144
            + " in HTML string \n  ", pos,  ch);  //NOI18N
1145
        }
1146
        return result;
1147
    }
1148
1149
    /** Find an entity at the passed character position in the passed array.
1150
     * If an entity is found, the trailing ; character will be substituted
1151
     * with the resulting character, and the position of that character
1152
     * in the array will be returned as the new position to render from,
1153
     * causing the renderer to skip the intervening characters */
1154
    private static final int substEntity(char[] ch, int pos) {
1155
        //There are no 1 character entities, abort
1156
        if (pos >= ch.length-2) {
1157
            return -1;
1158
        }
1159
        //if it's numeric, parse out the number
1160
        if (ch[pos] == '#') { //NOI18N
1161
            return substNumericEntity(ch, pos+1);
1162
        }
1163
        //Okay, we've potentially got a named character entity. Try to find it.
1164
        boolean match;
1165
        for (int i=0; i < entities.length; i++) {
1166
            char[] c = (char[]) entities[i];
1167
            match = true;
1168
            if (c.length < ch.length-pos) {
1169
                for (int j=0; j < c.length; j++) {
1170
                    match &= c[j] == ch[j+pos];
1171
                }
1172
            } else {
1173
                match = false;
1174
            }
1175
            if (match) {
1176
                //if it's a match, we still need the trailing ;
1177
                if (ch[pos+c.length] == ';') { //NOI18N
1178
                    //substitute the character referenced by the entity
1179
                    ch[pos+c.length] = entitySubstitutions[i];
1180
                    return pos+c.length;
1181
                }
1182
            }
1183
        }
1184
        return -1;
1185
    }
1186
1187
    /** Finds a character defined as a numeric entity (e.g. &amp;#8222;)
1188
     * and replaces the trailing ; with the referenced character, returning
1189
     * the position of it so the renderer can continue from there.
1190
     */
1191
    private static final int substNumericEntity(char[] ch, int pos) {
1192
        for (int i=pos; i < ch.length; i++) {
1193
            if (ch[i] == ';') {
1194
                try {
1195
                    ch[i] = (char) Integer.parseInt(
1196
                    new String(ch, pos, i - pos));
1197
                    return i;
1198
                } catch (NumberFormatException nfe) {
1199
                    throwBadHTML("Unparsable numeric entity: " + //NOI18N
1200
                        new String(ch, pos, i - pos), pos, ch); //NOI18N
1201
                }
1202
            }
1203
        }
1204
        return -1;
1205
    }
1206
1207
    /** Throw an exception for unsupported or bad html, indicating where the problem is
1208
     * in the message  */
1209
    private static void throwBadHTML(String msg, int pos, char[] chars) {
1210
        char[] chh = new char[pos];
1211
        Arrays.fill(chh, ' '); //NOI18N
1212
        chh[pos-1] = '^'; //NOI18N
1213
        String out = msg + "\n  " + new String(chars) + "\n  "
1214
            + new String(chh) + "\n Full HTML string:" + new String(chars); //NOI18N
1215
        if (!strictHtml) {
1216
            if (ErrorManager.getDefault().isLoggable(ErrorManager.WARNING)) {
1217
                if (badStrings == null) {
1218
                    badStrings = new HashSet();
1219
                }
1220
                if (!badStrings.contains(msg)) {
1221
                    //ErrorManager bug, issue 38372 - log messages containing
1222
                    //newlines are truncated - so for now we iterate the
1223
                    //string we've just constructed
1224
                    StringTokenizer tk = new StringTokenizer(out, "\n", false);
1225
                    while (tk.hasMoreTokens()) {
1226
                        ErrorManager.getDefault().log(ErrorManager.WARNING,
1227
                            tk.nextToken());
1228
                    }
1229
                    badStrings.add(msg.intern());
1230
                }
1231
            }
1232
        } else {
1233
            throw new IllegalArgumentException(out);
1234
        }
1235
    }
1236
}
(-)openide/src/org/openide/awt/HtmlRendererImpl.java (+671 lines)
Added Link Here
1
/*
2
 *                 Sun Public License Notice
3
 *
4
 * The contents of this file are subject to the Sun Public License
5
 * Version 1.0 (the "License"). You may not use this file except in
6
 * compliance with the License. A copy of the License is available at
7
 * http://www.sun.com/
8
 *
9
 * The Original Code is NetBeans. The Initial Developer of the Original
10
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
11
 * Microsystems, Inc. All Rights Reserved.
12
 */
13
package org.openide.awt;
14
import org.openide.awt.HtmlRenderer;
15
import org.openide.awt.HtmlLabelUI;
16
17
import javax.swing.*;
18
import javax.swing.plaf.basic.BasicLabelUI;
19
import javax.swing.plaf.LabelUI;
20
import javax.swing.event.AncestorListener;
21
import javax.swing.border.Border;
22
import java.awt.*;
23
import java.awt.event.*;
24
import java.awt.image.BufferedImage;
25
import java.lang.ref.Reference;
26
import java.lang.ref.SoftReference;
27
import java.beans.PropertyChangeListener;
28
import java.beans.VetoableChangeListener;
29
import java.io.*;
30
31
/**
32
 * Html renderer component implementation.  The actual painting is done by HtmlLabelUI, which uses
33
 * HtmlRenderer.renderString().  What this class does:   Provide some methods for resetting its state
34
 * between uses (see HtmlRenderer.sharedLabel() for why), overrides for a bunch of things for performance
35
 * reasons, and some conversions to handle the case that the lightweight html renderer is disabled
36
 * (-J-Dnb.useSwingHtmlRendering=true), to convert our minor extensions to html syntax to standard
37
 * syntax for the swing renderer.
38
 * <p>
39
 * Mainly this class provides an implementation of the various cell renderer interfaces which
40
 * HtmlRenderer.Renderer aggregates, and the convenience methods it provides.
41
 *
42
 * @author Tim Boudreau
43
 * @since 4.30
44
 *
45
 */
46
class HtmlRendererImpl extends JLabel implements HtmlRenderer.Renderer {
47
48
    private boolean centered = false;
49
    private boolean parentFocused = false;
50
    private Boolean html = null;
51
    private int indent = 0;
52
    private Border border = null;
53
    private boolean selected = false;
54
    private boolean leadSelection = false;
55
    private Dimension prefSize = null;
56
    private int type = TYPE_UNKNOWN;
57
    private int renderStyle = HtmlRenderer.STYLE_CLIP;
58
    private static final Rectangle bounds = new Rectangle();
59
    private boolean enabled = true;
60
61
    private static final boolean swingRendering = Boolean.getBoolean ("nb.useSwingHtmlRendering"); //NOI18N
62
    private static final Insets EMPTY_INSETS = new Insets (0, 0, 0, 0);
63
64
    static final int TYPE_UNKNOWN = -1;
65
    static final int TYPE_TREE = 0;
66
    static final int TYPE_LIST = 1;
67
    static final int TYPE_TABLE = 2;
68
69
    /** Restore the renderer to a pristine state */
70
    public void reset() {
71
        parentFocused = false;
72
        setCentered (false);
73
        html = null;
74
        indent = 0;
75
        border = null;
76
        setIcon (null);
77
        setOpaque (false);
78
        selected = false;
79
        leadSelection = false;
80
        prefSize = null;
81
        type = TYPE_UNKNOWN;
82
        renderStyle = HtmlRenderer.STYLE_CLIP;
83
        setFont (UIManager.getFont("controlFont")); //NOI18N
84
        setIconTextGap (3);
85
        setEnabled (true);
86
        border = null;
87
88
        //Defensively ensure the insets haven't been messed with
89
        EMPTY_INSETS.top = 0;
90
        EMPTY_INSETS.left = 0;
91
        EMPTY_INSETS.right = 0;
92
        EMPTY_INSETS.bottom = 0;
93
    }
94
95
    public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean leadSelection,
96
                                                   int row, int column) {
97
        reset();
98
        configureFrom (value, table, selected, leadSelection);
99
        type = TYPE_TABLE;
100
        if (swingRendering && selected) {
101
            setBackground (table.getSelectionBackground());
102
            setBackground (table.getSelectionForeground());
103
            setOpaque (true);
104
        }
105
        return this;
106
    }
107
108
    public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded,
109
                                                  boolean leaf, int row, boolean leadSelection) {
110
        reset();
111
        configureFrom (value, tree, selected, leadSelection);
112
        type = TYPE_TREE;
113
        if (swingRendering && selected) {
114
            setBackground (HtmlLabelUI.getBackgroundFor(this));
115
            setForeground (HtmlLabelUI.getForegroundFor(this));
116
            setOpaque (true);
117
        }
118
        return this;
119
    }
120
121
    public Component getListCellRendererComponent(JList list, Object value, int index, boolean selected,
122
                                                  boolean leadSelection) {
123
124
        reset();
125
        configureFrom (value, list, selected, leadSelection);
126
        type = TYPE_LIST;
127
        if (swingRendering && selected) {
128
            setBackground (list.getSelectionBackground());
129
            setForeground (list.getSelectionForeground());
130
            setOpaque (true);
131
        }
132
        return this;
133
    }
134
135
    /** Generic code to set properties appropriately from any of the renderer
136
     * fetching methods */
137
    private void configureFrom (Object value, JComponent target,
138
        boolean selected, boolean leadSelection) {
139
140
        if (value == null) {
141
            value = "";
142
        }
143
144
        setText (value instanceof String ? (String) value : value.toString());
145
146
        setSelected(selected);
147
        if (selected) {
148
            setParentFocused(checkFocused(target));
149
        } else {
150
            setParentFocused(false);
151
        }
152
153
        setEnabled (target.isEnabled());
154
155
        setLeadSelection (leadSelection);
156
157
        setFont (target.getFont());
158
    }
159
160
    private boolean checkFocused(JComponent c) {
161
        Component focused =
162
            KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
163
        boolean result = c == focused;
164
        if (!result) {
165
            result = c.isAncestorOf(focused);
166
        }
167
        return result;
168
    }
169
    
170
    public void addNotify() {
171
        if (swingRendering) {
172
            super.addNotify();
173
        }
174
    }
175
    
176
    public void removeNotify() {
177
        if (swingRendering) {
178
            super.removeNotify();
179
        }
180
    }
181
182
    public void setSelected (boolean val) {
183
        selected = val;
184
    }
185
186
    public void setParentFocused (boolean val) {
187
        parentFocused = val;
188
    }
189
190
    public void setLeadSelection (boolean val) {
191
        leadSelection = val;
192
    }
193
194
    public void setCentered(boolean val) {
195
        centered = val;
196
        if (val) {
197
            setIconTextGap (5);
198
        }
199
        if (swingRendering) {
200
            if (val) {
201
                setVerticalTextPosition(JLabel.BOTTOM);
202
                setHorizontalAlignment(JLabel.CENTER);
203
                setHorizontalTextPosition(JLabel.CENTER);
204
            } else {
205
                setVerticalTextPosition(JLabel.CENTER);
206
                setHorizontalAlignment(JLabel.LEADING);
207
                setHorizontalTextPosition(JLabel.TRAILING);
208
            }
209
        }
210
    }
211
212
    public void setIndent(int pixels) {
213
        this.indent = pixels;
214
    }
215
216
    public void setHtml (boolean val) {
217
        Boolean wasHtml = html;
218
        String txt = getText();
219
        html = val ? Boolean.TRUE : Boolean.FALSE;
220
        if (swingRendering && html != wasHtml) {
221
222
            //Ensure label UI gets updated and builds its little document tree...
223
            firePropertyChange ("text", txt, getText()); //NOI18N
224
        }
225
    }
226
227
    public void setRenderStyle(int style) {
228
        renderStyle = style;
229
    }
230
231
    int getRenderStyle () {
232
        return renderStyle;
233
    }
234
235
    boolean isLeadSelection () {
236
        return leadSelection;
237
    }
238
239
    boolean isCentered() {
240
        return centered;
241
    }
242
243
    boolean isParentFocused() {
244
        return parentFocused;
245
    }
246
247
    boolean isHtml() {
248
        if (html == null) {
249
            String s = getText();
250
            html = checkHtml (s);
251
        }
252
        return html.booleanValue();
253
    }
254
255
    private Boolean checkHtml(String s) {
256
        Boolean result;
257
        if (s == null) {
258
            result = Boolean.FALSE;
259
        } else if (s.startsWith ("<html") || s.startsWith("<HTML")) { //NOI18N
260
            result = Boolean.TRUE;
261
        } else {
262
            result = Boolean.FALSE;
263
        }
264
        return result;
265
    }
266
267
    boolean isSelected() {
268
        return selected;
269
    }
270
271
    int getIndent() {
272
        return indent;
273
    }
274
275
    int getType() {
276
        return type;
277
    }
278
279
    public Dimension getPreferredSize() {
280
        if (!swingRendering) {
281
            if (prefSize == null) {
282
                prefSize = getUI().getPreferredSize(this);
283
            }
284
            return prefSize;
285
        } else {
286
            return super.getPreferredSize();
287
        }
288
    }
289
290
    /**
291
     * Overridden for the case that we're running with the lightweight html renderer disabled, to convert
292
     * any less-than-legal html to legal html for purposes of Swing's html rendering.
293
     *
294
     * @return The text - unless the renderer is disabled, this just return super.getText()
295
     */
296
    public String getText() {
297
        String result = super.getText();
298
        if (swingRendering && Boolean.TRUE.equals(html)) {
299
            //Standard swing rendering needs an opening HTML tag to function, so make sure there is
300
            //one if we're not using HtmlLabelUI
301
            result = ensureHtmlTags (result);
302
        } else if (swingRendering && html == null) {
303
            //Cannot call isHtml() here, it will create an endless loop, so manually check the HTML status
304
            html = checkHtml (super.getText());
305
            if (Boolean.TRUE.equals(html)) {
306
                result = ensureHtmlTags (result);
307
            }
308
        }
309
        return result;
310
    }
311
312
    /**
313
     * Converts our extended html syntax (allowing UIManager color keys and omitting opening html tags
314
     * into standard html.  Only called if the lightweight html renderer is disabled and we're running with
315
     * a standard JLabel UI
316
     *
317
     * @param s The string that is the text of the label
318
     * @return The same string converted to standard HTML Swing's rendering infrastructure will know what to do
319
     *         with
320
     */
321
    private String ensureHtmlTags (String s) {
322
        s = ensureLegalFontColorTags(s);
323
        if (!s.startsWith("<HTML") && !s.startsWith("<html")) {  //NOI18N
324
            s = "<html>" + s + "</html>"; //NOI18N
325
        }
326
        return s;
327
    }
328
329
    /**
330
     * Converts extended UI manager color tags into legal html in the case that we're using swing rendering
331
     *
332
     * @param s string to convert if it has questionable font tags
333
     * @return The converted string
334
     */
335
    private static String ensureLegalFontColorTags(String s) {
336
        String check = s.toUpperCase();
337
        int start = 0;
338
        int fidx = check.indexOf("<FONT", start); //NOI18N
339
        StringBuffer sb = null;
340
341
        if (fidx != -1 && fidx <= s.length()) {
342
            while (fidx != -1 && fidx <= s.length()) {
343
               int cidx = check.indexOf("COLOR", start); //NOI18N
344
               int tagEnd = check.indexOf('>', start); //NOI18N
345
               start = tagEnd+1;
346
               if (tagEnd == -1) {
347
                   break;
348
               }
349
               if (cidx != -1) {
350
                   if (cidx < tagEnd) {
351
                       //we have a font color tag
352
                       int eidx = check.indexOf('=', cidx); //NOI18N
353
                       if (eidx != -1) {
354
                           int bangIdx = check.indexOf('!', eidx);  //NOI18N
355
                           if (bangIdx != -1 && bangIdx < tagEnd) {
356
                               int colorStart = bangIdx + 1;
357
                               int colorEnd = tagEnd;
358
                               for (int i=colorStart; i < tagEnd; i++) {
359
                                    char c = s.charAt(i);
360
                                    if (!Character.isLetter(c)) {
361
                                        colorEnd = i;
362
                                        break;
363
                                    }
364
                               }
365
                               if (sb == null) {
366
                                   sb = new StringBuffer (s);
367
                               }
368
                               String colorString = s.substring(colorStart, colorEnd);
369
                               String converted = convertToStandardColor (colorString);
370
                               sb.replace(bangIdx, colorEnd, converted);
371
                               s = sb.toString();
372
                               check = s.toUpperCase();
373
                           }
374
                       }
375
                   }
376
               }
377
               fidx = check.indexOf("<FONT", start); //NOI18N
378
               start = fidx;
379
            }
380
        }
381
        if (sb != null) {
382
            return sb.toString();
383
        } else {
384
            return s;
385
        }
386
    }
387
388
    /**
389
     * Creates a standard html #nnnnnn string from a string representing a UIManager key.  If the color is not found,
390
     * black will be used.  Only used if the lightweight html renderer is disabled.
391
     *
392
     * @param colorString  A string found after a ! character in a color definition, which needs to be converted to
393
     *        standard HTML
394
     * @return A hex number string
395
     */
396
    private static String convertToStandardColor (String colorString) {
397
        Color c = UIManager.getColor (colorString);
398
        if (c == null) {
399
            c = Color.BLACK;
400
        }
401
        StringBuffer sb = new StringBuffer(7);
402
        sb.append ('#');
403
        sb.append (hexString(c.getRed()));
404
        sb.append (hexString(c.getGreen()));
405
        sb.append (hexString(c.getBlue()));
406
        return sb.toString();
407
    }
408
409
    /**
410
     * Gets a hex string for an integer.  Ensures the result is always two characters long, which is not
411
     * true of Integer.toHexString().
412
     *
413
     * @param r an integer < 255
414
     * @return a 2 character hexadecimal string
415
     */
416
    private static String hexString (int r) {
417
        String s = Integer.toHexString(r);
418
        if (s.length() == 1) {
419
            s = '0' + s;
420
        }
421
        return s;
422
    }
423
424
    /** Overridden to do nothing under normal circumstances.  If the boolean flag to <strong>not</strong> use the
425
     * internal HTML renderer is in effect, this will fire changes normally */
426
    protected final void firePropertyChange(String name, Object old, Object nue) {
427
        if (swingRendering) {
428
            if ("text".equals(name) && isHtml()) {
429
                //Force in the HTML tags so the UI will set up swing HTML rendering appropriately
430
                nue = getText();
431
            }
432
            super.firePropertyChange(name, old, nue);
433
        }
434
    }
435
436
    public Border getBorder() {
437
        Border result;
438
        if (indent != 0 && swingRendering) {
439
            result = BorderFactory.createEmptyBorder (0, indent, 0, 0);
440
        } else {
441
            result = border;
442
        }
443
        return result;
444
    }
445
446
    public void setBorder (Border b) {
447
        Border old = border;
448
        border = b;
449
        if (swingRendering) {
450
            firePropertyChange ("border", old, b);
451
        }
452
    }
453
454
    public Insets getInsets() {
455
        Insets result;
456
457
        //Call getBorder(), not just read the field - if swingRendering, the border will be constructed, and the
458
        //insets are what will make the indent property work;  HtmlLabelUI doesn't need this, it just reads the
459
        //insets property, but BasicLabelUI and its ilk do
460
        Border b = getBorder();
461
        if (b == null) {
462
            result = EMPTY_INSETS;
463
        } else {
464
            result = b.getBorderInsets(this);
465
        }
466
        return result;
467
    }
468
469
    public void setEnabled (boolean b) {
470
        //OptimizeIt shows about 12Ms overhead calling back to Component.enable(), so avoid it if possible
471
        enabled = b;
472
        if (swingRendering) {
473
            super.setEnabled(b);
474
        }
475
    }
476
477
    public boolean isEnabled() {
478
        return enabled;
479
    }
480
481
    public void updateUI() {
482
        if (swingRendering) {
483
            super.updateUI();
484
        } else {
485
            setUI (HtmlLabelUI.createUI(this));
486
        }
487
    }
488
489
    /** Overridden to produce a graphics object even when isDisplayable() is
490
     * false, so that calls to getPreferredSize() will return accurate
491
     * dimensions (presuming the font and text are set correctly) even when
492
     * not onscreen. */
493
    public Graphics getGraphics() {
494
        Graphics result = null;
495
        if (isDisplayable()) {
496
            result = super.getGraphics();
497
        }
498
        if (result == null) {
499
            result = scratchGraphics();
500
        }
501
        return result;
502
    }
503
504
    //For experimentation - holding the graphics object may be the source of some
505
    //strange painting problems on Apple
506
    private static boolean noCacheGraphics = Boolean.getBoolean("nb.renderer.nocache"); //NOI18N
507
508
    private static Reference scratchGraphics = null;
509
    /** Fetch a scratch graphics object for calculating preferred sizes while
510
     * offscreen */
511
    private static final Graphics scratchGraphics() {
512
        Graphics result = null;
513
        if (scratchGraphics != null) {
514
            result = (Graphics) scratchGraphics.get();
515
            if (result != null) {
516
                result.setClip(null); //just in case somebody did something nasty
517
            }
518
        }
519
        if (result == null) {
520
                result = GraphicsEnvironment.
521
                    getLocalGraphicsEnvironment().
522
                    getDefaultScreenDevice().getDefaultConfiguration().
523
                    createCompatibleImage(1, 1).getGraphics();
524
525
            if (!noCacheGraphics){
526
                scratchGraphics = new SoftReference (result);
527
            }
528
        }
529
        return result;
530
    }
531
532
    public void setBounds (int x, int y, int w, int h) {
533
        if (swingRendering) {
534
            super.setBounds (x, y, w, h);
535
        }
536
        bounds.setBounds(x, y, w, h);
537
    }
538
    
539
    public void reshape (int x, int y, int w, int h) {
540
        if (swingRendering) {
541
            super.reshape(x, y, w, h);
542
        }
543
    }
544
545
    public int getWidth() {
546
        return bounds.width;
547
    }
548
549
    public int getHeight() {
550
        return bounds.height;
551
    }
552
553
    public Point getLocation() {
554
        return bounds.getLocation();
555
    }
556
557
    /** Overridden to do nothing for performance reasons */
558
    public void validate() {
559
        //do nothing
560
    }
561
562
    /** Overridden to do nothing for performance reasons */
563
    public void repaint (long tm, int x, int y, int w, int h) {
564
        //do nothing
565
    }
566
567
    /** Overridden to do nothing for performance reasons */
568
    public void repaint() {
569
        //do nothing
570
    }
571
572
    /** Overridden to do nothing for performance reasons */
573
    public void invalidate() {
574
        //do nothing
575
    }
576
577
    /** Overridden to do nothing for performance reasons */
578
    public void revalidate() {
579
        //do nothing
580
    }
581
582
    /** Overridden to do nothing for performance reasons */
583
    public void addAncestorListener (AncestorListener l) {
584
        if (swingRendering) {
585
            super.addAncestorListener (l);
586
        }
587
    }
588
589
    /** Overridden to do nothing for performance reasons */
590
    public void addComponentListener (ComponentListener l) {
591
        if (swingRendering) {
592
            super.addComponentListener (l);
593
        }
594
    }
595
596
    /** Overridden to do nothing for performance reasons */
597
    public void addContainerListener (ContainerListener l) {
598
        if (swingRendering) {
599
            super.addContainerListener (l);
600
        }
601
    }
602
603
    /** Overridden to do nothing for performance reasons */
604
    public void addHierarchyListener (HierarchyListener l) {
605
        if (swingRendering) {
606
            super.addHierarchyListener (l);
607
        }
608
    }
609
610
    /** Overridden to do nothing for performance reasons */
611
    public void addHierarchyBoundsListener (HierarchyBoundsListener l) {
612
        if (swingRendering) {
613
            super.addHierarchyBoundsListener (l);
614
        }
615
    }
616
617
    /** Overridden to do nothing for performance reasons */
618
    public void addInputMethodListener (InputMethodListener l) {
619
        if (swingRendering) {
620
            super.addInputMethodListener (l);
621
        }
622
    }
623
624
    /** Overridden to do nothing for performance reasons */
625
    public void addFocusListener (FocusListener fl) {
626
        if (swingRendering) {
627
            super.addFocusListener (fl);
628
        }
629
    }
630
631
    /** Overridden to do nothing for performance reasons */
632
    public void addMouseListener (MouseListener ml) {
633
        if (swingRendering) {
634
            super.addMouseListener (ml);
635
        }
636
    }
637
638
    /** Overridden to do nothing for performance reasons */
639
    public void addMouseWheelListener (MouseWheelListener ml) {
640
        if (swingRendering) {
641
            super.addMouseWheelListener (ml);
642
        }
643
    }
644
645
    /** Overridden to do nothing for performance reasons */
646
    public void addMouseMotionListener (MouseMotionListener ml) {
647
        if (swingRendering) {
648
            super.addMouseMotionListener (ml);
649
        }
650
    }
651
652
    /** Overridden to do nothing for performance reasons */
653
    public void addVetoableChangeListener (VetoableChangeListener vl) {
654
        if (swingRendering) {
655
            super.addVetoableChangeListener (vl);
656
        }
657
    }
658
659
    /** Overridden to do nothing for performance reasons, unless using standard swing rendering */
660
    public void addPropertyChangeListener (String s, PropertyChangeListener l) {
661
        if (swingRendering) {
662
            super.addPropertyChangeListener (s, l);
663
        }
664
    }
665
666
    public void addPropertyChangeListener (PropertyChangeListener l) {
667
        if (swingRendering) {
668
            super.addPropertyChangeListener (l);
669
        }
670
    }
671
}
(-)openide/src/org/openide/explorer/propertysheet/BaseTable.java (+4 lines)
Lines 552-557 Link Here
552
        Component result = renderer.getTableCellRendererComponent(this, value,
552
        Component result = renderer.getTableCellRendererComponent(this, value,
553
        isSelected, false,
553
        isSelected, false,
554
        row, col);
554
        row, col);
555
        if (result != null) {
556
            /*
555
        if (isSelected) {
557
        if (isSelected) {
556
            result.setBackground(getSelectionBackground());
558
            result.setBackground(getSelectionBackground());
557
            result.setForeground(getSelectionForeground());
559
            result.setForeground(getSelectionForeground());
Lines 565-570 Link Here
565
                result.setForeground(getForeground());
567
                result.setForeground(getForeground());
566
        }
568
        }
567
        result.setFont(getFont());
569
        result.setFont(getFont());
570
            */
571
        }
568
        return result;
572
        return result;
569
    }
573
    }
570
    
574
    
(-)openide/src/org/openide/explorer/propertysheet/ButtonPanel.java (+6 lines)
Lines 149-154 Link Here
149
        button.setRolloverIcon(PropUtils.getCustomButtonIcon());
149
        button.setRolloverIcon(PropUtils.getCustomButtonIcon());
150
    }
150
    }
151
    
151
    
152
    public void setOpaque(boolean b) {
153
        if (getInplaceEditor() != null) {
154
            getInplaceEditor().getComponent().setOpaque(true);
155
        }
156
    }
157
    
152
    public void setFont(Font f) {
158
    public void setFont(Font f) {
153
        if (comp != null) {
159
        if (comp != null) {
154
            comp.setFont(f);
160
            comp.setFont(f);
(-)openide/src/org/openide/explorer/propertysheet/ComboInplaceEditor.java (-3 / +4 lines)
Lines 60-66 Link Here
60
     * less borders & such */                        
60
     * less borders & such */                        
61
    public ComboInplaceEditor(boolean tableUI) {
61
    public ComboInplaceEditor(boolean tableUI) {
62
        if (tableUI) {
62
        if (tableUI) {
63
            putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
63
            putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE); //NOI18N
64
        }
64
        }
65
        if (Boolean.getBoolean("netbeans.ps.combohack")) { //NOI18N
65
        if (Boolean.getBoolean("netbeans.ps.combohack")) { //NOI18N
66
            setLightWeightPopupEnabled(false);
66
            setLightWeightPopupEnabled(false);
Lines 72-78 Link Here
72
        if (tableUI) {
72
        if (tableUI) {
73
            updateUI();
73
            updateUI();
74
        }
74
        }
75
    }
75
    }   
76
    
76
    
77
    /** Overridden to add a listener to the editor if necessary, since the
77
    /** Overridden to add a listener to the editor if necessary, since the
78
     * UI won't do that for us without a focus listener */
78
     * UI won't do that for us without a focus listener */
Lines 233-241 Link Here
233
    /** Overridden to use CleanComboUI on Metal L&F to avoid extra borders */
233
    /** Overridden to use CleanComboUI on Metal L&F to avoid extra borders */
234
    public void updateUI() {
234
    public void updateUI() {
235
        LookAndFeel lf = UIManager.getLookAndFeel();
235
        LookAndFeel lf = UIManager.getLookAndFeel();
236
        String id = lf.getID();
236
        boolean useClean = tableUI &&
237
        boolean useClean = tableUI &&
237
            (lf instanceof MetalLookAndFeel || 
238
            (lf instanceof MetalLookAndFeel || 
238
            "GTK".equals(lf.getID())); //NOI18N
239
            "GTK".equals(id) || "Kunststoff".equals(id)); //NOI18N
239
        
240
        
240
        if (useClean) {
241
        if (useClean) {
241
            super.setUI (PropUtils.createComboUI(this, tableUI));
242
            super.setUI (PropUtils.createComboUI(this, tableUI));
(-)openide/src/org/openide/explorer/propertysheet/DescriptionPanel.java (-4 / +1 lines)
Lines 98-113 Link Here
98
        }
98
        }
99
        
99
        
100
        descLabel.setFocusable(false);
100
        descLabel.setFocusable(false);
101
        Color bg = UIManager.getColor("window");
102
        if (bg != null) {
103
            descLabel.setBackground(bg); //NOI18N
104
        }
105
        descLabel.setEditable(false);
101
        descLabel.setEditable(false);
106
        descLabel.setFont (titleLabel.getFont());
102
        descLabel.setFont (titleLabel.getFont());
107
        descLabel.setForeground(titleLabel.getForeground());
103
        descLabel.setForeground(titleLabel.getForeground());
108
        descLabel.setWrapStyleWord(true);
104
        descLabel.setWrapStyleWord(true);
109
        descLabel.setLineWrap(true);
105
        descLabel.setLineWrap(true);
110
        descLabel.setRows (2);
106
        descLabel.setRows (2);
107
        descLabel.setOpaque(false);
111
108
112
        titleLabel.setFont (getFont().deriveFont (Font.BOLD));
109
        titleLabel.setFont (getFont().deriveFont (Font.BOLD));
113
        
110
        
(-)openide/src/org/openide/explorer/propertysheet/IconPanel.java (+6 lines)
Lines 174-179 Link Here
174
        }
174
        }
175
    }
175
    }
176
    
176
    
177
    public void setOpaque (boolean val) {
178
        if (getInplaceEditor() != null) {
179
            getInplaceEditor().getComponent().setOpaque(true);
180
        }
181
    }
182
    
177
    /** Proxies the embedded inplace editor */
183
    /** Proxies the embedded inplace editor */
178
    public javax.swing.JComponent getComponent() {
184
    public javax.swing.JComponent getComponent() {
179
        return this;
185
        return this;
(-)openide/src/org/openide/explorer/propertysheet/PropUtils.java (-18 / +5 lines)
Lines 25-31 Link Here
25
import java.io.StringWriter;
25
import java.io.StringWriter;
26
import java.lang.reflect.*;
26
import java.lang.reflect.*;
27
import java.util.*;
27
import java.util.*;
28
import javax.accessibility.*;
29
import javax.swing.*;
28
import javax.swing.*;
30
import javax.swing.plaf.metal.*;
29
import javax.swing.plaf.metal.*;
31
import java.text.MessageFormat;
30
import java.text.MessageFormat;
Lines 323-349 Link Here
323
       return true;
322
       return true;
324
    }
323
    }
325
    
324
    
325
    private static Graphics scratchGraphics = null;
326
326
    /** Get a scratch graphics object which can be used to calculate string
327
    /** Get a scratch graphics object which can be used to calculate string
327
     * widths offscreen */
328
     * widths offscreen */
328
    static Graphics getScratchGraphics(Component c) {
329
    static Graphics getScratchGraphics(Component c) {
329
        //OS-X 1.4.1 calling getGraphics() can cause cyclic repaints
330
        if (scratchGraphics == null) {
330
        //if called while painting.  Safer to use an offscreen, cached
331
            scratchGraphics = new BufferedImage (1, 1, BufferedImage.TYPE_INT_RGB).getGraphics();
331
        //resource, probably everywhere.
332
        Graphics result = null; //c.getGraphics();
333
        if (result == null) {
334
            //xxx this grabs the AWT tree lock
335
            /*
336
            result = 
337
                GraphicsEnvironment.getLocalGraphicsEnvironment 
338
                ().getDefaultScreenDevice().getDefaultConfiguration
339
                ().createCompatibleImage(1,1).getGraphics();
340
             */
341
            if (scratch == null) {
342
                scratch = new BufferedImage(1,1,BufferedImage.TYPE_INT_ARGB);
343
            }
344
            result = scratch.getGraphics();
345
        }
332
        }
346
        return result;
333
        return scratchGraphics;
347
    }    
334
    }    
348
335
349
    
336
    
(-)openide/src/org/openide/explorer/propertysheet/RendererFactory.java (-167 / +52 lines)
Lines 21-30 Link Here
21
import java.awt.Color;
21
import java.awt.Color;
22
import java.awt.Component;
22
import java.awt.Component;
23
import java.awt.Dimension;
23
import java.awt.Dimension;
24
import java.awt.FontMetrics;
25
import java.awt.Graphics;
24
import java.awt.Graphics;
26
import java.awt.Image;
25
import java.awt.Image;
27
import java.awt.Insets;
28
import java.awt.Rectangle;
26
import java.awt.Rectangle;
29
import java.awt.event.ActionEvent;
27
import java.awt.event.ActionEvent;
30
import java.awt.event.ActionListener;
28
import java.awt.event.ActionListener;
Lines 47-54 Link Here
47
import javax.swing.border.BevelBorder;
45
import javax.swing.border.BevelBorder;
48
import javax.swing.border.Border;
46
import javax.swing.border.Border;
49
import javax.swing.event.ChangeListener;
47
import javax.swing.event.ChangeListener;
50
import javax.swing.table.DefaultTableCellRenderer;
51
import org.openide.ErrorManager;
48
import org.openide.ErrorManager;
49
import org.openide.awt.HtmlRenderer;
52
import org.openide.nodes.Node.Property;
50
import org.openide.nodes.Node.Property;
53
import org.openide.util.Utilities;
51
import org.openide.util.Utilities;
54
52
Lines 236-246 Link Here
236
        return lbl;
234
        return lbl;
237
    }
235
    }
238
    
236
    
239
    public JLabel getStringRenderer() {
237
    public JComponent getStringRenderer() {
240
        StringRenderer result = stringRenderer();
238
        StringRenderer result = stringRenderer();
241
        result.clear();
239
        result.clear();
242
        result.setEnabled(true);
240
        result.setEnabled(true);
243
        return (JLabel) result;
241
        return result;
244
    }
242
    }
245
    
243
    
246
    private JComponent prepareRadioButtons(PropertyEditor editor, PropertyEnv env) {
244
    private JComponent prepareRadioButtons(PropertyEditor editor, PropertyEnv env) {
Lines 426-439 Link Here
426
        
424
        
427
        /** Overridden only fire those properties needed */
425
        /** Overridden only fire those properties needed */
428
        protected void firePropertyChange(String name, Object old, Object nue) {
426
        protected void firePropertyChange(String name, Object old, Object nue) {
429
            //gtk L&F needs these, although bg and fg don't work on it in 1.4.2
427
            //firing all changes for now - breaks text painting on OS-X
430
            /*
431
            if ("foreground".equals(name) || 
432
            "background".equals(name) || "font".equals(name) || 
433
            "editable".equals(name) || "enabled".equals(name)) { //NOI18N
434
                super.firePropertyChange(name, old, nue);
435
            }
436
             */
437
            super.firePropertyChange(name,old,nue);
428
            super.firePropertyChange(name,old,nue);
438
        }
429
        }
439
         
430
         
Lines 495-501 Link Here
495
    
486
    
496
    /** A renderer for string properties, which can also delegate to the 
487
    /** A renderer for string properties, which can also delegate to the 
497
     *   property editor's <code>paint()</code>method if possible. */
488
     *   property editor's <code>paint()</code>method if possible. */
498
    private static final class StringRenderer extends DefaultTableCellRenderer implements InplaceEditor {
489
    private static final class StringRenderer extends JLabel implements InplaceEditor {
499
        private PropertyEditor editor=null;
490
        private PropertyEditor editor=null;
500
        private PropertyEnv env=null;
491
        private PropertyEnv env=null;
501
        private boolean tableUI=false;
492
        private boolean tableUI=false;
Lines 517-545 Link Here
517
            return enabled;
508
            return enabled;
518
        }
509
        }
519
        
510
        
520
        public Dimension getPreferredSize() {
511
        /** Overridden to do nothing */
521
            Graphics g = PropUtils.getScratchGraphics(this);
512
        protected void firePropertyChange(String name, Object old, Object nue) {
522
            FontMetrics fm = g.getFontMetrics(getFont());
513
            //do nothing
523
            int w;
524
            if (getText() != null) {
525
                //avoid NPE in sun.awt.font.FontDesignMetrics.stringWidth():281
526
                w = fm.stringWidth(getText()) + 4;
527
            } else {
528
                w = PropUtils.getMinimumPropPanelWidth();
529
            }
514
            }
530
            int h = fm.getHeight() + 2;
515
531
            w = Math.max(w,PropUtils.getMinimumPropPanelWidth());
516
        public void validate() {
532
            h = Math.max(h,PropUtils.getMinimumPropPanelHeight());
517
            //do nothing
533
            Dimension result = new Dimension(w,h);
534
            if (getIcon() != null) {
535
                result.height = Math.max (result.height, getIcon().getIconHeight());
536
                result.width += getIcon().getIconHeight();
537
            }
538
            if (getBorder() != null) {
539
                Insets i = getBorder().getBorderInsets(this);
540
                result.width += i.right+i.left;
541
                result.height += i.top+i.bottom;
542
            }
518
            }
519
520
        public void invalidate() {
521
            //do nothing
522
        }
523
524
        public void revalidate() {
525
            //do nothing
526
        }
527
528
        public void repaint() {
529
            //do nothing
530
            }
531
532
        public void repaint (long tm, int x, int y, int w, int h) {
533
            //do nothing
534
        }
535
536
        public Dimension getPreferredSize() {
537
            Dimension result = super.getPreferredSize();
538
            result.width = Math.max(result.width, 
539
                PropUtils.getMinimumPropPanelWidth());
540
            
541
            result.height = Math.max(result.height, 
542
                PropUtils.getMinimumPropPanelHeight());
543
            
543
            return result;
544
            return result;
544
        }
545
        }
545
        
546
        
Lines 553-559 Link Here
553
            if (editor != null && editor.isPaintable()) {
554
            if (editor != null && editor.isPaintable()) {
554
                delegatedPaint (g);
555
                delegatedPaint (g);
555
            } else {
556
            } else {
556
                super.paint (g);
557
                JLabel lbl = (JLabel) HtmlRenderer.sharedLabel(HtmlRenderer.STYLE_TRUNCATE, null);
558
                lbl.setEnabled (isEnabled());
559
                lbl.setText (getText());
560
                lbl.setIcon (getIcon());
561
                lbl.setIconTextGap (getIconTextGap());
562
                lbl.setBounds (getBounds());
563
                lbl.setOpaque (true);
564
                lbl.setBackground (getBackground());
565
                lbl.setForeground (getForeground());
566
                lbl.paint (g);
557
            }
567
            }
558
            clear();
568
            clear();
559
        }
569
        }
Lines 572-577 Link Here
572
                    b.paintBorder(this, g, 0,  0, getWidth(), getHeight());
582
                    b.paintBorder(this, g, 0,  0, getWidth(), getHeight());
573
                }
583
                }
574
                Rectangle r = getBounds();
584
                Rectangle r = getBounds();
585
                //XXX May be the source of Rochelle's multiple rows of error 
586
                //marking misalignment problem...(I do not jest)
575
                r.x = getWidth() > 16 ? editor instanceof Boolean3WayEditor ? 0 : 3 : 0; //align text with other renderers
587
                r.x = getWidth() > 16 ? editor instanceof Boolean3WayEditor ? 0 : 3 : 0; //align text with other renderers
576
                r.width -= getWidth() > 16 ? editor instanceof Boolean3WayEditor ? 0 : 3 : 0; //align text with other renderers
588
                r.width -= getWidth() > 16 ? editor instanceof Boolean3WayEditor ? 0 : 3 : 0; //align text with other renderers
577
                r.y = 0;
589
                r.y = 0;
Lines 584-602 Link Here
584
        public void clear() {
596
        public void clear() {
585
            editor = null;
597
            editor = null;
586
            env = null;
598
            env = null;
587
            setText(""); //NOI18N
588
            setIcon(null);
599
            setIcon(null);
589
            setOpaque(true);
600
            setOpaque(true);
590
        }
601
        }
591
        
602
        
603
        private Object value = null;
592
        public void setValue(Object o) {
604
        public void setValue(Object o) {
593
            super.setValue(o);
605
            value = o;
594
        }
606
            setText (value instanceof String ? (String) value : value != null ? value.toString() : null);
595
        
596
        public void setText(String s) {
597
            //XXX hotfix for the form editor until the HTML renderer can
598
            //be put into trunk - per Trung's request
599
            super.setText(stripHTML(s));
600
        }
607
        }
601
608
602
        public void connect(PropertyEditor p, PropertyEnv env) {
609
        public void connect(PropertyEditor p, PropertyEnv env) {
Lines 664-670 Link Here
664
        }
671
        }
665
        
672
        
666
        public boolean supportsTextEntry() {
673
        public boolean supportsTextEntry() {
667
            return true;
674
            return false;
668
        }
675
        }
669
        
676
        
670
        /** Overridden to do nothing */
677
        /** Overridden to do nothing */
Lines 675-712 Link Here
675
        protected void fireStateChanged() {
682
        protected void fireStateChanged() {
676
        }
683
        }
677
        
684
        
678
        /** Overridden to do nothing */
679
        protected void firePropertyChange(String name, Object old, Object nue) {
680
        }
681
        
682
        /** Overridden to do nothing */
683
        public void firePropertyChange(String name, boolean old, boolean nue) {
684
        }
685
        
686
        /** Overridden to do nothing */
687
        public void firePropertyChange(String name, int old, int nue) {
688
        }
689
        
690
        /** Overridden to do nothing */
691
        public void firePropertyChange(String name, byte old, byte nue) {
692
        }
693
        
694
        /** Overridden to do nothing */
695
        public void firePropertyChange(String name, char old, char nue) {
696
        }
697
        
698
        /** Overridden to do nothing */
699
        public void firePropertyChange(String name, double old, double nue) {
700
        }
701
        
702
        /** Overridden to do nothing */
703
        public void firePropertyChange(String name, float old, float nue) {
704
        }
705
        
706
        /** Overridden to do nothing */
707
        public void firePropertyChange(String name, short old, short nue) {
708
        }
709
        
710
        public void addActionListener(ActionListener al) {
685
        public void addActionListener(ActionListener al) {
711
            //do nothing
686
            //do nothing
712
        }
687
        }
Lines 739-772 Link Here
739
                super.firePropertyChange(name, old, nue);
714
                super.firePropertyChange(name, old, nue);
740
            }
715
            }
741
        }
716
        }
742
        
743
        /** Overridden to do nothing */
744
        public void firePropertyChange(String name, boolean old, boolean nue) {
745
        }
746
        
747
        /** Overridden to do nothing */
748
        public void firePropertyChange(String name, int old, int nue) {
749
        }
750
        
751
        /** Overridden to do nothing */
752
        public void firePropertyChange(String name, byte old, byte nue) {
753
        }
754
        
755
        /** Overridden to do nothing */
756
        public void firePropertyChange(String name, char old, char nue) {
757
        }
758
        
759
        /** Overridden to do nothing */
760
        public void firePropertyChange(String name, double old, double nue) {
761
        }
762
        
763
        /** Overridden to do nothing */
764
        public void firePropertyChange(String name, float old, float nue) {
765
        }
766
        
767
        /** Overridden to do nothing */
768
        public void firePropertyChange(String name, short old, short nue) {
769
        }
770
    }
717
    }
771
    
718
    
772
    private static final class RadioRenderer extends RadioInplaceEditor {
719
    private static final class RadioRenderer extends RadioInplaceEditor {
Lines 1051-1118 Link Here
1051
        
998
        
1052
        public boolean supportsTextEntry() {
999
        public boolean supportsTextEntry() {
1053
            return false;
1000
            return false;
1054
        }
1055
        
1056
    }
1057
    
1058
    public static boolean requiresSwingPainting (Component c) {
1059
        if (!(c instanceof InplaceEditor)) {
1060
            return true;
1061
        }
1062
        InplaceEditor ine = (InplaceEditor) c;
1063
        if (ine instanceof ButtonPanel) {
1064
            //ine = ((ButtonPanel) ine).getInplaceEditor();
1065
            return ((ButtonPanel) ine).getInplaceEditor() instanceof JComboBox;
1066
        }
1067
        if (ine instanceof IconPanel) {
1068
            ine = ((IconPanel) ine).getInplaceEditor();
1069
        }
1070
        
1071
        if (ine.getComponent() instanceof JComboBox) {
1072
            return true;
1073
        }
1074
        
1075
        if (ine instanceof CheckboxInplaceEditor || ine instanceof RadioInplaceEditor ||
1076
            ine instanceof StringRenderer || ine instanceof ExceptionRenderer ||
1077
            ine instanceof Boolean3WayEditor.Boolean3Inplace || ine instanceof StringInplaceEditor) {
1078
                return false;
1079
        }
1080
        return true;
1081
    }
1082
    
1083
    private static boolean isHTML(String s) {
1084
        if (s == null) return false;
1085
        boolean result = s.startsWith("<html>") || s.startsWith("<HTML>"); //NOI18N
1086
        return result;
1087
    }
1088
    
1089
    private static String stripHTML(String s) {
1090
        if (s == null) {
1091
            return s;
1092
        }
1093
        if (isHTML(s)) {
1094
            StringBuffer result = new StringBuffer(s.length());
1095
            char[] c = s.toCharArray();
1096
            boolean inTag = false;
1097
            for (int i=0; i < c.length; i++) {
1098
                //XXX need to handle entity includes
1099
                boolean wasInTag = inTag;
1100
                if (!inTag) {
1101
                    if (c[i] == '<') {
1102
                        inTag = true;
1103
                    }
1104
                } else {
1105
                    if (c[i] == '>') {
1106
                        inTag = false;
1107
                    }
1108
                }
1109
                if (!inTag && wasInTag == inTag) {
1110
                    result.append(c[i]);
1111
                }
1112
            }
1113
            return result.toString();
1114
        } else {
1115
            return s;
1116
        }
1001
        }
1117
    }    
1002
    }    
1118
}
1003
}
(-)openide/src/org/openide/explorer/propertysheet/SheetCellRenderer.java (-111 / +31 lines)
Lines 17-35 Link Here
17
17
18
package org.openide.explorer.propertysheet;
18
package org.openide.explorer.propertysheet;
19
import java.awt.*;
19
import java.awt.*;
20
import java.awt.event.ActionEvent;
21
import java.beans.FeatureDescriptor;
20
import java.beans.FeatureDescriptor;
22
import javax.swing.*;
21
import javax.swing.*;
23
import javax.swing.table.TableCellRenderer;
22
import javax.swing.table.TableCellRenderer;
24
import javax.swing.table.DefaultTableCellRenderer;
23
import org.openide.awt.HtmlRenderer;
25
import org.openide.nodes.Node.*;
24
import org.openide.nodes.Node.*;
25
26
/** An implementation of SheetCellRenderer that wraps custom InplaceEditors
26
/** An implementation of SheetCellRenderer that wraps custom InplaceEditors
27
 * to efficiently render properties.
27
 * to efficiently render properties.
28
 *
28
 *
29
 * @author  Tim Boudreau
29
 * @author  Tim Boudreau
30
 */
30
 */
31
final class SheetCellRenderer implements TableCellRenderer {
31
final class SheetCellRenderer implements TableCellRenderer {
32
    static SetRenderer setRenderer = null;
33
    private RendererFactory factory=null;
32
    private RendererFactory factory=null;
34
    private boolean tableUI;
33
    private boolean tableUI;
35
    boolean includeMargin=false;
34
    boolean includeMargin=false;
Lines 52-65 Link Here
52
        rbMax = i;
51
        rbMax = i;
53
    }
52
    }
54
    
53
    
55
    private SetRenderer getSetRenderer() {
56
        if (setRenderer == null) {
57
            setRenderer = new SetRenderer();
58
        }
59
        setRenderer.dontPaint = true;
60
        return setRenderer;
61
    }
62
63
    public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
54
    public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
64
        FeatureDescriptor fd = (FeatureDescriptor) value;
55
        FeatureDescriptor fd = (FeatureDescriptor) value;
65
        Component result;
56
        Component result;
Lines 68-88 Link Here
68
        selected |= hasFocus && table.getSelectedRow() == row;
59
        selected |= hasFocus && table.getSelectedRow() == row;
69
        
60
        
70
        if (fd instanceof PropertySet) {
61
        if (fd instanceof PropertySet) {
71
            SetRenderer sr = getSetRenderer();
62
            return null;
72
            sr.setText(fd.getDisplayName());
73
            sr.setExpanded(((SheetTable) table).getPropertySetModel().isExpanded(fd));
74
            result = sr;
75
        } else {
63
        } else {
76
            if (column == 0) {
64
            if (column == 0) {
77
                JLabel lbl = factory().getStringRenderer();
65
                String txt = ((Property) fd).getHtmlDisplayName();
78
                lbl.setText(fd.getDisplayName());
66
                boolean isHtml = txt != null;
67
                if (!isHtml) {
68
                    txt = fd.getDisplayName();
69
                }
70
                JLabel lbl = HtmlRenderer.sharedLabel(HtmlRenderer.STYLE_TRUNCATE, isHtml ? Boolean.TRUE :
71
                        Boolean.FALSE);
72
                HtmlRenderer.Renderer ren = (HtmlRenderer.Renderer) lbl;
73
74
                lbl.setText (txt);
75
                lbl.setOpaque (selected);
76
                if (selected) {
77
                    lbl.setBackground (table.getSelectionBackground());
78
                    lbl.setForeground (table.getSelectionForeground());
79
                    lbl.setOpaque (true);
80
                }
81
79
                if (includeMargin) {
82
                if (includeMargin) {
80
                    lbl.setBorder(BorderFactory.createEmptyBorder(
83
                    ren.setIndent (PropUtils.getMarginWidth() + 2);
81
                        0, PropUtils.getMarginWidth() + 2, 0, 1));
82
                } else {
84
                } else {
83
                    //Use a 2 pixel margin so it's not flush
85
                    ren.setIndent (PropUtils.getTextMargin());
84
                    lbl.setBorder(BorderFactory.createEmptyBorder(0, 
85
                        PropUtils.getTextMargin(), 0, 0));
86
                }
86
                }
87
                
87
                
88
                //Support for name marking with icon requested by form editor
88
                //Support for name marking with icon requested by form editor
Lines 91-111 Link Here
91
                    lbl.setIcon((Icon) o);
91
                    lbl.setIcon((Icon) o);
92
                } else if (o instanceof Image) {
92
                } else if (o instanceof Image) {
93
                    lbl.setIcon(new ImageIcon((Image) o));
93
                    lbl.setIcon(new ImageIcon((Image) o));
94
                } else {
95
                    lbl.setIcon(null);
96
                }
94
                }
97
                
95
                
98
                result = lbl;
96
                result = lbl;
99
            } else {
97
            } else {
100
                result = factory().getRenderer((Property) fd);
98
                result = factory().getRenderer((Property) fd);
101
                //Use a 2 pixel margin so it's not flush
99
                if (selected) {
102
                /*
100
                    result.setBackground (table.getSelectionBackground());
103
                ((JComponent)result).setBorder(BorderFactory.createEmptyBorder(0, 
101
                    result.setForeground (table.getSelectionForeground());
104
                    PropUtils.getTextMargin(), 0, 0));
102
                    ((JComponent)result).setOpaque(true);
105
                 */
103
                } else {
104
                    result.setBackground (table.getBackground());
105
                    result.setForeground (table.getForeground());
106
                    ((JComponent)result).setOpaque(false);
107
                }
106
            }
108
            }
107
        }
109
        }
108
//        result.setFont(table.getFont());
109
        return result;
110
        return result;
110
    }
111
    }
111
    
112
    
Lines 114-199 Link Here
114
            factory = new RendererFactory(true);
115
            factory = new RendererFactory(true);
115
        }
116
        }
116
        return factory;
117
        return factory;
117
    }
118
    
119
    /** A renderer for property sets, which should be rendered double-width.
120
     *  This renderer intentionally does nothing when called in the normal
121
     *  paint loop;  if the boolean field <code>dontPaint</code> is set
122
     *  to true (calling getTableCellRendererComponent will set this to true)
123
     *  its paint method is a no-op.  The table will retrieve this component
124
     *  and paint it across two columns after the rest of the paint cycle
125
     *  is completed.
126
     */
127
    static class SetRenderer extends DefaultTableCellRenderer {
128
        
129
        public boolean dontPaint = true;
130
        int textY = -1;
131
        int iconY = -1;
132
133
        /** Discard/recalc UI dependent values */
134
        public void updateUI() {
135
            super.updateUI();
136
            iconY=-1;
137
            textY=-1;
138
        }
139
        
140
        private boolean expanded;
141
        public void setExpanded (boolean val) {
142
            expanded = val;
143
        }
144
        
145
        public Icon getIcon() {
146
            return expanded ? PropUtils.getExpandedIcon() : 
147
                PropUtils.getCollapsedIcon();
148
        }
149
        
150
        /** Calculate y position to center text and icon vertically    */
151
        private void calcYPos(FontMetrics fm) {
152
            int h = getHeight();
153
            int ih = getIcon().getIconHeight();
154
            int fh = fm.getHeight();
155
            if (fh >= h) {
156
                textY = 0 + fm.getAscent();
157
            } else {
158
                textY = ((h - fh) / 2) + fm.getAscent();
159
            }
160
            
161
            if (ih >= h) {
162
                iconY = 0;
163
            } else {
164
                iconY = (h - ih) / 2;
165
            }
166
        }
167
        
168
        /** Paint the component, if the <code>dontPaint</code> field is
169
         *  false.  In the standard rendering pass of JTable, this will
170
         *  be true;  the table will iterate the available sets and paint
171
         *  them across the entire width of the table after the rest has
172
         *  been painted.
173
         */
174
        public void paint(Graphics g) {
175
            if (!dontPaint) {
176
                if (iconY == -1) calcYPos(g.getFontMetrics(getFont()));
177
                g.setFont(getFont());
178
                g.setColor(getBackground());
179
                g.fillRect(0,0,getWidth(),getHeight());
180
                Icon ic = getIcon();
181
                ic.paintIcon(this, g, PropUtils.getIconMargin(), iconY);
182
                g.setColor(getForeground());
183
                g.drawString(getText(), PropUtils.getIconMargin()+ PropUtils.getMarginWidth() + 2, //XXX text icon gap
184
                textY);
185
            }
186
        }
187
        /** Overridden to do nothing */
188
        protected void fireActionPerformed(ActionEvent ae) {
189
        }
190
        
191
        /** Overridden to do nothing */
192
        protected void fireStateChanged() {
193
        }
194
        
195
        /** Overridden to do nothing */
196
        protected void firePropertyChange(String name, Object old, Object nue) {
197
        }
198
    }
118
    }
199
}
119
}
(-)openide/src/org/openide/explorer/propertysheet/SheetTable.java (-37 / +61 lines)
Lines 20-25 Link Here
20
import javax.swing.table.*;
20
import javax.swing.table.*;
21
import javax.swing.event.*;
21
import javax.swing.event.*;
22
import org.openide.ErrorManager;
22
import org.openide.ErrorManager;
23
import org.openide.awt.HtmlRenderer;
23
import org.openide.util.NbBundle;
24
import org.openide.util.NbBundle;
24
import org.openide.nodes.Node.Property;
25
import org.openide.nodes.Node.Property;
25
import org.openide.nodes.Node.PropertySet;
26
import org.openide.nodes.Node.PropertySet;
Lines 369-375 Link Here
369
            return;
370
            return;
370
        }
371
        }
371
        
372
        
372
        int max = min + getVisibleRowCount();  //psm.getCount();
373
        int max = min + getVisibleRowCount();
373
        
374
        
374
        for (int i=min; i < max; i++) {
375
        for (int i=min; i < max; i++) {
375
            FeatureDescriptor fd = psm.getFeatureDescriptor(i);
376
            FeatureDescriptor fd = psm.getFeatureDescriptor(i);
Lines 405-415 Link Here
405
    }
406
    }
406
    
407
    
407
    public Component prepareRenderer(TableCellRenderer renderer, int row, int col) {
408
    public Component prepareRenderer(TableCellRenderer renderer, int row, int col) {
409
        Component result = super.prepareRenderer(renderer, row, col);
408
        if (row < 0 || row >= getRowCount()) {
410
        if (row < 0 || row >= getRowCount()) {
409
            return super.prepareRenderer(renderer, row, col);
411
            return result;
410
        }
412
        }
411
        Object value = getValueAt(row, col);
413
        Object value = getValueAt(row, col);
412
        Component result = super.prepareRenderer(renderer, row, col);
414
        /*
413
        if (value instanceof PropertySet) {
415
        if (value instanceof PropertySet) {
414
            boolean selected = row == getSelectedRow();
416
            boolean selected = row == getSelectedRow();
415
            result.setBackground (selected ? 
417
            result.setBackground (selected ? 
Lines 426-474 Link Here
426
                result.setEnabled(writable);
428
                result.setEnabled(writable);
427
            }
429
            }
428
        }
430
        }
431
        */
432
        if (result != null && value instanceof Property && col == 1) {
433
            result.setEnabled (((Property) value).canWrite());
434
        }
429
        return result;
435
        return result;
430
    }    
436
    }
431
    
437
438
    private HtmlRenderer.Renderer htmlrenderer = null;
439
432
    /** Paint the expandable sets.  These are painted double width,
440
    /** Paint the expandable sets.  These are painted double width,
433
     *  across the entire width of the table. */
441
     *  across the entire width of the table. */
434
    private void paintExpandableSets (Graphics g) {
442
    private void paintExpandableSets (Graphics g) {
435
        //special paint handling for double-wide expando sets
443
        int start = getFirstVisibleRow();
436
        //XXX build an array of appropriate component indices in getCellRenderer and don't iterate
444
        int end = getVisibleRowCount();
437
            //the entire set of rows!
445
438
        int max = this.getRowCount();
446
        Insets ins = getInsets();
439
        for (int i = 0; i < max; i++) {
447
440
            //find the items that are property sets
448
        boolean canBeSelected = isKnownComponent(
441
            if (!(getSheetModel().getPropertySetModel().isProperty(i))) {
442
                //get the rectangle and double its width
443
                Rectangle r = getCellRect (i, 0, false);
444
                if (r.y > getHeight()) {
445
                    //Don't paint unnecessarily
446
                    return;
447
                }
448
                r.width = getWidth();
449
                if (g.hitClip (r.x, r.y, r.width, r.height)) {
450
                    //get the appropriate renderer
451
                    TableCellRenderer tcr = getCellRenderer (i, 0);
452
                    boolean selected = getSelectedRow()==i && isKnownComponent(
453
                        KeyboardFocusManager.getCurrentKeyboardFocusManager().
449
                        KeyboardFocusManager.getCurrentKeyboardFocusManager().
454
                        getPermanentFocusOwner());
450
                        getPermanentFocusOwner());
455
                    Component c = tcr.getTableCellRendererComponent(this, 
456
                        this.getValueAt (i,0), selected, false, i, 0);
457
                    
451
                    
458
                    //A minor optimization - when the standard table painting
459
                    //happens, the set renderer will simply do nothing.  This
460
                    //field controls whether its paint method is a no-op.
461
                    ((SheetCellRenderer.SetRenderer) c).dontPaint = false;
462
                    
452
                    
463
                    c.setBackground (selected ? 
453
        for (int i=0; i < end; i++) {
464
                        PropUtils.getSelectedSetRendererColor() :
454
            int idx = start + i;
465
                        PropUtils.getSetRendererColor());
455
            Object value = getValueAt (idx, 0);
466
456
467
                    c.setForeground (selected ?
457
            if (value instanceof PropertySet) {
468
                        PropUtils.getSelectedSetForegroundColor() :
469
                        PropUtils.getSetForegroundColor());
470
                        
458
                        
471
                    paintComponent(g, c, r.x, r.y, r.width, r.height);
459
                Rectangle r = getCellRect (idx, 0, false);
460
                r.x = ins.left;
461
                r.width = getWidth() - (ins.left + ins.right);
462
                if (g.hitClip (r.x, r.y, r.width, r.height)) {
463
                    PropertySet ps = (PropertySet) value;
464
465
                    String txt = ps.getHtmlDisplayName();
466
                    boolean isHtml = txt != null;
467
                    if (!isHtml) {
468
                        txt = ps.getDisplayName();
469
                    }
470
                    boolean selected = canBeSelected && getSelectedRow() == idx;
471
                    if (htmlrenderer == null) {
472
                        htmlrenderer = HtmlRenderer.createRenderer();
473
                    }
474
475
                    JComponent painter = (JComponent) htmlrenderer.getTableCellRendererComponent(this, txt, selected, selected, idx, 0);
476
477
                    htmlrenderer.setHtml (isHtml);
478
479
                    htmlrenderer.setIconTextGap (2);
480
481
                    htmlrenderer.setIcon(
482
                        getPropertySetModel().isExpanded(ps) ?
483
                            PropUtils.getExpandedIcon() : PropUtils.getCollapsedIcon()
484
                    );
485
486
                    if (!selected) {
487
                        painter.setBackground (PropUtils.getSetRendererColor());
488
                        painter.setForeground (PropUtils.getSetForegroundColor());
489
                    } else {
490
                        painter.setBackground (PropUtils.getSelectedSetRendererColor());
491
                        painter.setForeground (PropUtils.getSelectedSetForegroundColor());
492
                    }
493
                    painter.setOpaque (true);
494
495
                    paintComponent (g, painter, r.x, r.y, r.width, r.height);
472
                }
496
                }
473
            }
497
            }
474
        }
498
        }
(-)openide/src/org/openide/explorer/view/ChoiceView.java (-1 / +1 lines)
Lines 51-57 Link Here
51
51
52
    /** Initialize view. */
52
    /** Initialize view. */
53
    private void initializeChoice () {
53
    private void initializeChoice () {
54
        setRenderer (new NodeRenderer ());
54
        setRenderer (NodeRenderer.sharedInstance());
55
55
56
        setModel (model = createModel ());
56
        setModel (model = createModel ());
57
57
(-)openide/src/org/openide/explorer/view/IconView.java (-1 / +5 lines)
Lines 27-32 Link Here
27
 - improve cell renderer (two lines of text or hints)
27
 - improve cell renderer (two lines of text or hints)
28
 - better behaviour during scrolling (ListPane)
28
 - better behaviour during scrolling (ListPane)
29
 - external selection bug (BUG ID: 01110034)
29
 - external selection bug (BUG ID: 01110034)
30
 -
31
 - XXX if doing anything with this class other than deleting it, rewrite it to use a JTable - that would be
32
 - much more sensible and scalable.  -Tim
33
 -
30
*/
34
*/
31
35
32
/** A view displaying icons.
36
/** A view displaying icons.
Lines 67-73 Link Here
67
		return null;
71
		return null;
68
	    }
72
	    }
69
	};
73
	};
70
        list.setCellRenderer (new NodeRenderer (true));
74
        list.setCellRenderer (NodeRenderer.sharedInstance());
71
        return list;
75
        return list;
72
    }
76
    }
73
    
77
    
(-)openide/src/org/openide/explorer/view/ListViewDropSupport.java (-13 lines)
Lines 53-61 Link Here
53
    /** The component we are supporting with drop support */
53
    /** The component we are supporting with drop support */
54
    protected JList list;
54
    protected JList list;
55
55
56
    /** For managing visual appearance of JList cells. */
57
    protected NodeRenderer.List cellRenderer;
58
59
    // Operations
56
    // Operations
60
    public ListViewDropSupport (ListView view, JList list) {
57
    public ListViewDropSupport (ListView view, JList list) {
61
        this( view, list, true );
58
        this( view, list, true );
Lines 66-72 Link Here
66
    {
63
    {
67
        this.view = view;
64
        this.view = view;
68
        this.list = list;
65
        this.list = list;
69
        //cellRenderer = (NodeListCellRenderer)list.getCellRenderer();
70
        this.dropTargetPopupAllowed = dropTargetPopupAllowed;
66
        this.dropTargetPopupAllowed = dropTargetPopupAllowed;
71
    }
67
    }
72
68
Lines 265-277 Link Here
265
        }
261
        }
266
        return dropTarget;
262
        return dropTarget;
267
    }
263
    }
268
269
    /** Safe getter for the cell renderer of asociated list */
270
    NodeRenderer.List getCellRenderer () {
271
        if (cellRenderer == null)
272
            cellRenderer = (NodeRenderer.List)list.getCellRenderer();
273
        return cellRenderer;
274
    }
275
276
277
}
264
}
(-)openide/src/org/openide/explorer/view/NodeRenderer.java (-441 / +111 lines)
Lines 20-48 Link Here
20
import java.awt.Image;
20
import java.awt.Image;
21
import java.awt.KeyboardFocusManager;
21
import java.awt.KeyboardFocusManager;
22
22
23
import javax.swing.BorderFactory;
24
import javax.swing.ImageIcon;
25
import javax.swing.JLabel;
26
import javax.swing.JList;
27
import javax.swing.JTree;
28
import javax.swing.ListCellRenderer;
29
import javax.swing.UIManager;
30
import javax.swing.border.Border;
31
import javax.swing.border.LineBorder;
32
import javax.swing.tree.DefaultTreeCellRenderer;
33
import javax.swing.tree.TreeCellRenderer;
23
import javax.swing.tree.TreeCellRenderer;
34
24
35
import java.beans.BeanInfo;
25
import javax.swing.*;
36
import java.lang.ref.Reference;
37
import java.lang.ref.WeakReference;
38
import java.util.WeakHashMap;
39
import javax.swing.JRootPane;
40
import javax.swing.SwingUtilities;
41
import javax.swing.plaf.ColorUIResource;
42
26
43
import org.openide.ErrorManager;
27
import org.openide.awt.HtmlRenderer;
28
import org.openide.awt.ListPane;
44
import org.openide.nodes.Node;
29
import org.openide.nodes.Node;
45
import org.openide.util.Utilities;
46
30
47
31
48
/** Default renderer for nodes. Can paint either Nodes directly or
32
/** Default renderer for nodes. Can paint either Nodes directly or
Lines 50-93 Link Here
50
 *
34
 *
51
 * @see org.openide.nodes.Node
35
 * @see org.openide.nodes.Node
52
 *
36
 *
53
 * @author Jaroslav Tulach
37
 * @author Jaroslav Tulach, Tim Boudreau
54
 */
38
 */
55
public class NodeRenderer extends Object
39
public class NodeRenderer extends Object
56
implements TreeCellRenderer, ListCellRenderer {
40
implements TreeCellRenderer, ListCellRenderer {
41
    
57
    /** Shared instance of <code>NodeRenderer</code>. */
42
    /** Shared instance of <code>NodeRenderer</code>. */
58
    private static NodeRenderer sharedInstance;
43
    //no point in lazy initialization, it's the only thing this class
44
    //is used for
45
    private static NodeRenderer sharedInstance = new NodeRenderer(); 
59
46
60
    /** Flag indicating if to use big icons. */
47
    /** Flag indicating if to use big icons. */
61
    private boolean bigIcons;
48
    private boolean bigIcons = false;
49
50
    private HtmlRenderer.Renderer renderer = HtmlRenderer.createRenderer();
62
51
63
    static Border emptyBorder = BorderFactory.createEmptyBorder (1, 1, 1, 1);
64
    
65
    /** Creates default renderer. */
52
    /** Creates default renderer. */
66
    public NodeRenderer () {
53
    public NodeRenderer () {
67
    }
54
    }
68
55
69
70
    /** Creates renderer.
56
    /** Creates renderer.
71
     * @param bigIcons use big icons if possible
57
     * @param bigIcons use big icons if possible
58
     * @deprecated bigIcons was only used by IconView, and not used by that anymore.  Use <code>sharedInstance()</code>
59
     * to get an instance of NodeRenderer.
72
     */
60
     */
73
    public NodeRenderer (boolean bigIcons) {
61
    public NodeRenderer (boolean bigIcons) {
74
        this.bigIcons = bigIcons;
62
        this.bigIcons = bigIcons;
75
    }
63
    }
76
64
77
65
78
    /** Gets for one singleton <code>sharedInstance</code>. */
66
    /** Get the singleton instance used by all explorer views. */
79
    public static NodeRenderer sharedInstance () {
67
    public static NodeRenderer sharedInstance () {
80
        if (sharedInstance == null) {
81
            sharedInstance = new NodeRenderer ();
82
        }
83
        return sharedInstance;
68
        return sharedInstance;
84
    }
69
    }
85
70
86
    
71
    
87
    //
88
    // Rendering methods
89
    //
90
91
    /** Finds the component that is capable of drawing the cell in a tree.
72
    /** Finds the component that is capable of drawing the cell in a tree.
92
     * @param value value can be either <code>Node</code> 
73
     * @param value value can be either <code>Node</code> 
93
     * or a <code>VisualizerNode</code>.
74
     * or a <code>VisualizerNode</code>.
Lines 98-543 Link Here
98
        boolean sel, boolean expanded,
79
        boolean sel, boolean expanded,
99
        boolean leaf, int row, boolean hasFocus
80
        boolean leaf, int row, boolean hasFocus
100
    ) {
81
    ) {
101
        return getTree().getTreeCellRendererComponent (
82
        VisualizerNode vis = findVisualizerNode (value);
102
                   tree, value, sel, expanded, leaf, row, hasFocus
103
               );
104
    }
105
106
107
    /** This is the only method defined by <code>ListCellRenderer</code>.  We just
108
     * reconfigure the <code>Jlabel</code> each time we're called.
109
     */
110
    public Component getListCellRendererComponent (
111
        JList list,
112
        Object value,            // value to display
113
        int index,               // cell index
114
        boolean isSelected,      // is the cell selected
115
        boolean cellHasFocus     // the list and the cell have the focus
116
    ) {
117
        // accepting either Node or Visualizers
118
        VisualizerNode vis = (value instanceof Node) ?
119
                             VisualizerNode.getVisualizer (null, (Node)value)
120
                             :
121
                             (VisualizerNode)value;
122
83
123
        if (vis == null) {
84
        String text = vis.getHtmlDisplayName();
124
            vis = VisualizerNode.EMPTY;
85
        boolean isHtml = text != null;
86
        if (!isHtml) {
87
            text = vis.getDisplayName();
125
        }
88
        }
126
89
127
        ListCellRenderer r = bigIcons ? (ListCellRenderer)getPane() : getList();
90
        //Get our result value - really it is ren, but this call causes
128
91
        //it to configure itself with the passed values
129
        Component result = r.getListCellRendererComponent (
92
        Component result = renderer.getTreeCellRendererComponent(
130
                   list, vis, index, isSelected, cellHasFocus
93
            tree, text, sel, expanded, leaf, row, hasFocus);
131
               );
132
        result.setFont(list.getFont());
133
        return result;
134
    }
135
136
    // ********************
137
    // Support for dragging
138
    // ********************
139
140
    /** Value of the cell with 'drag under' visual feedback */
141
    private static VisualizerNode draggedOver;
142
143
144
    /** DnD operation enters. Update look and feel to the 'drag under' state.
145
     * @param value the value of cell which should have 'drag under' visual feedback
146
     */
147
    static void dragEnter (Object dragged) {
148
        draggedOver = (VisualizerNode)dragged;
149
    }
150
151
    /** DnD operation exits. Revert to the normal look and feel. */
152
    static void dragExit () {
153
        draggedOver = null;
154
    }
155
156
157
    // ********************
158
    // Cache for ImageIcons
159
    // ********************
160
94
161
    /** default icon to use when none is present */
95
        renderer.setHtml(isHtml);
162
    private static final String DEFAULT_ICON = "org/openide/resources/defaultNode.gif"; // NOI18N
163
96
164
    /** loaded default icon */
97
        //Do our additional configuration - set up the icon and possibly
165
    private static ImageIcon defaultIcon;
98
        //do some hacks to make it look focused for TreeTableView
99
        configureFrom (renderer, tree, expanded, sel, vis);
166
100
167
    /** of icons used (Image, IconImage)*/
101
        return result;
168
    private static final WeakHashMap map = new WeakHashMap ();
169
170
    /** Loades default icon if not loaded. */
171
    static ImageIcon getDefaultIcon () {
172
        if (defaultIcon == null) {
173
            defaultIcon = new ImageIcon(Utilities.loadImage(DEFAULT_ICON));
174
        }
175
176
        return defaultIcon;
177
    }
102
    }
178
103
179
    /** Finds imager for given resource.
104
    /** This is the only method defined by <code>ListCellRenderer</code>.  We just
180
     * @param image image to get
105
     * reconfigure the <code>Jlabel</code> each time we're called.
181
     * @return icon for the image
182
     */
106
     */
183
    static ImageIcon getIcon (Image image) {
107
    public Component getListCellRendererComponent (
184
        Reference ref = (Reference)map.get (image);
108
        JList list, Object value, int index,  boolean sel, 
185
109
        boolean cellHasFocus) {
186
        ImageIcon icon = ref == null ? null : (ImageIcon)ref.get ();
187
        if (icon != null) {
188
            return icon;
189
        }
190
191
        icon = new ImageIcon (image);
192
        map.put (image, new WeakReference (icon));
193
194
        return icon;
195
    }
196
197
    //
198
    // Renderers
199
    //
200
201
202
    private static NodeRenderer.Tree tree = null;
203
204
    private synchronized static NodeRenderer.Tree getTree () {
205
        if (tree == null)
206
            tree = new NodeRenderer.Tree ();
207
        return tree;
208
    }
209
210
    private static NodeRenderer.Pane pane = null;
211
212
    private synchronized static NodeRenderer.Pane getPane() {
213
        if (pane == null)
214
            pane = new NodeRenderer.Pane ();
215
        return pane;
216
    }
217
218
    private static NodeRenderer.List list = null;
219
220
    private synchronized static NodeRenderer.List getList() {
221
        if (list == null)
222
            list = new NodeRenderer.List ();
223
        return list;
224
    }
225
110
111
        VisualizerNode vis = findVisualizerNode(value);
226
112
227
    /** Tree cell renderer. Accepts only <code>VisualizerNode</code> values. */
113
        String text = vis.getHtmlDisplayName();
228
    final static class Tree extends JLabel implements TreeCellRenderer {
114
        boolean isHtml = text != null;
229
        /** generated Serialized Version UID */
115
        if (!isHtml) {
230
        static final long serialVersionUID = -183570483117501696L;
116
            text = vis.getDisplayName();
231
        
117
        }
232
        private boolean treeHasFocus = false;
118
233
        
119
        //Get our result value - really it is ren, but this call causes
234
        private boolean hasFocus = false;
120
        //it to configure itself with the passed values
235
        private boolean selected = false;
121
        Component result = renderer.getListCellRendererComponent(
236
        
122
            list, text, index, sel, cellHasFocus || value == draggedOver);
237
        public Tree() {
123
        renderer.setHtml(isHtml);
238
        }
124
        
239
        
125
        //Do our additional configuration - set up the icon and possibly
240
        public boolean isOpaque() {
126
        //do some hacks to make it look focused for TreeTableView
241
            return false;
127
        int iconWidth = configureFrom (renderer, list, false, sel, vis);
242
        }
128
        
243
        
129
        boolean bigIcons = this.bigIcons || list instanceof ListPane;
244
        public void setBackground (Color c) {
130
        
245
            bg = c;
131
        if (bigIcons) {
246
        }
132
            renderer.setCentered(true);
133
        } else {
134
            //Indent elements in a ListView/ChoiceView relative to their position
135
            //in the node tree.  Only does anything if you've subclassed and
136
            //overridden createModel().  Does anybody do that?
137
            if (list.getModel() instanceof NodeListModel && ((NodeListModel) list.getModel()).getDepth() > 1) {
138
                int indent = iconWidth *
139
                    NodeListModel.findVisualizerDepth (list.getModel (), vis);
247
        
140
        
248
        private Color bg = Color.WHITE;
141
                renderer.setIndent (indent);
249
        
250
        public void paint (java.awt.Graphics g) {
251
            if (!isSynth && selected) {
252
                Color c = g.getColor();
253
                g.setColor (bg);
254
                g.fillRect (0,0,getWidth(), getHeight());
255
            }
256
            super.paint(g);
257
        }
258
        
259
        /** @return Rendered cell component */
260
        public Component getTreeCellRendererComponent(
261
            JTree tree, Object value,
262
            boolean sel, boolean expanded,
263
            boolean leaf, int row, boolean hasFocus
264
        ) {
265
            setEnabled(tree.isEnabled());
266
            // accepts only VisualizerNode
267
            VisualizerNode vis = (VisualizerNode)value;
268
            
269
	    Image iconImg;
270
            if (expanded) {
271
                iconImg = vis.node.getOpenedIcon(BeanInfo.ICON_COLOR_16x16);
272
            } else {
273
                iconImg = vis.node.getIcon(BeanInfo.ICON_COLOR_16x16);
274
	    }
275
            
276
            // bugfix #28515, check if getIcon contract isn't broken
277
            if (iconImg == null) {
278
                String method = expanded ? "getOpenedIcon" : "getIcon"; // NOI18N
279
                ErrorManager.getDefault ().log (ErrorManager.WARNING, "Node \"" + vis.node.getName () + // NOI18N
280
                    "\" [" +vis.node.getClass().getName()+ "] cannot return null from " + method + "(). See Node." + method + " contract."); // NOI18N
281
            } else {
282
                ImageIcon nodeicon = NodeRenderer.getIcon(iconImg);
283
284
                setIconTextGap (4 - nodeicon.getIconWidth()
285
                                    + ( nodeicon.getIconWidth() > 24 ? nodeicon.getIconWidth() : 24 ) );
286
                setIcon(nodeicon);
287
            }
288
289
            setText(vis.getDisplayName ());
290
291
            // provide "drag under" feedback if DnD operation is active // NOI18N
292
            if (vis == draggedOver) {
293
                sel = true;
294
            }
142
            }
295
296
	    this.hasFocus = hasFocus;
297
            selected = sel;
298
            
299
            if (sel) {
300
                //Find out who has focus
301
                Component focusOwner = 
302
                    KeyboardFocusManager.getCurrentKeyboardFocusManager().
303
                    getPermanentFocusOwner();
304
305
                treeHasFocus = focusOwner == tree || 
306
                    tree.isAncestorOf(focusOwner) || focusOwner instanceof TreeTable;
307
                
308
                if (!treeHasFocus) {
309
                    TreeTable tt = (TreeTable)SwingUtilities.getAncestorOfClass(TreeTable.class, focusOwner);
310
                    if (tt != null) {
311
                        treeHasFocus = focusOwner != null &&  
312
                            tt.getDefaultRenderer(TreeTableModelAdapter.class) 
313
                            == tree;
314
                    }
315
                }
316
                setForeground(treeHasFocus || isSynth ? 
317
                    UIManager.getColor("Tree.selectionForeground") :
318
                    getNoFocusSelectionForeground()); //NOI18N
319
                    
320
                setBackground(treeHasFocus || isSynth ? 
321
                    UIManager.getColor("Tree.selectionBackground") :
322
                    getNoFocusSelectionBackground()); 
323
                    
324
            } else {
325
                setForeground(tree.getForeground());
326
                setBackground(tree.getBackground());
327
            }
328
            
329
            return this;
330
        }
331
        
332
	protected void firePropertyChange(String name, Object old, Object nw) {
333
	    // do really nothing!
334
        }
335
        
336
        public void validate() {
337
            
338
        }
339
        
340
        public void doLayout() {
341
            
342
        }
343
        
344
        public void revalidate() {
345
            
346
        }
347
        
348
        public void repaint(long l, int x, int y, int w, int h) {
349
            
350
        }
143
        }
144
        return result;
145
    }
351
        
146
        
352
        public void repaint() {
147
    /** Utility method which performs configuration which is common to all of the renderer 
353
            
148
     * implementations - sets the icon and focus properties on the renderer
354
        }
149
     * from the VisualizerNode.
355
356
    } // End of class Tree.
357
358
    /** Hack for broken backgrounds on synth look and feel */
359
    private static boolean isSynth = UIManager.getLookAndFeel().getClass().getName().indexOf ("com.sun.java.swing.plaf.gtk") != -1;
360
361
    
362
    /** Implements a <code>ListCellRenderer</code> for rendering items 
363
     * of a <code>List</code> containing <code>Node</code>s.
364
     * It displays the node's 16x16 icon and its display name.
365
     *
150
     *
366
     * @author   Ian Formanek
367
     */
151
     */
368
    static final class List extends JLabel implements ListCellRenderer {
152
    private int configureFrom (HtmlRenderer.Renderer ren, Container
369
        /** generated Serialized Version UID */
153
        target, boolean useOpenedIcon, boolean sel, VisualizerNode vis) {
370
        static final long serialVersionUID = -8387317362588264203L;
154
            
371
155
        Icon icon = vis.getIcon(useOpenedIcon, bigIcons);
372
	/** Focused Node border. */
156
            
373
        protected static Border focusBorder = UIManager.getColor ("List.focusCellHighlight") != null ? // NOI18N
157
        if (icon.getIconWidth() > 0) {
374
                            BorderFactory.createLineBorder (UIManager.getColor ("List.focusCellHighlight")) : // NOI18N
158
            //Max annotated icon width is 24, so to have all the text and all
375
                            // recommended by issue 37335
159
            //the icons come out aligned, set the icon text gap to the difference
376
                            LineBorder.createGrayLineBorder ();
160
            //plus a two pixel margin
377
161
            ren.setIconTextGap (24 - icon.getIconWidth());
378
	public List() {
162
        } else {
379
            setOpaque(true);
163
            //If the icon width is 0, fill the space and add in
380
	}
164
            //the extra two pixels so the node names are aligned (btw, this
381
165
            //does seem to waste a frightful amount of horizontal space in
382
        /** This is the only method defined by ListCellRenderer.  We just
166
            //a tree that can use all it can get)
383
         * reconfigure the Jlabel each time we're called.
167
            ren.setIndent (26);
384
         */
385
        public Component getListCellRendererComponent (
386
            JList list,
387
            Object value,            // value to display
388
            int index,               // cell index
389
            boolean isSelected,      // is the cell selected
390
            boolean cellHasFocus)    // the list and the cell have the focus
391
        {
392
            VisualizerNode vis = (VisualizerNode)value;
393
            ImageIcon nodeicon = NodeRenderer.getIcon(vis.node.getIcon(BeanInfo.ICON_COLOR_16x16));
394
            setIcon(nodeicon);
395
            setText(vis.getDisplayName ());
396
            if (isSelected) {
397
                Component focusOwner = 
398
                    KeyboardFocusManager.getCurrentKeyboardFocusManager().
399
                    getPermanentFocusOwner();
400
401
                boolean hasFocus = focusOwner == list || list.isAncestorOf(focusOwner);
402
403
                setBackground(hasFocus ? list.getSelectionBackground() : 
404
                    getNoFocusSelectionBackground());
405
                    
406
                setForeground(hasFocus ? list.getSelectionForeground() : 
407
                    getNoFocusSelectionForeground());
408
            } else {
409
                setBackground(list.getBackground());
410
                setForeground(list.getForeground());
411
            }
412
413
            setIconTextGap (4 - nodeicon.getIconWidth()
414
                            + ( nodeicon.getIconWidth() > 24 ? nodeicon.getIconWidth() : 24 ) );
415
416
417
            int delta = NodeListModel.findVisualizerDepth (list.getModel (), vis);
418
419
            Border border = (cellHasFocus || value == draggedOver) ? focusBorder : emptyBorder;
420
            if (delta > 0) {
421
                border = BorderFactory.createCompoundBorder (
422
                    BorderFactory.createEmptyBorder (0, nodeicon.getIconWidth() * delta, 0, 0),
423
                    border
424
                );
425
            }
426
            setBorder(border);
427
428
            return this;
429
        }
430
431
	protected void firePropertyChange(String name, Object old, Object nw) {
432
	    // do really nothing!
433
        }
168
        }
434
169
435
    } // End of class List.
170
        ren.setIcon (icon);
436
171
437
172
        if (target instanceof TreeTable.TreeTableCellRenderer) {
438
    /** List cell renderer which renders icon and display name from <code>VisualizerNode</code>. */
173
            TreeTable tt = ((TreeTable.TreeTableCellRenderer) target).getTreeTable();
439
    final static class Pane extends JLabel implements ListCellRenderer {
174
            ren.setParentFocused (tt.hasFocus() || tt.isEditing());
440
        /** generated Serialized Version UID */
441
        static final long serialVersionUID = -5100925551665387243L;
442
443
	/** Focused Node border. */
444
        static Border focusBorder = UIManager.getColor ("List.focusCellHighlight") != null ? // NOI18N
445
                            BorderFactory.createLineBorder (UIManager.getColor ("List.focusCellHighlight")) : // NOI18N
446
                            // recommended by issue 37335
447
                            LineBorder.createGrayLineBorder ();
448
449
        /** Creates a new NetbeansListCellRenderer */
450
        public Pane () {
451
            setOpaque(true);
452
            setVerticalTextPosition(JLabel.BOTTOM);
453
            setHorizontalAlignment(JLabel.CENTER);
454
            setHorizontalTextPosition(JLabel.CENTER);
455
        }
175
        }
176
        return icon.getIconWidth() == 0 ? 24 : icon.getIconWidth();
177
    }
456
178
457
        /** This is the only method defined by ListCellRenderer.  We just
179
    /** Utility method to find a visualizer node for the object passed to
458
         * reconfigure the Jlabel each time we're called.
180
     * any of the cell renderer methods as the value */
459
         * @param list the JList
181
    private static final VisualizerNode findVisualizerNode(Object value) {
460
         * @param value the value returned by list.getModel().getElementAt(index)
182
        VisualizerNode vis = (value instanceof Node) ?
461
         * @param index the cells index
183
        VisualizerNode.getVisualizer (null, (Node) value) :
462
         * @param isSelected <code>true</code> if the specified cell was selected
184
            (VisualizerNode)value;
463
         * @param cellHasFocus <code>true</code> if the specified cell has the focus
464
         * @return a component whose paint() method will render the specified value
465
         */
466
        public Component getListCellRendererComponent (
467
            JList list, Object value, int index,
468
            boolean isSelected, boolean cellHasFocus
469
        ) {
470
            VisualizerNode vis = (VisualizerNode)value;
471
472
            setIcon(NodeRenderer.getIcon(vis.node.getIcon(BeanInfo.ICON_COLOR_32x32)));
473
            setText(vis.getDisplayName ());
474
            Component focusOwner = 
475
                KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
476
            
477
            boolean hasFocus = focusOwner == list || 
478
                list.isAncestorOf(focusOwner);
479
185
480
            if (isSelected){
186
            if (vis == null) {
481
                setBackground(hasFocus ? list.getSelectionBackground() 
187
                vis = VisualizerNode.EMPTY;
482
                : getNoFocusSelectionBackground());
483
                
484
                setForeground(hasFocus ? list.getSelectionForeground() :
485
                    getNoFocusSelectionForeground());
486
            }
487
            else {
488
                setBackground(list.getBackground());
489
                setForeground(list.getForeground());
490
            }
188
            }
491
189
492
            setBorder(cellHasFocus ? focusBorder : emptyBorder);
190
            return vis;
493
            return this;
494
        }
191
        }
495
192
496
	protected void firePropertyChange(String name, Object old, Object nw) {
193
    // ********************
497
	    // do really nothing!
194
    // Support for dragging
498
        }
195
    // ********************
499
196
500
    } // End of class Pane.
197
    /** Value of the cell with 'drag under' visual feedback */
198
    private static VisualizerNode draggedOver;
501
199
502
200
503
    private static Color noFocusSelectionBackground=null;
201
    /** DnD operation enters. Update look and feel to the 'drag under' state.
504
    static Color getNoFocusSelectionBackground() {
202
     * @param dragged the value of cell which should have 'drag under' visual feedback
505
        if (noFocusSelectionBackground == null) {
203
         */
506
            //allow theme/ui custom definition
204
    static void dragEnter (Object dragged) {
507
            noFocusSelectionBackground = 
205
        draggedOver = (VisualizerNode)dragged;
508
                UIManager.getColor("nb.explorer.noFocusSelectionBackground"); //NOI18N
509
            if (noFocusSelectionBackground == null) {
510
                //try to get standard shadow color
511
                noFocusSelectionBackground = UIManager.getColor("controlShadow"); //NOI18N
512
                if (noFocusSelectionBackground == null) {
513
                    //Okay, the look and feel doesn't suport it, punt
514
                    noFocusSelectionBackground = new ColorUIResource(Color.lightGray);
515
                }
516
                //Lighten it a bit because disabled text will use controlShadow/
517
                //gray
518
                noFocusSelectionBackground = noFocusSelectionBackground.brighter();
519
            }
520
        }
521
        return noFocusSelectionBackground;
522
    }
206
    }
523
    
207
    
524
    private static Color noFocusSelectionForeground=null;
208
    /** DnD operation exits. Revert to the normal look and feel. */
525
    static Color getNoFocusSelectionForeground() {
209
    static void dragExit () {
526
        if (noFocusSelectionForeground == null) {
210
        draggedOver = null;
527
            //allow theme/ui custom definition
528
            noFocusSelectionForeground =
529
            UIManager.getColor("nb.explorer.noFocusSelectionForeground"); //NOI18N
530
            if (noFocusSelectionForeground == null) {
531
                //try to get standard shadow color
532
                noFocusSelectionForeground = UIManager.getColor("textText"); //NOI18N
533
                if (noFocusSelectionForeground == null) {
534
                    //Okay, the look and feel doesn't suport it, punt
535
                    noFocusSelectionForeground = Color.BLACK;
536
                }
537
            }
538
        }
539
        return noFocusSelectionForeground;
540
    }
211
    }
541
    
542
    
212
    
543
}
213
}
(-)openide/src/org/openide/explorer/view/TableSheetCell.java (-3 / +3 lines)
Lines 259-269 Link Here
259
                
259
                
260
                propPanel.setBackground(tableHasFocus ? 
260
                propPanel.setBackground(tableHasFocus ? 
261
                    table.getSelectionBackground() : 
261
                    table.getSelectionBackground() : 
262
                    NodeRenderer.getNoFocusSelectionBackground());
262
                    TreeTable.getUnfocusedSelectedBackground());
263
                
263
                
264
                propPanel.setForeground(tableHasFocus ?
264
                propPanel.setForeground(tableHasFocus ?
265
                    table.getSelectionForeground() :
265
                    table.getSelectionForeground() :
266
                    NodeRenderer.getNoFocusSelectionForeground());
266
                    TreeTable.getUnfocusedSelectedForeground());
267
                
267
                
268
            } else {
268
            } else {
269
                propPanel.setBackground(table.getBackground());
269
                propPanel.setBackground(table.getBackground());
Lines 290-296 Link Here
290
            
290
            
291
            nullPanel.setBackground(tableHasFocus ? 
291
            nullPanel.setBackground(tableHasFocus ? 
292
            table.getSelectionBackground() : 
292
            table.getSelectionBackground() : 
293
            NodeRenderer.getNoFocusSelectionBackground());
293
                TreeTable.getUnfocusedSelectedBackground());
294
            //XXX may want to handle inverse theme here and use brighter if
294
            //XXX may want to handle inverse theme here and use brighter if
295
            //below a threshold.  Deferred to centralized color management
295
            //below a threshold.  Deferred to centralized color management
296
            //being implemented.
296
            //being implemented.
(-)openide/src/org/openide/explorer/view/TreeTable.java (-28 / +68 lines)
Lines 73-80 Link Here
73
        this.tree = new TreeTableCellRenderer(treeModel);
73
        this.tree = new TreeTableCellRenderer(treeModel);
74
        this.tableModel = new TreeTableModelAdapter(tree, tableModel);
74
        this.tableModel = new TreeTableModelAdapter(tree, tableModel);
75
75
76
        NodeRenderer rend = NodeRenderer.sharedInstance ();
76
        tree.setCellRenderer(NodeRenderer.sharedInstance ());
77
        tree.setCellRenderer(rend);
78
77
79
	// Install a tableModel representing the visible rows in the tree. 
78
	// Install a tableModel representing the visible rows in the tree. 
80
	setModel(this.tableModel);
79
	setModel(this.tableModel);
Lines 276-294 Link Here
276
        int rowHeight = fm.getHeight() + 4;
275
        int rowHeight = fm.getHeight() + 4;
277
        needCalcRowHeight = false;
276
        needCalcRowHeight = false;
278
        rowHeight = Math.min(20, rowHeight);
277
        rowHeight = Math.min(20, rowHeight);
279
        setRowHeight (rowHeight);
278
        tree.setRowHeight (rowHeight);
280
    }    
279
    }    
281
280
282
    /*
283
     * Overridden to pass the new rowHeight to the tree.
284
     */
285
    public void setRowHeight(int rowHeight) { 
286
        super.setRowHeight(rowHeight); 
287
	if (tree != null && tree.getRowHeight() != rowHeight) {
288
            tree.setRowHeight(getRowHeight()); 
289
	}
290
    }
291
292
    /**
281
    /**
293
     * Returns the tree that is being shared between the model.
282
     * Returns the tree that is being shared between the model.
294
     */
283
     */
Lines 578-584 Link Here
578
        
567
        
579
	public TreeTableCellRenderer(TreeModel model) {
568
	public TreeTableCellRenderer(TreeModel model) {
580
	    super(model); 
569
	    super(model); 
581
            setRowHeight(getRowHeight()); 
582
            setToggleClickCount(0);
570
            setToggleClickCount(0);
583
            putClientProperty("JTree.lineStyle", "None"); // NOI18N
571
            putClientProperty("JTree.lineStyle", "None"); // NOI18N
584
	}
572
	}
Lines 599-618 Link Here
599
            //do nothing
587
            //do nothing
600
        }
588
        }
601
589
590
        /**
591
         * Accessor so NodeRenderer can check if the tree table or its child has
592
         * focus and paint with the appropriate color.
593
         *
594
         * @see NodeRenderer#configureFrom
595
         * @return The tree table
596
         */
597
        TreeTable getTreeTable() {
598
            return TreeTable.this;
599
        }
600
602
	/**
601
	/**
603
	 * Sets the row height of the tree, and forwards the row height to
602
	 * Sets the row height of the tree, and forwards the row height to
604
	 * the table.
603
	 * the table.
605
	 */
604
	 */
606
	public void setRowHeight(int rowHeight) { 
605
	public void setRowHeight(int rowHeight) {
607
	    if (rowHeight > 0) {
606
            if (rowHeight > 0) {
608
                synchronized (getTreeLock()) {
607
                super.setRowHeight(rowHeight); 
609
                    super.setRowHeight(rowHeight); 
608
                TreeTable.this.setRowHeight(rowHeight);
610
                    if (TreeTable.this != null &&
609
            }
611
                        TreeTable.this.getRowHeight() != rowHeight) {
612
                        TreeTable.this.setRowHeight(getRowHeight()); 
613
                    }
614
                }
615
	    }
616
	}
610
	}
617
611
618
	/**
612
	/**
Lines 703-714 Link Here
703
                    focusOwner == TreeTable.this
697
                    focusOwner == TreeTable.this
704
                    || TreeTable.this.isAncestorOf(focusOwner);
698
                    || TreeTable.this.isAncestorOf(focusOwner);
705
                
699
                
700
                //TODO - it should be possible to simply set the correct
701
                //color in prepareRenderer for the tree's cell renderer,
702
                //rather than set it for the whole tree.  Might fix a 
703
                //couple problems. -Tim
706
                setBackground(tableHasFocus ?
704
                setBackground(tableHasFocus ?
707
                    table.getSelectionBackground() :
705
                    table.getSelectionBackground() :
708
                    NodeRenderer.getNoFocusSelectionBackground());
706
                    getUnfocusedSelectedBackground());
709
                setForeground(tableHasFocus ?
707
                setForeground(tableHasFocus ?
710
                    table.getSelectionForeground() :
708
                    table.getSelectionForeground() :
711
                    NodeRenderer.getNoFocusSelectionForeground());
709
                    getUnfocusedSelectedForeground());
712
            } else {
710
            } else {
713
		setBackground(table.getBackground());
711
		setBackground(table.getBackground());
714
                setForeground(table.getForeground());
712
                setForeground(table.getForeground());
Lines 818-827 Link Here
818
        if (isEditing() && editorComp != null) {
816
        if (isEditing() && editorComp != null) {
819
            editorComp.setBackground(focused ?
817
            editorComp.setBackground(focused ?
820
                getSelectionBackground() :
818
                getSelectionBackground() :
821
                NodeRenderer.getNoFocusSelectionBackground());
819
                getUnfocusedSelectedBackground());
822
            editorComp.setForeground(focused ?
820
            editorComp.setForeground(focused ?
823
                getSelectionForeground() :
821
                getSelectionForeground() :
824
                NodeRenderer.getNoFocusSelectionForeground());
822
                getUnfocusedSelectedForeground());
825
        }
823
        }
826
    }
824
    }
827
825
Lines 1611-1614 Link Here
1611
            }
1609
            }
1612
        }
1610
        }
1613
    }
1611
    }
1612
    
1613
    private static Color unfocusedSelBg = null;
1614
    private static Color unfocusedSelFg = null;
1615
    
1616
    /** Get the system-wide unfocused selection background color */
1617
    static Color getUnfocusedSelectedBackground() {
1618
        if (unfocusedSelBg == null) {
1619
            //allow theme/ui custom definition
1620
            unfocusedSelBg =
1621
            UIManager.getColor("nb.explorer.unfocusedSelBg"); //NOI18N
1622
            if (unfocusedSelBg == null) {
1623
                //try to get standard shadow color
1624
                unfocusedSelBg = UIManager.getColor("controlShadow"); //NOI18N
1625
                if (unfocusedSelBg == null) {
1626
                    //Okay, the look and feel doesn't suport it, punt
1627
                    unfocusedSelBg = Color.lightGray;
1628
                }
1629
                //Lighten it a bit because disabled text will use controlShadow/
1630
                //gray
1631
                unfocusedSelBg = unfocusedSelBg.brighter();
1632
            }
1633
        }
1634
        return unfocusedSelBg;
1635
    }
1636
    
1637
    /** Get the system-wide unfocused selection foreground color */
1638
    static Color getUnfocusedSelectedForeground() {
1639
        if (unfocusedSelFg == null) {
1640
            //allow theme/ui custom definition
1641
            unfocusedSelFg =
1642
            UIManager.getColor("nb.explorer.unfocusedSelFg"); //NOI18N
1643
            if (unfocusedSelFg == null) {
1644
                //try to get standard shadow color
1645
                unfocusedSelFg = UIManager.getColor("textText"); //NOI18N
1646
                if (unfocusedSelFg == null) {
1647
                    //Okay, the look and feel doesn't suport it, punt
1648
                    unfocusedSelFg = Color.BLACK;
1649
                }
1650
            }
1651
        }
1652
        return unfocusedSelFg;
1653
    }    
1614
}
1654
}
(-)openide/src/org/openide/explorer/view/TreeTableView.java (-5 / +10 lines)
Lines 361-367 Link Here
361
361
362
        hScrollBar = new JScrollBar(JScrollBar.HORIZONTAL);
362
        hScrollBar = new JScrollBar(JScrollBar.HORIZONTAL);
363
	hScrollBar.putClientProperty(MetalScrollBarUI.FREE_STANDING_PROP, Boolean.FALSE);
363
	hScrollBar.putClientProperty(MetalScrollBarUI.FREE_STANDING_PROP, Boolean.FALSE);
364
364
        hScrollBar.setVisible(false);
365
        
365
        listener = new ScrollListener();
366
        listener = new ScrollListener();
366
        
367
        
367
        treeTable.addPropertyChangeListener(listener);
368
        treeTable.addPropertyChangeListener(listener);
Lines 511-524 Link Here
511
    public void addNotify() {
512
    public void addNotify() {
512
        // to allow displaying popup also in blank area
513
        // to allow displaying popup also in blank area
513
        if ( treeTable.getParent() != null ) {
514
        if ( treeTable.getParent() != null ) {
514
            treeTable.getParent().addMouseListener( tableMouseListener );
515
            treeTableParent = treeTable.getParent();
516
            treeTableParent.addMouseListener( tableMouseListener );
515
        }
517
        }
516
        
517
        super.addNotify();
518
        super.addNotify();
519
        listener.revalidateScrollBar();
518
    }
520
    }
521
    private Component treeTableParent = null;
519
522
520
    public void removeNotify() {
523
    public void removeNotify() {
521
        super.removeNotify();
524
        super.removeNotify();
525
        treeTableParent.removeMouseListener ( tableMouseListener );
526
        treeTableParent = null;
522
        
527
        
523
        // clear node listeners
528
        // clear node listeners
524
        tableModel.setNodes(new Node[] {});
529
        tableModel.setNodes(new Node[] {});
Lines 799-808 Link Here
799
        }
804
        }
800
        
805
        
801
        private void revalidateScrollBar() {
806
        private void revalidateScrollBar() {
802
            if (!isShowing()) {
807
            if (!isDisplayable()) {
803
                return;
808
                return;
804
            }
809
            }
805
            if (treeTable.getColumnModel().getColumnCount() > 0 && ((TreeTable)treeTable).getTreeColumnIndex() > 0) {
810
            if (treeTable.getColumnModel().getColumnCount() > 0 && ((TreeTable)treeTable).getTreeColumnIndex() >= 0) {
806
                int extentWidth = treeTable.getColumnModel().getColumn(((TreeTable)treeTable).getTreeColumnIndex()).getWidth();
811
                int extentWidth = treeTable.getColumnModel().getColumn(((TreeTable)treeTable).getTreeColumnIndex()).getWidth();
807
                int maxWidth = tree.getPreferredSize().width;
812
                int maxWidth = tree.getPreferredSize().width;
808
                int extentHeight = scrollPane.getViewport().getSize().height;
813
                int extentHeight = scrollPane.getViewport().getSize().height;
(-)openide/src/org/openide/explorer/view/TreeView.java (-2 / +31 lines)
Lines 350-355 Link Here
350
        dropSupport.activate(dropActive);
350
        dropSupport.activate(dropActive);
351
    }
351
    }
352
352
353
    //XXX does anybody know what the XXX comments below mean? - Tim
354
353
    /** Actions constants comes from DnDConstants.XXX constants.
355
    /** Actions constants comes from DnDConstants.XXX constants.
354
    * All actions (copy, move, link) are allowed by default.
356
    * All actions (copy, move, link) are allowed by default.
355
    * @return Set of actions which are allowed when dragging from
357
    * @return Set of actions which are allowed when dragging from
Lines 1106-1112 Link Here
1106
                // note: dropTarget is activated in constructor
1108
                // note: dropTarget is activated in constructor
1107
            }
1109
            }
1108
            // lazy cell editor init
1110
            // lazy cell editor init
1109
            tree.setCellEditor(new TreeViewCellEditor(tree, new NodeRenderer.Tree ()));
1111
            tree.setCellEditor(new TreeViewCellEditor(tree));
1110
            tree.setEditable(true);
1112
            tree.setEditable(true);
1111
        }
1113
        }
1112
        
1114
        
Lines 1186-1192 Link Here
1186
1188
1187
            setupSearch();
1189
            setupSearch();
1188
        }
1190
        }
1189
        
1191
1192
        private boolean firstPaint = true;
1193
1194
        private void calcRowHeight (Graphics g) {
1195
            int height = g.getFontMetrics(getFont()).getHeight();
1196
            setRowHeight(Math.max (18, height + 2));
1197
            firstPaint = false;
1198
        }
1199
1200
        public void paint (Graphics g) {
1201
            if (firstPaint) {
1202
                calcRowHeight(g);
1203
                //This will generate a repaint, so don't bother continuing with super.paint()
1204
                //but do paint the background color so it doesn't paint gray the first time
1205
                g.setColor (getBackground());
1206
                g.fillRect (0, 0, getWidth(), getHeight());
1207
                return;
1208
            }
1209
            super.paint (g);
1210
        }
1211
1212
        public void setFont (Font f) {
1213
            if (f != getFont()) {
1214
                firstPaint = true;
1215
                super.setFont (f);
1216
            }
1217
        }
1218
1190
        protected void processFocusEvent (FocusEvent fe) {
1219
        protected void processFocusEvent (FocusEvent fe) {
1191
            super.processFocusEvent(fe);
1220
            super.processFocusEvent(fe);
1192
            //Since the selected when focused is different, we need to force a
1221
            //Since the selected when focused is different, we need to force a
(-)openide/src/org/openide/explorer/view/TreeViewCellEditor.java (-6 / +11 lines)
Lines 44-54 Link Here
44
44
45
    /** Construct a cell editor.
45
    /** Construct a cell editor.
46
    * @param tree the tree
46
    * @param tree the tree
47
    * @param renderer the renderer to use for the cell
48
    */
47
    */
49
    public TreeViewCellEditor(JTree tree, TreeCellRenderer renderer) {
48
    public TreeViewCellEditor(JTree tree) {
50
        super(tree, renderer instanceof DefaultTreeCellRenderer ? 
49
        //Use a dummy DefaultTreeCellEditor - we'll set up the correct
51
            (DefaultTreeCellRenderer) renderer : new DefaultTreeCellRenderer()); //XXX for testing GTK hacks - will be deleted with the HTML mini renderer integration
50
        //icon when we fetch the editor component (see EOF).  Not sure
51
        //it's wildly vaulable to subclass DefaultTreeCellEditor here - 
52
        //we override most everything
53
        super(tree, new DefaultTreeCellRenderer()); 
52
        // deal with selection if already exists
54
        // deal with selection if already exists
53
        if (tree.getSelectionCount() == 1) {
55
        if (tree.getSelectionCount() == 1) {
54
            lastPath = tree.getSelectionPath();
56
            lastPath = tree.getSelectionPath();
Lines 155-161 Link Here
155
                            }
157
                            }
156
                        };
158
                        };
157
159
158
        tf.registerKeyboardAction(
160
        tf.registerKeyboardAction( //TODO update to use inputMap/actionMap
159
            this,
161
            this,
160
            KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true),
162
            KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true),
161
            JComponent.WHEN_FOCUSED
163
            JComponent.WHEN_FOCUSED
Lines 264-269 Link Here
264
    
266
    
265
    protected void prepareForEditing () {
267
    protected void prepareForEditing () {
266
        tree.removeMouseMotionListener (this);
268
        tree.removeMouseMotionListener (this);
269
        
267
        super.prepareForEditing ();
270
        super.prepareForEditing ();
268
    }
271
    }
269
    
272
    
Lines 274-280 Link Here
274
    }
277
    }
275
    
278
    
276
    /** Redefined default cell editor to convert nodes to name */
279
    /** Redefined default cell editor to convert nodes to name */
277
    static class Ed extends DefaultCellEditor {
280
    class Ed extends DefaultCellEditor {
278
        /** generated Serialized Version UID */
281
        /** generated Serialized Version UID */
279
        static final long serialVersionUID = -6373058702842751408L;
282
        static final long serialVersionUID = -6373058702842751408L;
280
283
Lines 294-299 Link Here
294
            else
297
            else
295
                delegate.setValue(""); // NOI18N
298
                delegate.setValue(""); // NOI18N
296
299
300
            editingIcon = ((VisualizerNode) value).getIcon(expanded, false);
301
            
297
            ((JTextField) editorComponent).selectAll();
302
            ((JTextField) editorComponent).selectAll();
298
            return editorComponent;
303
            return editorComponent;
299
        }
304
        }
(-)openide/src/org/openide/explorer/view/VisualizerNode.java (-11 / +86 lines)
Lines 13-21 Link Here
13
13
14
package org.openide.explorer.view;
14
package org.openide.explorer.view;
15
15
16
import java.awt.Image;
17
import java.beans.BeanInfo;
16
import java.lang.ref.Reference;
18
import java.lang.ref.Reference;
17
import java.lang.ref.WeakReference;
19
import java.lang.ref.WeakReference;
18
import java.util.*;
20
import java.util.*;
21
import javax.swing.Icon;
22
import javax.swing.ImageIcon;
19
import javax.swing.SwingUtilities;
23
import javax.swing.SwingUtilities;
20
import javax.swing.event.EventListenerList;
24
import javax.swing.event.EventListenerList;
21
import javax.swing.tree.TreeNode;
25
import javax.swing.tree.TreeNode;
Lines 23-36 Link Here
23
import org.openide.ErrorManager;
27
import org.openide.ErrorManager;
24
import org.openide.nodes.*;
28
import org.openide.nodes.*;
25
import org.openide.util.Mutex;
29
import org.openide.util.Mutex;
30
import org.openide.util.Utilities;
26
import org.openide.util.enum.QueueEnumeration;
31
import org.openide.util.enum.QueueEnumeration;
27
32
28
/** Visual representation of one node. Holds necessary information about nodes
33
/** Visual representation of one node. Holds necessary information about nodes
29
* like icon, name, description and also list of its children. 
34
* like icon, name, description and also list of its children. 
30
* <P>
35
* <P>
31
* There is at most one VisualizerNode for one node. All of them are hold in a cache.
36
* There is at most one VisualizerNode for one node. All of them are held in a cache.
32
* <P>
37
* <P>
33
* The VisualizerNode level provides secure layer between Nodes and Swing AWT dispatch 
38
* VisualizerNode provides a thread-safe layer between Nodes, which may fire
39
* property changes on any thread, and the AWT dispatch thread
34
* thread.
40
* thread.
35
*
41
*
36
* @author Jaroslav Tulach
42
* @author Jaroslav Tulach
Lines 54-59 Link Here
54
    
60
    
55
    private static final ErrorManager err = ErrorManager.getDefault().getInstance("org.openide.explorer.view.VisualizerNode"); // NOI18N
61
    private static final ErrorManager err = ErrorManager.getDefault().getInstance("org.openide.explorer.view.VisualizerNode"); // NOI18N
56
62
63
    /** Cached icon - pre-html, there was a separate cache in NodeRenderer, but
64
     * if we're keeping a weak cache of VisualizerNodes, there's no reason not
65
     * to keep it here */
66
    private Icon icon = null;
67
    
68
57
    // bugfix #29435, getVisualizer is synchronized in place of be called only from EventQueue
69
    // bugfix #29435, getVisualizer is synchronized in place of be called only from EventQueue
58
    /** Finds VisualizerNode for given node.
70
    /** Finds VisualizerNode for given node.
59
    * @param ch the children this visualizer should belong to
71
    * @param ch the children this visualizer should belong to
Lines 274-285 Link Here
274
    */
286
    */
275
    public void propertyChange(final java.beans.PropertyChangeEvent evt) {
287
    public void propertyChange(final java.beans.PropertyChangeEvent evt) {
276
        String name = evt.getPropertyName ();
288
        String name = evt.getPropertyName ();
289
        boolean isIconChange = Node.PROP_ICON.equals(name) || Node.PROP_OPENED_ICON.equals(name);
277
        if (
290
        if (
278
            Node.PROP_NAME.equals (name) ||
291
            Node.PROP_NAME.equals (name) ||
279
            Node.PROP_DISPLAY_NAME.equals (name) ||
292
            Node.PROP_DISPLAY_NAME.equals (name) ||
280
            Node.PROP_ICON.equals (name) ||
293
            isIconChange
281
            Node.PROP_OPENED_ICON.equals (name)
282
        ) {
294
        ) {
295
            if (isIconChange) {
296
                //Ditch the cached icon type so the next call to getIcon() will
297
                //recreate the ImageIcon
298
                cachedIconType = -1;
299
            }
300
            if (Node.PROP_DISPLAY_NAME.equals(name)) {
301
                htmlDisplayName = null;
302
            }
283
            SwingUtilities.invokeLater (this);
303
            SwingUtilities.invokeLater (this);
284
            return;
304
            return;
285
        }
305
        }
Lines 305-317 Link Here
305
                } 
325
                } 
306
            } );
326
            } );
307
        }
327
        }
308
        /*
309
        if (
310
            "lookAndFeel".equals (name) // NOI18N
311
        ) {
312
            SwingUtilities.invokeLater (this);
313
        }
314
        */
315
    }
328
    }
316
329
317
    /** Update the state of this class by retrieving new name, etc.
330
    /** Update the state of this class by retrieving new name, etc.
Lines 391-396 Link Here
391
        return getDisplayName ();
404
        return getDisplayName ();
392
    }
405
    }
393
406
407
    private static final String NO_HTML_DISPLAYNAME="noHtmlDisplayName";
408
    private String htmlDisplayName = null;
409
    public String getHtmlDisplayName() {
410
        if (htmlDisplayName == null) {
411
            htmlDisplayName = node.getHtmlDisplayName();
412
            if (htmlDisplayName == null) {
413
                htmlDisplayName = NO_HTML_DISPLAYNAME;
414
            }
415
        }
416
        return htmlDisplayName == NO_HTML_DISPLAYNAME ? null : htmlDisplayName;
417
    }
418
    
419
    Icon getIcon(boolean opened, boolean large) {
420
        int newCacheType = getCacheType(opened, large);
421
        
422
        if (cachedIconType != newCacheType) {
423
            int iconType = large ? BeanInfo.ICON_COLOR_32x32 : 
424
                BeanInfo.ICON_COLOR_16x16;
425
            
426
            Image image = opened ? node.getOpenedIcon(
427
                iconType) : node.getIcon(
428
                iconType);
429
430
            // bugfix #28515, check if getIcon contract isn't broken
431
            if (image == null) {
432
                String method = opened ? "getOpenedIcon" : "getIcon"; // NOI18N
433
                    ErrorManager.getDefault ().log (ErrorManager.WARNING, "Node \""
434
                    + node.getName () +
435
                    "\" [" + node.getClass().getName()+
436
                    "] cannot return null from " + method + "(). See Node." +
437
                    method + " contract."); // NOI18N
438
439
                icon = defaultIcon;
440
            } else {
441
                icon = new ImageIcon (image);
442
            }
443
        }
444
        cachedIconType = newCacheType;
445
        return icon;
446
    }
447
    
448
    /** Some simple bitmasking to determine the type of the cached icon. 
449
     * Generally, it's worth caching one, but not a bunch - generally one will
450
     * be used repeatedly. */
451
    private static final int getCacheType (boolean opened, boolean large) {
452
        return (opened ? 2 : 0) | (large ? 1 : 0);
453
    }
454
    
455
    /** loaded default icon */
456
    private static Icon defaultIcon;
457
    private int cachedIconType = -1;
458
    
459
    /** default icon to use when none is present */
460
    private static final String DEFAULT_ICON = "org/openide/resources/defaultNode.gif"; // NOI18N
461
    /** Loads default icon if not loaded. */
462
    private static Icon getDefaultIcon () {
463
        if (defaultIcon == null) {
464
            defaultIcon = new ImageIcon(Utilities.loadImage(DEFAULT_ICON));
465
        }
466
        return defaultIcon;
467
    }
468
    
394
    /** Strong reference.
469
    /** Strong reference.
395
    */
470
    */
396
    private static final class StrongReference extends WeakReference {
471
    private static final class StrongReference extends WeakReference {
(-)openide/src/org/openide/filesystems/FileSystem.java (+34 lines)
Lines 700-705 Link Here
700
        public java.awt.Image annotateIcon (java.awt.Image icon, int iconType, java.util.Set files);
700
        public java.awt.Image annotateIcon (java.awt.Image icon, int iconType, java.util.Set files);
701
    }
701
    }
702
702
703
    /** Extension interface for Status provides HTML-formatted annotations.
704
     * Principally this is used to de&euml;mphasize status text by presenting
705
     * it in a lighter color, by placing it inside 
706
     * &lt;font color=!controlShadow&gt; tags.  Note that it is preferable to
707
     * use logical colors (such as controlShadow) which are resolved by calling
708
     * UIManager.getColor(key) &mdash; this way they will always fit with the
709
     * look and feel.  To use a logical color, prefix the color name with a 
710
     * ! character.
711
     * <p>
712
     * Please use only the limited markup subset of HTML supported by the 
713
     * lightweight HTML renderer.
714
     * @see org.openide.awt.HtmlRenderer
715
     * @since 4.30
716
     */
717
    public static interface HtmlStatus extends Status { 
718
        /** Annotate a name such that the returned value contains HTML markup.
719
         * The return value less the html content should typically be the same 
720
         * as the return value from <code>annotateName()</code>.  This is used,
721
         * for example, by VCS filesystems to de&euml;phasize the status information
722
         * included in the file name by using a light grey font color. 
723
         * <p>
724
         * For consistency with <code>Node.getHtmlDisplayName()</code>, 
725
         * filesystems that proxy other filesystems (and so must implement
726
         * this interface to supply HTML annotations) should return null if
727
         * the filesystem they proxy does not provide an implementation of
728
         * HTMLStatus.
729
         * @since 4.30
730
         * @see org.openide.awt.HtmlRenderer
731
         * @see org.openide.loaders.DataNode#getHtmlDisplayName
732
         * @see org.openide.nodes.Node#getHtmlDisplayName
733
         **/
734
        public String annotateNameHtml (String name, java.util.Set files);
735
    }
736
    
703
    /** Empty status */
737
    /** Empty status */
704
    private static final Status STATUS_NONE = new Status () {
738
    private static final Status STATUS_NONE = new Status () {
705
                public String annotateName (String name, java.util.Set files) {
739
                public String annotateName (String name, java.util.Set files) {
(-)openide/src/org/openide/nodes/FilterNode.java (+42 lines)
Lines 22-27 Link Here
22
22
23
import java.util.*;
23
import java.util.*;
24
import java.lang.ref.WeakReference;
24
import java.lang.ref.WeakReference;
25
import java.lang.reflect.Method;
25
import org.openide.ErrorManager;
26
import org.openide.ErrorManager;
26
27
27
import org.openide.util.datatransfer.NewType;
28
import org.openide.util.datatransfer.NewType;
Lines 643-648 Link Here
643
        }
644
        }
644
        
645
        
645
        return retValue;
646
        return retValue;
647
    }
648
    
649
    /** Get a display name containing HTML markup.  <strong><b>Note:</b> If you subclass
650
     * FilterNode and override <code>getDisplayName()</code>, this method will
651
     * always return null unless you override it as well (assuming that if you're
652
     * changing the display name, you don't want an HTML display name constructed
653
     * from the original node's display name to be what shows up in views of
654
     * this node).</strong>  If <code>getDisplayName()</code> is not overridden,
655
     * this method will return whatever the original node returns from this 
656
     * method.
657
     * <p>
658
     * Note that if you do override <code>getDisplayName</code>, you should also override
659
     * this method to return null.
660
     *
661
     * 
662
     *
663
     * @see org.openide.nodes.Node#getHtmlDisplayName
664
     * @return An HTML display name, if available, or null if no display name
665
     * is available   */
666
    public String getHtmlDisplayName() {
667
        if (overridesGetDisplayName()) {
668
            return null;
669
        } else {
670
            return delegating (DELEGATE_GET_DISPLAY_NAME) ? 
671
                original.getHtmlDisplayName() : super.getHtmlDisplayName();
672
        }
673
    }
674
    
675
    private boolean overridesGetDisplayName() {
676
        if (getClass() != FilterNode.class) {
677
            try {
678
                Method m = getClass().getMethod("getDisplayName", null); //NOI18N
679
                return m.getDeclaringClass() != FilterNode.class;
680
            } catch (NoSuchMethodException nsme) {
681
                //can't happen
682
                ErrorManager.getDefault().notify(nsme);
683
                return true;
684
            }
685
        } else {
686
            return false;
687
        }
646
    }
688
    }
647
    
689
    
648
    /*
690
    /*
(-)openide/src/org/openide/nodes/Node.java (+65 lines)
Lines 655-660 Link Here
655
        }
655
        }
656
    }
656
    }
657
    
657
    
658
    /** Return a variant of the display name containing HTML markup
659
     * conforming to the limited subset of font-markup HTML supported by
660
     * the lightweight HTML renderer <code>org.openide.awt.HtmlRenderer</code>
661
     * (font color, bold, italic and strikethrough supported; font
662
     * colors can be UIManager color keys if they are prefixed with
663
     * a ! character, i.e. &lt;font color=&amp;'controlShadow'&gt;).
664
     * Enclosing html tags are not needed. If returning non-null, HTML
665
     * markup characters that should be literally renderered must be 
666
     * escaped ( &gt; becomes &amp;gt; and so forth).
667
     * <p><strong>This method should return either an HTML display name
668
     * or null; it should not return the non-html display name.
669
     * <p>
670
     * Note there is no property corresponding to the html display name - 
671
     * if it should change, a change in the display name should be fired; this
672
     * should not be a mechanism for returning anything other than a marked
673
     * up version of the return value of <code>getDisplayName</code>.
674
     *
675
     * @see org.openide.awt.HtmlRenderer
676
     * @since 4.30
677
     * @return a String containing conformant HTML markup which
678
     *  represents the display name, or null.  The default implementation
679
     *  returns null.  */
680
    public String getHtmlDisplayName() {
681
        return null;
682
    }
683
    
658
    /** Register delegating lookup so it can always be found.
684
    /** Register delegating lookup so it can always be found.
659
     */
685
     */
660
    final void registerDelegatingLookup (NodeLookup l) {
686
    final void registerDelegatingLookup (NodeLookup l) {
Lines 1051-1056 Link Here
1051
        public int hashCode () {
1077
        public int hashCode () {
1052
            return getName().hashCode ();
1078
            return getName().hashCode ();
1053
        }
1079
        }
1080
        
1081
        /** Return a variant of the display name containing HTML markup
1082
         * conforming to the limited subset of font-markup HTML supported by
1083
         * the lightweight HTML renderer org.openide.awt.HtmlRenderer
1084
         * (font color, bold, italic and strikethrough supported; font
1085
         * colors can be UIManager color keys if they are prefixed with
1086
         * a ! character, i.e. &lt;font color=&amp;'controlShadow'&gt;).
1087
         * Enclosing html tags are not needed.
1088
         * <p><strong>This method should return either an HTML display name
1089
         * or null; it should not return the non-html display name if no
1090
         * markup is needed.
1091
         *
1092
         * @see org.openide.awt.HtmlRenderer
1093
         * @since 4.30
1094
         * @return a String containing conformant, legal HTML markup which
1095
         *  represents the display name, or null.  The default implementation
1096
         *  returns null.  */
1097
        public String getHtmlDisplayName() {
1098
            return null;
1099
        }
1054
    }
1100
    }
1055
1101
1056
    /** Description of a Bean property on a node, and operations on it.
1102
    /** Description of a Bean property on a node, and operations on it.
Lines 1197-1202 Link Here
1197
            
1243
            
1198
            return getName ().hashCode () * 
1244
            return getName ().hashCode () * 
1199
                   ( valueType == null ? 1 : valueType.hashCode () );
1245
                   ( valueType == null ? 1 : valueType.hashCode () );
1246
        }
1247
1248
        /** Return a variant of the display name containing HTML markup 
1249
         * conforming to the limited subset of font-markup HTML supported by
1250
         * the lightweight HTML renderer org.openide.awt.HtmlRenderer 
1251
         * (font color, bold, italic and strikethrough supported; font
1252
         * colors can be UIManager color keys if they are prefixed with
1253
         * a ! character, i.e. &lt;font color=&amp;'controlShadow'&gt;).
1254
         * Enclosing html tags are not needed.
1255
         * <p><strong>This method should return either an HTML display name
1256
         * or null; it should not return the non-html display name.
1257
         * 
1258
         * @see org.openide.awt.HtmlRenderer
1259
         * @since 4.30
1260
         * @return a String containing conformant, legal HTML markup which
1261
         *  represents the display name, or null.  The default implementation
1262
         *  returns null.  */
1263
        public String getHtmlDisplayName() {
1264
            return null;
1200
        }
1265
        }
1201
    }
1266
    }
1202
1267
(-)openide/test/unit/src/org/openide/nodes/NodeHtmlTest.java (+102 lines)
Added Link Here
1
/*
2
 *                 Sun Public License Notice
3
 * 
4
 * The contents of this file are subject to the Sun Public License
5
 * Version 1.0 (the "License"). You may not use this file except in
6
 * compliance with the License. A copy of the License is available at
7
 * http://www.sun.com/
8
 * 
9
 * The Original Code is NetBeans. The Initial Developer of the Original
10
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun
11
 * Microsystems, Inc. All Rights Reserved.
12
 */
13
14
package org.openide.nodes;
15
16
import java.util.*;
17
import junit.framework.*;
18
import junit.textui.TestRunner;
19
import org.openide.nodes.*;
20
21
import org.netbeans.junit.*;
22
23
/** Tests HTML display name contracts for filter nodes and regular nodes 
24
 *
25
 * @author Tim Boudreau
26
 */
27
public class NodeHtmlTest extends NbTestCase {
28
    
29
    public NodeHtmlTest(String name) {
30
        super(name);
31
    }
32
    
33
    public static void main(String[] args) {
34
        TestRunner.run(new NbTestSuite(FilterNodeTest.class));
35
    }
36
    
37
    public void testDefaultHtmlDisplayNameIsNull() {
38
        AbstractNode a = new AbstractNode (Children.LEAF);
39
        a.setDisplayName("Finch");
40
        assertEquals("AbstractNode.getDisplayName is broken", "Finch", 
41
            a.getDisplayName());
42
        
43
        assertNull("Unless overridden, getHtmlDisplayName should return null, " +
44
            "not " + a.getHtmlDisplayName(), a.getHtmlDisplayName());
45
        
46
        FilterNode fn = new FilterNode (a);
47
        assertNull ("Filternode should have no default html display name unless" +
48
            " its original overrides getHtmlDisplayName", fn.getHtmlDisplayName());
49
    }
50
    
51
    public void testFilteredHtmlNameIsPropagated() {
52
        Node n = new HtmlNode();
53
        n.setDisplayName ("Whipporwill");
54
        FilterNode fn = new FilterNode (n);
55
        
56
        assertNotNull("This test is broken", n.getHtmlDisplayName());
57
        
58
        assertNotNull("If a filter node's original supplies an html display " +
59
            "name, the filter node's html display name should be non-null",
60
            fn.getHtmlDisplayName());
61
        
62
        assertEquals("FilterNode should propagate the html name of the original",
63
            fn.getHtmlDisplayName(), n.getHtmlDisplayName());
64
    }
65
    
66
    public void testFilteredHtmlNameNotPropagatedIfGetDisplayNameOverridden() {
67
        Node n = new HtmlNode();
68
        n.setDisplayName ("Lark");
69
        FilterNode fn = new HtmlDisplayNameNode (n);
70
71
        assertNotNull("This test is broken", n.getHtmlDisplayName());
72
        
73
        assertNotNull("This test is broken", n.getHtmlDisplayName());
74
        
75
        assertNull ("A filternode whose getDisplayName() method is overridden" +
76
            " should return null from getHtmlDisplayName() even though its " +
77
            " original returns non-null - got " + fn.getHtmlDisplayName(), 
78
            fn.getHtmlDisplayName());
79
    }
80
    
81
    
82
    private static final String HTML_STRING = "<b>this is <i>html</i></b>";
83
    private static class HtmlNode extends AbstractNode {
84
        public HtmlNode() {
85
            super (Children.LEAF);
86
        }
87
        
88
        public String getHtmlDisplayName() {
89
            return HTML_STRING;
90
        }
91
    }
92
    
93
    private static class HtmlDisplayNameNode extends FilterNode {
94
        public HtmlDisplayNameNode (Node orig) {
95
            super (orig);
96
        }
97
        public String getDisplayName() {
98
            return "Not the same name!";
99
        }
100
    }
101
}
102
(-)openide/test/unit/src/org/openide/windows/TopComponentActivatedNodesTest.java (+4 lines)
Lines 62-67 Link Here
62
        return suite;
62
        return suite;
63
    }
63
    }
64
    
64
    
65
    protected boolean runInEQ() {
66
        return true;
67
    }    
68
    
65
    private ExplorerPanel p;
69
    private ExplorerPanel p;
66
    private ExplorerManager em;
70
    private ExplorerManager em;
67
    private Node[] nodes;
71
    private Node[] nodes;
(-)projects/projectui/src/org/netbeans/modules/project/ui/ProjectsRootNode.java (+4 lines)
Lines 212-217 Link Here
212
            return isMain() ? MessageFormat.format( badgedNamePattern, new Object[] { original } ) : original;
212
            return isMain() ? MessageFormat.format( badgedNamePattern, new Object[] { original } ) : original;
213
        }
213
        }
214
214
215
        public String getHtmlDisplayName() {
216
            return isMain() ? "<b>" + super.getDisplayName() + "</b>" : null; //NOI18N
217
        }
218
215
        public Image getIcon( int type ) {
219
        public Image getIcon( int type ) {
216
            Image original = super.getIcon( type );                
220
            Image original = super.getIcon( type );                
217
            return isMain() ? Utilities.mergeImages( original, mainProjectBadge, 5, 10 ) : original;
221
            return isMain() ? Utilities.mergeImages( original, mainProjectBadge, 5, 10 ) : original;
(-)vcscore/src/org/netbeans/modules/vcscore/VcsFileSystem.java (-1 / +9 lines)
Lines 105-111 Link Here
105
                                                                          AbstractFileSystem.List, AbstractFileSystem.Info,
105
                                                                          AbstractFileSystem.List, AbstractFileSystem.Info,
106
                                                                          AbstractFileSystem.Change, FileSystem.Status,
106
                                                                          AbstractFileSystem.Change, FileSystem.Status,
107
                                                                          CommandExecutionContext, CacheHandlerListener,
107
                                                                          CommandExecutionContext, CacheHandlerListener,
108
                                                                          FileObjectExistence, VcsOISActivator, Serializable {
108
                                                                          FileObjectExistence, VcsOISActivator, Serializable,
109
                                                                          FileSystem.HtmlStatus {
109
110
110
    public static interface IgnoreListSupport {
111
    public static interface IgnoreListSupport {
111
112
Lines 2591-2596 Link Here
2591
        } else {
2592
        } else {
2592
            result = displayName;
2593
            result = displayName;
2593
        }
2594
        }
2595
        return result;
2596
    }
2597
2598
    public String annotateNameHtml (String name, java.util.Set files) {
2599
        String result = annotateName (name, files);
2600
        result = Utilities.replaceString(result, name,
2601
            name + "<font color='!controlShadow'>") + "</font>"; //NOI18N
2594
        return result;
2602
        return result;
2595
    }
2603
    }
2596
2604

Return to bug 29466