OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 149 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 /*
15
 * Created on 23 janv. 2005
16
 */
17
package org.openconcerto.ui.component;
18
 
19
import static org.openconcerto.ui.component.ComboLockedMode.LOCKED;
20
import static org.openconcerto.ui.component.ComboLockedMode.UNLOCKED;
142 ilm 21
 
93 ilm 22
import org.openconcerto.ui.component.InteractionMode.InteractionComponent;
80 ilm 23
import org.openconcerto.ui.component.combo.ISearchableComboPopup;
17 ilm 24
import org.openconcerto.ui.component.text.TextComponent;
25
import org.openconcerto.ui.valuewrapper.ValueChangeSupport;
26
import org.openconcerto.ui.valuewrapper.ValueWrapper;
27
import org.openconcerto.utils.CompareUtils;
93 ilm 28
import org.openconcerto.utils.IFutureTask;
17 ilm 29
import org.openconcerto.utils.checks.ValidListener;
21 ilm 30
import org.openconcerto.utils.checks.ValidState;
17 ilm 31
import org.openconcerto.utils.model.ListComboBoxModel;
32
import org.openconcerto.utils.text.DocumentFilterList;
33
import org.openconcerto.utils.text.SimpleDocumentFilter;
34
import org.openconcerto.utils.text.SimpleDocumentListener;
35
 
36
import java.awt.Button;
37
import java.awt.Component;
38
import java.awt.Dimension;
39
import java.awt.event.ActionEvent;
40
import java.awt.event.ActionListener;
41
import java.awt.event.KeyAdapter;
42
import java.awt.event.KeyEvent;
80 ilm 43
import java.awt.event.KeyListener;
44
import java.beans.PropertyChangeEvent;
17 ilm 45
import java.beans.PropertyChangeListener;
73 ilm 46
import java.util.Arrays;
17 ilm 47
import java.util.List;
93 ilm 48
import java.util.concurrent.Future;
49
import java.util.concurrent.FutureTask;
80 ilm 50
import java.util.regex.Pattern;
17 ilm 51
 
52
import javax.swing.ComboBoxEditor;
80 ilm 53
import javax.swing.DefaultListCellRenderer;
17 ilm 54
import javax.swing.JButton;
55
import javax.swing.JComboBox;
56
import javax.swing.JComponent;
80 ilm 57
import javax.swing.JList;
156 ilm 58
import javax.swing.JTextField;
80 ilm 59
import javax.swing.SwingUtilities;
144 ilm 60
import javax.swing.SwingWorker;
17 ilm 61
import javax.swing.event.DocumentEvent;
62
import javax.swing.text.AbstractDocument;
63
import javax.swing.text.BadLocationException;
80 ilm 64
import javax.swing.text.DocumentFilter;
19 ilm 65
import javax.swing.text.DocumentFilter.FilterBypass;
17 ilm 66
import javax.swing.text.JTextComponent;
67
 
80 ilm 68
import net.jcip.annotations.GuardedBy;
69
 
17 ilm 70
/**
71
 * A comboBox that can be editable or not, and whose values are taken from a ITextComboCache.
72
 *
73
 * @author Sylvain CUAZ
74
 */
