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/swing/tabcontrol/nbproject/project.xml (-2 / +11 lines)
Lines 17-29 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.30</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>
24
                <package>org.netbeans.swing.tabcontrol.event</package>
34
                <package>org.netbeans.swing.tabcontrol.event</package>
25
            </public-packages>
35
            </public-packages>
26
            <!--<unit-tests/>-->
27
            <javadoc/>
36
            <javadoc/>
28
        </data>
37
        </data>
29
    </configuration>
38
    </configuration>
(-)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 (-97 / +109 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
    /** Creates a new instance of TabListPanel */
80
        //Apparently the code that changes selection on enter is
77
    private TabListPopup() {
81
        //not an action in JTable's action map.  Override it entirely.
78
        super (new TabListPopupTableModel());
82
        if (ke.getKeyCode() == ke.VK_ENTER) {
79
        //Set up a line border around the edges
83
            int row = getSelectedRow();
80
        setBorder (
84
            int col = getSelectedColumn();
81
            BorderFactory.createLineBorder(getForeground()));        
85
            setSelectedTab(row, col);
82
        setShowHorizontalLines(false);
86
            hideCurrentPopup();
83
        setBackground (UIManager.getColor("ComboBox.background")); //NOI18N
87
        } else if (ke.getKeyCode() == ke.VK_ESCAPE) {
84
        setDefaultRenderer(Object.class, HtmlRenderer.sharedRenderer());
88
            hideCurrentPopup();
89
        } else {
90
            super.processKeyEvent(ke);
91
        }
92
    }
85
    }
93
86
94
    /**
87
    /**
Lines 131-164 Link Here
131
        super.setFont(f);
124
        super.setFont(f);
132
    }
125
    }
133
126
134
    public Component prepareRenderer(TableCellRenderer renderer, int row,
127
    public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
135
                                     int column) {
128
        //Will always use the default instance of HtmlRenderer
136
        Component result = super.prepareRenderer(renderer, row, column);
129
        JComponent result = (JComponent)
137
        TabData value = (TabData) getValueAt(row, column);
130
            super.prepareRenderer (renderer, row, column);
138
131
132
        //Find our TabData object
133
        Object value = getTTModel().getValueAt(row, column);
134
        if (value == null) {
135
            //it's a filler space, we're done
136
            return result;
137
        }
138
        
139
        //Set up font, selection, icon, colors, borders
139
        int selIdx = displayer.getSelectionModel().getSelectedIndex();
140
        int selIdx = displayer.getSelectionModel().getSelectedIndex();
140
        boolean isSelTab = false;
141
        boolean isSelTab = selIdx != -1 ? 
141
        if (selIdx != -1) {
142
            value == displayer.getModel().getTab(selIdx) 
142
            isSelTab = value == displayer.getModel().getTab (selIdx);
143
            : false;
144
145
        if (isSelTab) {
146
            result.setFont (getFont().deriveFont (Font.BOLD));
143
        }
147
        }
144
148
145
        result.setFont(
149
        Icon icon = ((TabData) value).getIcon();
146
                isSelTab ? getFont().deriveFont(Font.BOLD) : getFont());
150
147
        boolean sel = row == getSelectedRow()
151
        HtmlRenderer.Renderer ren = (HtmlRenderer.Renderer) result;
148
                && column == getSelectedColumn() && value != null;
152
        ren.setIcon(icon);
149
        if (sel) {
153
        
150
            result.setBackground(getSelectionBackground());
154
        if (icon.getIconWidth() > 0) {
151
            result.setForeground(getSelectionForeground());
155
            //Max annotated icon width is 24, so to have all the text and all
156
            //the icons come out aligned, set the icon text gap to the difference
157
            //plus a two pixel margin
158
            ren.setIconTextGap (26 - icon.getIconWidth());
152
        } else {
159
        } else {
153
            result.setBackground(getBackground());
160
            //If the icon width is 0, fill the space and add in
154
            result.setForeground(getForeground());
161
            //the extra two pixels so the node names are aligned (btw, this
162
            //does seem to waste a frightful amount of horizontal space in
163
            //a tree that can use all it can get)
164
            ren.setIndent (26);
155
        }
165
        }
166
        
167
        //The table may not really have focus, but it should always use the focus
168
        //color for the selection, not controlShadow
169
        ((HtmlRenderer.Renderer) result).setParentFocused(true);
170
        result.setBorder (rendererBorder);
171
        ((JComponent)result).setOpaque(true);
172
        if (!isSelTab) {
173
            result.setBackground (getSelectionBackground());
174
        } 
175
        
156
        return result;
176
        return result;
157
    }
177
    }
158
178
159
179
160
    boolean needCalcRowHeight = true;
161
162
    /**
180
    /**
163
     * Calculate the height of rows based on the current font.  This is done
181
     * 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
182
     * when the first paint occurs, to ensure that a valid Graphics object is
Lines 213-220 Link Here
213
        int currCt = mdl.getColumnCount();
231
        int currCt = mdl.getColumnCount();
214
        if (currCt < count) {
232
        if (currCt < count) {
215
            for (int i = currCt; i < count; i++) {
233
            for (int i = currCt; i < count; i++) {
216
                mdl.addColumn(new TableColumn(i, 75, getTTModel(), null));
234
                mdl.addColumn(new TableColumn(i, 75, 
217
            }
235
                    HtmlRenderer.sharedRenderer(), null));            }
218
        } else if (currCt > count) {
236
        } else if (currCt > count) {
219
            for (int i = currCt - 1; i >= count; i--) {
237
            for (int i = currCt - 1; i >= count; i--) {
220
                mdl.removeColumn(mdl.getColumn(i));
238
                mdl.removeColumn(mdl.getColumn(i));
Lines 222-256 Link Here
222
        }
240
        }
223
    }
241
    }
224
242
225
    private Dimension prefSize = null;
243
    /** Overridden to calculate a preferred size based on the current optimal
226
244
     * number of columns, and set up the preferred width for each column based
245
     * on the maximum width tab name & icon displayed in it */
227
    public Dimension getPreferredSize() {
246
    public Dimension getPreferredSize() {
228
        if (prefSize == null) {
247
        if (prefSize == null) {
229
            Dimension d = new Dimension();
248
            Insets ins = getInsets();
249
            
250
            prefSize = new Dimension(ins.left + ins.top, ins.right + ins.bottom);
230
            int cols = getColumnCount();
251
            int cols = getColumnCount();
231
            int rows = getRowCount();
252
            int rows = getRowCount();
232
            for (int i = 0; i < cols; i++) {
253
            
233
                int currWidth = 0;
254
            //Iterate the columns
234
                for (int j = 0; j < rows; j++) {
255
            for (int i=0; i < cols; i++) {
235
                    TableCellRenderer ren = getCellRenderer(j, i);
256
                int columnWidth = 0;
236
                    Component c = ren.getTableCellRendererComponent(this,
257
                //For each column, iterate the rows
237
                                                                    getModel()
258
                for (int j=0; j < rows; j++) {
238
                                                                    .getValueAt(
259
                    TableCellRenderer ren = getCellRenderer(j,i);
239
                                                                            j,
260
                    Component c = prepareRenderer (ren, j, i);
240
                                                                            i),
261
                    //find the widest cell
241
                                                                    false,
262
                    columnWidth = Math.max (c.getPreferredSize().width, 
242
                                                                    false, j,
263
                        columnWidth);
243
                                                                    i);
244
                    prepareRenderer(ren, j, i);
245
                    Dimension curr = c.getPreferredSize();
246
                    currWidth = Math.max(curr.width, currWidth);
247
                }
264
                }
248
                d.width += currWidth;
265
                //Add in the max width needed for this column to the total
249
                getColumnModel().getColumn(i).setPreferredWidth(currWidth);
266
                //width
267
                prefSize.width += columnWidth;
268
                //Store it in the column model so it will be displayed with
269
                //the right width
270
                getColumnModel().getColumn(i).setPreferredWidth(columnWidth);
250
            }
271
            }
251
            d.height = rows * getRowHeight();
272
            //Rows will be fixed height, so just multiply it out
252
273
            prefSize.height += rows * getRowHeight();
253
            prefSize = d;
254
        }
274
        }
255
        return prefSize;
275
        return prefSize;
256
    }
276
    }
Lines 290-298 Link Here
290
        invocationTime = System.currentTimeMillis();
310
        invocationTime = System.currentTimeMillis();
291
    }
311
    }
292
312
293
    Component invokingComponent = null;
294
    long invocationTime = -1;
295
296
    public void removeNotify() {
313
    public void removeNotify() {
297
        super.removeNotify();
314
        super.removeNotify();
298
        removeMouseListener(this);
315
        removeMouseListener(this);
Lines 465-473 Link Here
465
482
466
    }
483
    }
467
484
468
    private static Popup currentPopup = null;
469
    private static BackupListener blistener = null;
470
471
    public synchronized static void hideCurrentPopup() {
485
    public synchronized static void hideCurrentPopup() {
472
        if (currentPopup != null) {
486
        if (currentPopup != null) {
473
            //Issue 41121 - use invokeLater to allow any pending
487
            //Issue 41121 - use invokeLater to allow any pending
Lines 497-504 Link Here
497
            toHide.hide();
511
            toHide.hide();
498
         }
512
         }
499
     }     
513
     }     
500
501
    private static Reference instance = null;
