OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 17 | Rev 61 | Go to most recent revision | 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
 package org.openconcerto.sql.sqlobject;
15
 
16
import org.openconcerto.sql.model.SQLRow;
17
import org.openconcerto.sql.model.SQLSelect;
18
import org.openconcerto.sql.model.SQLTable;
25 ilm 19
import org.openconcerto.sql.model.SQLTableEvent;
20
import org.openconcerto.sql.model.SQLTableModifiedListener;
17 ilm 21
import org.openconcerto.sql.request.ComboSQLRequest;
22
import org.openconcerto.sql.view.search.SearchSpec;
23
import org.openconcerto.sql.view.search.SearchSpecUtils;
24
import org.openconcerto.ui.component.combo.Log;
25
import org.openconcerto.utils.RTInterruptedException;
26
import org.openconcerto.utils.cc.ITransformer;
27
import org.openconcerto.utils.checks.EmptyChangeSupport;
28
import org.openconcerto.utils.checks.EmptyListener;
29
import org.openconcerto.utils.checks.EmptyObj;
30
import org.openconcerto.utils.checks.MutableValueObject;
31
import org.openconcerto.utils.model.DefaultIMutableListModel;
32
 
33
import java.beans.PropertyChangeEvent;
34
import java.beans.PropertyChangeListener;
35
import java.beans.PropertyChangeSupport;
36
import java.util.ArrayList;
37
import java.util.HashMap;
38
import java.util.List;
39
import java.util.Map;
40
import java.util.concurrent.CancellationException;
41
import java.util.concurrent.ExecutionException;
42
 
43
import javax.swing.SwingUtilities;
44
import javax.swing.SwingWorker;
45
import javax.swing.event.ListDataEvent;
46
import javax.swing.event.ListDataListener;
47
 
48
/**
49
 * A model that takes its values from a {@link ComboSQLRequest}. It listens to table changes, but
50
 * can also be reloaded by calling {@link #fillCombo()}. It can be searched using
51
 * {@link #search(SearchSpec)}. Like all Swing model, it ought too be manipulated in the EDT.
52
 *
53
 * @author Sylvain CUAZ
54
 */