93 ilm 75
public class ITextCombo extends JComboBox implements ValueWrapper<String>, TextComponent, InteractionComponent {
17 ilm 76
 
73 ilm 77
    /**
78
     * System property, if <code>true</code> buttons children will not be focusable (allowing
79
     * quicker tab navigation).
80
     */
81
    public static final String SIMPLE_TRAVERSAL = "org.openconcerto.ui.simpleTraversal";
82
 
80 ilm 83
    private static final Pattern DIGIT_PATTERN = Pattern.compile("\\d+");
84
 
17 ilm 85
    private static final String DEFAULTVALUE = "";
86
 
87
    private final String defaultValue;
88
    private final ComboLockedMode locked;
25 ilm 89
    private final ValueChangeSupport<String> supp;
80 ilm 90
    private KeyListener keyListener = null;
91
    private DocumentFilter docFilter = null;
17 ilm 92
    protected final boolean autoComplete;
93
    protected boolean keyPressed;
94
    private boolean completing;
95
 
96
    // cache
80 ilm 97
    @GuardedBy("EDT")
17 ilm 98
    private boolean cacheLoading;
80 ilm 99
    // only valid while cache is loading
17 ilm 100
    private String objToSelect;
80 ilm 101
    @GuardedBy("EDT")
93 ilm 102
    private InteractionMode modeToSet;
103
    private InteractionMode interactionBridge;
17 ilm 104
    protected boolean modifyingDoc;
105
 
106
    private ITextComboCache cache;
107
 
108
    public ITextCombo() {
109
        this(DEFAULTVALUE);
110
    }
111
 
112
    public ITextCombo(String defaultValue) {
113
        this(defaultValue, UNLOCKED);
114
    }
115
 
116
    public ITextCombo(boolean locked) {
117
        this(locked ? LOCKED : UNLOCKED);
118
    }
119
 
120
    public ITextCombo(ComboLockedMode mode) {
121
        this(DEFAULTVALUE, mode);
122
    }
123
 
124
    public ITextCombo(String defaultValue, ComboLockedMode mode) {
125
        super(new ListComboBoxModel());
126
        // messes with our checkCache
127
        this.getListModel().setSelectOnAdd(false);
128
        this.supp = new ValueChangeSupport<String>(this);
129
        this.locked = mode;
130
 
131
        this.defaultValue = defaultValue;
132
 
133
        this.autoComplete = true;
134
        this.keyPressed = false;
135
        this.completing = false;
136
 
137
        this.cache = null;
138
        this.cacheLoading = false;
139
        this.modifyingDoc = false;
140
 
156 ilm 141
        final int h = new JTextField("12").getPreferredSize().height;
142
        this.setMinimumSize(new Dimension(80, h));
17 ilm 143
        // Test de Preferred Size pour ne pas exploser les GridBagLayouts
156 ilm 144
        this.setPreferredSize(new Dimension(120, h));
93 ilm 145
        this.setInteractionMode(InteractionMode.READ_WRITE);
17 ilm 146
 
147
        // ATTN marche car locked est final, sinon il faudrait pouvoir enlever/ajouter les listeners
148
        if (this.isLocked()) {
149
            this.addActionListener(new ActionListener() {
150
                public void actionPerformed(ActionEvent e) {
151
                    ITextCombo.this.supp.fireValueChange();
152
                }
153
            });
154
        } else {
155
            // pour écouter quand notre contenu change
156
            // marche à la fois pour edition du texte et la sélection d'un élément
80 ilm 157
            final SimpleDocumentListener docListener = new SimpleDocumentListener() {
17 ilm 158
                public void update(DocumentEvent e) {
159
                    // if we are responsible for this event, ignore it
160
                    if (!ITextCombo.this.modifyingDoc)
80 ilm 161
                        setValue(SimpleDocumentListener.getText(e.getDocument()));
17 ilm 162
                    ITextCombo.this.supp.fireValueChange();
163
                }
80 ilm 164
            };
165
            // listen to editor changes as BasicComboBoxUI.uninstallUI() removes it (happens when
166
            // changing l&f or locking windows pro)
167
            this.addPropertyChangeListener("editor", new PropertyChangeListener() {
168
                {
169
                    // init
170
                    changeListener(getTextComp(), true);
171
                    assert ITextCombo.this.keyListener == null && ITextCombo.this.docFilter == null;
172
                }
173
 
174
                @Override
175
                public void propertyChange(PropertyChangeEvent evt) {
176
                    final JTextComponent oldTextComp = getTextComp((ComboBoxEditor) evt.getOldValue());
177
                    if (oldTextComp != null) {
178
                        changeListener(oldTextComp, false);
179
                        oldTextComp.removeKeyListener(ITextCombo.this.keyListener);
180
                        DocumentFilterList.remove((AbstractDocument) oldTextComp.getDocument(), ITextCombo.this.docFilter);
181
                    }
182
 
183
                    final JTextComponent newTextComp = getTextComp((ComboBoxEditor) evt.getNewValue());
184
                    if (newTextComp != null) {
185
                        changeListener(newTextComp, true);
186
                        addCompletionListeners(newTextComp);
187
                    }
188
                }
189
 
190
                private final void changeListener(final JTextComponent textComp, final boolean add) {
191
                    if (add)
192
                        textComp.getDocument().addDocumentListener(docListener);
193
                    else
194
                        textComp.getDocument().removeDocumentListener(docListener);
195
                }
17 ilm 196
            });
197
        }
80 ilm 198
        this.setRenderer(new DefaultListCellRenderer() {
199
            @Override
200
            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
201
                final Component res = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
202
                // works because DefaultListCellRenderer reset the background (which is not the
203
                // case for DefaultTableCellRenderer)
204
                if (!isSelected && value != null && value.equals(getValue()))
205
                    ISearchableComboPopup.setCurrentValueBG(list, res);
206
                return res;
207
            }
208
        });
17 ilm 209
 
80 ilm 210
        if (Boolean.getBoolean(SIMPLE_TRAVERSAL)) {
17 ilm 211
            for (final Component child : this.getComponents()) {
212
                if (child instanceof JButton || child instanceof Button)
213
                    child.setFocusable(false);
214
            }
80 ilm 215
        }
216
        // set default value
217
        this.resetValue();
17 ilm 218
    }