502
514
503
    private static TabListPopup sharedInstance() {
515
    private static TabListPopup sharedInstance() {
504
        TabListPopup result = null;
516
        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 = true;
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.29
7
openide.specification.version=4.30
(-)openide/loaders/src/org/openide/loaders/DataNode.java (+31 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
     public String getHtmlDisplayName() {
189
         try {
190
             FileSystem.Status stat = 
191
                 obj.getPrimaryFile().getFileSystem().getStatus();
192
             if (stat instanceof HtmlStatus) {
193
                 HtmlStatus hstat = (HtmlStatus) stat;
194
                 
195
                 String result = hstat.annotateNameHtml (
196
                     super.getDisplayName(), new LazyFilesSet());
197
                 
198
                 //Make sure the super string was really modified
199
                 if (!super.getDisplayName().equals(result)) {
200
                     return result;
201
                 }
202
             }
203
         } catch (FileStateInvalidException e) {
204
             //do nothing and fall through
205
         }
206
         return super.getHtmlDisplayName();
207
     }    
177
208
178
    /** Get the displayed icon for this node.
209
    /** Get the displayed icon for this node.
179
     * A filesystem may {@link org.openide.filesystems.FileSystem#getStatus specially alter} this.
210
     * 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 249-255 Link Here
249
    }
249
    }
250
250
251
    
251
    
252
    private static final class StatusImpl implements FileSystem.Status {
252
    private static final class StatusImpl implements FileSystem.HtmlStatus {
253
        public Image annotateIcon(Image icon, int iconType, Set files) {
253
        public Image annotateIcon(Image icon, int iconType, Set files) {
254
            //int size = files.size();
254
            //int size = files.size();
255
            Set transformedSet = new HashSet();
255
            Set transformedSet = new HashSet();
Lines 280-285 Link Here
280
                name = (fs != null) ? fs.getStatus().annotateName(name, transformedSet) : name;
280
                name = (fs != null) ? fs.getStatus().annotateName(name, transformedSet) : name;
281
            }
281
            }
282
            return name;
282
            return name;
283
        }
284
285
        public String annotateNameHtml(String name, Set files) {
286
            Set transformedSet = new HashSet();
287
            MasterFileObject hfo = Utils.transformSet(files, transformedSet);
288
            if (hfo != null) {
289
                FileSystem fs = hfo.getDelegateFileSystem();
290
                if (fs != null && fs.getStatus() instanceof FileSystem.HtmlStatus) {
291
                    return ((FileSystem.HtmlStatus) fs.getStatus()).annotateNameHtml(name, transformedSet);
292
                }
293
            }
294
            return null;
283
        }
295
        }
284
    }
296
    }
285
}
297
}
(-)openide/src/org/openide/awt/HtmlLabelUI.java (+368 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
import org.openide.awt.HtmlRenderer;
17
18
import javax.swing.*;
19
import javax.swing.plaf.LabelUI;
20
import javax.swing.plaf.ComponentUI;
21
import java.awt.*;
22
import java.util.Map;
23
import java.util.HashMap;
24
25
/**
26
 * A LabelUI which uses the lightweight HTML renderer.  Stateless - only one instance should ever exist.
27
 */
28
public class HtmlLabelUI extends LabelUI {
29
    /** System property to automatically turn on antialiasing for html strings */
30
    private static final boolean antialias = Boolean.getBoolean(
31
        "nb.cellrenderer.antialiasing"); //NOI18N
32
33
    private static HtmlLabelUI uiInstance = null;
34
35
    public static ComponentUI createUI (JComponent c) {
36
        assert c instanceof HtmlRendererImpl;
37
        if (uiInstance == null) {
38
            uiInstance = new HtmlLabelUI();
39
        }
40
        return uiInstance;
41
    }
42
43
    public Dimension getPreferredSize(JComponent c) {
44
        return calcPreferredSize ((HtmlRendererImpl) c);
45
    }
46
47
    /** Get the width of the text */
48
    private static int textWidth(String text, Graphics g, Font f, boolean html) {
49
        if (text != null) {
50
            if (html) {
51
                return Math.round(Math.round(HtmlRenderer.renderHTML(text, g, 0, 0,
52
                    Integer.MAX_VALUE, Integer.MAX_VALUE, f,
53
                    Color.BLACK, HtmlRenderer.STYLE_TRUNCATE, false)));
54
            } else {
55
                return Math.round(Math.round(HtmlRenderer.renderPlainString(text, g, 0, 0,
56
                    Integer.MAX_VALUE, Integer.MAX_VALUE, f,
57
                    Color.BLACK, HtmlRenderer.STYLE_TRUNCATE, false)));
58
            }
59
        } else {
60
            return 0;
61
        }
62
    }
63
64
    private Dimension calcPreferredSize(HtmlRendererImpl r) {
65
        Insets ins = r.getInsets();
66
        Dimension prefSize = new java.awt.Dimension(ins.left + ins.right, ins.top + ins.bottom);
67
        String text = r.getText();
68
69
        Graphics g = r.getGraphics();
70
        Icon icon = r.getIcon();
71
72
        if (text != null) {
73
            FontMetrics fm = g.getFontMetrics (r.getFont());
74
                prefSize.height += fm.getMaxAscent() + fm.getMaxDescent();
75
        }
76
77
        if (icon != null) {
78
            if (r.isCentered()) {
79
                prefSize.height += icon.getIconHeight() + r.getIconTextGap();
80
                prefSize.width += icon.getIconWidth();
81
            } else {
82
                prefSize.height = Math.max (icon.getIconHeight(), prefSize.height);
83
                prefSize.width += icon.getIconWidth() + r.getIconTextGap();
84
            }
85
        }
86
87
        //Antialiasing affects the text metrics, so use it if needed when
88
        //calculating preferred size or the result here will be narrower
89
        //than the space actually needed
90
        if (antialias) {
91
            //For L&Fs such as Aqua and SmoothMetal, we will need to manually apply
92
            //rendering hints to get antialiasing, since we're doing our
93
            //own painting logic - they don't do this for things they don't
94
            //know about
95
            ((Graphics2D)g).addRenderingHints(getHints());
96
        }
97
98
        int textwidth = textWidth(text, g, r.getFont(), r.isHtml()) + 2;
99
        if (r.isCentered()) {
100
            prefSize.width = Math.max (prefSize.width, textwidth + ins.right + ins.left);
101
        } else {
102
            prefSize.width += textwidth + r.getIndent();
103
        }
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
        assert SwingUtilities.isEventDispatchThread();
158
159
        Font f = r.getFont();
160
        g.setFont(f);
161
        FontMetrics fm = g.getFontMetrics();
162
163
        //Find out what height we need
164
        int txtH = fm.getHeight();
165
        Insets ins = r.getInsets();
166
167
        //find out the available height less the insets
168
        int availH = r.getHeight() - (ins.top + ins.bottom);
169
        int txtY;
170
        if (availH >= txtH) {
171
            //Center the text if we have space
172
            txtY = txtH + ins.top + ((availH / 2) - (txtH / 2)) - 4;
173
        } else {
174
            //Okay, it's not going to fit, punt.
175
            txtY = 0;//txtH + ins.top - 1;
176
        }
177
        int txtX = r.getIndent();
178
179
        Icon icon = r.getIcon();
180
        //Check the icon non-null and height (see TabData.NO_ICON for why)
181
        if (icon != null && icon.getIconWidth() > 0 && icon.getIconHeight() > 0) {
182
            int iconY;
183
            if (availH > icon.getIconHeight()) {
184
                //add 2 to make sure icon top pixels are not cut off by outline
185
                iconY = ins.top + ((availH / 2) - (icon.getIconHeight() / 2));// + 2;
186
            } else if (availH == icon.getIconHeight()){
187
                //They're an exact match, make it 0
188
                iconY = 0;
189
            } else {
190
                //Won't fit; make the top visible and cut the rest off (option:
191
                //center it and clip it on top and bottom - probably even harder
192
                //to recognize that way, though)
193
                iconY = ins.top;
194
            }
195
            //add in the insets
196
            int iconX = ins.left + r.getIndent();
197
            try {
198
                //Diagnostic - the CPP module currently is constructing
199
                //some ImageIcon from a null image in Options.  So, catch it and at
200
                //least give a meaningful message that indicates what node
201
                //is the culprit
202
                icon.paintIcon(r, g, iconX, iconY);
203
204
            } catch (NullPointerException npe) {
205
                ErrorManager.getDefault().annotate(npe, ErrorManager.EXCEPTION,
206
                    "Probably an ImageIcon with a null source image: " +
207
                    r.getText(), null, null, null); //NOI18N
208
                ErrorManager.getDefault().notify(npe);
209
            }
210
            txtX = iconX + icon.getIconWidth() + r.getIconTextGap();
211
        } else {
212
            //If there's no icon, paint the text where the icon would start
213
            txtX += ins.left;
214
        }
215
216
        String text = r.getText();
217
        if (text == null) {
218
            //No text, we're done
219
            return;
220
        }
221
222
        //Get the available horizontal pixels for text
223
        int txtW = icon != null ? r.getWidth() - (ins.left + ins.right +
224
            icon.getIconWidth() + r.getIconTextGap() + r.getIndent()) : r.getWidth() -
225
            (ins.left + ins.right + r.getIndent());
226
227
        Color foreground = getForegroundFor (r);
228
229
        if (r.isHtml()) {
230
            HtmlRenderer.renderHTML(text, g, txtX, txtY, txtW, txtH, f,
231
                foreground, r.getRenderStyle(), true);
232
        } else {
233
            HtmlRenderer.renderString(text, g, txtX, txtY, txtW, txtH, f,
234
                foreground, r.getRenderStyle(), true);
235
        }
236
    }
237
238
    private void paintIconAndTextCentered (Graphics g, HtmlRendererImpl r) {
239
        Insets ins = r.getInsets();
240
        Icon ic = r.getIcon();
241
        int w = r.getWidth() - (ins.left + ins.right);
242
        int txtX = ins.left;
243
        int txtY = 0;
244
        if (ic != null && ic.getIconWidth() > 0 && ic.getIconHeight() > 0) {
245
            int iconx = w > ic.getIconWidth() ? (w / 2) - (ic.getIconWidth() / 2) : txtX;
246
            int icony = 0;
247
            ic.paintIcon(r, g, iconx, icony);
248
            txtY += ic.getIconHeight() + r.getIconTextGap();
249
        }
250
        int txtW = r.getPreferredSize().width;
251
        txtX = txtW < r.getWidth() ? (r.getWidth() / 2) - (txtW / 2) : 0;
252
        int txtH = r.getHeight() - txtY;
253
254
        Font f = r.getFont();
255
        g.setFont(f);
256
        FontMetrics fm = g.getFontMetrics(f);
257
        txtY += fm.getMaxAscent();
258
259
        Color foreground = getForegroundFor (r);
260
        if (r.isHtml()) {
261
            HtmlRenderer.renderHTML(r.getText(), g, txtX, txtY, txtW, txtH,f,
262
                foreground, r.getRenderStyle(), true);
263
        } else {
264
            HtmlRenderer.renderString(r.getText(), g, txtX, txtY, txtW, txtH, r.getFont(),
265
                foreground, r.getRenderStyle(), true);
266
        }
267
    }
268
269
    static Color getBackgroundFor (HtmlRendererImpl r) {
270
        if (r.isOpaque()) {
271
            return r.getBackground();
272
        }
273
274
        if (r.isSelected() && !r.isParentFocused() && !isGTK()) {
275
            return getUnfocusedSelectionBackground();
276
        }
277
278
        if (isGTK()) {
279
            //GTK does its own thing, we'll only screw it up by painting the background ourselves
280
            return null;
281
        }
282
283
        Color result = null;
284
285
        if (r.isSelected()) {
286
            switch (r.getType()) {
287
                case HtmlRendererImpl.TYPE_LIST :
288
                    result = UIManager.getColor ("List.selectionBackground"); //NOI18N
289
                    break;
290
                case HtmlRendererImpl.TYPE_TABLE :
291
                    result = UIManager.getColor ("Table.selectionBackground"); //NOI18N
292
                    break;
293
                case HtmlRendererImpl.TYPE_TREE :
294
                    return UIManager.getColor ("Tree.selectionBackground"); //NOI18N
295
            }
296
            return result == null ? r.getBackground() : result;
297
        }
298
        return null;
299
    }
300
301
    static Color getForegroundFor (HtmlRendererImpl r) {
302
        if (r.isSelected() && !r.isParentFocused()) {
303
            return getUnfocusedSelectionForeground();
304
        }
305
306
        if (!r.isEnabled()) {
307
            return UIManager.getColor ("textInactiveText"); //NOI18N
308
        }
309
310
        Color result = null;
311
        if (r.isSelected()) {
312
            switch (r.getType()) {
313
                case HtmlRendererImpl.TYPE_LIST :
314
                    result = UIManager.getColor ("List.selectionForeground"); //NOI18N
315
                case HtmlRendererImpl.TYPE_TABLE :
316
                    result = UIManager.getColor ("Table.selectionForeground"); //NOI18N
317
                case HtmlRendererImpl.TYPE_TREE :
318
                    result = UIManager.getColor ("Tree.selectionForeground"); //NOI18N
319
            }
320
        }
321
        return result == null ? r.getForeground() : result;
322
    }
323
324
    private static boolean isGTK() {
325
        return "GTK".equals (UIManager.getLookAndFeel().getID());
326
    }
327
328
    private static Color unfocusedSelBg = null;
329
    private static Color unfocusedSelFg = null;
330
    /** Get the system-wide unfocused selection background color */
331
    private static Color getUnfocusedSelectionBackground() {
332
        if (unfocusedSelBg == null) {
333
            //allow theme/ui custom definition
334
            unfocusedSelBg =
335
            UIManager.getColor("nb.explorer.unfocusedSelBg"); //NOI18N
336
            if (unfocusedSelBg == null) {
337
                //try to get standard shadow color
338
                unfocusedSelBg = UIManager.getColor("controlShadow"); //NOI18N
339
                if (unfocusedSelBg == null) {
340
                    //Okay, the look and feel doesn't suport it, punt
341
                    unfocusedSelBg = Color.lightGray;
342
                }
343
                //Lighten it a bit because disabled text will use controlShadow/
344
                //gray
345
                unfocusedSelBg = unfocusedSelBg.brighter();
346
            }
347
        }
348
        return unfocusedSelBg;
349
    }
350
351
    /** Get the system-wide unfocused selection foreground color */
352
    private static Color getUnfocusedSelectionForeground() {
353
        if (unfocusedSelFg == null) {
354
            //allow theme/ui custom definition
355
            unfocusedSelFg =
356
            UIManager.getColor("nb.explorer.unfocusedSelFg"); //NOI18N
357
            if (unfocusedSelFg == null) {
358
                //try to get standard shadow color
359
                unfocusedSelFg = UIManager.getColor("textText"); //NOI18N
360
                if (unfocusedSelFg == null) {
361
                    //Okay, the look and feel doesn't suport it, punt
362
                    unfocusedSelFg = Color.BLACK;
363
                }
364
            }
365
        }
366
        return unfocusedSelFg;
367
    }
368
}
(-)openide/src/org/openide/awt/HtmlRenderer.java (+1187 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
/** A generic cell renderer class which implements
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
 * If you only need to paint some HTML quickly, use the static methods for
40
 * painting - <code>renderString</code>, <code>renderPlainString</code> or
41
 * <code>renderHtml</code>.  These methods differ as follows:
42
 * <ul>
43
 * <li><b>renderString</b> will check the string for opening HTML tags
44
 * (upper or lower but not mixed case) and call either <code>renderPlainString</code>
45
 * or <code>renderHtml</code> as appropriate.  Note this method does not tolerate
46
 * whitespace in opening html tags - it expects exactly 6 characters to make up
47
 * the opening tag if present.</li>
48
 * <li><b>renderPlainString</b> simply renders a string to the graphics context,
49
 * takes the same agruments as <code>renderHtml</code>, but will also honor
50
 * <code>STYLE_TRUNCATE</code>, so strings can be rendered with trailing
51
 * elipsis if there is not enough space</li>
52
 * <li><b>renderHtml</b> renders whatever is passed to it as HTML, regardless
53
 * of whether it has opening HTML tags or not.  It can be used to render plain
54
 * strings, but <code>renderPlainString</code> is faster for that. It is useful
55
 * if you want to render a string you <strong>know</strong> to be compliant
56
 * HTML markup, but which does not have opening and closing HTML tags (though
57
 * they are harmless if present). </li>
58
 * <p>
59
 * This parser is designed entirely for performance; there are no separate parsing
60
 * and rendering loops.  In order to acheive its performance, some trade offs
61
 * are required.
62
 * <strong>To reiterate: This is not a forgiving HTML parser - the HTML supplied
63
 * must follow the guidelines documented here!</strong>
64
 * <p>
65
 * The following tags are supported, in upper or lower (but not mixed) case:
66
 *
67
 * <table>
68
 * <tr>
69
 *  <td>&lt;B&gt;</td>
70
 *  <td>Boldface text</td>
71
 * </tr>
72
 * <tr>
73
 *  <td>&lt;S&gt;</td>
74
 *  <td>Strikethrough text</td>
75
 * </tr>
76
 * <tr>
77
 *  <td>&lt;U&gt;</td>
78
 *  <td>Underline text</td>
79
 * </tr>
80
 * <tr>
81
 *  <td>&lt;I&gt;</td>
82
 *  <td>Italic text</td>
83
 * </tr>
84
 * <tr>
85
 *  <td>&lt;EM&gt;</td>
86
 *  <td>Emphasized text (same as italic)</td>
87
 * </tr>
88
 * <tr>
89
 *  <td>&lt;STRONG&gt;</td>
90
 *  <td>Strong text (same as bold)</td>
91
 * </tr>
92
 * <tr>
93
 *  <td>&lt;font&gt;</td>
94
 *  <td>Font color - font attributes other than color are not supported.  Colors
95
 *  may be specified as hexidecimal strings, such as #FF0000 or as logical colors
96
 *  defined in the current look and feel by specifying a ! character as the first
97
 *  character of the color name.  Logical colors are colors available from the
98
 *  current look and feel's UIManager.  For example, <code>&lt;font
99
 *  color=&quot;!Tree.background&quot;&gt;</code> will set the font color to the
100
 *  result of <code>UIManager.getColor(&quot;Tree.background&quot;)</code>.
101
 * <strong>Font size tags are not supported.</strong>
102
 * </td>
103
 * </tr>
104
 * </table>
105
 * The lightweight html renderer supports the following named sgml character
106
 * entities: <code>quot, lt, amp, lsquo, rsquo, ldquo, rdquo, ndash, mdash, ne,
107
 * le, ge, copy, reg, trade.  </code>.  It also supports numeric entities
108
 * (e.g. <code>&amp;8822;</code>).
109
 * <p><b>Why not use the JDK's HTML support?</b> The JDK's HTML support works
110
 * well for stable components, but suffers from performance problems in the
111
 * case of cell renderers - each call to set the text (which happens once per
112
 * cell, per paint) causes a document tree to be created in memory.  For small,
113
 * markup only strings, this is overkill.   For rendering short strings
114
 * (for example, in a tree or table cell renderer)
115
 * with limited HTML, this method is approximately 10x faster than standard
116
 * Swing HTML rendering.
117
 *
118
 * <P><B><U>Specifying logical colors</U></B><BR>
119
 * Hardcoded text colors are undesirable, as they can be incompatible (even
120
 * invisible) on some look and feels or themes, depending on the background
121
 * color.
122
 * The lightweight HTML renderer supports a non-standard syntax for specifying
123
 * font colors via a key for a color in the UI defaults for the current look
124
 * and feel.  This is accomplished by prefixing the key name with a <code>!</code>
125
 * character.  For example: <code>&lt;font color='!controlShadow'&gt;</code>.
126
 *
127
 * <P><B><U>Modes of operation</U></B><BR>
128
 * This method supports two modes of operation:
129
 * <OL>
130
 * <LI><CODE>STYLE_CLIP</CODE> - as much text as will fit in the pixel width passed
131
 * to the method should be painted, and the text should be cut off at the maximum
132
 * width or clip rectangle maximum X boundary for the graphics object, whichever is
133
 * smaller.</LI>
134
 * <LI><CODE>STYLE_TRUNCATE</CODE> - paint as much text as will fit in the pixel
135
 * width passed to the method, but paint the last three characters as .'s, in the
136
 * same manner as a JLabel truncates its text when the available space is too
137
 * small.</LI>
138
 * </OL>
139
 * <P>
140
 * The paint methods can also be used in non-painting mode to establish the space
141
 * necessary to paint a string.  This is accomplished by passing the value of the
142
 * <code>paint</code> argument as false.  The return value will be the required
143
 * width in pixels
144
 * to display the text.  Note that in order to retrieve an
145
 * accurate value, the argument for available width should be passed
146
 * as <code>Integer.MAX_VALUE</code> or an appropriate maximum size - otherwise
147
 * the return value will either be the passed maximum width or the required
148
 * width, whichever is smaller.  Also, the clip shape for the passed graphics
149
 * object should be null or a value larger than the maximum possible render size,
150
 * or text size measurement will stop at the clip bounds.  <code>HtmlRenderer.getGraphics()</code>
151
 * will always return non-null and non-clipped, and is suitable to pass in such a
152
 * situation.
153
 * <P>
154
 *
155
 * @author  Tim Boudreau
156
 */
157
public final class HtmlRenderer {
158
159
    private static final HtmlRendererImpl INSTANCE = new HtmlRendererImpl();
160
161
    private HtmlRenderer() {
162
163
    }
164
165
    /**
166
     * Returns an instance of Renderer which may be used as a table/tree/list cell renderer.
167
     * This method must be called on the AWT event thread.  If you <strong>know</strong> you will
168
     * be passing it legal HTML (legal as documented here), call <code>setHtml(true)</code> on the
169
     * result of this call <strong>after calling getNNNCellRenderer</code> to provide this hint.
170
     *
171
     * @return A cell renderer that can render HTML.
172
     */
173
    public static final Renderer sharedRenderer () {
174
        assert SwingUtilities.isEventDispatchThread();
175
        return INSTANCE;
176
    }
177
178
    /**
179
     * For HTML rendering jobs outside of trees/lists/tables, returns a JLabel which will paint its text using
180
     * the lightweight HTML renderer.  The result of this call will implement the <code>Renderer</code> interface.
181
     * <p>
182
     * Do <strong>not</strong> add the result of this call to the AWT hierarchy - it is designed for offscreen
183
     * painting, and may be reconfigured to paint something else at any time.
184
     * <p>
185
     * This method must be called from the AWT event thread.
186
     *
187
     * @return A JLabel
188
     */
189
    public static final JLabel sharedLabel () {
190
        assert SwingUtilities.isEventDispatchThread();
191
        INSTANCE.reset();
192
        return INSTANCE;
193
    }
194
195
    /** Interface aggregating TableCellRenderer, TreeCellRenderer and ListCellRenderer.
196
     * Return type of <code>sharedInstance()</code>.
197
     */
198
    public interface Renderer extends TableCellRenderer, TreeCellRenderer, ListCellRenderer {
199
        /** Indicate that the component being rendered has keyboard focus.  NetBeans requires that a different
200
         * selection color be used depending on whether the view has focus.
201
         *
202
         * @param parentFocused Whether or not the focused selection color should be used
203
         */
204
        void setParentFocused (boolean parentFocused);
205
206
        /**
207
         * Indicate that the text should be painted centered below the icon.  This is primarily used
208
         * by org.openide.explorer.view.IconView
209
         *
210
         * @param centered Whether or not centered painting should be used.
211
         */
212
        void setCentered (boolean centered);
213
214
        /**
215
         * Set a number of pixels the icon and text should be indented.  Used by ChoiceView and ListView to
216
         * fake tree-style nesting.  This value has no effect if <code>setCentered(true)</code> has been called.
217
         *
218
         * @param pixels The number of pixels to indent
219
         */
220
        void setIndent (int pixels);
221
222
        /**
223
         * Explicitly tell the renderer it is going to receive HTML markup, or it is not.  If the renderer should
224
         * check the string for opening HTML tags to determine this, don't call this method.  If you <strong>know</strong>
225
         * the string will be compliant HTML, it is preferable to call this method with true; if you want to intentionally
226
         * render HTML markup literally, call this method with false.
227
         *
228
         * @param val
229
         */
230
        void setHtml (boolean val);
231
232
        /**
233
         * Set the rendering style - this can be JLabel-style truncated-with-elipsis (...) text, or clipped text.
234
         * The default is STYLE_CLIP.
235
         *
236
         * @param style The text style
237
         */
238
        void setRenderStyle (int style);
239
240
        /** Set the icon to be used for painting
241
         *
242
         * @param icon An icon or null
243
         */
244
        void setIcon (Icon icon);
245
246
        /** Clear any stale data from previous use by other components,
247
         * clearing the icon, text, disabled state and other customizations, returning the component
248
         * to its initialized state.  This is done automatically when get*CellRenderer() is called.
249
         */
250
        void reset();
251
252
        /** Set the text to be displayed.  Use this if the object being rendered's toString() does not
253
         * return a real user-displayable string, after calling get**CellRenderer().  Typically after calling
254
         * this one calls setHtml() if the text is known to either be or not be HTML markup.
255
         *
256
         * @param txt The text that should be displayed
257
         */
258
        void setText (String txt);
259
260
        /**
261
         * Convenience method to set the gap between the icon and text.
262
         *
263
         * @param gap an integer number of pixels
264
         */
265
        void setIconTextGap (int gap);
266
    }
267
268
    /** Stack object used during HTML rendering to hold previous colors in
269
     * the case of nested color entries. */
270
    private static Stack colorStack = new Stack();
271
    /** Constant used by <code>renderString</code>, <code>renderPlainString</code>
272
     * <code>renderHTML</code>, and <code>HtmlRenderer.setRenderStyle</code>
273
     * if painting should simply be cut off at the boundary of the cooordinates passed.     */
274
    public static final int STYLE_CLIP=0;
275
    /** Constant used by <code>renderString</code>, <code>renderPlainString</code>
276
     * <code>renderHTML</code>  and <code>HtmlRenderer.setRenderStyle</code> if
277
     * painting should produce an ellipsis (...)
278
     * if the text would overlap the boundary of the coordinates passed */
279
    public static final int STYLE_TRUNCATE=1;
280
    /** Constant used by <code>renderString</code>, <code>renderPlainString</code>
281
     * <code>renderHTML</code> and <code>HtmlRenderer.setRenderStyle</code>
282
     * if painting should word wrap the text.  In
283
     * this case, the return value of any of the above methods will be the
284
     * height, rather than width painted. */
285
    private static final int STYLE_WORDWRAP=2;
286
    /** System property to cause exceptions to be thrown when unparsable
287
     * html is encountered */
288
    private static final boolean strictHTML = Boolean.getBoolean(
289
        "netbeans.lwhtml.strict"); //NOI18N
290
    /** Cache for strings which have produced errors, so we don't post an
291
     * error message more than once */
292
    private static Set badStrings=null;
293
    /** Definitions for a limited subset of sgml character entities */
294
    private static final Object[] entities = new Object[] {
295
        new char[] {'g','t'}, new char[] {'l','t'},
296
        new char[] {'q','u','o','t'}, new char[] {'a','m','p'},
297
        new char[] {'l','s','q','u','o'},
298
        new char[] {'r','s','q','u','o'},
299
        new char[] {'l','d','q','u','o'},
300
        new char[] {'r','d','q','u','o'},
301
        new char[] {'n','d','a','s','h'},
302
        new char[] {'m','d','a','s','h'},
303
        new char[] {'n','e'},
304
        new char[] {'l','e'},
305
        new char[] {'g','e'},
306
307
        new char[] {'c','o','p','y'},
308
        new char[] {'r','e','g'},
309
        new char[] {'t','r','a','d','e'}
310
        //The rest of the SGML entities are left as an excercise for the reader
311
    }; //NOI18N
312
    /** Mappings for the array of sgml character entities to characters */
313
    private static final char[] entitySubstitutions = new char[] {
314
        '>','<','"','&',8216, 8217, 8220, 8221, 8211, 8212, 8800, 8804, 8805,
315
        169, 174, 8482
316
    };
317
318
    /**Render a string to a graphics instance, using the same API as renderHTML().
319
     * Can render a string using JLabel-style ellipsis (...) in the case that
320
     * it will not fit in the passed rectangle, if the style parameter is
321
     * STYLE_CLIP. Returns the width in pixels successfully painted.
322
     * <strong>This method is not thread-safe and should not be called off
323
     * the AWT thread.</strong>
324
     *
325
     * @see #renderHTML */
326
    public static double renderPlainString(String s, Graphics g, int x, int y, int w, int h, Font f, Color defaultColor, int style, boolean paint) {
327
        assert SwingUtilities.isEventDispatchThread();
328
        //per Jarda's request, keep the word wrapping code but don't expose it.
329
        if (style < 0 || style > 1) {
330
            throw new IllegalArgumentException(
331
            "Unknown rendering mode: " + style); //NOI18N
332
        }
333
        return _renderPlainString(s, g, x, y, w, h, f, defaultColor, style,
334
        paint);
335
    }
336
337
    private static double _renderPlainString(String s, Graphics g, int x, int y, int w, int h, Font f, Color foreground, int style, boolean paint) {
338
        g.setColor(foreground);
339
        g.setFont(f);
340
        FontMetrics fm = g.getFontMetrics(f);
341
        Rectangle2D r = fm.getStringBounds(s, g);
342
        if ((r.getWidth() <= w) || (style == STYLE_CLIP)) {
343
            if (paint) {
344
                g.drawString(s, x, y);
345
            }
346
        } else {
347
            char[] chars = new char[s.length()];
348
            s.getChars(0, s.length()-1, chars, 0);
349
            if (chars.length == 0) {
350
                return 0;
351
            }
352
            double chWidth = r.getWidth() / chars.length;
353
            int estCharsOver = new Double((r.getWidth() - w) / chWidth).intValue();
354
            if (style == STYLE_TRUNCATE) {
355
                int length = chars.length - estCharsOver;
356
                if (length <=0) {
357
                    return 0;
358
                }
359
                if (paint) {
360
                    if (length > 3) {
361
                        Arrays.fill(chars, length-3, length, '.');
362
                        g.drawChars(chars, 0, length, x, y);
363
                    } else {
364
                        Shape shape = g.getClip();
365
                        if (s != null) {
366
                            Area area = new Area(shape);
367
                            area.intersect (new Area(new Rectangle(x,y,w,h)));
368
                            g.setClip(area);
369
                        } else {
370
                            g.setClip(new Rectangle(x,y,w,h));
371
                        }
372
                        g.drawString("...", x,y);
373
                        g.setClip(shape);
374
                    }
375
                }
376
            } else {
377
                //TODO implement plaintext word wrap if we want to support it at some point
378
            }
379
        }
380
        return r.getWidth();
381
    }
382
383
    /** Render a string to a graphics context, using HTML markup if the string
384
     * begins with html tags.  Delegates to <code>renderPlainString()</code>
385
     * or <code>renderHTML()</code> as appropriate.  See the documentation for
386
     * <code>renderHTML()</code> for details of the subset of HTML that is
387
     * supported.
388
     * <P><strong>This method is not thread-safe and should not be called off
389
     * the AWT thread.</strong>
390
     * @param s The string to render
391
     * @param g A graphics object into which the string should be drawn, or which should be
392
     * used for calculating the appropriate size
393
     * @param x The x coordinate to paint at.
394
     * @param y The y position at which to paint.  Note that this method does not calculate font
395
     * height/descent - this value should be the baseline for the line of text, not
396
     * the upper corner of the rectangle to paint in.
397
     * @param w The maximum width within which to paint.
398
     * @param h The maximum height within which to paint.
399
     * @param f The base font to be used for painting or calculating string width/height.
400
     * @param defaultColor The base color to use if no font color is specified as html tags
401
     * @param style The wrapping style to use, either <code>STYLE_CLIP</CODE>,
402
     * or <CODE>STYLE_TRUNCATE</CODE>
403
     * @param paint True if actual painting should occur.  If false, this method will not actually
404
     * paint anything, only return a value representing the width/height needed to
405
     * paint the passed string.
406
     * @return The width in pixels required
407
     * to paint the complete string, or the passed parameter <code>w</code> if it is
408
     * smaller than the required width.
409
     */
410
    public static double renderString(String s, Graphics g, int x, int y, int w, int h, Font f, Color defaultColor, int style, boolean paint) {
411
        if (s.startsWith("<html") || s.startsWith("<HTML")) { //NOI18N
412
            if (style < 0 || style > 1) {
413
                throw new IllegalArgumentException(
414
                "Unknown rendering mode: " + style); //NOI18N
415
            }
416
            return _renderHTML(6, s, g, x, y, w, h, f, defaultColor, style, paint);
417
        } else {
418
            return renderPlainString(s, g, x, y, w, h, f, defaultColor, style, paint);
419
        }
420
    }
421
422
    /** Render a string as HTML using a fast, lightweight renderer supporting a limited
423
     * subset of HTML.  The following tags are supported, in upper or lower case:
424
     *
425
     * <table>
426
     * <tr>
427
     *  <td>&lt;B&gt;</td>
428
     *  <td>Boldface text</td>
429
     * </tr>
430
     * <tr>
431
     *  <td>&lt;S&gt;</td>
432
     *  <td>Strikethrough text</td>
433
     * </tr>
434
     * <tr>
435
     *  <td>&lt;U&gt;</td>
436
     *  <td>Underline text</td>
437
     * </tr>
438
     * <tr>
439
     *  <td>&lt;I&gt;</td>
440
     *  <td>Italic text</td>
441
     * </tr>
442
     * <tr>
443
     *  <td>&lt;EM&gt;</td>
444
     *  <td>Emphasized text (same as italic)</td>
445
     * </tr>
446
     * <tr>
447
     *  <td>&lt;STRONG&gt;</td>
448
     *  <td>Strong text (same as bold)</td>
449
     * </tr>
450
     * <tr>
451
     *  <td>&lt;font&gt;</td>
452
     *  <td>Font color - font attributes other than color are not supported.  Colors
453
     *  may be specified as hexidecimal strings, such as #FF0000 or as logical colors
454
     *  defined in the current look and feel by specifying a ! character as the first
455
     *  character of the color name.  Logical colors are colors available from the
456
     *  current look and feel's UIManager.  For example, <code>&lt;font
457
     *  color=&quot;!Tree.background&quot;&gt;</code> will set the font color to the
458
     *  result of <code>UIManager.getColor(&quot;Tree.background&quot)</code>.
459
     * <strong>Font size tags are not supported.</strong>
460
     * </td>
461
     * </tr>
462
     * </table>
463
     * The lightweight html renderer supports the following named sgml character
464
     * entities: <code>quot, lt, amp, lsquo, rsquo, ldquo, rdquo, ndash, mdash, ne,
465
     * le, ge, copy, reg, trade.  </code>.  It also supports numeric entities
466
     * (e.g. <code>&amp;8822;</code>).
467
     * <p><b>When to use this method instead of the JDK's HTML support: </b> when
468
     * rendering short strings (for example, in a tree or table cell renderer)
469
     * with limited HTML, this method is approximately 10x faster than JDK HTML
470
     * rendering (it does not build and parse a document tree).
471
     *
472
     * <P><B><U>Specifying logical colors</U></B><BR>
473
     * Hardcoded text colors are undesirable, as they can be incompatible (even
474
     * invisible) on some look and feels or themes.
475
     * The lightweight HTML renderer supports a non-standard syntax for specifying
476
     * font colors via a key for a color in the UI defaults for the current look
477
     * and feel.  This is accomplished by prefixing the key name with a <code>!</code>
478
     * character.  For example: <code>&lt;font color='!controlShadow'&gt;</code>.
479
     *
480
     * <P><B><U>Modes of operation</U></B><BR>
481
     * This method supports two modes of operation:
482
     * <OL>
483
     * <LI><CODE>STYLE_CLIP</CODE> - as much text as will fit in the pixel width passed
484
     * to the method should be painted, and the text should be cut off at the maximum
485
     * width or clip rectangle maximum X boundary for the graphics object, whichever is
486
     * smaller.</LI>
487
     * <LI><CODE>STYLE_TRUNCATE</CODE> - paint as much text as will fit in the pixel
488
     * width passed to the method, but paint the last three characters as .'s, in the
489
     * same manner as a JLabel truncates its text when the available space is too
490
     * small.</LI>
491
     * </OL>
492
     * <P>
493
     * This method can also be used in non-painting mode to establish the space
494
     * necessary to paint a string.  This is accomplished by passing the value of the
495
     * <code>paint</code> argument as false.  The return value will be the required
496
     * width in pixels
497
     * to display the text.  Note that in order to retrieve an
498
     * accurate value, the argument for available width should be passed
499
     * as <code>Integer.MAX_VALUE</code> or an appropriate maximum size - otherwise
500
     * the return value will either be the passed maximum width or the required
501
     * width, whichever is smaller.  Also, the clip shape for the passed graphics
502
     * object should be null or a value larger than the maximum possible render size.
503
     * <P>
504
     * This method will log a warning if it encounters HTML markup it cannot
505
     * render.  To aid diagnostics, if NetBeans is run with the argument
506
     * <code>-J-Dnetbeans.lwhtml.strict=true</code> an exception will be thrown
507
     * when an attempt is made to render unsupported HTML.</code><p>
508
     * <strong>This method is not thread-safe and should not be called off
509
     * the AWT thread!</strong>
510
     * <p>
511
     * @param s The string to render
512
     * @param g A graphics object into which the string should be drawn, or which should be
513
     * used for calculating the appropriate size
514
     * @param x The x coordinate to paint at.
515
     * @param y The y position at which to paint.  Note that this method does not calculate font
516
     * height/descent - this value should be the baseline for the line of text, not
517
     * the upper corner of the rectangle to paint in.
518
     * @param w The maximum width within which to paint.
519
     * @param h The maximum height within which to paint.
520
     * @param f The base font to be used for painting or calculating string width/height.
521
     * @param defaultColor The base color to use if no font color is specified as html tags
522
     * @param style The wrapping style to use, either <code>STYLE_CLIP</CODE>,
523
     * or <CODE>STYLE_TRUNCATE</CODE>
524
     * @param paint True if actual painting should occur.  If false, this method will not actually
525
     * paint anything, only return a value representing the width/height needed to
526
     * paint the passed string.
527
     * @return The width in pixels required
528
     * to paint the complete string, or the passed parameter <code>w</code> if it is
529
     * smaller than the required width.
530
     */
531
    public static double renderHTML(String s, Graphics g, int x, int y,
532
        int w, int h, Font f,
533
        Color defaultColor, int style,
534
        boolean paint) {
535
536
        assert SwingUtilities.isEventDispatchThread();
537
538
        //per Jarda's request, keep the word wrapping code but don't expose it.
539
        if (style < 0 || style > 1) {
540
            throw new IllegalArgumentException(
541
            "Unknown rendering mode: " + style); //NOI18N
542
        }
543
        return _renderHTML(0, s, g, x, y, w, h, f, defaultColor, style,
544
            paint);
545
    }
546
547
    /** Implementation of HTML rendering */
548
    private static double _renderHTML(int pos, String s, Graphics g, int x,
549
        int y, int w, int h, Font f, Color defaultColor, int style,
550
        boolean paint) {
551
552
        g.setColor(defaultColor);
553
        g.setFont(f);
554
        char[] chars = s.toCharArray();
555
        int origX = x;
556
        boolean done = false;  //flag if rendering completed, either by finishing the string or running out of space
557
        boolean inTag = false; //flag if the current position is inside a tag, and the tag should be processed rather than rendering
558
        boolean inClosingTag = false; //flag if the current position is inside a closing tag
559
        boolean strikethrough = false; //flag if a strikethrough line should be painted
560
        boolean underline = false; //flag if an underline should be painted
561
        boolean bold = false; //flag if text is currently bold
562
        boolean italic = false; //flag if text is currently italic
563
        boolean truncated = false; //flag if the last possible character has been painted, and the next loop should paint "..." and return
564
        double widthPainted = 0; //the total width painted, for calculating needed space
565
        double heightPainted = 0; //the total height painted, for calculating needed space
566
        boolean lastWasWhitespace = false; //flag to skip additional whitespace if one whitespace char already painted
567
        double lastHeight=0; //the last line height, for calculating total required height
568
569
        double dotWidth = 0;
570
        //Calculate the width of a . character if we may need to truncate
571
        if (style == STYLE_TRUNCATE) {
572
            dotWidth = g.getFontMetrics().charWidth('.'); //NOI18N
573
        }
574
575
        /* How this all works, for anyone maintaining this code (hopefully it will
576
          never need it):
577
          1. The string is converted to a char array
578
          2. Loop over the characters.  Variable pos is the current point.
579
            2a. See if we're in a tag by or'ing inTag with currChar == '<'
580
              If WE ARE IN A TAG:
581
               2a1: is it an opening tag?
582
                 If YES:
583
                   - Identify the tag, Configure the Graphics object with
584
                     the appropriate font, color, etc.  Set pos = the first
585
                     character after the tag
586
                 If NO (it's a closing tag)
587
                   - Identify the tag.  Reconfigure the Graphics object
588
                     with the state it should be in outside the tag
589
                     (reset the font if italic, pop a color off the stack, etc.)
590
            2b. If WE ARE NOT IN A TAG
591
               - Locate the next < or & character or the end of the string
592
               - Paint the characters using the Graphics object
593
               - Check underline and strikethrough tags, and paint line if
594
                 needed
595
            See if we're out of space, and do the right thing for the style
596
            (paint ..., give up or skip to the next line)
597
         */
598
599
        //Clear any junk left behind from a previous rendering loop
600
        colorStack.clear();
601
602
        //Enter the painting loop
603
        while (!done) {
604
            if (pos == s.length()) {
605
                return widthPainted;
606
            }
607
            //see if we're in a tag
608
            try {
609
                inTag |= chars[pos] == '<';
610
            } catch (ArrayIndexOutOfBoundsException e) {
611
                //Should there be any problem, give a meaningful enough
612
                //message to reproduce the problem
613
                ArrayIndexOutOfBoundsException aib =
614
                new ArrayIndexOutOfBoundsException(
615
                "HTML rendering failed at position " + pos + " in String \""
616
                + s + "\".  Please report this at http://www.netbeans.org"); //NOI18N
617
                throw aib;
618
            }
619
            inClosingTag = inTag && (pos+1 < chars.length) && chars[pos+1]
620
             == '/'; //NOI18N
621
622
            if (truncated) {
623
                //Then we've almost run out of space, time to print ... and quit
624
                g.setColor(defaultColor);
625
                g.setFont(f);
626
                if (paint) {
627
                    g.drawString("...", x, y); //NOI18N
628
                }
629
                done = true;
630
            } else if (inTag) {
631
                //If we're in a tag, don't paint, process it
632
                pos++;
633
                int tagEnd = pos;
634
                while (!done && (chars[tagEnd] != '>')) {
635
                    done = tagEnd == chars.length -1;
636
                    tagEnd++;
637
                }
638
639
                if (inClosingTag) {
640
                    //Handle closing tags by resetting the Graphics object (font, etc.)
641
                    pos++;
642
                    switch (chars[pos]) {
643
                        case 'P' :
644
                        case 'p' :
645
                        case 'H' :
646
                        case 'h' : break; //ignore html opening/closing tags
647
                        case 'B' :
648
                        case 'b' :
649
                            if (chars[pos+1] == 'r' || chars[pos+1] == 'R') {
650
                                break;
651
                            }
652
                            if (!bold) {
653
                                throwBadHTML("Closing bold tag w/o " + //NOI18N
654
                                "opening bold tag", pos, chars); //NOI18N
655
                            }
656
                            if (italic) {
657
                                g.setFont(f.deriveFont(Font.ITALIC));
658
                            } else {
659
                                g.setFont(f.deriveFont(Font.PLAIN));
660
                            }
661
                            bold = false;
662
                            break;
663
                        case 'E' :
664
                        case 'e' : //em tag
665
                        case 'I' :
666
                        case 'i' :
667
                            if (bold) {
668
                                g.setFont(f.deriveFont(Font.BOLD));
669
                            } else {
670
                                g.setFont(f.deriveFont(Font.PLAIN));
671
                            }
672
                            if (!italic) {
673
                                throwBadHTML("Closing italics tag w/o" //NOI18N
674
                                + "opening italics tag", pos, chars); //NOI18N
675
                            }
676
                            italic = false;
677
                            break;
678
                        case 'S' :
679
                        case 's' :
680
                            switch (chars[pos+1]) {
681
                                case 'T' :
682
                                case 't' : if (italic) {
683
                                    g.setFont(f.deriveFont(
684
                                    Font.ITALIC));
685
                                } else {
686
                                    g.setFont(f.deriveFont(
687
                                    Font.PLAIN));
688
                                }
689
                                bold = false;
690
                                break;
691
                                case '>' :
692
                                    strikethrough = false;
693
                                    break;
694
                            }
695
                            break;
696
                        case 'U' :
697
                        case 'u' : underline = false;
698
                        break;
699
                        case 'F' :
700
                        case 'f' :
701
                            if (colorStack.isEmpty()) {
702
                                g.setColor(defaultColor);
703
                            } else {
704
                                g.setColor((Color) colorStack.pop());
705
                            }
706
                            break;
707
                        default  :
708
                            throwBadHTML(
709
                            "Malformed or unsupported HTML", //NOI18N
710
                            pos,  chars);
711
                    }
712
                } else {
713
                    //Okay, we're in an opening tag.  See which one and configure the Graphics object
714
                    switch (chars[pos]) {
715
                        case 'B' :
716
                        case 'b' :
717
                            switch (chars[pos+1]) {
718
                                case 'R' :
719
                                case 'r' :
720
                                    if (style == STYLE_WORDWRAP) {
721
                                        x = origX;
722
                                        int lineHeight = g.getFontMetrics().getHeight();
723
                                        y += lineHeight;
724
                                        heightPainted += lineHeight;
725
                                        widthPainted = 0;
726
                                    }
727
                                    break;
728
                                case '>' :
729
                                    bold = true;
730
                                    if (italic) {
731
                                        g.setFont(f.deriveFont(Font.BOLD | Font.ITALIC));
732
                                    } else {
733
                                        g.setFont(f.deriveFont(Font.BOLD));
734
                                    }
735
                                    break;
736
                            }
737
                            break;
738
                        case 'e' : //em tag
739
                        case 'E' :
740
                        case 'I' :
741
                        case 'i' :
742
                            italic = true;
743
                            if (bold) {
744
                                g.setFont(f.deriveFont(Font.ITALIC | Font.BOLD));
745
                            } else {
746
                                g.setFont(f.deriveFont(Font.ITALIC));
747
                            }
748
                            break;
749
                        case 'S' :
750
                        case 's' :
751
                            switch (chars[pos+1]) {
752
                                case '>' :
753
                                    strikethrough = true;
754
                                    break;
755
                                case 'T' :
756
                                case 't' :
757
                                    bold = true;
758
                                    if (italic) {
759
                                        g.setFont(f.deriveFont(Font.BOLD | Font.ITALIC));
760
                                    } else {
761
                                        g.setFont(f.deriveFont(Font.BOLD));
762
                                    }
763
                                    break;
764
                            }
765
                            break;
766
                        case 'U' :
767
                        case 'u' :
768
                            underline = true;
769
                            break;
770
                        case 'f' :
771
                        case 'F' :
772
                            Color c = findColor(chars, pos, tagEnd);
773
                            colorStack.push(g.getColor());
774
                            g.setColor(c);
775
                            break;
776
                        case 'P' :
777
                        case 'p' :
778
                            if (style == STYLE_WORDWRAP) {
779
                                x = origX;
780
                                int lineHeight=g.getFontMetrics().getHeight();
781
                                y +=  lineHeight + (lineHeight / 2);
782
                                heightPainted = y + lineHeight;
783
                                widthPainted = 0;
784
                            }
785
                            break;
786
                        case 'H' :
787
                        case 'h' : //Just an opening HTML tag
788
                            if (pos == 1) {
789
                                break;
790
                            }
791
                        default  : throwBadHTML(
792
                            "Malformed or unsupported HTML", pos, chars); //NOI18N
793
                    }
794
                }
795
796
                pos = tagEnd + (done ? 0 : 1);
797
                inTag = false;
798
            } else {
799
                //Okay, we're not in a tag, we need to paint
800
801
                if (lastWasWhitespace) {
802
                    //Skip multiple whitespace characters
803
                    while (pos < s.length() && Character.isWhitespace(chars[pos])) {
804
                        pos++;
805
                    }
806
                    //Check strings terminating with multiple whitespace -
807
                    //otherwise could get an AIOOBE here
808
                    if (pos == chars.length - 1) {
809
                        return style != STYLE_WORDWRAP ? widthPainted : heightPainted;
810
                    }
811
                }
812
813
                //Flag to indicate if an ampersand entity was processed,
814
                //so the resulting & doesn't get treated as the beginning of
815
                //another entity (and loop endlessly)
816
                boolean isAmp=false;
817
                //Flag to indicate the next found < character really should
818
                //be painted (it came from an entity), it is not the beginning
819
                //of a tag
820
                boolean nextLtIsEntity=false;
821
                int nextTag = chars.length-1;
822
                if ((chars[pos] == '&')) {
823
                    boolean inEntity=pos != chars.length-1;
824
                    if (inEntity) {
825
                        int newPos = substEntity(chars, pos+1);
826
                        inEntity = newPos != -1;
827
                        if (inEntity) {
828
                            pos = newPos;
829
                            isAmp = chars[pos] == '&';
830
                            //flag it so the next iteration won't think the <
831
                            //starts a tag
832
                            nextLtIsEntity = chars[pos] == '<';
833
                        } else {
834
                            nextLtIsEntity = false;
835
                            isAmp = true;
836
                        }
837
                    }
838
                } else {
839
                    nextLtIsEntity=false;
840
                }
841
842
                for (int i=pos; i < chars.length; i++) {
843
                    if (((chars[i] == '<') && (!nextLtIsEntity)) || ((chars[i] == '&') && !isAmp)) {
844
                        nextTag = i-1;
845
                        break;
846
                    }
847
                    //Reset these flags so we don't skip all & or < chars for the rest of the string
848
                    isAmp = false;
849
                    nextLtIsEntity=false;
850
                }
851
852
853
                FontMetrics fm = g.getFontMetrics(g.getFont());
854
                //Get the bounds of the substring we'll paint
855
                Rectangle2D r = fm.getStringBounds(chars, pos, nextTag + 1, g);
856
                //Store the height, so we can add it if we're in word wrap mode,
857
                //to return the height painted
858
                lastHeight = r.getHeight();
859
                //Work out the length of this tag
860
                int length = (nextTag + 1) - pos;
861
862
                //Flag to be set to true if we run out of space
863
                boolean goToNextRow = false;
864
865
                //Flag that the current line is longer than the available width,
866
                //and should be wrapped without finding a word boundary
867
                boolean brutalWrap = false;
868
                //Work out the per-character avg width of the string, for estimating
869
                //when we'll be out of space and should start the ... in truncate
870
                //mode
871
                double chWidth;
872
873
                if (style == STYLE_TRUNCATE) {
874
                    //if we're truncating, use the width of one dot from an
875
                    //ellipsis to get an accurate result for truncation
876
                    chWidth = dotWidth;
877
                } else {
878
                    //calculate an average character width
879
                    chWidth= r.getWidth() / (nextTag - pos);
880
                    //can return this sometimes, so handle it
881
                    if (chWidth == Double.POSITIVE_INFINITY || chWidth == Double.NEGATIVE_INFINITY) {
882
                        chWidth = fm.getMaxAdvance();
883
                    }
884
                }
885
886
887
                if ((style != STYLE_CLIP) &&
888
                    ((style == STYLE_TRUNCATE &&
889
                    (widthPainted + r.getWidth() > w - (chWidth * 2)))) ||
890
                    (style == STYLE_WORDWRAP &&
891
                    (widthPainted + r.getWidth() > w))) {
892
893
                    if (chWidth > 3) {
894
                        double pixelsOff = (widthPainted + (
895
                            r.getWidth() + 5)
896
                            ) - w;
897
898
                        double estCharsOver = pixelsOff / chWidth;
899
900
                        if (style == STYLE_TRUNCATE) {
901
                            int charsToPaint = Math.round(Math.round((w - widthPainted) / chWidth));
902
/*                            System.err.println("estCharsOver = " + estCharsOver);
903
                            System.err.println("Chars to paint " + charsToPaint + " chwidth = " + chWidth + " widthPainted " + widthPainted);
904
                            System.err.println("Width painted + width of tag: " + (widthPainted + r.getWidth()) + " available: " + w);
905
 */
906
907
                            int startPeriodsPos = pos + charsToPaint -3;
908
                            if (startPeriodsPos >= chars.length) {
909
                                startPeriodsPos = chars.length - 4;
910
                            }
911
                            length = (startPeriodsPos - pos);
912
                            if (length < 0) length = 0;
913
                            r = fm.getStringBounds(chars, pos, pos+length, g);
914
//                            System.err.println("Truncated set to true at " + pos + " (" + chars[pos] + ")");
915
                            truncated = true;
916
                        } else {
917
                            goToNextRow = true;
918
                            int lastChar = new Double(nextTag -
919
                            estCharsOver).intValue();
920
                            brutalWrap = x == 0;
921
                            for (int i = lastChar; i > pos; i--) {
922
                                lastChar--;
923
                                if (Character.isWhitespace(chars[i])) {
924
                                    length = (lastChar - pos) + 1;
925
                                    brutalWrap = false;
926
                                    break;
927
                                }
928
                            }
929
                            if ((lastChar <= pos) && (length > estCharsOver)
930
                            && !brutalWrap) {
931
                                x = origX;
932
                                y += r.getHeight();
933
                                heightPainted += r.getHeight();
934
                                boolean boundsChanged = false;
935
                                while (!done && Character.isWhitespace(
936
                                chars[pos]) && (pos < nextTag)) {
937
                                    pos++;
938
                                    boundsChanged = true;
939
                                    done = pos == chars.length -1;
940
                                }
941
                                if (pos == nextTag) {
942
                                    lastWasWhitespace = true;
943
                                }
944
                                if (boundsChanged) {
945
                                    //recalculate the width we will add
946
                                    r = fm.getStringBounds(chars, pos,
947
                                    nextTag + 1, g);
948
                                }
949
                                goToNextRow = false;
950
                                widthPainted = 0;
951
                                if (chars[pos - 1 + length] == '<') {
952
                                    length --;
953
                                }
954
                            } else if (brutalWrap) {
955
                                //wrap without checking word boundaries
956
                                length = (new Double(
957
                                (w - widthPainted) / chWidth)
958
                                ).intValue();
959
                                if (pos + length > nextTag) {
960
                                    length = (nextTag - pos);
961
                                }
962
                                goToNextRow = true;
963
                            }
964
                        }
965
                    }
966
                }
967
                if (!done) {
968
                    if (paint) {
969
                        g.drawChars(chars, pos, length, x, y);
970
                    }
971
972
                    if ((strikethrough || underline)){
973
                        LineMetrics lm = fm.getLineMetrics(chars, pos,
974
                        length - 1, g);
975
                        int lineWidth = new Double(x +
976
                        r.getWidth()).intValue();
977
                        if (paint) {
978
                            if (strikethrough) {
979
                                int stPos = Math.round(
980
                                lm.getStrikethroughOffset()) +
981
                                g.getFont().getBaselineFor(chars[pos])
982
                                + 1;
983
                                //                                int stThick = Math.round (lm.getStrikethroughThickness()); //XXX
984
                                g.drawLine(x, y + stPos, lineWidth, y + stPos);
985
                            }
986
                            if (underline) {
987
                                int stPos = Math.round(
988
                                lm.getUnderlineOffset()) +
989
                                g.getFont().getBaselineFor(chars[pos])
990
                                + 1;
991
                                //                                int stThick = new Float (lm.getUnderlineThickness()).intValue(); //XXX
992
                                g.drawLine(x, y + stPos, lineWidth, y + stPos);
993
                            }
994
                        }
995
                    }
996
                    if (goToNextRow) {
997
                        //if we're in word wrap mode and need to go to the next
998
                        //line, reconfigure the x and y coordinates
999
                        x = origX;
1000
                        y += r.getHeight();
1001
                        heightPainted += r.getHeight();
1002
                        widthPainted = 0;
1003
                        pos += (length);
1004
                        //skip any leading whitespace
1005
                        while ((pos < chars.length) &&
1006
                        (Character.isWhitespace(chars[pos])) &&
1007
                        (chars[pos] != '<')) {
1008
                            pos++;
1009
                        }
1010
                        lastWasWhitespace = true;
1011
                        done |= pos >= chars.length;
1012
                    } else {
1013
                        x += r.getWidth();
1014
                        widthPainted += r.getWidth();
1015
                        lastWasWhitespace = Character.isWhitespace(
1016
                        chars[nextTag]);
1017
                        pos = nextTag + 1;
1018
                    }
1019
                    done |= nextTag == chars.length;
1020
                }
1021
            }
1022
        }
1023
        if (style != STYLE_WORDWRAP) {
1024
            return widthPainted;
1025
        } else {
1026
            return heightPainted + lastHeight;
1027
        }
1028
    }
1029
1030
    /** Parse a font color tag and return an appopriate java.awt.Color instance */
1031
    private static Color findColor(final char[] ch, final int pos,
1032
    final int tagEnd) {
1033
        int colorPos = pos;
1034
        boolean useUIManager = false;
1035
        for (int i=pos; i < tagEnd; i ++) {
1036
            if (ch[i] == 'c') {
1037
                colorPos = i + 6;
1038
                if (ch[colorPos] == '\'' || ch[colorPos] == '"') {
1039
                    colorPos++;
1040
                }
1041
                //skip the leading # character
1042
                if (ch[colorPos] == '#') {
1043
                    colorPos++;
1044
                } else if (ch[colorPos] == '!') {
1045
                    useUIManager = true;
1046
                    colorPos++;
1047
                }
1048
                break;
1049
            }
1050
        }
1051
        if (colorPos == pos) {
1052
            String out = "Could not find color identifier in font declaration";
1053
            throwBadHTML(out, pos, ch);
1054
        }
1055
        //Okay, we're now on the first character of the hex color definition
1056
        String s;
1057
        if (useUIManager) {
1058
            int end = ch.length-1;
1059
            for (int i=colorPos; i < ch.length; i++) {
1060
                if (ch[i] == '"' || ch[i] == '\'') { //NOI18N
1061
                    end = i;
1062
                    break;
1063
                }
1064
            }
1065
            s = new String(ch, colorPos, end-colorPos);
1066
        } else {
1067
            s = new String(ch, colorPos, 6);
1068
        }
1069
        Color result=null;
1070
        if (useUIManager) {
1071
            result = UIManager.getColor(s);
1072
            //Not all look and feels will provide standard colors; handle it gracefully
1073
            if (result == null) {
1074
                throwBadHTML(
1075
                "Could not resolve logical font declared in HTML: " + s,
1076
                pos, ch);
1077
                result = UIManager.getColor("textText"); //NOI18N
1078
                //Avoid NPE in headless situation?
1079
                if (result == null) {
1080
                    result = Color.BLACK;
1081
                }
1082
            }
1083
        } else {
1084
            try {
1085
                int rgb = Integer.parseInt(s, 16);
1086
                result = new Color(rgb);
1087
            } catch (NumberFormatException nfe) {
1088
                throwBadHTML(
1089
                "Illegal hexadecimal color text: " + s + //NOI18N
1090
                " in HTML string", colorPos, ch);
1091
            }
1092
        }
1093
        if (result == null) {
1094
            throwBadHTML("Unresolvable html color: " + s //NOI18N
1095
            + " in HTML string \n  ", pos,  ch);
1096
        }
1097
        return result;
1098
    }
1099
1100
    /** Find an entity at the passed character position in the passed array.
1101
     * If an entity is found, the trailing ; character will be substituted
1102
     * with the resulting character, and the position of that character
1103
     * in the array will be returned as the new position to render from,
1104
     * causing the renderer to skip the intervening characters */
1105
    private static final int substEntity(char[] ch, int pos) {
1106
        //There are no 1 character entities, abort
1107
        if (pos >= ch.length-2) {
1108
            return -1;
1109
        }
1110
        //if it's numeric, parse out the number
1111
        if (ch[pos] == '#') {
1112
            return substNumericEntity(ch, pos+1);
1113
        }
1114
        //Okay, we've potentially got a named character entity. Try to find it.
1115
        boolean match;
1116
        for (int i=0; i < entities.length; i++) {
1117
            char[] c = (char[]) entities[i];
1118
            match = true;
1119
            if (c.length < ch.length-pos) {
1120
                for (int j=0; j < c.length; j++) {
1121
                    match &= c[j] == ch[j+pos];
1122
                }
1123
            } else {
1124
                match = false;
1125
            }
1126
            if (match) {
1127
                //if it's a match, we still need the trailing ;
1128
                if (ch[pos+c.length] == ';') {
1129
                    //substitute the character referenced by the entity
1130
                    ch[pos+c.length] = entitySubstitutions[i];
1131
                    return pos+c.length;
1132
                }
1133
            }
1134
        }
1135
        return -1;
1136
    }
1137
1138
    /** Finds a character defined as a numeric entity (e.g. &amp;#8222;)
1139
     * and replaces the trailing ; with the referenced character, returning
1140
     * the position of it so the renderer can continue from there.
1141
     */
1142
    private static final int substNumericEntity(char[] ch, int pos) {
1143
        for (int i=pos; i < ch.length; i++) {
1144
            if (ch[i] == ';') {
1145
                try {
1146
                    ch[i] = (char) Integer.parseInt(
1147
                    new String(ch, pos, i - pos));
1148
                    return i;
1149
                } catch (NumberFormatException nfe) {
1150
                    throwBadHTML("Unparsable numeric entity: " +
1151
                        new String(ch, pos, i - pos), pos, ch); //NOI18N
1152
                }
1153
            }
1154
        }
1155
        return -1;
1156
    }
1157
1158
    /** Throw an exception for unsupported or bad html, indicating where the problem is
1159
     * in the message  */
1160
    private static void throwBadHTML(String msg, int pos, char[] chars) {
1161
        char[] chh = new char[pos];
1162
        Arrays.fill(chh, ' '); //NOI18N
1163
        chh[pos-1] = '^'; //NOI18N
1164
        String out = msg + "\n  " + new String(chars) + "\n  "
1165
            + new String(chh) + "\n Full HTML string:" + new String(chars); //NOI18N
1166
        if (!strictHTML) {
1167
            if (ErrorManager.getDefault().isLoggable(ErrorManager.WARNING)) {
1168
                if (badStrings == null) {
1169
                    badStrings = new HashSet();
1170
                }
1171
                if (!badStrings.contains(msg)) {
1172
                    //ErrorManager bug, issue 38372 - log messages containing
1173
                    //newlines are truncated - so for now we iterate the
1174
                    //string we've just constructed
1175
                    StringTokenizer tk = new StringTokenizer(out, "\n", false);
1176
                    while (tk.hasMoreTokens()) {
1177
                        ErrorManager.getDefault().log(ErrorManager.WARNING,
1178
                            tk.nextToken());
1179
                    }
1180
                    badStrings.add(msg.intern());
1181
                }
1182
            }
1183
        } else {
1184
            throw new IllegalArgumentException(out);
1185
        }
1186
    }
1187
}
(-)openide/src/org/openide/awt/HtmlRendererImpl.java (+524 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().
34
 *
35
 */
36
public class HtmlRendererImpl extends JLabel implements HtmlRenderer.Renderer {
37
38
    private boolean centered = false;
39
    private boolean parentFocused = false;
40
    private Boolean html = null;
41
    private int indent = 0;
42
    private Border border = null;
43
    private boolean selected = false;
44
    private boolean leadSelection = false;
45
    private Dimension prefSize = null;
46
    private int type = TYPE_UNKNOWN;
47
    private int renderStyle = HtmlRenderer.STYLE_CLIP;
48
    private static final Rectangle bounds = new Rectangle();
49
    private boolean enabled = true;
50
51
    private static final boolean swingRendering = Boolean.getBoolean ("nb.useSwingHtmlRendering"); //NOI18N
52
    private static final Insets EMPTY_INSETS = new Insets (0, 0, 0, 0);
53
54
    static final int TYPE_UNKNOWN = -1;
55
    static final int TYPE_TREE = 0;
56
    static final int TYPE_LIST = 1;
57
    static final int TYPE_TABLE = 2;
58
59
    /** Restore the renderer to a pristine state */
60
    public void reset() {
61
        parentFocused = false;
62
        setCentered (false);
63
        html = null;
64
        indent = 0;
65
        border = null;
66
        setIcon (null);
67
        setOpaque (false);
68
        selected = false;
69
        leadSelection = false;
70
        prefSize = null;
71
        type = TYPE_UNKNOWN;
72
        renderStyle = HtmlRenderer.STYLE_CLIP;
73
        setFont (UIManager.getFont("controlFont")); //NOI18N
74
        setIconTextGap (3);
75
        setEnabled (true);
76
        border = null;
77
78
        //Defensively ensure the insets haven't been messed with
79
        EMPTY_INSETS.top = 0;
80
        EMPTY_INSETS.left = 0;
81
        EMPTY_INSETS.right = 0;
82
        EMPTY_INSETS.bottom = 0;
83
    }
84
85
    public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean leadSelection,
86
                                                   int row, int column) {
87
        reset();
88
        configureFrom (value, table, selected, leadSelection);
89
        type = TYPE_TABLE;
90
        if (swingRendering && selected) {
91
            setBackground (table.getSelectionBackground());
92
            setBackground (table.getSelectionForeground());
93
            setOpaque (true);
94
        }
95
        return this;
96
    }
97
98
    public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded,
99
                                                  boolean leaf, int row, boolean leadSelection) {
100
        reset();
101
        configureFrom (value, tree, selected, leadSelection);
102
        type = TYPE_TREE;
103
        if (swingRendering && selected) {
104
            setBackground (HtmlLabelUI.getBackgroundFor(this));
105
            setForeground (HtmlLabelUI.getForegroundFor(this));
106
            setOpaque (true);
107
        }
108
        return this;
109
    }
110
111
    public Component getListCellRendererComponent(JList list, Object value, int index, boolean selected,
112
                                                  boolean leadSelection) {
113
114
        reset();
115
        configureFrom (value, list, selected, leadSelection);
116
        type = TYPE_LIST;
117
        if (swingRendering && selected) {
118
            setBackground (list.getSelectionBackground());
119
            setForeground (list.getSelectionForeground());
120
            setOpaque (true);
121
        }
122
        return this;
123
    }
124
125
    /** Generic code to set properties appropriately from any of the renderer
126
     * fetching methods */
127
    private void configureFrom (Object value, JComponent target,
128
        boolean selected, boolean leadSelection) {
129
130
        if (value == null) {
131
            value = "";
132
        }
133
134
        setText (value instanceof String ? (String) value : value.toString());
135
136
        setSelected(selected);
137
        if (selected) {
138
            setParentFocused(checkFocused(target));
139
        } else {
140
            setParentFocused(false);
141
        }
142
143
        setEnabled (target.isEnabled());
144
145
        setLeadSelection (leadSelection);
146
147
        setFont (target.getFont());
148
    }
149
150
    private boolean checkFocused(JComponent c) {
151
        Component focused =
152
            KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
153
        boolean result = c == focused;
154
        if (!result) {
155
            result = c.isAncestorOf(focused);
156
        }
157
        return result;
158
    }
159
160
    public void setSelected (boolean val) {
161
        selected = val;
162
    }
163
164
    public void setParentFocused (boolean val) {
165
        parentFocused = val;
166
    }
167
168
    public void setLeadSelection (boolean val) {
169
        leadSelection = val;
170
    }
171
172
    public void setCentered(boolean val) {
173
        centered = val;
174
        if (val) {
175
            setIconTextGap (5);
176
        }
177
        if (swingRendering) {
178
            if (val) {
179
                setVerticalTextPosition(JLabel.BOTTOM);
180
                setHorizontalAlignment(JLabel.CENTER);
181
                setHorizontalTextPosition(JLabel.CENTER);
182
            } else {
183
                setVerticalTextPosition(JLabel.CENTER);
184
                setHorizontalAlignment(JLabel.LEADING);
185
                setHorizontalTextPosition(JLabel.TRAILING);
186
            }
187
        }
188
    }
189
190
    public void setIndent(int pixels) {
191
        this.indent = pixels;
192
    }
193
194
    public void setHtml (boolean val) {
195
        Boolean wasHtml = html;
196
        String txt = getText();
197
        html = val ? Boolean.TRUE : Boolean.FALSE;
198
        if (swingRendering && html != wasHtml) {
199
200
            //Ensure label UI gets updated and builds its little document tree...
201
            firePropertyChange ("text", txt, getText());
202
        }
203
    }
204
205
    public void setRenderStyle(int style) {
206
        renderStyle = style;
207
    }
208
209
    int getRenderStyle () {
210
        return renderStyle;
211
    }
212
213
    boolean isLeadSelection () {
214
        return leadSelection;
215
    }
216
217
    boolean isCentered() {
218
        return centered;
219
    }
220
221
    boolean isParentFocused() {
222
        return parentFocused;
223
    }
224
225
    boolean isHtml() {
226
        if (html == null) {
227
            String s = getText();
228
            html = checkHtml (s);
229
        }
230
        return html.booleanValue();
231
    }
232
233
    private Boolean checkHtml(String s) {
234
        Boolean result;
235
        if (s == null) {
236
            result = Boolean.FALSE;
237
        } else if (s.startsWith ("<html") || s.startsWith("<HTML")) { //NOI18N
238
            result = Boolean.TRUE;
239
        } else {
240
            result = Boolean.FALSE;
241
        }
242
        return result;
243
    }
244
245
    boolean isSelected() {
246
        return selected;
247
    }
248
249
    int getIndent() {
250
        return indent;
251
    }
252
253
    int getType() {
254
        return type;
255
    }
256
257
    public Dimension getPreferredSize() {
258
        if (getUI() instanceof HtmlLabelUI) {
259
            if (prefSize == null) {
260
                prefSize = getUI().getPreferredSize(this);
261
            }
262
            return prefSize;
263
        } else {
264
            return super.getPreferredSize();
265
        }
266
    }
267
268
    public String getText() {
269
        String result = super.getText();
270
        if (swingRendering && Boolean.TRUE.equals(html)) {
271
            //Standard swing rendering needs an opening HTML tag to function, so make sure there is
272
            //one if we're not using HtmlLabelUI
273
            result = ensureHtmlTags (result);
274
        } else if (swingRendering && html == null) {
275
            //Cannot call isHtml() here, it will create an endless loop, so manually check the HTML status
276
            html = checkHtml (super.getText());
277
            if (Boolean.TRUE.equals(html)) {
278
                result = ensureHtmlTags (result);
279
            }
280
        }
281
        return result;
282
    }
283
284
    private String ensureHtmlTags (String s) {
285
        if (!s.startsWith("<HTML") && !s.startsWith("<html")) {  //NOI18N
286
            s = "<html>" + s + "</html>"; //NOI18N
287
        }
288
        return s;
289
    }
290
291
    /** Overridden to do nothing under normal circumstances.  If the boolean flag to <strong>not</strong> use the
292
     * internal HTML renderer is in effect, this will fire changes normally */
293
    protected final void firePropertyChange(String name, Object old, Object nue) {
294
        if (swingRendering) {
295
            if ("text".equals(name) && isHtml()) {
296
                //Force in the HTML tags so the UI will set up swing HTML rendering appropriately
297
                nue = getText();
298
            }
299
            super.firePropertyChange(name, old, nue);
300
        }
301
    }
302
303
    public Border getBorder() {
304
        Border result;
305
        if (indent != 0 && swingRendering) {
306
            result = BorderFactory.createEmptyBorder (0, indent, 0, 0);
307
        } else {
308
            result = border;
309
        }
310
        return result;
311
    }
312
313
    public void setBorder (Border b) {
314
        Border old = border;
315
        border = b;
316
        if (swingRendering) {
317
            firePropertyChange ("border", old, b);
318
        }
319
    }
320
321
    public Insets getInsets() {
322
        Insets result;
323
324
        //Call getBorder(), not just read the field - if swingRendering, the border will be constructed, and the
325
        //insets are what will make the indent property work;  HtmlLabelUI doesn't need this, it just reads the
326
        //insets property, but BasicLabelUI and its ilk do
327
        Border b = getBorder();
328
        if (b == null) {
329
            result = EMPTY_INSETS;
330
        } else {
331
            result = b.getBorderInsets(this);
332
        }
333
        return result;
334
    }
335
336
    public void setEnabled (boolean b) {
337
        //OptimizeIt shows about 12Ms overhead calling back to Component.enable(), so avoid it if possible
338
        enabled = b;
339
        if (swingRendering) {
340
            super.setEnabled(b);
341
        }
342
    }
343
344
    public boolean isEnabled() {
345
        return enabled;
346
    }
347
348
    public void updateUI() {
349
        if (swingRendering) {
350
            super.updateUI();
351
        } else {
352
            setUI (HtmlLabelUI.createUI(this));
353
        }
354
    }
355
356
    /** Overridden to produce a graphics object even when isDisplayable() is
357
     * false, so that calls to getPreferredSize() will return accurate
358
     * dimensions (presuming the font and text are set correctly) even when
359
     * not onscreen. */
360
    public Graphics getGraphics() {
361
        Graphics result = null;
362
        if (isDisplayable()) {
363
            result = super.getGraphics();
364
        }
365
        if (result == null) {
366
            result = scratchGraphics();
367
        }
368
        return result;
369
    }
370
371
    private static Reference scratchGraphics = null;
372
    /** Fetch a scratch graphics object for calculating preferred sizes while
373
     * offscreen */
374
    private static final Graphics scratchGraphics() {
375
        Graphics result = null;
376
        if (scratchGraphics != null) {
377
            result = (Graphics) scratchGraphics.get();
378
            if (result != null) {
379
                result.setClip(null); //just in case somebody did something nasty
380
            }
381
        }
382
        if (result == null) {
383
            result = new BufferedImage (1, 1,
384
                BufferedImage.TYPE_INT_RGB).getGraphics(); //XXX use GraphicsEnvironment?
385
386
            scratchGraphics = new SoftReference (result);
387
        }
388
        return result;
389
    }
390
391
    public void setBounds (int x, int y, int w, int h) {
392
        if (swingRendering) {
393
            super.setBounds (x, y, w, h);
394
        }
395
        bounds.setBounds(x, y, w, h);
396
    }
397
398
    public int getWidth() {
399
        return bounds.width;
400
    }
401
402
    public int getHeight() {
403
        return bounds.height;
404
    }
405
406
    public Point getLocation() {
407
        return bounds.getLocation();
408
    }
409
410
    /** Overridden to do nothing for performance reasons */
411
    public void validate() {
412
        //do nothing
413
    }
414
415
    /** Overridden to do nothing for performance reasons */
416
    public void repaint (long tm, int x, int y, int w, int h) {
417
        //do nothing
418
    }
419
420
    /** Overridden to do nothing for performance reasons */
421
    public void repaint() {
422
        //do nothing
423
    }
424
425
    /** Overridden to do nothing for performance reasons */
426
    public void invalidate() {
427
        //do nothing
428
    }
429
430
    /** Overridden to do nothing for performance reasons */
431
    public void revalidate() {
432
        //do nothing
433
    }
434
435
    /** Overridden to do nothing for performance reasons */
436
    public void addAncestorListener (AncestorListener l) {
437
        if (swingRendering) {
438
            super.addAncestorListener (l);
439
        }
440
    }
441
442
    /** Overridden to do nothing for performance reasons */
443
    public void addComponentListener (ComponentListener l) {
444
        if (swingRendering) {
445
            super.addComponentListener (l);
446
        }
447
    }
448
449
    /** Overridden to do nothing for performance reasons */
450
    public void addContainerListener (ContainerListener l) {
451
        if (swingRendering) {
452
            super.addContainerListener (l);
453
        }
454
    }
455
456
    /** Overridden to do nothing for performance reasons */
457
    public void addHierarchyListener (HierarchyListener l) {
458
        if (swingRendering) {
459
            super.addHierarchyListener (l);
460
        }
461
    }
462
463
    /** Overridden to do nothing for performance reasons */
464
    public void addHierarchyBoundsListener (HierarchyBoundsListener l) {
465
        if (swingRendering) {
466
            super.addHierarchyBoundsListener (l);
467
        }
468
    }
469
470
    /** Overridden to do nothing for performance reasons */
471
    public void addInputMethodListener (InputMethodListener l) {
472
        if (swingRendering) {
473
            super.addInputMethodListener (l);
474
        }
475
    }
476
477
    /** Overridden to do nothing for performance reasons */
478
    public void addFocusListener (FocusListener fl) {
479
        if (swingRendering) {
480
            super.addFocusListener (fl);
481
        }
482
    }
483
484
    /** Overridden to do nothing for performance reasons */
485
    public void addMouseListener (MouseListener ml) {
486
        if (swingRendering) {
487
            super.addMouseListener (ml);
488
        }
489
    }
490
491
    /** Overridden to do nothing for performance reasons */
492
    public void addMouseWheelListener (MouseWheelListener ml) {
493
        if (swingRendering) {
494
            super.addMouseWheelListener (ml);
495
        }
496
    }
497
498
    /** Overridden to do nothing for performance reasons */
499
    public void addMouseMotionListener (MouseMotionListener ml) {
500
        if (swingRendering) {
501
            super.addMouseMotionListener (ml);
502
        }
503
    }
504
505
    /** Overridden to do nothing for performance reasons */
506
    public void addVetoableChangeListener (VetoableChangeListener vl) {
507
        if (swingRendering) {
508
            super.addVetoableChangeListener (vl);
509
        }
510
    }
511
512
    /** Overridden to do nothing for performance reasons, unless using standard swing rendering */
513
    public void addPropertyChangeListener (String s, PropertyChangeListener l) {
514
        if (swingRendering) {
515
            super.addPropertyChangeListener (s, l);
516
        }
517
    }
518
519
    public void addPropertyChangeListener (PropertyChangeListener l) {
520
        if (swingRendering) {
521
            super.addPropertyChangeListener (l);
522
        }
523
    }
524
}
(-)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 (-1 / +12 lines)
Lines 21-26 Link Here
21
import javax.swing.plaf.ComboBoxUI;
21
import javax.swing.plaf.ComboBoxUI;
22
import javax.swing.plaf.metal.MetalLookAndFeel;
22
import javax.swing.plaf.metal.MetalLookAndFeel;
23
import javax.swing.text.JTextComponent;
23
import javax.swing.text.JTextComponent;
24
import org.openide.awt.HtmlRenderer;
24
import org.openide.explorer.propertysheet.editors.EnhancedPropertyEditor;
25
import org.openide.explorer.propertysheet.editors.EnhancedPropertyEditor;
25
26
26
/** A combo box inplace editor.  Does a couple of necessary things:
27
/** A combo box inplace editor.  Does a couple of necessary things:
Lines 60-66 Link Here
60
     * less borders & such */                        
61
     * less borders & such */                        
61
    public ComboInplaceEditor(boolean tableUI) {
62
    public ComboInplaceEditor(boolean tableUI) {
62
        if (tableUI) {
63
        if (tableUI) {
63
            putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
64
            putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE); //NOI18N
64
        }
65
        }
65
        if (Boolean.getBoolean("netbeans.ps.combohack")) { //NOI18N
66
        if (Boolean.getBoolean("netbeans.ps.combohack")) { //NOI18N
66
            setLightWeightPopupEnabled(false);
67
            setLightWeightPopupEnabled(false);
Lines 73-78 Link Here
73
            updateUI();
74
            updateUI();
74
        }
75
        }
75
    }
76
    }
77
    
78
    /** Uses the shared instance of the fast HTML renderer */
79
    public ListCellRenderer getRenderer() {
80
        HtmlRenderer.Renderer result = HtmlRenderer.sharedRenderer();
81
        //Popups should paint as the focused component even though the combo
82
        //is what has focus
83
        result.setParentFocused (true);
84
        result.setIcon (null);
85
        return result;
86
    }    
76
    
87
    
77
    /** Overridden to add a listener to the editor if necessary, since the
88
    /** Overridden to add a listener to the editor if necessary, since the
78
     * UI won't do that for us without a focus listener */
89
     * UI won't do that for us without a focus listener */
(-)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 / +54 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.sharedRenderer();
558
                ((HtmlRenderer.Renderer) lbl).reset();
559
                lbl.setEnabled (isEnabled());
560
                lbl.setText (getText());
561
                lbl.setIcon (getIcon());
562
                lbl.setIconTextGap (getIconTextGap());
563
                lbl.setBounds (getBounds());
564
                lbl.setOpaque (true);
565
                lbl.setBackground (getBackground());
566
                lbl.setForeground (getForeground());
567
                ((HtmlRenderer.Renderer) lbl).setRenderStyle(HtmlRenderer.STYLE_TRUNCATE);
568
                lbl.paint (g);
557
            }
569
            }