25 ilm 55
public class IComboModel extends DefaultIMutableListModel<IComboSelectionItem> implements SQLTableModifiedListener, MutableValueObject<IComboSelectionItem>, EmptyObj {
17 ilm 56
 
57
    private final ComboSQLRequest req;
58
 
59
    private boolean filledOnce = false;
60
    private ITransformer<List<IComboSelectionItem>, IComboSelectionItem> firstFillTransf = null;
61
    private boolean isADirtyDrityGirl = true;
62
    private boolean isOnScreen = false;
63
    private boolean sleepAllowed = true;
64
 
65
    // supports
66
    private final EmptyChangeSupport emptySupp;
67
    private final PropertyChangeSupport propSupp;
68
 
69
    // est ce que la combo va se remplir, access must be synchronized
70
    private SwingWorker<?, ?> willUpdate;
71
    protected final List<Runnable> runnables;
72
    // true from when the combo is filled with the sole "dots" item until it is loaded with actual
73
    // items, no need to synchronize (EDT)
74
    private boolean updating;
75
    // l'id à sélectionner à la fin du updateAll
76
    private int idToSelect;
77
 
78
    // index des éléments par leurs IDs
79
    private Map<Integer, IComboSelectionItem> itemsByID;
80
 
81
    private SearchSpec search;
82
 
83
    private PropertyChangeListener filterListener;
84
    // whether this is listening in order to self-update
85
    private boolean running;
86
 
87
    private boolean debug = false;
88
    private boolean addMissingItem;
89
 
90
    public IComboModel(final ComboSQLRequest req) {
91
        if (req == null)
92
            throw new NullPointerException("null request");
93
        this.req = req;
94
 
95
        this.emptySupp = new EmptyChangeSupport(this);
96
        this.propSupp = new PropertyChangeSupport(this);
97
 
98
        this.search = null;
99
        this.runnables = new ArrayList<Runnable>();
100
        this.setWillUpdate(null);
101
        this.itemsByID = new HashMap<Integer, IComboSelectionItem>();
102
        this.addMissingItem = true;
103
 
104
        this.running = false;
105
 
106
        this.setSelectOnAdd(false);
107
        this.setSelectOnRm(false);
108
 
109
        this.uiInit();
110
    }
111
 
112
    public final boolean neverBeenFilled() {
113
        return !this.filledOnce;
114
    }
115
 
116
    private final ITransformer<List<IComboSelectionItem>, IComboSelectionItem> getFirstFillSelection() {
117
        return this.firstFillTransf;
118
    }
119
 
120
    /**
121
     * Specify which item will be selected the first time the combo is filled (unless setValue() is
122
     * called before the fill).
123
     *
124
     * @param firstFillTransf will be passed the items and should return the wanted selection.
125
     */
126
    public final void setFirstFillSelection(ITransformer<List<IComboSelectionItem>, IComboSelectionItem> firstFillTransf) {
127
        this.firstFillTransf = firstFillTransf;
128
    }
129
 
130
    // consider that undef means empty if the undefined row is not in the combo
131
    // otherwise treat it like any other row.
132
    private boolean isUndefIDEmpty() {
133
        return this.getRequest().getUndefLabel() == null;
134
    }
135
 
136
    private boolean isUndefIDEmpty(int id) {
137
        return isUndefIDEmpty() && (id == this.getRequest().getPrimaryTable().getUndefinedID());
138
    }
139
 
140
    private final void uiInit() {
141
        // listeners
142
        this.filterListener = new PropertyChangeListener() {
143
            @Override
144
            public void propertyChange(PropertyChangeEvent evt) {
145
                fillCombo();
146
            }
147
        };
148
 
149
        this.addListDataListener(new ListDataListener() {
150
            @Override
151
            public void intervalRemoved(ListDataEvent e) {
152
                contentsChanged(e);
153
            }
154
 
155
            @Override
156
            public void intervalAdded(ListDataEvent e) {
157
                contentsChanged(e);
158
            }
159
 
160
            @Override
161
            public void contentsChanged(ListDataEvent e) {
162
                if (e.getIndex0() == -1 && e.getIndex1() == -1) {
163
                    // selection change
164
                    comboValueChanged();
165
                } else {
25 ilm 166
                    itemsChanged();
17 ilm 167
                }
168
            }
169
        });
170
    }
171
 
172
    void setRunning(final boolean b) {
173
        if (this.running != b) {
174
            this.running = b;
175
            if (this.running) {
176
                this.req.addTableListener(this);
177
                this.req.addWhereListener(this.filterListener);
178
                // since we weren't listening, we must have missed lots of things
179
                this.fillCombo();
180
            } else {
181
                this.req.removeTableListener(this);
182
                this.req.rmWhereListener(this.filterListener);
183
            }
184
        }
185
    }
186
 
187
    public final ComboSQLRequest getRequest() {
188
        return this.req;
189
    }
190
 
191
    public void setDebug(boolean trace) {
192
        this.debug = trace;
193
    }
194
 
195
    private void log(String s) {
196
        if (this.debug)
197
            Log.get().info(s);
198
    }
199
 
200
    synchronized void setOnScreen(boolean isOnScreen) {
201
        if (this.isOnScreen != isOnScreen) {
202
            this.isOnScreen = isOnScreen;
203
            if (this.isOnScreen && this.isADirtyDrityGirl) {
204
                this.fillCombo();
205
            }
206
        }
207
    }
208
 
209
    private synchronized boolean isOnScreen() {
210
        return this.isOnScreen;
211
    }
212
 
213
    /**
214
     * Whether this combo is allowed to delay {@link #fillCombo()} when it isn't visible.
215
     *
216
     * @param sleepAllowed <code>true</code> if reloads can be delayed.
217
     */
218
    public final void setSleepAllowed(boolean sleepAllowed) {
219
        this.sleepAllowed = sleepAllowed;
220
    }
221
 
222
    public final boolean isSleepAllowed() {
223
        return this.sleepAllowed;
224
    }
225
 
226
    /**
227
     * Reload this combo. This method is thread-safe.
228
     */
229
    public synchronized final void fillCombo() {
25 ilm 230
        this.fillCombo(null, true);
17 ilm 231
    }
232
 
25 ilm 233
    public synchronized final void fillCombo(final Runnable r, final boolean readCache) {
17 ilm 234
        // wholly synch otherwise we might get onScreen after the if
235
        // and thus completely ignore that fillCombo()
236
        if (!this.isSleepAllowed() || this.isOnScreen() || r != null) {
25 ilm 237
            this.doUpdateAll(r, readCache);
17 ilm 238
        } else {
239
            this.isADirtyDrityGirl = true;
240
        }
241
    }
242
 
243
    private void updateAllBegun() {
244
        // need to be in EDT since we access selection and modify items
245
        if (!SwingUtilities.isEventDispatchThread()) {
246
            SwingUtilities.invokeLater(new Runnable() {
247
                @Override
248
                public void run() {
249
                    updateAllBegun();
250
                }
251
            });
252
        } else {
253
            log("entering updateAllBegun");
254
            assert !isUpdating() : "Otherwise our modeToSelect = DISABLED and setEnabled() would overwrite modeToSelect";
255
            // no need to synch only in EDT
256
            this.idToSelect = this.getSelectedId();
257
 
258
            this.setUpdating(true);
259
 
25 ilm 260
            // Like ITableModel, don't remove all items, so that if the request fails we still
261
            // keep old items (we used to have uiItems=true while setting the list to "Loading...")
17 ilm 262
        }
263
    }
264
 
25 ilm 265
    private void doUpdateAll(final Runnable r, final boolean readCache) {
17 ilm 266
        log("entering doUpdateAll");
267
        synchronized (this) {
268
            this.isADirtyDrityGirl = false;
269
            // déjà en train de se rafraîchir
270
            if (this.willUpdate != null) {
271
                this.willUpdate.cancel(true);
272
            } else {
273
                updateAllBegun();
274
            }
275
            // add the runnable to an attribute since the worker we are creating might be canceled
276
            // and thus done() and r might never be called
277
            if (r != null)
278
                this.runnables.add(r);
279
            // copy the current search, if it changes fillCombo() will be called
280
            final SearchSpec search = this.getSearch();
281
            // commencer l'update après, sinon modeToSelect == 0
282
            final SwingWorker<List<IComboSelectionItem>, Object> worker = new SwingWorker<List<IComboSelectionItem>, Object>() {
283
 
284
                @Override
285
                protected List<IComboSelectionItem> doInBackground() throws InterruptedException {
286
                    // attends 1 peu pour voir si on va pas être annulé
287
                    Thread.sleep(50);
25 ilm 288
                    return SearchSpecUtils.filter(IComboModel.this.req.getComboItems(readCache), search);
17 ilm 289
                }
290
 
291
                // Runs on the event-dispatching thread.
292
                @Override
293
                public void done() {
25 ilm 294
                    synchronized (IComboModel.this) {
295
                        // if cancel() is called after doInBackground() nothing happens
296
                        // but updating is set to a new instance
297
                        if (this.isCancelled() || IComboModel.this.willUpdate != this)
298
                            // une autre maj arrive
299
                            return;
17 ilm 300
 
25 ilm 301
                        final boolean firstFill = !IComboModel.this.filledOnce;
302
                        // store before removing since it can trigger a selection change
303
                        final int idToSelect = IComboModel.this.idToSelect;
304
                        List<IComboSelectionItem> items = null;
305
                        try {
306
                            items = this.get();
17 ilm 307
                            removeAllItems();
308
                            addAllItems(items);
309
                            IComboModel.this.filledOnce = true;
25 ilm 310
                        } catch (InterruptedException e) {
311
                            // ne devrait pas arriver puisque done() appelée après doInBackground()
312
                            e.printStackTrace();
313
                        } catch (CancellationException e) {
314
                            // ne devrait pas arriver puisqu'on teste isCancelled()
315
                            e.printStackTrace();
316
                        } catch (ExecutionException e) {
317
                            if (!(e.getCause() instanceof RTInterruptedException))
318
                                // pas normal
319
                                e.printStackTrace();
320
                        } finally {
321
                            // always clear willUpdate otherwise the combo can't recover
322
                            assert IComboModel.this.willUpdate == this;
17 ilm 323
                            IComboModel.this.setWillUpdate(null);
25 ilm 324
                        }
325
                        // check if items could be retrieved
326
                        // TODO otherwise show the error to the user so he knows that items are
327
                        // stale and he could reload them
328
                        if (items != null) {
17 ilm 329
                            // restaurer l'état
330
                            // if there's only one item in the list and no previous ID to select
331
                            // and org.openconcerto.sql.sqlCombo.selectSoleItem=true,select the item
25 ilm 332
                            final boolean noSelection = idToSelect == SQLRow.NONEXISTANT_ID;
17 ilm 333
                            if (items.size() == 1 && noSelection && Boolean.getBoolean("org.openconcerto.sql.sqlCombo.selectSoleItem"))
334
                                IComboModel.this.setSelectedItem(items.get(0));
335
                            else if (noSelection && firstFill && getFirstFillSelection() != null)
336
                                IComboModel.this.setSelectedItem(getFirstFillSelection().transformChecked(items));
337
                            else
25 ilm 338
                                selectID(idToSelect);
17 ilm 339
 
340
                            for (final Runnable r : IComboModel.this.runnables)
341
                                r.run();
342
                            IComboModel.this.runnables.clear();
343
                        }
344
                    }
345
                }
346
            };
347
            this.setWillUpdate(worker);
348
            worker.execute();
349
        }
350
    }
351
 
352
    // combo
353
 
354
    private DefaultIMutableListModel<IComboSelectionItem> getComboModel() {
355
        return this;
356
    }
357
 
358
    private void addAllItems(List<IComboSelectionItem> items) {
359
        this.getComboModel().addAll(items);
360
        for (final IComboSelectionItem item : items)
361
            this.itemsByID.put(item.getId(), item);
362
    }
363
 
364
    private void addItem(IComboSelectionItem item) {
365
        this.getComboModel().addElement(item);
366
        this.itemsByID.put(item.getId(), item);
367
    }
368
 
369
    private void removeAllItems() {
370
        // combo.removeAll() does n fire() whereas our model does 1
371
        this.getComboModel().removeAllElements();
372
        this.itemsByID.clear();
373
    }
374
 
375
    private IComboSelectionItem getComboItem(int id) {
376
        return this.itemsByID.get(id);
377
    }
378
 
379
    public final IComboSelectionItem getItem(int id) {
380
        final IComboSelectionItem privateItem = this.getComboItem(id);
381
        return privateItem == null ? null : new IComboSelectionItem(privateItem);
382
    }
383
 
384
    // refresh, delete or add the passed row
385
    private void reloadComboItem(int id) {
386
        final IComboSelectionItem item = this.getComboItem(id);
387
        // does this combo currently displays id
388
        if (item != null) {
389
            final IComboSelectionItem nItem = this.req.getComboItem(id);
390
            if (nItem == null) {
391
                this.getComboModel().removeElement(item);
392
                this.itemsByID.remove(item.getId());
393
            } else {
394
                // before replace() which empties the selection
395
                final boolean selectedID = this.getSelectedId() == id;
396
                this.getComboModel().replace(item, nItem);
397
                this.itemsByID.put(id, nItem);
398
                if (selectedID) {
399
                    // selectedItem is NOT part of the items, even for non-editable combos
400
                    this.setValue(id);
401
                }
402
            }
403
        } else {
404
            // don't know if and where to put the new item, so call fillCombo()
405
            this.fillCombo();
406
        }
407
    }
408
 
25 ilm 409
    private final void itemsChanged() {
410
        final List<IComboSelectionItem> newVal = this.getList();
17 ilm 411
        this.propSupp.firePropertyChange("items", null, newVal);
412
    }
413
 
414
    // *** value
415
 
416
    @Override
417
    public final void resetValue() {
418
        this.setValue(null);
419
    }
420
 
421
    public final void setValue(int id) {
422
        // check if undefinedID means empty
423
        this.selectID(isUndefIDEmpty(id) ? SQLRow.NONEXISTANT_ID : id);
424
    }
425
 
426
    @Override
427
    public final void setValue(IComboSelectionItem o) {
428
        if (o == null)
429
            this.setValue(SQLRow.NONEXISTANT_ID);
430
        else
431
            this.setValue(o.getId());
432
    }
433
 
434
    @Override
435
    public final IComboSelectionItem getValue() {
436
        return this.getSelectedItem();
437
    }
438
 
439
    public final SQLTable getForeignTable() {
440
        return this.req.getPrimaryTable();
441
    }
442
 
443
    /**
444
     * Return the ID that is or *will* be selected (after {@link #fillCombo()}).
445
     *
446
     * @return the wanted ID.
447
     */
448
    public final int getWantedID() {
449
        if (this.isUpdating()) {
450
            return this.idToSelect;
451
        } else
452
            return this.getSelectedId();
453
    }
454
 
455
    /**
456
     * Renvoie l'ID de l'item sélectionné.
457
     *
458
     * @return l'ID de l'item sélectionné, <code>SQLRow.NONEXISTANT_ID</code> si combo vide.
459
     */
460
    public final int getSelectedId() {
461
        final IComboSelectionItem o = this.getValue();
462
        if (o != null && o.getId() >= SQLRow.MIN_VALID_ID)
463
            return o.getId();
464
        else {
465
            return SQLRow.NONEXISTANT_ID;
466
        }
467
    }
468
 
469
    /**
470
     * The selected row or <code>null</code> if this is empty.
471
     *
472
     * @return a SQLRow (non fetched) or <code>null</code>.
473
     */
474
    public final SQLRow getSelectedRow() {
475
        if (this.isEmpty())
476
            return null;
477
        else {
478
            return new SQLRow(this.getForeignTable(), this.getSelectedId());
479
        }
480
    }
481
 
482
    private final void comboValueChanged() {
483
        this.propSupp.firePropertyChange("value", null, getValue());
484
        this.emptySupp.fireEmptyChange(this.isEmpty());
485
    }
486
 
487
    private void selectID(int id) {
488
        log("entering selectID " + id);
489
        assert SwingUtilities.isEventDispatchThread();
490
 
491
        // no need to launch another updateAll() if one is already underway
492
        if (this.neverBeenFilled() && !isUpdating())
493
            // don't use fillCombo() which won't really update unless we're on screen
25 ilm 494
            this.doUpdateAll(null, true);
17 ilm 495
 
496
        if (this.isUpdating()) {
497
            this.idToSelect = id;
498
            log("isUpdating: this.idToSelect = " + id);
499
        } else if (id == SQLRow.NONEXISTANT_ID) {
500
            this.setSelectedItem(null);
501
            log("NONEXISTANT_ID: setSelectedItem(null)");
502
        } else if (id != this.getSelectedId()) {
503
            log("id != this.getSelectedId() : " + this.getSelectedId());
504
            final IComboSelectionItem item = this.getComboItem(id);
505
            log("item: " + item);
506
            if (item == null && this.addMissingItem()) {
507
                // si l'ID voulu n'est pas la, essayer d'aller le chercher directement dans la base
508
                // sans respecter le filtre
509
                final ComboSQLRequest comboSQLRequest = new ComboSQLRequest(this.req);
510
                comboSQLRequest.setFilterEnabled(false);
511
                comboSQLRequest.setWhere(null);
512
                final ITransformer<SQLSelect, SQLSelect> transf = comboSQLRequest.getSelectTransf();
513
                if (transf != null)
514
                    comboSQLRequest.setSelectTransf(new ITransformer<SQLSelect, SQLSelect>() {
515
                        @Override
516
                        public SQLSelect transformChecked(SQLSelect input) {
517
                            final SQLSelect res = transf.transformChecked(input);
518
                            res.setWhere(null);
519
                            return res;
520
                        }
521
                    });
522
                IComboSelectionItem newItem = comboSQLRequest.getComboItem(id);
523
                if (newItem != null) {
524
                    newItem.setFlag(IComboSelectionItem.WARNING_FLAG);
525
                } else {
526
                    // TODO y faire un cran plus haut pour savoir quelle table référence
527
                    // cette erreur
528
                    new IllegalStateException("ID " + id + " cannot be found in " + this.req).printStackTrace();
529
                    final SQLRow row = new SQLRow(this.req.getPrimaryTable(), id);
530
                    final String error;
531
                    if (!row.exists())
532
                        error = " inexistante";
533
                    else if (row.isArchived())
534
                        error = " archivée";
535
                    else
536
                        error = " existe mais est non atteignable: " + row.findDistantArchived(2);
537
                    newItem = new IComboSelectionItem(id, "ERREUR !!! " + row + error);
538
                    newItem.setFlag(IComboSelectionItem.ERROR_FLAG);
539
                }
540
                this.addItem(newItem);
541
                this.setSelectedItem(newItem);
542
            } else {
543
                if (item == null && this.getSelectedItem() == item)
544
                    // if both are equals setValue() would do nothing but we want to force fire when
545
                    // the wanted ID doesn't exist (ie item == null)
546
                    // Otherwise if a listener filters with isUpdating() and the selection
547
                    // disappears from the items it won't know, since when the update begins the
548
                    // selection clear is ignored.
549
                    this.comboValueChanged();
550
                else
551
                    this.setSelectedItem(item);
552
            }
553
        }
554
    }
555
 
556
    /**
557
     * Whether missing item are fetched from the database. If {@link #setValue(int)} is called with
558
     * an ID not present in the list and addMissingItem is <code>true</code> then that ID will be
559
     * fetched and added to the list, if it is <code>false</code> the selection will be cleared.
560
     *
561
     * @return <code>true</code> if missing item are fetched.
562
     */
563
    public final boolean addMissingItem() {
564
        return this.addMissingItem;
565
    }
566
 
567
    public final void setAddMissingItem(boolean addMissingItem) {
568
        this.addMissingItem = addMissingItem;
569
    }
570
 
571
    @Override
572
    public String toString() {
573
        return this.getClass().getName() + " " + this.req;
574
    }
575
 
576
    public final String dump() {
577
        String res = this.toString();
578
        for (final IComboSelectionItem it : this.getComboModel().getList()) {
579
            res += "\n" + it.dump();
580
        }
581
        return res;
582
    }
583
 
584
    @Override
585
    public final boolean isEmpty() {
586
        return this.getValue() == null || this.isUndefIDEmpty(this.getSelectedId());
587
    }
588
 
589
    @Override
590
    public final void addEmptyListener(EmptyListener l) {
591
        this.emptySupp.addEmptyListener(l);
592
    }
593
 
594
    @Override
595
    public final void addValueListener(PropertyChangeListener l) {
596
        this.addListener("value", l);
597
    }
598
 
599
    @Override
600
    public final void rmValueListener(PropertyChangeListener l) {
601
        this.rmListener("value", l);
602
    }
603
 
604
    public final void addListener(final String propName, PropertyChangeListener l) {
605
        this.propSupp.addPropertyChangeListener(propName, l);
606
    }
607
 
608
    public final void rmListener(final String propName, PropertyChangeListener l) {
609
        this.propSupp.removePropertyChangeListener(propName, l);
610
    }
611
 
612
    public final void addItemsListener(PropertyChangeListener l) {
613
        this.addItemsListener(l, false);
614
    }
615
 
616
    /**
617
     * Adds a listener on the items of this combo.
618
     *
619
     * @param l the listener.
620
     * @param all <code>true</code> if <code>l</code> should be called for all changes, including UI
621
     *        ones (e.g. adding a '-- loading --' item).
622
     */
623
    public final void addItemsListener(PropertyChangeListener l, final boolean all) {
25 ilm 624
        // there's no uiItems anymore, so ignore the boolean
625
        this.addListener("items", l);
17 ilm 626
    }
627
 
628
    public final void rmItemsListener(PropertyChangeListener l) {
629
        this.rmListener("items", l);
630
    }
631
 
632
    // *** une table que nous affichons a changé
633
 
25 ilm 634
    @Override
635
    public void tableModified(SQLTableEvent evt) {
636
        final int id = evt.getId();
637
        if (id >= SQLRow.MIN_VALID_ID && this.getForeignTable().equals(evt.getTable())) {
17 ilm 638
            this.reloadComboItem(id);
639
        } else
640
            this.fillCombo();
641
    }
642
 
643
    // *** search
644
 
645
    public final void search(SearchSpec spec) {
646
        this.search = spec;
647
        this.fillCombo();
648
    }
649
 
650
    private SearchSpec getSearch() {
651
        return this.search;
652
    }
653
 
654
    protected final boolean isFiltered() {
655
        return this.getSearch() != null && !this.getSearch().isEmpty();
656
    }
657
 
658
    private synchronized void setWillUpdate(SwingWorker<?, ?> w) {
659
        this.willUpdate = w;
660
        this.propSupp.firePropertyChange("willUpdate", null, this.willUpdate);
661
        if (this.willUpdate == null) {
662
            assert SwingUtilities.isEventDispatchThread() : "The end of an update should be in the EDT to be able change swing related attributes";
663
            this.setUpdating(false);
664
        }
665
    }
666
 
667
    private final void setUpdating(boolean b) {
668
        assert SwingUtilities.isEventDispatchThread();
669
        this.updating = b;
670
        this.propSupp.firePropertyChange("updating", null, this.updating);
671
    }
672
 
673
    public final boolean isUpdating() {
674
        assert SwingUtilities.isEventDispatchThread();
675
        return this.updating;
676
    }
677
}