219
 
93 ilm 220
    @Override
17 ilm 221
    public void configureEditor(ComboBoxEditor anEditor, Object anItem) {
222
        // quand on quitte une combo, elle fait setSelectedItem(), qui appelle editor.setItem()
223
        // qui fait editor.getComponent().setText(), quit fait un removeAll() suivi d'un addAll()
224
        // donc emptyChange(true) puis emptyChange(false).
225
        // Ce qui fait que quand on quitte une combo required pour cliquer sur "ajouter", le bouton
226
        // flashe (il passe brièvement en grisé) et on ne peut ajouter.
227
        if (!anEditor.getItem().equals(anItem))
228
            super.configureEditor(anEditor, anItem);
229
    }
230
 
231
    protected final ComboLockedMode getMode() {
232
        return this.locked;
233
    }
234
 
235
    private boolean isLocked() {
236
        return this.locked == LOCKED;
237
    }
238
 
65 ilm 239
    public final boolean hasCache() {
240
        return this.cache != null;
241
    }
242
 
93 ilm 243
    public final Future<? extends ITextCombo> initCache(String... values) {
244
        return this.initCache(Arrays.asList(values));
73 ilm 245
    }
246
 
93 ilm 247
    public final Future<? extends ITextCombo> initCache(List<String> values) {
248
        return this.initCache(new ImmutableITextComboCache(values));
73 ilm 249
    }
250
 
93 ilm 251
    public final Future<? extends ITextCombo> initCache(ITextComboCache cache) {
65 ilm 252
        if (cache == null)
253
            throw new NullPointerException("null cache");
254
        if (this.hasCache())
17 ilm 255
            throw new IllegalStateException("cache already set " + this.cache);
256
 
257
        this.cache = cache;
80 ilm 258
        assert this.hasCache();
17 ilm 259
 
260
        new MutableListComboPopupListener(new MutableListCombo() {
261
            public ComboLockedMode getMode() {
262
                return ITextCombo.this.getMode();
263
            }
264
 
265
            public Component getPopupComp() {
266
                return getEditor().getEditorComponent();
267
            }
268
 
83 ilm 269
            @Override
270
            public boolean canModifyCache() {
271
                return true;
272
            }
273
 
17 ilm 274
            public void addCurrentText() {
275
                ITextCombo.this.addCurrentText();
276
            }
277
 
278
            public void removeCurrentText() {
279
                ITextCombo.this.removeCurrentText();
280
            }
25 ilm 281
 
282
            @Override
283
            public boolean canReload() {
284
                return true;
285
            }
286
 
287
            @Override
288
            public void reload() {
289
                ITextCombo.this.loadCache(true);
290
            }
17 ilm 291
        }).listen();
292
 
93 ilm 293
        final Future<? extends ITextCombo> future = this.loadCache(false);
17 ilm 294
 
295
        // ATTN marche car locked est final
296
        if (!this.isLocked()) {
80 ilm 297
            this.keyListener = new KeyAdapter() {
17 ilm 298
                @Override
299
                public void keyTyped(KeyEvent e) {
300
                    // not keyPressed() else we activate the completion as soon as any key is
301
                    // pressed (even ctrl)
302
                    ITextCombo.this.keyPressed = true;
303
                }
304
 
305
                @Override
306
                public void keyReleased(KeyEvent e) {
307
                    ITextCombo.this.keyPressed = false;
308
                }
80 ilm 309
            };
310
            this.docFilter = new SimpleDocumentFilter() {
17 ilm 311
                @Override
312
                protected boolean change(FilterBypass fb, String newText, Mode mode) throws BadLocationException {
313
                    // do not complete a remove (otherwise impossible to remove the last char for
314
                    // example), only complete when the user is typing (eg a key is pressed)
315
                    // otherwise just setting the value to something that can be completed changes
316
                    // it.
317
                    if (mode != Mode.REMOVE && ITextCombo.this.autoComplete && ITextCombo.this.keyPressed)
318
                        return complete(fb, newText);
319
                    else
320
                        return true;
321
                }
80 ilm 322
            };
323
            addCompletionListeners(this.getTextComp());
17 ilm 324
        }
93 ilm 325
        return future;
17 ilm 326
    }