558
            clear();
570
            clear();
559
        }
571
        }
Lines 572-577 Link Here
572
                    b.paintBorder(this, g, 0,  0, getWidth(), getHeight());
584
                    b.paintBorder(this, g, 0,  0, getWidth(), getHeight());
573
                }
585
                }
574
                Rectangle r = getBounds();
586
                Rectangle r = getBounds();
587
                //XXX May be the source of Rochelle's multiple rows of error 
588
                //marking misalignment problem...(I do not jest)
575
                r.x = getWidth() > 16 ? editor instanceof Boolean3WayEditor ? 0 : 3 : 0; //align text with other renderers
589
                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
590
                r.width -= getWidth() > 16 ? editor instanceof Boolean3WayEditor ? 0 : 3 : 0; //align text with other renderers
577
                r.y = 0;
591
                r.y = 0;
Lines 584-602 Link Here
584
        public void clear() {
598
        public void clear() {
585
            editor = null;
599
            editor = null;
586
            env = null;
600
            env = null;
587
            setText(""); //NOI18N
588
            setIcon(null);
601
            setIcon(null);
589
            setOpaque(true);
602
            setOpaque(true);
590
        }
603
        }
591
        
604
        
605
        private Object value = null;
592
        public void setValue(Object o) {
606
        public void setValue(Object o) {
593
            super.setValue(o);
607
            value = o;
594
        }
608
            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
        }
