Bundle: fix memory leak at init/reset
[fanfix.git] / ui / ConfigItem.java
CommitLineData
d350b96b
NR
1package be.nikiroo.utils.ui;
2
3import java.awt.BorderLayout;
daf0fd5a 4import java.awt.Cursor;
c637d2e0 5import java.awt.Dimension;
424dcb0d
NR
6import java.awt.event.ActionEvent;
7import java.awt.event.ActionListener;
daf0fd5a
NR
8import java.awt.event.MouseAdapter;
9import java.awt.event.MouseEvent;
424dcb0d 10import java.awt.image.BufferedImage;
daf0fd5a 11import java.io.IOException;
d5026c09 12import java.util.List;
424dcb0d 13
d5026c09 14import javax.swing.BoxLayout;
424dcb0d
NR
15import javax.swing.ImageIcon;
16import javax.swing.JButton;
0877d6f5 17import javax.swing.JComponent;
8517b60c 18import javax.swing.JLabel;
0877d6f5 19import javax.swing.JOptionPane;
d350b96b
NR
20import javax.swing.JPanel;
21import javax.swing.JTextField;
d350b96b 22
daf0fd5a 23import be.nikiroo.utils.Image;
0877d6f5
NR
24import be.nikiroo.utils.StringUtils;
25import be.nikiroo.utils.StringUtils.Alignment;
d350b96b 26import be.nikiroo.utils.resources.Bundle;
9e834013 27import be.nikiroo.utils.resources.MetaInfo;
d350b96b
NR
28
29/**
30 * A graphical item that reflect a configuration option from the given
31 * {@link Bundle}.
76b51de9
NR
32 * <p>
33 * This graphical item can be edited, and the result will be saved back into the
34 * linked {@link MetaInfo}; you still have to save the {@link MetaInfo} should
35 * you wish to, of course.
d350b96b
NR
36 *
37 * @author niki
db31c358 38 *
d350b96b
NR
39 * @param <E>
40 * the type of {@link Bundle} to edit
41 */
a0376372 42public abstract class ConfigItem<E extends Enum<E>> extends JPanel {
d350b96b 43 private static final long serialVersionUID = 1L;
db31c358 44
e95f4fb6
NR
45 private static int minimumHeight = -1;
46
01a5d26c 47 /** A small 16x16 "?" blue in PNG, base64 encoded. */
a0376372 48 private static String img64info = //
daf0fd5a
NR
49 ""
50 + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI"
51 + "WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wURFRg6IrtcdgAAATdJREFUOMvtkj8sQ1EUxr9z/71G"
52 + "m1RDogYxq7WDDYMYTSajSG4n6YRYzSaSLibWbiaDIGwdiLIYDFKDNJEgKu969xi8UNHy7H7LPcN3"
53 + "v/Odcy+hG9oOIeIcBCJS9MAvlZtOMtHxsrFrJHGqe0RVGnHAHpcIbPlng8BS3HmKBJYzabGUzcrJ"
54 + "XK+ckIrqANYR2JEv2nYDEVck0WKGfHzyq82Go+btxoX3XAcAIqTj8wPqOH6mtMeM4bGCLhyfhTMA"
55 + "qlLhKHqujCfaweCAmV0p50dPzsNpEKpK01V/n55HIvTnfDC2odKlfeYadZN/T+AqDACUsnkhqaU1"
56 + "LRIVuX1x7ciuSWQxVIrunONrfq3dI6oh+T94Z8453vEem/HTqT8ZpFJ0qDXtGkPbAGAMeSRngQCA"
57 + "eUvgn195AwlZWyvjtQdhAAAAAElFTkSuQmCC";
58
249e4ef4 59 /** A small 16x16 "+" image with colours */
a0376372 60 private static String img64add = //
01a5d26c
NR
61 ""
62 + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI"
63 + "WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wUeES0QBFvvnAAAAB1pVFh0Q29tbWVudAAAAAAAQ3Jl"
64 + "YXRlZCB3aXRoIEdJTVBkLmUHAAACH0lEQVQ4y42Tz0sVURTHP+fMmC7CQMpH1EjgIimCsEVBEIg/"
65 + "qIbcBAW2Uai1m/oH2rlJXLQpeRJt2gQhTO0iTTKC1I2JBf5gKCJCRPvhPOed22LmvV70Fn7hwr3c"
66 + "+z3ne+73HCFHEClxaASRHgduA91AW369BkwDI3Foy0GkEofmACQnSxyaCyItAkMClMzYdeCAJgVP"
67 + "tJJrPA7tVoUjNZlngXMAiRmXClfoK/Tjq09x7T6LW+8RxOVJ5+LQzgSRojm5WCEDlMrQVbjIQNtN"
68 + "rh0d5FTzaTLBmWKgM4h0Ig4NzWseohYCJUuqx123Sx0MBpF2+MAdyWUnlqX4lf4bIDHjR+rwJJPR"
69 + "qNCgCjDsA10lM/oKIRcO9lByCYklnG/pqQa4euQ6J5tPoKI0yD6ef33Ku40Z80R7CSJNWyZxT+Ki"
70 + "2ytGP911hyZxQaRp1RtPPPYKD4+sGJwPrDUp7Q9Xxnj9fYrUUnaszEAwQHfrZQAerT/g7cYMiuCp"
71 + "z8LmLI0qBqz6wLQn2v5he57FrXkAtlPH2ZZOuskCzG2+4dnnx3iSuSgCKqLAlAIjmXPiVIRsgYjU"
72 + "usrfO0Gq7cA9jUNbBsZrmiQnac1e6n3FeBzakpf39OSBG9IPHAZwzlFoagVg5edHXn57wZed9dpA"
73 + "C3FoYRDpf8M0AQwKwu9yubxjeA7Y72ENqlp3mOqMcwcwDPQCx8gGchV4BYzGoS1V3gL8AVA5C5/0"
74 + "oRFoAAAAAElFTkSuQmCC";
75
249e4ef4 76 /** A small 32x32 "-" image with colours */
a0376372 77 private static String img64remove = //
01a5d26c
NR
78 ""
79 + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI"
80 + "WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wUeESw5X/JGsQAAAB1pVFh0Q29tbWVudAAAAAAAQ3Jl"
81 + "YXRlZCB3aXRoIEdJTVBkLmUHAAACKUlEQVQ4y5WTO2iTYRSG3+//v/+SJrG5SSABh1JQBHFJNUNR"
82 + "YodCLoMoTkK0YKhQtBmsl01wKVZRBwcrgosg3SwFW9Cippe0VmlpB6uYqYIaNSZtbv/lOKRx0iR9"
83 + "4YOzvOc8vOd8wLbG4nYGAKP9tshKr3Pq0zFXORt0UzbopvUeZ2ml1/niUcIWAYBzwwqr+xgAjCSt"
84 + "wpXjWzx105Ha+1XsMgT8U6IJfPAacyfO50OXJi3VwbtbxMbidtZ3tiClbzi/eAuCmxgai4AfNvNn"
85 + "KJn3X5xWKgwA0lHHYud3MdDUXMcmIOMx0oGJXJCN9tuiJ98p4//DbtTk2cFKhB/OSBcMgQHVMkir"
86 + "AqwJBhGYrIIkCQc2eJK3aewI9Crko2FIh0K1Jo0mcwmV6XFUlmfRXhK7eXuRKaRVIYdiUGKnW8Kn"
87 + "0ia0t6/hKHJVqCcLzncQgLhtIvBfbWbZZahq+cl96AuvQLre2Mw59NUlkCwjZ6USL0uYgSj26B/X"
88 + "oK+vtkYgMAhMRF4x5oWlPdod0UQtfUFo7YEBBKz59BEGAAtRx1xHVgzu5AYyHmMmMJHrZolhhU3t"
89 + "05XJe7s2PJuCq9k1MgKyNjOXiBf8kWW5JDy4XKHBl2ql6+pvX8ZjzDOqrcWsFQAAE/T3H3z2GG/6"
90 + "zhT8sfdKeehWkUQAeJ7WcH23xTz1uPBwf1hclA3mBZjPojFOIOSsVPpmN1OznfpA+Gn+2kCHqg/d"
91 + "LhIA/AFU5d0V6gTjtQAAAABJRU5ErkJggg==";
92
aa9c3626
NR
93 /** The code base */
94 private final ConfigItemBase<JComponent, E> base;
a0376372
NR
95
96 /** The main panel with all the fields in it. */
97 private JPanel main;
98
aa9c3626
NR
99 /**
100 * Prepare a new {@link ConfigItem} instance, linked to the given
101 * {@link MetaInfo}.
102 *
103 * @param info
104 * the info
105 * @param autoDirtyHandling
106 * TRUE to automatically manage the setDirty/Save operations,
107 * FALSE if you want to do it yourself via
108 * {@link ConfigItem#setDirtyItem(int)}
109 */
110 protected ConfigItem(MetaInfo<E> info, boolean autoDirtyHandling) {
111 base = new ConfigItemBase<JComponent, E>(info, autoDirtyHandling) {
112 @Override
113 protected JComponent createEmptyField(int item) {
114 return ConfigItem.this.createEmptyField(item);
115 }
116
117 @Override
118 protected Object getFromInfo(int item) {
119 return ConfigItem.this.getFromInfo(item);
120 }
121
122 @Override
123 protected void setToInfo(Object value, int item) {
124 ConfigItem.this.setToInfo(value, item);
125 }
126
127 @Override
128 protected Object getFromField(int item) {
129 return ConfigItem.this.getFromField(item);
130 }
131
132 @Override
133 protected void setToField(Object value, int item) {
134 ConfigItem.this.setToField(value, item);
135 }
136
137 @Override
138 public JComponent createField(int item) {
139 JComponent field = super.createField(item);
140
141 int height = Math.max(getMinimumHeight(),
142 field.getMinimumSize().height);
143 field.setPreferredSize(new Dimension(200, height));
144
145 return field;
146 }
147
148 @Override
149 public List<JComponent> reload() {
150 List<JComponent> removed = base.reload();
151 if (!removed.isEmpty()) {
152 for (JComponent c : removed) {
153 main.remove(c);
154 }
155 main.revalidate();
156 main.repaint();
157 }
158
159 return removed;
160 }
161
162 @Override
163 protected JComponent removeItem(int item) {
164 JComponent removed = super.removeItem(item);
165 main.remove(removed);
166 main.revalidate();
167 main.repaint();
168
169 return removed;
170 }
171 };
172 }
01a5d26c 173
76b51de9
NR
174 /**
175 * Create a new {@link ConfigItem} for the given {@link MetaInfo}.
176 *
d18e136e
NR
177 * @param nhgap
178 * negative horisontal gap in pixel to use for the label, i.e.,
179 * the step lock sized labels will start smaller by that amount
180 * (the use case would be to align controls that start at a
181 * different horisontal position)
76b51de9 182 */
a0376372 183 public void init(int nhgap) {
aa9c3626 184 if (getInfo().isArray()) {
d5026c09
NR
185 this.setLayout(new BorderLayout());
186 add(label(nhgap), BorderLayout.WEST);
187
a0376372
NR
188 main = new JPanel();
189
d5026c09 190 main.setLayout(new BoxLayout(main, BoxLayout.Y_AXIS));
aa9c3626 191 int size = getInfo().getListSize(false);
d5026c09 192 for (int i = 0; i < size; i++) {
aa9c3626 193 addItemWithMinusPanel(i);
d5026c09 194 }
a0376372
NR
195 main.revalidate();
196 main.repaint();
fde375c1 197
01a5d26c 198 final JButton add = new JButton();
a0376372 199 setImage(add, img64add, "+");
01a5d26c 200
d5026c09
NR
201 add.addActionListener(new ActionListener() {
202 @Override
203 public void actionPerformed(ActionEvent e) {
aa9c3626 204 addItemWithMinusPanel(base.getFieldsSize());
ee7a6ccb
NR
205 main.revalidate();
206 main.repaint();
d5026c09
NR
207 }
208 });
fde375c1 209
d5026c09
NR
210 JPanel tmp = new JPanel(new BorderLayout());
211 tmp.add(add, BorderLayout.WEST);
fde375c1 212
d5026c09
NR
213 JPanel mainPlus = new JPanel(new BorderLayout());
214 mainPlus.add(main, BorderLayout.CENTER);
215 mainPlus.add(tmp, BorderLayout.SOUTH);
fde375c1 216
d5026c09
NR
217 add(mainPlus, BorderLayout.CENTER);
218 } else {
219 this.setLayout(new BorderLayout());
220 add(label(nhgap), BorderLayout.WEST);
0877d6f5 221
aa9c3626 222 JComponent field = base.createField(-1);
d5026c09
NR
223 add(field, BorderLayout.CENTER);
224 }
0877d6f5 225 }
9e834013 226
aa9c3626
NR
227 /** The {@link MetaInfo} linked to the field. */
228 public MetaInfo<E> getInfo() {
229 return base.getInfo();
230 }
231
232 /**
233 * Retrieve the associated graphical component that was created with
234 * {@link ConfigItemBase#createEmptyField(int)}.
235 *
236 * @param item
237 * the item number to get for an array of values, or -1 to get
238 * the whole value (has no effect if {@link MetaInfo#isArray()}
239 * is FALSE)
240 *
241 * @return the graphical component
242 */
243 protected JComponent getField(int item) {
244 return base.getField(item);
245 }
246
247 /**
248 * Manually specify that the given item is "dirty" and thus should be saved
249 * when asked.
250 * <p>
251 * Has no effect if the class is using automatic dirty handling (see
252 * {@link ConfigItemBase#ConfigItem(MetaInfo, boolean)}).
253 *
254 * @param item
255 * the item number to get for an array of values, or -1 to get
256 * the whole value (has no effect if {@link MetaInfo#isArray()}
257 * is FALSE)
258 */
259 protected void setDirtyItem(int item) {
260 base.setDirtyItem(item);
261 }
262
263 /**
264 * Check if the value changed since the last load/save into the linked
265 * {@link MetaInfo}.
266 * <p>
267 * Note that we consider NULL and an Empty {@link String} to be equals.
268 *
269 * @param value
270 * the value to test
271 * @param item
272 * the item number to get for an array of values, or -1 to get
273 * the whole value (has no effect if {@link MetaInfo#isArray()}
274 * is FALSE)
275 *
276 * @return TRUE if it has
277 */
278 protected boolean hasValueChanged(Object value, int item) {
279 return base.hasValueChanged(value, item);
280 }
281
282 private void addItemWithMinusPanel(int item) {
283 JPanel minusPanel = createMinusPanel(item);
284 JComponent field = base.addItem(item, minusPanel);
285 minusPanel.add(field, BorderLayout.CENTER);
286 }
a0376372 287
aa9c3626
NR
288 private JPanel createMinusPanel(final int item) {
289 JPanel minusPanel = new JPanel(new BorderLayout());
a0376372
NR
290
291 final JButton remove = new JButton();
292 setImage(remove, img64remove, "-");
293
294 remove.addActionListener(new ActionListener() {
295 @Override
296 public void actionPerformed(ActionEvent e) {
aa9c3626 297 base.removeItem(item);
a0376372
NR
298 }
299 });
300
a0376372
NR
301 minusPanel.add(remove, BorderLayout.EAST);
302
303 main.add(minusPanel);
304 main.revalidate();
305 main.repaint();
a0376372 306
aa9c3626 307 return minusPanel;
d5026c09 308 }
0877d6f5 309
d5026c09
NR
310 /**
311 * Create an empty graphical component to be used later by
a0376372 312 * {@link ConfigItem#createField(int)}.
d5026c09
NR
313 * <p>
314 * Note that {@link ConfigItem#reload(int)} will be called after it was
a0376372 315 * created by {@link ConfigItem#createField(int)}.
d5026c09
NR
316 *
317 * @param item
318 * the item number to get for an array of values, or -1 to get
319 * the whole value (has no effect if {@link MetaInfo#isArray()}
320 * is FALSE)
321 *
322 * @return the graphical component
323 */
a0376372 324 abstract protected JComponent createEmptyField(int item);
0877d6f5 325
d5026c09
NR
326 /**
327 * Get the information from the {@link MetaInfo} in the subclass preferred
328 * format.
329 *
330 * @param item
331 * the item number to get for an array of values, or -1 to get
332 * the whole value (has no effect if {@link MetaInfo#isArray()}
333 * is FALSE)
334 *
335 * @return the information in the subclass preferred format
336 */
a0376372 337 abstract protected Object getFromInfo(int item);
d18e136e 338
d5026c09
NR
339 /**
340 * Set the value to the {@link MetaInfo}.
341 *
342 * @param value
343 * the value in the subclass preferred format
344 * @param item
345 * the item number to get for an array of values, or -1 to get
346 * the whole value (has no effect if {@link MetaInfo#isArray()}
347 * is FALSE)
348 */
a0376372 349 abstract protected void setToInfo(Object value, int item);
0877d6f5 350
d5026c09 351 /**
a0376372
NR
352 * The value present in the given item's related field in the subclass
353 * preferred format.
354 *
d5026c09
NR
355 * @param item
356 * the item number to get for an array of values, or -1 to get
357 * the whole value (has no effect if {@link MetaInfo#isArray()}
358 * is FALSE)
359 *
a0376372
NR
360 * @return the value present in the given item's related field in the
361 * subclass preferred format
d5026c09 362 */
a0376372 363 abstract protected Object getFromField(int item);
0877d6f5 364
d5026c09
NR
365 /**
366 * Set the value (in the subclass preferred format) into the field.
367 *
368 * @param value
369 * the value in the subclass preferred format
370 * @param item
371 * the item number to get for an array of values, or -1 to get
372 * the whole value (has no effect if {@link MetaInfo#isArray()}
373 * is FALSE)
374 */
a0376372 375 abstract protected void setToField(Object value, int item);
0877d6f5 376
c637d2e0
NR
377 /**
378 * Create a label which width is constrained in lock steps.
379 *
d18e136e
NR
380 * @param nhgap
381 * negative horisontal gap in pixel to use for the label, i.e.,
382 * the step lock sized labels will start smaller by that amount
383 * (the use case would be to align controls that start at a
384 * different horisontal position)
c637d2e0
NR
385 *
386 * @return the label
387 */
d5026c09 388 protected JComponent label(int nhgap) {
aa9c3626 389 final JLabel label = new JLabel(getInfo().getName());
c637d2e0
NR
390
391 Dimension ps = label.getPreferredSize();
392 if (ps == null) {
393 ps = label.getSize();
394 }
395
e95f4fb6
NR
396 ps.height = Math.max(ps.height, getMinimumHeight());
397
c637d2e0 398 int w = ps.width;
daf0fd5a 399 int step = 150;
d18e136e 400 for (int i = 2 * step - nhgap; i < 10 * step; i += step) {
c637d2e0
NR
401 if (w < i) {
402 w = i;
403 break;
404 }
405 }
406
daf0fd5a 407 final Runnable showInfo = new Runnable() {
0877d6f5 408 @Override
daf0fd5a 409 public void run() {
0877d6f5 410 StringBuilder builder = new StringBuilder();
aa9c3626 411 String text = (getInfo().getDescription().replace("\\n", "\n"))
d18e136e 412 .trim();
0877d6f5
NR
413 for (String line : StringUtils.justifyText(text, 80,
414 Alignment.LEFT)) {
415 if (builder.length() > 0) {
416 builder.append("\n");
417 }
418 builder.append(line);
419 }
420 text = builder.toString();
aa9c3626
NR
421 JOptionPane.showMessageDialog(ConfigItem.this, text, getInfo()
422 .getName(), JOptionPane.INFORMATION_MESSAGE);
daf0fd5a
NR
423 }
424 };
425
426 JLabel help = new JLabel("");
427 help.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
a0376372 428 setImage(help, img64info, "?");
daf0fd5a
NR
429
430 help.addMouseListener(new MouseAdapter() {
431 @Override
432 public void mouseClicked(MouseEvent e) {
433 showInfo.run();
0877d6f5
NR
434 }
435 });
436
437 JPanel pane2 = new JPanel(new BorderLayout());
438 pane2.add(help, BorderLayout.WEST);
439 pane2.add(new JLabel(" "), BorderLayout.CENTER);
440
e95f4fb6
NR
441 JPanel contentPane = new JPanel(new BorderLayout());
442 contentPane.add(label, BorderLayout.WEST);
443 contentPane.add(pane2, BorderLayout.CENTER);
0877d6f5
NR
444
445 ps.width = w + 30; // 30 for the (?) sign
e95f4fb6
NR
446 contentPane.setSize(ps);
447 contentPane.setPreferredSize(ps);
448
449 JPanel pane = new JPanel(new BorderLayout());
450 pane.add(contentPane, BorderLayout.NORTH);
c637d2e0 451
0877d6f5 452 return pane;
c637d2e0 453 }
424dcb0d 454
a0376372
NR
455 /**
456 * Create a new {@link ConfigItem} for the given {@link MetaInfo}.
457 *
458 * @param <E>
459 * the type of {@link Bundle} to edit
460 *
461 * @param info
462 * the {@link MetaInfo}
463 * @param nhgap
464 * negative horisontal gap in pixel to use for the label, i.e.,
465 * the step lock sized labels will start smaller by that amount
466 * (the use case would be to align controls that start at a
467 * different horisontal position)
468 *
469 * @return the new {@link ConfigItem}
470 */
471 static public <E extends Enum<E>> ConfigItem<E> createItem(
472 MetaInfo<E> info, int nhgap) {
e95f4fb6 473
a0376372
NR
474 ConfigItem<E> configItem;
475 switch (info.getFormat()) {
476 case BOOLEAN:
477 configItem = new ConfigItemBoolean<E>(info);
478 break;
479 case COLOR:
480 configItem = new ConfigItemColor<E>(info);
481 break;
482 case FILE:
483 configItem = new ConfigItemBrowse<E>(info, false);
484 break;
485 case DIRECTORY:
486 configItem = new ConfigItemBrowse<E>(info, true);
487 break;
488 case COMBO_LIST:
489 configItem = new ConfigItemCombobox<E>(info, true);
490 break;
491 case FIXED_LIST:
492 configItem = new ConfigItemCombobox<E>(info, false);
493 break;
494 case INT:
495 configItem = new ConfigItemInteger<E>(info);
496 break;
497 case PASSWORD:
498 configItem = new ConfigItemPassword<E>(info);
499 break;
500 case LOCALE:
501 configItem = new ConfigItemLocale<E>(info);
502 break;
503 case STRING:
504 default:
505 configItem = new ConfigItemString<E>(info);
506 break;
e95f4fb6
NR
507 }
508
a0376372
NR
509 configItem.init(nhgap);
510 return configItem;
e95f4fb6 511 }
01a5d26c
NR
512
513 /**
514 * Set an image to the given {@link JButton}, with a fallback text if it
515 * fails.
516 *
517 * @param button
518 * the button to set
519 * @param image64
520 * the image in BASE64 (should be PNG or similar)
521 * @param fallbackText
522 * text to use in case the image cannot be created
523 */
249e4ef4 524 static protected void setImage(JLabel button, String image64,
01a5d26c
NR
525 String fallbackText) {
526 try {
527 Image img = new Image(image64);
528 try {
529 BufferedImage bImg = ImageUtilsAwt.fromImage(img);
530 button.setIcon(new ImageIcon(bImg));
531 } finally {
532 img.close();
533 }
534 } catch (IOException e) {
535 // This is an hard-coded image, should not happen
536 button.setText(fallbackText);
537 }
538 }
539
540 /**
541 * Set an image to the given {@link JButton}, with a fallback text if it
542 * fails.
543 *
544 * @param button
545 * the button to set
546 * @param image64
547 * the image in BASE64 (should be PNG or similar)
548 * @param fallbackText
549 * text to use in case the image cannot be created
550 */
249e4ef4 551 static protected void setImage(JButton button, String image64,
01a5d26c
NR
552 String fallbackText) {
553 try {
554 Image img = new Image(image64);
555 try {
556 BufferedImage bImg = ImageUtilsAwt.fromImage(img);
557 button.setIcon(new ImageIcon(bImg));
558 } finally {
559 img.close();
560 }
561 } catch (IOException e) {
562 // This is an hard-coded image, should not happen
563 button.setText(fallbackText);
564 }
565 }
a0376372
NR
566
567 static private int getMinimumHeight() {
568 if (minimumHeight < 0) {
569 minimumHeight = new JTextField("Test").getMinimumSize().height;
570 }
571
572 return minimumHeight;
573 }
d350b96b 574}