327
 
80 ilm 328
    protected final void addCompletionListeners(final JTextComponent textComp) {
329
        textComp.addKeyListener(ITextCombo.this.keyListener);
330
        DocumentFilterList.add((AbstractDocument) textComp.getDocument(), ITextCombo.this.docFilter);
331
    }
332
 
17 ilm 333
    protected final boolean complete(FilterBypass fb, final String originalText) throws BadLocationException {
334
        // no need to check the cache since we only use the combo items
335
        // and they only are modified by the EDT, our executing thread too
336
        boolean res = true;
337
        if (!this.completing) {
338
            this.completing = true;
339
            // ne completer que si le texte fait plus de 2 char et n'est pas que des chiffres
149 ilm 340
            if (canComplete(originalText)) {
17 ilm 341
                String completion = this.getCompletion(originalText);
342
                if (completion != null && !originalText.trim().equalsIgnoreCase(completion.trim())) {
343
                    fb.replace(0, fb.getDocument().getLength(), completion, null);
344
                    // we handled the modification
345
                    res = false;
346
                    this.getTextComp().setSelectionStart(originalText.length());
347
                    this.getTextComp().setSelectionEnd(completion.length());
348
                }
349
            }
350
            this.completing = false;
351
        }
352
        return res;
353
    }
354
 
355
    /**
149 ilm 356
     * hook to activate or not complete for a given text
357
     *
358
     * @return true if completion must occur
359
     */
360
    public boolean canComplete(final String originalText) {
361
        return originalText.length() > 2 && !DIGIT_PATTERN.matcher(originalText).matches();
362
    }
363
 
364
    /**
17 ilm 365
     * Recherche si on peut completer la string avec les items de completion
366
     *
367
     * @param string the start
368
     * @return <code>null</code> si pas trouve, sinon le mot complet
369
     */
370
    private String getCompletion(String string) {
371
        if (string.length() < 1) {
372
            return null;
373
        }
374
 
375
        int count = 0;
376
        String result = null;
377
        for (final Object obj : this.getListModel().getList()) {
378
            final String item = (String) obj;
379
            if (item.startsWith(string)) {
380
                count++;
381
                result = item;
382
            }
383
        }
384
        if (count == 1)
385
            return result;
386
        else
387
            return null;
388
    }
389
 
390
    private ListComboBoxModel getListModel() {
391
        return (ListComboBoxModel) this.getModel();
392
    }
393
 
93 ilm 394
    @Override
395
    public InteractionMode getInteractionMode() {
396
        return this.interactionBridge;
17 ilm 397
    }
398
 
399
    @Override
93 ilm 400
    public void setInteractionMode(InteractionMode mode) {
80 ilm 401
        assert SwingUtilities.isEventDispatchThread();
93 ilm 402
        if (this.cacheLoading) {
403
            this.modeToSet = mode;
404
        } else if (mode != this.interactionBridge) {
405
            this.interactionBridge = mode;
406
            // to support R/O we disable the combo and put and non editable text field
407
            super.setEnabled(mode.isEditable());
408
            final boolean superEditable = !isLocked() || mode == InteractionMode.READ_ONLY;
409
            super.setEditable(superEditable);
410
            if (superEditable) {
411
                final JTextComponent comp = getTextComp(this.getEditor());
412
                mode.applyTo(comp);
413
            } else {
414
                assert getTextComp(this.getEditor()) == null || !getTextComp(this.getEditor()).isDisplayable();
415
            }
17 ilm 416
        }
417
    }