609
        }
601
610
602
        public void connect(PropertyEditor p, PropertyEnv env) {
611
        public void connect(PropertyEditor p, PropertyEnv env) {
Lines 664-670 Link Here
664
        }
673
        }
665
        
674
        
666
        public boolean supportsTextEntry() {
675
        public boolean supportsTextEntry() {
667
            return true;
676
            return false;
668
        }
677
        }
669
        
678
        
670
        /** Overridden to do nothing */
679
        /** Overridden to do nothing */
Lines 675-712 Link Here
675
        protected void fireStateChanged() {
684
        protected void fireStateChanged() {
676
        }
685
        }
677
        
686
        
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) {
687
        public void addActionListener(ActionListener al) {
711
            //do nothing
688
            //do nothing
712
        }
689
        }
Lines 739-772 Link Here
739
                super.firePropertyChange(name, old, nue);
716
                super.firePropertyChange(name, old, nue);
740
            }
717
            }
741
        }
718
        }
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
    }
719
    }
771
    
720
    
772
    private static final class RadioRenderer extends RadioInplaceEditor {
721
    private static final class RadioRenderer extends RadioInplaceEditor {
Lines 1051-1118 Link Here
1051
        
1000
        
1052
        public boolean supportsTextEntry() {
1001
        public boolean supportsTextEntry() {
1053
            return false;
1002
            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
        }
1003
        }
1117
    }    
1004
    }    
1118
}
1005
}
(-)openide/src/org/openide/explorer/propertysheet/SheetCellRenderer.java (-111 / +32 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();
71
                HtmlRenderer.Renderer ren = (HtmlRenderer.Renderer) lbl;
72
                ren.setRenderStyle(HtmlRenderer.STYLE_TRUNCATE);
73
74
                lbl.setText (txt);
75
                ren.setHtml (isHtml);
76
                lbl.setOpaque (selected);
77
                if (selected) {
78
                    lbl.setBackground (table.getSelectionBackground());
79
                    lbl.setForeground (table.getSelectionForeground());
80
                    lbl.setOpaque (true);
81
                }
82
79
                if (includeMargin) {
83
                if (includeMargin) {
80
                    lbl.setBorder(BorderFactory.createEmptyBorder(
84
                    ren.setIndent (PropUtils.getMarginWidth() + 2);
81
                        0, PropUtils.getMarginWidth() + 2, 0, 1));
82
                } else {
85
                } else {
83
                    //Use a 2 pixel margin so it's not flush
86
                    ren.setIndent (PropUtils.getTextMargin());
84
                    lbl.setBorder(BorderFactory.createEmptyBorder(0, 
85
                        PropUtils.getTextMargin(), 0, 0));
86
                }
87
                }
87
                
88
                
88
                //Support for name marking with icon requested by form editor
89
                //Support for name marking with icon requested by form editor
Lines 91-111 Link Here
91
                    lbl.setIcon((Icon) o);
92
                    lbl.setIcon((Icon) o);
92
                } else if (o instanceof Image) {
93
                } else if (o instanceof Image) {
93
                    lbl.setIcon(new ImageIcon((Image) o));
94
                    lbl.setIcon(new ImageIcon((Image) o));
94
                } else {
95
                    lbl.setIcon(null);
96
                }