418
 
93 ilm 419
    @Override
420
    public void setEditable(boolean b) {
421
        // cannot overload setEditable() to behave like JTextComponent : since we cannot overload
422
        // isEditable() this would translate to an incoherence between setEditable() and
423
        // isEditable()
424
        super.setEditable(b);
425
    }
426
 
427
    // cannot overload isEditable() to behave like JTextComponent since the look and feels don't
428
    // support it
429
 
430
    @Override
431
    public void setEnabled(boolean b) {
432
        this.setInteractionMode(b ? InteractionMode.READ_WRITE : InteractionMode.DISABLED);
433
    }
434
 
17 ilm 435
    // *** cache
436
 
93 ilm 437
    /**
438
     * Load the cache passed to {@link #initCache(ITextComboCache)} into this.
439
     *
440
     * @param force <code>true</code> if the cache should be refreshed.
441
     * @return a future returning <code>this</code> after this is filled by the cache or
442
     *         <code>null</code> if this already being filled. NOTE : do not block on this future in
443
     *         the EDT, as it needs to finish in the EDT. So this would cause a deadlock.
444
     */
445
    public synchronized final Future<? extends ITextCombo> loadCache(final boolean force) {
80 ilm 446
        assert SwingUtilities.isEventDispatchThread();
17 ilm 447
        if (!this.cacheLoading) {
93 ilm 448
            this.modeToSet = this.getInteractionMode();
17 ilm 449
            this.setEnabled(false);
80 ilm 450
            // value cannot be changed by user since this UI is disabled
25 ilm 451
            this.objToSelect = this.getValue();
17 ilm 452
            this.cacheLoading = true;
93 ilm 453
            final FutureTask<? extends ITextCombo> noop = IFutureTask.createNoOp(this);
144 ilm 454
            final SwingWorker<List<String>, Object> sw = new SwingWorker<List<String>, Object>() {
17 ilm 455
                @Override
456
                protected List<String> doInBackground() throws Exception {
25 ilm 457
                    return force ? ITextCombo.this.cache.loadCache(false) : ITextCombo.this.cache.getCache();
17 ilm 458
                }
459
 
460
                @Override
461
                protected void done() {
462
                    synchronized (this) {
463
                        ITextCombo.this.modifyingDoc = true;
464
                    }
465
                    getListModel().removeAllElements();
466
                    try {
467
                        getListModel().addAll(this.get());
468
                    } catch (Exception e) {
469
                        e.printStackTrace();
470
                        getListModel().addElement(e.getLocalizedMessage());
471
                    }
472
                    synchronized (this) {
473
                        ITextCombo.this.modifyingDoc = false;
474
                        ITextCombo.this.cacheLoading = false;
475
                    }
476
                    // otherwise getSelectedItem() always returns null
477
                    if (isLocked() && getModel().getSize() == 0)
478
                        throw new IllegalStateException(ITextCombo.this + " locked but no items.");
479
                    // restaurer l'état
93 ilm 480
                    setInteractionMode(ITextCombo.this.modeToSet);
17 ilm 481
                    setValue(ITextCombo.this.objToSelect);
93 ilm 482
                    // we can't notify our caller at the time invokeLater() is called by the
483
                    // background thread since there's always a delay after setState() : see
484
                    // DoSubmitAccumulativeRunnable
485
                    noop.run();
17 ilm 486
                }
487
            };
488
            sw.execute();
93 ilm 489
            return noop;
490
        } else {
491
            return null;
17 ilm 492
        }
493
    }
494
 
495
    private final Object makeObj(final String item) {
496
        return item;
497
        // see #addItem ; not necessary since there's never any duplicates
498
    }
499
 
500
    /**
501
     * Add <code>s</code> to the list if it's not empty and not already present.
502
     *
503
     * @param s the string to be added, can be <code>null</code>.
504
     * @return <code>true</code> if s is really added.
505
     */