95
                }
97
                
96
                
98
                result = lbl;
97
                result = lbl;
99
            } else {
98
            } else {
100
                result = factory().getRenderer((Property) fd);
99
                result = factory().getRenderer((Property) fd);
101
                //Use a 2 pixel margin so it's not flush
100
                if (selected) {
102
                /*
101
                    result.setBackground (table.getSelectionBackground());
103
                ((JComponent)result).setBorder(BorderFactory.createEmptyBorder(0, 
102
                    result.setForeground (table.getSelectionForeground());
104
                    PropUtils.getTextMargin(), 0, 0));
103
                    ((JComponent)result).setOpaque(true);
105
                 */
104
                } else {
105
                    result.setBackground (table.getBackground());
106
                    result.setForeground (table.getForeground());
107
                    ((JComponent)result).setOpaque(false);
108
                }
106
            }
109
            }
107
        }
110
        }
108
//        result.setFont(table.getFont());
109
        return result;
111
        return result;
110
    }
112
    }
111
    
113
    
Lines 114-199 Link Here
114
            factory = new RendererFactory(true);
116
            factory = new RendererFactory(true);
115
        }
117
        }
116
        return factory;
118
        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
    }
119
    }
199
}
120
}
(-)openide/src/org/openide/explorer/propertysheet/SheetTable.java (-35 / +55 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
    
432
    /** Paint the expandable sets.  These are painted double width,
438
    /** Paint the expandable sets.  These are painted double width,
433
     *  across the entire width of the table. */
439
     *  across the entire width of the table. */
434
    private void paintExpandableSets (Graphics g) {
440
    private void paintExpandableSets (Graphics g) {
435
        //special paint handling for double-wide expando sets
441
        int start = getFirstVisibleRow();
436
        //XXX build an array of appropriate component indices in getCellRenderer and don't iterate
442
        int end = getVisibleRowCount();
437
            //the entire set of rows!
443
438
        int max = this.getRowCount();
444
        Insets ins = getInsets();
439
        for (int i = 0; i < max; i++) {
445
440
            //find the items that are property sets
446
        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().
447
                        KeyboardFocusManager.getCurrentKeyboardFocusManager().
454
                        getPermanentFocusOwner());
448
                        getPermanentFocusOwner());
455
                    Component c = tcr.getTableCellRendererComponent(this, 
456
                        this.getValueAt (i,0), selected, false, i, 0);
457
                    
449
                    
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
                    
450
                    
463
                    c.setBackground (selected ? 
451
        for (int i=0; i < end; i++) {
464
                        PropUtils.getSelectedSetRendererColor() :
452
            int idx = start + i;
465
                        PropUtils.getSetRendererColor());
453
            Object value = getValueAt (idx, 0);
466
454
467
                    c.setForeground (selected ?
455
            if (value instanceof PropertySet) {
468
                        PropUtils.getSelectedSetForegroundColor() :
469
                        PropUtils.getSetForegroundColor());
470
                        
456
                        
471
                    paintComponent(g, c, r.x, r.y, r.width, r.height);
457
                Rectangle r = getCellRect (idx, 0, false);
458
                r.x = ins.left;
459
                r.width = getWidth() - (ins.left + ins.right);
460
                if (g.hitClip (r.x, r.y, r.width, r.height)) {
461
                    PropertySet ps = (PropertySet) value;
462
463
                    String txt = ps.getHtmlDisplayName();
464
                    boolean isHtml = txt != null;
465
                    if (!isHtml) {
466
                        txt = ps.getDisplayName();
467
                    }
468
                    boolean selected = canBeSelected && getSelectedRow() == idx;
469
470
                    HtmlRenderer.Renderer ren = HtmlRenderer.sharedRenderer();
471
                    JComponent painter = (JComponent) ren.getTableCellRendererComponent(this, txt, selected, selected, idx, 0);
472
473
                    ren.setHtml (isHtml);
474
475
                    ren.setIconTextGap (2);
476
477
                    ren.setIcon(
478
                        getPropertySetModel().isExpanded(ps) ?
479
                            PropUtils.getExpandedIcon() : PropUtils.getCollapsedIcon()
480
                    );
481
482
                    if (!selected) {
483
                        painter.setBackground (PropUtils.getSetRendererColor());
484
                        painter.setForeground (PropUtils.getSetForegroundColor());
485
                    } else {
486
                        painter.setBackground (PropUtils.getSelectedSetRendererColor());
487
                        painter.setForeground (PropUtils.getSelectedSetForegroundColor());
488
                    }
489
                    painter.setOpaque (true);
490
491
                    paintComponent (g, painter, r.x, r.y, r.width, r.height);
472
                }
492
                }
473
            }
493
            }
474
        }
494
        }
(-)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 (-423 / +126 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;
23
import javax.swing.JList;
27
import javax.swing.JTree;
24
import javax.swing.JTree;
28
import javax.swing.ListCellRenderer;
25
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;
26
import javax.swing.tree.TreeCellRenderer;
34
27
35
import java.beans.BeanInfo;
28
import javax.swing.Icon;
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;
29
import javax.swing.SwingUtilities;
41
import javax.swing.plaf.ColorUIResource;
42
30
43
import org.openide.ErrorManager;
31
import org.openide.awt.HtmlRenderer;
32
import org.openide.awt.ListPane;
44
import org.openide.nodes.Node;
33
import org.openide.nodes.Node;
45
import org.openide.util.Utilities;
46
34
47
35
48
/** Default renderer for nodes. Can paint either Nodes directly or
36
/** Default renderer for nodes. Can paint either Nodes directly or
Lines 50-93 Link Here
50
 *
38
 *
51
 * @see org.openide.nodes.Node
39
 * @see org.openide.nodes.Node
52
 *
40
 *
53
 * @author Jaroslav Tulach
41
 * @author Jaroslav Tulach, Tim Boudreau
54
 */
42
 */
55
public class NodeRenderer extends Object
43
public class NodeRenderer extends Object
56
implements TreeCellRenderer, ListCellRenderer {
44
implements TreeCellRenderer, ListCellRenderer {
45
    
57
    /** Shared instance of <code>NodeRenderer</code>. */
46
    /** Shared instance of <code>NodeRenderer</code>. */
58
    private static NodeRenderer sharedInstance;
47
    //no point in lazy initialization, it's the only thing this class
48
    //is used for
49
    private static NodeRenderer sharedInstance = new NodeRenderer(); 
59
50
60
    /** Flag indicating if to use big icons. */
51
    /** Flag indicating if to use big icons. */
61
    private boolean bigIcons;
52
    private boolean bigIcons = false;
62
63
    static Border emptyBorder = BorderFactory.createEmptyBorder (1, 1, 1, 1);
64
    
53
    
65
    /** Creates default renderer. */
54
    /** Creates default renderer. */
66
    public NodeRenderer () {
55
    public NodeRenderer () {
67
    }
56
    }
68
57
69
70
    /** Creates renderer.
58
    /** Creates renderer.
71
     * @param bigIcons use big icons if possible
59
     * @param bigIcons use big icons if possible
60
     * @deprecated bigIcons was only used by IconView, and not used by that anymore.  Use <code>sharedInstance()</code>
61
     * to get an instance of NodeRenderer.
72
     */
62
     */
73
    public NodeRenderer (boolean bigIcons) {
63
    public NodeRenderer (boolean bigIcons) {
74
        this.bigIcons = bigIcons;
64
        this.bigIcons = bigIcons;
75
    }
65
    }
76
66
77
67
78
    /** Gets for one singleton <code>sharedInstance</code>. */
68
    /** Get the singleton instance used by all explorer views. */
79
    public static NodeRenderer sharedInstance () {
69
    public static NodeRenderer sharedInstance () {
80
        if (sharedInstance == null) {
81
            sharedInstance = new NodeRenderer ();
82
        }
83
        return sharedInstance;
70
        return sharedInstance;
84
    }
71
    }
85
72
86
    
73
    
87
    //
88
    // Rendering methods
89
    //
90
91
    /** Finds the component that is capable of drawing the cell in a tree.
74
    /** Finds the component that is capable of drawing the cell in a tree.
92
     * @param value value can be either <code>Node</code> 
75
     * @param value value can be either <code>Node</code> 
93
     * or a <code>VisualizerNode</code>.
76
     * or a <code>VisualizerNode</code>.
Lines 98-300 Link Here
98
        boolean sel, boolean expanded,
81
        boolean sel, boolean expanded,
99
        boolean leaf, int row, boolean hasFocus
82
        boolean leaf, int row, boolean hasFocus
100
    ) {
83
    ) {
101
        return getTree().getTreeCellRendererComponent (
84
        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
85
123
        if (vis == null) {
86
        String text = vis.getHtmlDisplayName();
124
            vis = VisualizerNode.EMPTY;
87
        boolean isHtml = text != null;
88
        if (!isHtml) {
89
            text = vis.getDisplayName();
125
        }
90
        }
126
91
127
        ListCellRenderer r = bigIcons ? (ListCellRenderer)getPane() : getList();
92
        HtmlRenderer.Renderer ren = HtmlRenderer.sharedRenderer();
128
129
        Component result = r.getListCellRendererComponent (
130
                   list, vis, index, isSelected, cellHasFocus
131
               );
132
        result.setFont(list.getFont());
133
        return result;
134
    }
135
93
136
    // ********************
94
        //Get our result value - really it is ren, but this call causes
137
    // Support for dragging
95
        //it to configure itself with the passed values
138
    // ********************
96
        Component result = ren.getTreeCellRendererComponent(
139
97
            tree, text, sel, expanded, leaf, row, hasFocus);
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
98
161
    /** default icon to use when none is present */
99
        ren.setHtml(isHtml);
162
    private static final String DEFAULT_ICON = "org/openide/resources/defaultNode.gif"; // NOI18N
163
100
164
    /** loaded default icon */
101
        //Do our additional configuration - set up the icon and possibly
165
    private static ImageIcon defaultIcon;
102
        //do some hacks to make it look focused for TreeTableView
103
        configureFrom (ren, tree, expanded, sel, vis);
166
104
167
    /** of icons used (Image, IconImage)*/
105
        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
    }
106
    }
178
107
179
    /** Finds imager for given resource.
108
    /** This is the only method defined by <code>ListCellRenderer</code>.  We just
180
     * @param image image to get
109
     * reconfigure the <code>Jlabel</code> each time we're called.
181
     * @return icon for the image
182
     */
110
     */
183
    static ImageIcon getIcon (Image image) {
111
    public Component getListCellRendererComponent (
184
        Reference ref = (Reference)map.get (image);
112
        JList list, Object value, int index,  boolean sel, 
185
113
        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
114
115
        VisualizerNode vis = findVisualizerNode(value);
226
116
227
    /** Tree cell renderer. Accepts only <code>VisualizerNode</code> values. */
117
        String text = vis.getHtmlDisplayName();
228
    final static class Tree extends JLabel implements TreeCellRenderer {
118
        boolean isHtml = text != null;
229
        /** generated Serialized Version UID */
119
        if (!isHtml) {
230
        static final long serialVersionUID = -183570483117501696L;
120
            text = vis.getDisplayName();
231
        
121
        }
232
        private boolean treeHasFocus = false;
122
233
        
123
        HtmlRenderer.Renderer ren = HtmlRenderer.sharedRenderer();
234
        private boolean hasFocus = false;
124
235
        private boolean selected = false;
125
        //Get our result value - really it is ren, but this call causes
236
        
126
        //it to configure itself with the passed values
237
        public Tree() {
127
        Component result = ren.getListCellRendererComponent(
238
        }
128
            list, text, index, sel, cellHasFocus || value == draggedOver);
239
        
129
        ren.setHtml(isHtml);
240
        public boolean isOpaque() {
130
        
241
            return false;
131
        //Do our additional configuration - set up the icon and possibly
242
        }
132
        //do some hacks to make it look focused for TreeTableView
133
        int iconWidth = configureFrom (ren, list, false, sel, vis);
134
        
135
        boolean bigIcons = this.bigIcons || list instanceof ListPane;
136
        
137
        if (bigIcons) {
138
            ren.setCentered(true);
139
        } else {
140
            //Indent elements in a ListView/ChoiceView relative to their position
141
            //in the node tree.  Only does anything if you've subclassed and
142
            //overridden createModel().  Does anybody do that?
143
            if (list.getModel() instanceof NodeListModel && ((NodeListModel) list.getModel()).getDepth() > 1) {
144
                int indent = iconWidth *
145
                    NodeListModel.findVisualizerDepth (list.getModel (), vis);
243
        
146
        
244
        public void setBackground (Color c) {
147
                ren.setIndent (indent);
245
            bg = c;
246
        }
247
        
248
        private Color bg = Color.WHITE;
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
            }
148
            }
256
            super.paint(g);
257
        }
149
        }
150
        return result;
151
    }
258
        
152
        
259
        /** @return Rendered cell component */
153
    /** Utility method which performs configuration which is common to all of the renderer 
260
        public Component getTreeCellRendererComponent(
154
     * implementations - sets the icon and focus properties on the renderer
261
            JTree tree, Object value,
155
     * from the VisualizerNode.
262
            boolean sel, boolean expanded,
156
     *
263
            boolean leaf, int row, boolean hasFocus
157
     */
264
        ) {
158
    private int configureFrom (HtmlRenderer.Renderer ren, Container
265
            setEnabled(tree.isEnabled());
159
        target, boolean useOpenedIcon, boolean sel, VisualizerNode vis) {
266
            // accepts only VisualizerNode
267
            VisualizerNode vis = (VisualizerNode)value;
268
            
160
            
269
	    Image iconImg;
161
        Icon icon = vis.getIcon(useOpenedIcon, bigIcons);
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
            
162
            
276
            // bugfix #28515, check if getIcon contract isn't broken
163
        if (icon.getIconWidth() > 0) {
277
            if (iconImg == null) {
164
            //Max annotated icon width is 24, so to have all the text and all
278
                String method = expanded ? "getOpenedIcon" : "getIcon"; // NOI18N
165
            //the icons come out aligned, set the icon text gap to the difference
279
                ErrorManager.getDefault ().log (ErrorManager.WARNING, "Node \"" + vis.node.getName () + // NOI18N
166
            //plus a two pixel margin
280
                    "\" [" +vis.node.getClass().getName()+ "] cannot return null from " + method + "(). See Node." + method + " contract."); // NOI18N
167
            ren.setIconTextGap (24 - icon.getIconWidth());
281
            } else {
168
        } else {
282
                ImageIcon nodeicon = NodeRenderer.getIcon(iconImg);
169
            //If the icon width is 0, fill the space and add in
283
170
            //the extra two pixels so the node names are aligned (btw, this
284
                setIconTextGap (4 - nodeicon.getIconWidth()
171
            //does seem to waste a frightful amount of horizontal space in
285
                                    + ( nodeicon.getIconWidth() > 24 ? nodeicon.getIconWidth() : 24 ) );
172
            //a tree that can use all it can get)
286
                setIcon(nodeicon);
173
            ren.setIndent (26);
287
            }
174
        }
288
175
289
            setText(vis.getDisplayName ());
176
        ren.setIcon (icon);
290
177
291
            // provide "drag under" feedback if DnD operation is active // NOI18N
178
        //Do the kooky focus dance so the tree that is a renderer for
292
            if (vis == draggedOver) {
179
        //TreeTableView will paint as though focused even though it's
293
                sel = true;
180
        //never onscreen.
294
            }
181
        if (target instanceof JTree) {
295
182
            boolean treeHasFocus = false;
296
	    this.hasFocus = hasFocus;
297
            selected = sel;
298
            
183
            
299
            if (sel) {
184
            if (sel) {
300
                //Find out who has focus
185
                //Find out who has focus
Lines 302-543 Link Here
302
                    KeyboardFocusManager.getCurrentKeyboardFocusManager().
187
                    KeyboardFocusManager.getCurrentKeyboardFocusManager().
303
                    getPermanentFocusOwner();
188
                    getPermanentFocusOwner();
304
189
305
                treeHasFocus = focusOwner == tree || 
190
                treeHasFocus = focusOwner == target ||
306
                    tree.isAncestorOf(focusOwner) || focusOwner instanceof TreeTable;
191
                    target.isAncestorOf(focusOwner) || focusOwner 
192
                    instanceof TreeTable;
307
                
193
                
308
                if (!treeHasFocus) {
194
                if (!treeHasFocus) {
309
                    TreeTable tt = (TreeTable)SwingUtilities.getAncestorOfClass(TreeTable.class, focusOwner);
195
                    TreeTable tt = (TreeTable)SwingUtilities.getAncestorOfClass(
196
                        TreeTable.class, focusOwner);
197
                    
310
                    if (tt != null) {
198
                    if (tt != null) {
311
                        treeHasFocus = focusOwner != null &&  
199
                        treeHasFocus = focusOwner != null &&  
312
                            tt.getDefaultRenderer(TreeTableModelAdapter.class) 
200
                            tt.getDefaultRenderer(TreeTableModelAdapter.class) 
313
                            == tree;
201
                            == target;
314
                    }
202
                    }
315
                }
203
                }
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
            }
204
            }
328
            
205
            if (treeHasFocus) {
329
            return this;
206
                ren.setParentFocused(true);
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
        }
351
        
352
        public void repaint() {
353
            
354
        }
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
     *
366
     * @author   Ian Formanek
367
     */
368
    static final class List extends JLabel implements ListCellRenderer {
369
        /** generated Serialized Version UID */
370
        static final long serialVersionUID = -8387317362588264203L;
371
372
	/** Focused Node border. */
373
        protected static Border focusBorder = UIManager.getColor ("List.focusCellHighlight") != null ? // NOI18N
374
                            BorderFactory.createLineBorder (UIManager.getColor ("List.focusCellHighlight")) : // NOI18N
375
                            // recommended by issue 37335
376
                            LineBorder.createGrayLineBorder ();
377
378
	public List() {
379
            setOpaque(true);
380
	}
381
382
        /** This is the only method defined by ListCellRenderer.  We just
383
         * reconfigure the Jlabel each time we're called.
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
            }
207
            }
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
        }
434
435
    } // End of class List.
436
437
438
    /** List cell renderer which renders icon and display name from <code>VisualizerNode</code>. */
439
    final static class Pane extends JLabel implements ListCellRenderer {
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
        }
208
        }
209
        return icon.getIconWidth() == 0 ? 24 : icon.getIconWidth();
210
    }
456
211
457
        /** This is the only method defined by ListCellRenderer.  We just
212
    /** Utility method to find a visualizer node for the object passed to
458
         * reconfigure the Jlabel each time we're called.
213
     * any of the cell renderer methods as the value */
459
         * @param list the JList
214
    private static final VisualizerNode findVisualizerNode(Object value) {
460
         * @param value the value returned by list.getModel().getElementAt(index)
215
        VisualizerNode vis = (value instanceof Node) ?
461
         * @param index the cells index
216
        VisualizerNode.getVisualizer (null, (Node) value) :
462
         * @param isSelected <code>true</code> if the specified cell was selected
217
            (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
218
480
            if (isSelected){
219
            if (vis == null) {
481
                setBackground(hasFocus ? list.getSelectionBackground() 
220
                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
            }
221
            }
491
222
492
            setBorder(cellHasFocus ? focusBorder : emptyBorder);
223
            return vis;
493
            return this;
494
        }
224
        }
495
225
496
	protected void firePropertyChange(String name, Object old, Object nw) {
226
    // ********************
497
	    // do really nothing!
227
    // Support for dragging
498
        }
228
    // ********************
499
229
500
    } // End of class Pane.
230
    /** Value of the cell with 'drag under' visual feedback */
231
    private static VisualizerNode draggedOver;
501
232
502
233
503
    private static Color noFocusSelectionBackground=null;
234
    /** DnD operation enters. Update look and feel to the 'drag under' state.
504
    static Color getNoFocusSelectionBackground() {
235
     * @param dragged the value of cell which should have 'drag under' visual feedback
505
        if (noFocusSelectionBackground == null) {
236
         */
506
            //allow theme/ui custom definition
237
    static void dragEnter (Object dragged) {
507
            noFocusSelectionBackground = 
238
        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
    }
239
    }
523
    
240
    
524
    private static Color noFocusSelectionForeground=null;
241
    /** DnD operation exits. Revert to the normal look and feel. */
525
    static Color getNoFocusSelectionForeground() {
242
    static void dragExit () {
526
        if (noFocusSelectionForeground == null) {
243
        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
    }
244
    }
541
    
542
    
245
    
543
}
246
}
(-)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 (-4 / +50 lines)
Lines 703-714 Link Here
703
                    focusOwner == TreeTable.this