506
    private final boolean addToCache(String s) {
142 ilm 507
        final boolean added = s != null && s.length() > 0 && this.getListModel().getList().indexOf(s) < 0;
508
        if (added) {
509
            final Object obj = makeObj(s);
510
            // BasicComboBoxUI$Handler.intervalAdded(ListDataEvent) calls contentsChanged() which
511
            // calls JComboBox.configureEditor() with the current selection. But the selection is
512
            // only set by BasicComboBoxUI.Handler.focusLost() so this.addItem() replaces the
513
            // current editor value by the current selection.
514
            this.completing = true;
515
            this.setSelectedItem(obj);
516
            this.completing = false;
517
            this.addItem(obj);
518
        }
519
        return added;
17 ilm 520
    }
521
 
522
    private final void removeCurrentText() {
523
        final String t = this.getTextComp().getText();
524
        this.cache.deleteFromCache(t);
525
        for (int i = 0; i < this.getItemCount(); i++) {
526
            final String o = (String) this.getItemAt(i);
527
            if (o.equals(t)) {
528
                this.removeItemAt(i);
529
                break;
530
            }
531
        }
532
    }
533
 
534
    private final void addCurrentText() {
535
        final String t = this.getTextComp().getText();
536
        if (this.addToCache(t)) {
537
            this.cache.addToCache(t);
538
        }
539
    }
540
 
541
    // *** value
542
 
543
    public void addValueListener(PropertyChangeListener l) {
544
        this.supp.addValueListener(l);
545
    }
546
 
547
    public void rmValueListener(PropertyChangeListener l) {
548
        this.supp.rmValueListener(l);
549
    }
550
 
80 ilm 551
    final boolean isCacheLoading() {
552
        return this.cacheLoading;
553
    }
554
 
17 ilm 555
    synchronized public final void setValue(String val) {
80 ilm 556
        if (!CompareUtils.equals(this.getValue(), val)) {
557
            if (this.cacheLoading) {
558
                this.objToSelect = val;
559
                this.supp.fireValueChange();
560
            } else {
561
                // complete only user input, not programmatic
562
                this.completing = true;
563
                this.setSelectedItem(makeObj(val));
564
                this.completing = false;
565
            }
17 ilm 566
        }
567
    }
568
 
569
    public void resetValue() {
570
        this.setValue(this.defaultValue);
571
    }
572
 
80 ilm 573
    @Override
17 ilm 574
    public String getValue() {
80 ilm 575
        if (this.cacheLoading)
576
            return this.objToSelect;
577
        else
578
            return this.getCurrentValue();
579
    }
580
 
581
    public String getCurrentValue() {
17 ilm 582
        // this.getSelectedItem() renvoie vide quand on tape du texte sans sélection
149 ilm 583
        final Object res;
584
        if (this.isLocked()) {
585
            res = this.getSelectedItem();
586
        } else {
587
            final ComboBoxEditor editor = this.getEditor();
588
            // as documented in the constructor, the editor can sometimes be null
589
            res = editor == null ? null : editor.getItem();
590
        }
591
        return (String) res;
17 ilm 592
    }
593
 
594
    public JComponent getComp() {
595
        return this;
596
    }
597
 
21 ilm 598
    @Override
599
    public ValidState getValidState() {
17 ilm 600
        // string toujours valide
21 ilm 601
        return ValidState.getTrueInstance();
17 ilm 602
    }
603
 
21 ilm 604
    @Override
17 ilm 605
    public void addValidListener(ValidListener l) {
606
        // nothing to do
607
    }
608
 
19 ilm 609
    @Override
610
    public void removeValidListener(ValidListener l) {
611
        // nothing to do
612
    }
613
 
17 ilm 614
    // document
615
 
80 ilm 616
    private final JTextComponent getTextComp(final ComboBoxEditor editor) {
617
        if (editor != null) {
618
            final Component editorComp = editor.getEditorComponent();
619
            if (editorComp instanceof JTextComponent)
620
                return (JTextComponent) editorComp;
621
        }
622
        return null;
623
    }
624
 
625
    @Override
17 ilm 626
    public JTextComponent getTextComp() {
627
        if (this.isLocked())
628
            return null;
629
        else
80 ilm 630
            return getTextComp(this.getEditor());
17 ilm 631
    }
632
 
633
    @Override
634
    public String toString() {
635
        return this.getClass().getName() + " " + this.locked + " cache: " + this.cache;
636
    }
637
 
638
}