703
                    focusOwner == TreeTable.this
704
                    || TreeTable.this.isAncestorOf(focusOwner);
704
                    || TreeTable.this.isAncestorOf(focusOwner);
705
                
705
                
706
                //TODO - it should be possible to simply set the correct
707
                //color in prepareRenderer for the tree's cell renderer,
708
                //rather than set it for the whole tree.  Might fix a 
709
                //couple problems. -Tim
706
                setBackground(tableHasFocus ?
710
                setBackground(tableHasFocus ?
707
                    table.getSelectionBackground() :
711
                    table.getSelectionBackground() :
708
                    NodeRenderer.getNoFocusSelectionBackground());
712
                    getUnfocusedSelectedBackground());
709
                setForeground(tableHasFocus ?
713
                setForeground(tableHasFocus ?
710
                    table.getSelectionForeground() :
714
                    table.getSelectionForeground() :
711
                    NodeRenderer.getNoFocusSelectionForeground());
715
                    getUnfocusedSelectedForeground());
712
            } else {
716
            } else {
713
		setBackground(table.getBackground());
717
		setBackground(table.getBackground());
714
                setForeground(table.getForeground());
718
                setForeground(table.getForeground());
Lines 818-827 Link Here
818
        if (isEditing() && editorComp != null) {
822
        if (isEditing() && editorComp != null) {
819
            editorComp.setBackground(focused ?
823
            editorComp.setBackground(focused ?
820
                getSelectionBackground() :
824
                getSelectionBackground() :
821
                NodeRenderer.getNoFocusSelectionBackground());
825
                getUnfocusedSelectedBackground());
822
            editorComp.setForeground(focused ?
826
            editorComp.setForeground(focused ?
823
                getSelectionForeground() :
827
                getSelectionForeground() :
824
                NodeRenderer.getNoFocusSelectionForeground());
828
                getUnfocusedSelectedForeground());
825
        }
829
        }
826
    }
830
    }
827
831
Lines 1611-1614 Link Here
1611
            }
1615
            }
1612
        }
1616
        }
1613
    }
1617
    }
1618
    
1619
    private static Color unfocusedSelBg = null;
1620
    private static Color unfocusedSelFg = null;
1621
    
1622
    /** Get the system-wide unfocused selection background color */
1623
    static Color getUnfocusedSelectedBackground() {
1624
        if (unfocusedSelBg == null) {
1625
            //allow theme/ui custom definition
1626
            unfocusedSelBg =
1627
            UIManager.getColor("nb.explorer.unfocusedSelBg"); //NOI18N
1628
            if (unfocusedSelBg == null) {
1629
                //try to get standard shadow color
1630
                unfocusedSelBg = UIManager.getColor("controlShadow"); //NOI18N
1631
                if (unfocusedSelBg == null) {
1632
                    //Okay, the look and feel doesn't suport it, punt
1633
                    unfocusedSelBg = Color.lightGray;
1634
                }
1635
                //Lighten it a bit because disabled text will use controlShadow/
1636
                //gray
1637
                unfocusedSelBg = unfocusedSelBg.brighter();
1638
            }
1639
        }
1640
        return unfocusedSelBg;
1641
    }
1642
    
1643
    /** Get the system-wide unfocused selection foreground color */
1644
    static Color getUnfocusedSelectedForeground() {
1645
        if (unfocusedSelFg == null) {
1646
            //allow theme/ui custom definition
1647
            unfocusedSelFg =
1648
            UIManager.getColor("nb.explorer.unfocusedSelFg"); //NOI18N
1649
            if (unfocusedSelFg == null) {
1650
                //try to get standard shadow color
1651
                unfocusedSelFg = UIManager.getColor("textText"); //NOI18N
1652
                if (unfocusedSelFg == null) {
1653
                    //Okay, the look and feel doesn't suport it, punt
1654
                    unfocusedSelFg = Color.BLACK;
1655
                }
1656
            }
1657
        }
1658
        return unfocusedSelFg;
1659
    }    
1614
}
1660
}
(-)openide/src/org/openide/explorer/view/TreeView.java (-1 / +1 lines)
Lines 1106-1112 Link Here
1106
                // note: dropTarget is activated in constructor
1106
                // note: dropTarget is activated in constructor
1107
            }
1107
            }
1108
            // lazy cell editor init
1108
            // lazy cell editor init
1109
            tree.setCellEditor(new TreeViewCellEditor(tree, new NodeRenderer.Tree ()));
1109
            tree.setCellEditor(new TreeViewCellEditor(tree));
1110
            tree.setEditable(true);
1110
            tree.setEditable(true);
1111
        }
1111
        }
1112
        
1112
        
(-)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 / +75 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
            }
283
            SwingUtilities.invokeLater (this);
300
            SwingUtilities.invokeLater (this);
284
            return;
301
            return;
285
        }
302
        }
Lines 305-317 Link Here
305
                } 
322
                } 
306
            } );
323
            } );
307
        }
324
        }
308
        /*
309
        if (
310
            "lookAndFeel".equals (name) // NOI18N
311
        ) {
312
            SwingUtilities.invokeLater (this);
313
        }
314
        */
315
    }
325
    }
316
326
317
    /** Update the state of this class by retrieving new name, etc.
327
    /** Update the state of this class by retrieving new name, etc.
Lines 391-396 Link Here
391
        return getDisplayName ();
401
        return getDisplayName ();
392
    }
402
    }
393
403
404
    public String getHtmlDisplayName() {
405
        return node.getHtmlDisplayName();
406
    }
407
    
408
    Icon getIcon(boolean opened, boolean large) {
409
        int newCacheType = getCacheType(opened, large);
410
        
411
        if (cachedIconType != newCacheType) {
412
            int iconType = large ? BeanInfo.ICON_COLOR_32x32 : 
413
                BeanInfo.ICON_COLOR_16x16;
414
            
415
            Image image = opened ? node.getOpenedIcon(
416
                iconType) : node.getIcon(
417
                iconType);
418
419
            // bugfix #28515, check if getIcon contract isn't broken
420
            if (image == null) {
421
                String method = opened ? "getOpenedIcon" : "getIcon"; // NOI18N
422
                    ErrorManager.getDefault ().log (ErrorManager.WARNING, "Node \""
423
                    + node.getName () +
424
                    "\" [" + node.getClass().getName()+
425
                    "] cannot return null from " + method + "(). See Node." +
426
                    method + " contract."); // NOI18N
427
428
                icon = defaultIcon;
429
            } else {
430
                icon = new ImageIcon (image);
431
            }
432
        }
433
        cachedIconType = newCacheType;
434
        return icon;
435
    }
436
    
437
    /** Some simple bitmasking to determine the type of the cached icon. 
438
     * Generally, it's worth caching one, but not a bunch - generally one will
439
     * be used repeatedly. */
440
    private static final int getCacheType (boolean opened, boolean large) {
441
        return (opened ? 2 : 0) | (large ? 1 : 0);
442
    }
443
    
444
    /** loaded default icon */
445
    private static Icon defaultIcon;
446
    private int cachedIconType = -1;
447
    
448
    /** default icon to use when none is present */
449
    private static final String DEFAULT_ICON = "org/openide/resources/defaultNode.gif"; // NOI18N
450
    /** Loads default icon if not loaded. */
451
    private static Icon getDefaultIcon () {
452
        if (defaultIcon == null) {
453
            defaultIcon = new ImageIcon(Utilities.loadImage(DEFAULT_ICON));
454
        }
455
        return defaultIcon;
456
    }
457
    
394
    /** Strong reference.
458
    /** Strong reference.
395
    */
459
    */
396
    private static final class StrongReference extends WeakReference {
460
    private static final class StrongReference extends WeakReference {
(-)openide/src/org/openide/filesystems/FileSystem.java (+27 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
    public static interface HtmlStatus extends Status { 
716
        /** Annotate a name such that the returned value contains HTML markup.
717
         * The return value less the html content should typically be the same 
718
         * as the return value from <code>annotateName()</code>.
719
         * <p>
720
         * For consistency with <code>Node.getHtmlDisplayName()</code>, 
721
         * filesystems that proxy other filesystems (and so must implement
722
         * this interface to supply HTML annotations) should return null if
723
         * the filesystem they proxy does not provide an implementation of
724
         * HTMLStatus.
725
         *
726
         * @see org.openide.awt.HtmlRenderer  */
727
        public String annotateNameHtml (String name, java.util.Set files);
728
    }
729
    
703
    /** Empty status */
730
    /** Empty status */
704
    private static final Status STATUS_NONE = new Status () {
731
    private static final Status STATUS_NONE = new Status () {
705
                public String annotateName (String name, java.util.Set files) {
732
                public String annotateName (String name, java.util.Set files) {
(-)openide/src/org/openide/nodes/FilterNode.java (+34 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>
655
     * @see org.openide.nodes.Node#getHtmlDisplayName
656
     * @return An HTML display name, if available, or null if no display name
657
     * is available   */
658
    public String getHtmlDisplayName() {
659
        if (overridesGetDisplayName()) {
660
            return null;
661
        } else {
662
            return delegating (DELEGATE_GET_DISPLAY_NAME) ? 
663
                original.getHtmlDisplayName() : super.getHtmlDisplayName();
664
        }
665
    }
666
    
667
    private boolean overridesGetDisplayName() {
668
        if (getClass() != FilterNode.class) {
669
            try {
670
                Method m = getClass().getMethod("getDisplayName", null); //NOI18N
671
                return m.getDeclaringClass() != FilterNode.class;
672
            } catch (NoSuchMethodException nsme) {
673
                //can't happen
674
                ErrorManager.getDefault().notify(nsme);
675
                return true;
676
            }
677
        } else {
678
            return false;
679
        }
646
    }
680
    }
647
    
681
    
648
    /*
682
    /*
(-)openide/src/org/openide/nodes/Node.java (+54 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.
665
     * <p><strong>This method should return either an HTML display name
666
     * or null; it should not return the non-html display name.
667
     *
668
     * @see org.openide.awt.HtmlRenderer
669
     * @return a String containing conformant, legal HTML markup which
670
     *  represents the display name, or null.  The default implementation
671
     *  returns null.  */
672
    public String getHtmlDisplayName() {
673
        return null;
674
    }
675
    
658
    /** Register delegating lookup so it can always be found.
676
    /** Register delegating lookup so it can always be found.
659
     */
677
     */
660
    final void registerDelegatingLookup (NodeLookup l) {
678
    final void registerDelegatingLookup (NodeLookup l) {
Lines 1051-1056 Link Here
1051
        public int hashCode () {
1069
        public int hashCode () {
1052
            return getName().hashCode ();
1070
            return getName().hashCode ();
1053
        }
1071
        }
1072
        
1073
        /** Return a variant of the display name containing HTML markup
1074
         * conforming to the limited subset of font-markup HTML supported by
1075
         * the lightweight HTML renderer org.openide.awt.HtmlRenderer
1076
         * (font color, bold, italic and strikethrough supported; font
1077
         * colors can be UIManager color keys if they are prefixed with
1078
         * a ! character, i.e. &lt;font color=&amp;'controlShadow'&gt;).
1079
         * Enclosing html tags are not needed.
1080
         * <p><strong>This method should return either an HTML display name
1081
         * or null; it should not return the non-html display name.
1082
         *
1083
         * @see org.openide.awt.HtmlRenderer
1084
         * @return a String containing conformant, legal HTML markup which
1085
         *  represents the display name, or null.  The default implementation
1086
         *  returns null.  */
1087
        public String getHtmlDisplayName() {
1088
            return null;
1089
        }
1054
    }
1090
    }
1055
1091
1056
    /** Description of a Bean property on a node, and operations on it.
1092
    /** Description of a Bean property on a node, and operations on it.
Lines 1197-1202 Link Here
1197
            
1233
            
1198
            return getName ().hashCode () * 
1234
            return getName ().hashCode () * 
1199
                   ( valueType == null ? 1 : valueType.hashCode () );
1235
                   ( valueType == null ? 1 : valueType.hashCode () );
1236
        }
1237
1238
        /** Return a variant of the display name containing HTML markup 
1239
         * conforming to the limited subset of font-markup HTML supported by
1240
         * the lightweight HTML renderer org.openide.awt.HtmlRenderer 
1241
         * (font color, bold, italic and strikethrough supported; font
1242
         * colors can be UIManager color keys if they are prefixed with
1243
         * a ! character, i.e. &lt;font color=&amp;'controlShadow'&gt;).
1244
         * Enclosing html tags are not needed.
1245
         * <p><strong>This method should return either an HTML display name
1246
         * or null; it should not return the non-html display name.
1247
         * 
1248
         * @see org.openide.awt.HtmlRenderer  
1249
         * @return a String containing conformant, legal HTML markup which
1250
         *  represents the display name, or null.  The default implementation
1251
         *  returns null.  */
1252
        public String getHtmlDisplayName() {
1253
            return null;
1200
        }
1254
        }
1201
    }
1255
    }
1202
1256
(-)projects/projectui/src/org/netbeans/modules/project/ui/ProjectsRootNode.java (+4 lines)
Lines 252-257 Link Here
252
            return isMain() ? MessageFormat.format( badgedNamePattern, new Object[] { original } ) : original;
252
            return isMain() ? MessageFormat.format( badgedNamePattern, new Object[] { original } ) : original;
253
        }
253
        }
254
254
255
        public String getHtmlDisplayName() {
256
            return isMain() ? "<b>" + super.getDisplayName() + "</b>" : null; //NOI18N
257
        }
258
255
        public Image getIcon( int type ) {
259
        public Image getIcon( int type ) {
256
            Image original = super.getIcon( type );                
260
            Image original = super.getIcon( type );                
257
            return isMain() ? Utilities.mergeImages( original, mainProjectBadge, 5, 10 ) : original;
261
            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