OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 156 | Rev 182 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 156 Rev 174
1
/*
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 * 
3
 * 
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 * 
5
 * 
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
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
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
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.
9
 * language governing permissions and limitations under the License.
10
 * 
10
 * 
11
 * When distributing the software, include this License Header Notice in each file.
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
12
 */
13
 
13
 
14
 package org.openconcerto.sql.view.list;
14
 package org.openconcerto.sql.view.list;
15
 
15
 
16
import org.openconcerto.openoffice.XMLFormatVersion;
16
import org.openconcerto.openoffice.XMLFormatVersion;
17
import org.openconcerto.openoffice.spreadsheet.SpreadSheet;
17
import org.openconcerto.openoffice.spreadsheet.SpreadSheet;
18
import org.openconcerto.sql.Log;
18
import org.openconcerto.sql.Log;
19
import org.openconcerto.sql.TM;
19
import org.openconcerto.sql.TM;
20
import org.openconcerto.sql.element.SQLComponent;
20
import org.openconcerto.sql.element.SQLComponent;
21
import org.openconcerto.sql.element.SQLElement;
21
import org.openconcerto.sql.element.SQLElement;
22
import org.openconcerto.sql.element.SQLElementDirectory;
22
import org.openconcerto.sql.element.SQLElementDirectory;
23
import org.openconcerto.sql.model.SQLField;
23
import org.openconcerto.sql.model.SQLField;
24
import org.openconcerto.sql.model.SQLRow;
24
import org.openconcerto.sql.model.SQLRow;
25
import org.openconcerto.sql.model.SQLRowAccessor;
25
import org.openconcerto.sql.model.SQLRowAccessor;
26
import org.openconcerto.sql.model.SQLRowValues;
26
import org.openconcerto.sql.model.SQLRowValues;
27
import org.openconcerto.sql.model.SQLTable;
27
import org.openconcerto.sql.model.SQLTable;
28
import org.openconcerto.sql.model.Where;
28
import org.openconcerto.sql.model.Where;
29
import org.openconcerto.sql.request.ListSQLRequest;
29
import org.openconcerto.sql.request.ListSQLRequest;
30
import org.openconcerto.sql.request.UpdateBuilder;
30
import org.openconcerto.sql.request.UpdateBuilder;
31
import org.openconcerto.sql.users.User;
31
import org.openconcerto.sql.users.User;
32
import org.openconcerto.sql.users.UserManager;
32
import org.openconcerto.sql.users.UserManager;
-
 
33
import org.openconcerto.sql.users.rights.TableAllRights;
33
import org.openconcerto.sql.view.FileTransfertHandler;
34
import org.openconcerto.sql.view.FileTransfertHandler;
34
import org.openconcerto.sql.view.IListener;
35
import org.openconcerto.sql.view.IListener;
35
import org.openconcerto.sql.view.list.IListeAction.ButtonsBuilder;
36
import org.openconcerto.sql.view.list.IListeAction.ButtonsBuilder;
36
import org.openconcerto.sql.view.list.IListeAction.IListeEvent;
37
import org.openconcerto.sql.view.list.IListeAction.IListeEvent;
37
import org.openconcerto.sql.view.list.IListeAction.PopupBuilder;
38
import org.openconcerto.sql.view.list.IListeAction.PopupBuilder;
38
import org.openconcerto.sql.view.list.IListeAction.PopupEvent;
39
import org.openconcerto.sql.view.list.IListeAction.PopupEvent;
39
import org.openconcerto.sql.view.list.RowAction.PredicateRowAction;
40
import org.openconcerto.sql.view.list.RowAction.PredicateRowAction;
40
import org.openconcerto.ui.FontUtils;
41
import org.openconcerto.ui.FontUtils;
41
import org.openconcerto.ui.FormatEditor;
42
import org.openconcerto.ui.FormatEditor;
42
import org.openconcerto.ui.MenuUtils;
43
import org.openconcerto.ui.MenuUtils;
43
import org.openconcerto.ui.PopupMouseListener;
44
import org.openconcerto.ui.PopupMouseListener;
44
import org.openconcerto.ui.SwingThreadUtils;
45
import org.openconcerto.ui.SwingThreadUtils;
45
import org.openconcerto.ui.list.selection.BaseListStateModel;
46
import org.openconcerto.ui.list.selection.BaseListStateModel;
46
import org.openconcerto.ui.list.selection.ListSelection;
47
import org.openconcerto.ui.list.selection.ListSelection;
47
import org.openconcerto.ui.list.selection.ListSelectionState;
48
import org.openconcerto.ui.list.selection.ListSelectionState;
48
import org.openconcerto.ui.state.JTableStateManager;
49
import org.openconcerto.ui.state.JTableStateManager;
49
import org.openconcerto.ui.table.AlternateTableCellRenderer;
50
import org.openconcerto.ui.table.AlternateTableCellRenderer;
50
import org.openconcerto.ui.table.ColumnSizeAdjustor;
51
import org.openconcerto.ui.table.ColumnSizeAdjustor;
51
import org.openconcerto.ui.table.TableColumnModelAdapter;
52
import org.openconcerto.ui.table.TableColumnModelAdapter;
52
import org.openconcerto.ui.table.TablePopupMouseListener;
53
import org.openconcerto.ui.table.TablePopupMouseListener;
53
import org.openconcerto.ui.table.ViewTableModel;
54
import org.openconcerto.ui.table.ViewTableModel;
54
import org.openconcerto.ui.table.XTableColumnModel;
55
import org.openconcerto.ui.table.XTableColumnModel;
55
import org.openconcerto.utils.CollectionUtils;
56
import org.openconcerto.utils.CollectionUtils;
56
import org.openconcerto.utils.CompareUtils;
57
import org.openconcerto.utils.CompareUtils;
57
import org.openconcerto.utils.FormatGroup;
58
import org.openconcerto.utils.FormatGroup;
58
import org.openconcerto.utils.StringUtils;
59
import org.openconcerto.utils.StringUtils;
59
import org.openconcerto.utils.TableModelSelectionAdapter;
60
import org.openconcerto.utils.TableModelSelectionAdapter;
60
import org.openconcerto.utils.TableSorter;
61
import org.openconcerto.utils.TableSorter;
61
import org.openconcerto.utils.Tuple2;
62
import org.openconcerto.utils.Tuple2;
62
import org.openconcerto.utils.cc.IPredicate;
63
import org.openconcerto.utils.cc.IPredicate;
63
import org.openconcerto.utils.cc.ITransformer;
64
import org.openconcerto.utils.cc.ITransformer;
64
import org.openconcerto.utils.convertor.StringClobConvertor;
65
import org.openconcerto.utils.convertor.StringClobConvertor;
65
import org.openconcerto.utils.text.BooleanFormat;
66
import org.openconcerto.utils.text.BooleanFormat;
66
 
67
 
67
import java.awt.Color;
68
import java.awt.Color;
68
import java.awt.Component;
69
import java.awt.Component;
69
import java.awt.FlowLayout;
70
import java.awt.FlowLayout;
70
import java.awt.Font;
71
import java.awt.Font;
71
import java.awt.GridBagConstraints;
72
import java.awt.GridBagConstraints;
72
import java.awt.GridBagLayout;
73
import java.awt.GridBagLayout;
73
import java.awt.Insets;
74
import java.awt.Insets;
74
import java.awt.event.ActionEvent;
75
import java.awt.event.ActionEvent;
75
import java.awt.event.HierarchyEvent;
76
import java.awt.event.HierarchyEvent;
76
import java.awt.event.HierarchyListener;
77
import java.awt.event.HierarchyListener;
77
import java.awt.event.KeyAdapter;
78
import java.awt.event.KeyAdapter;
78
import java.awt.event.KeyEvent;
79
import java.awt.event.KeyEvent;
79
import java.awt.event.MouseAdapter;
80
import java.awt.event.MouseAdapter;
80
import java.awt.event.MouseEvent;
81
import java.awt.event.MouseEvent;
81
import java.beans.PropertyChangeEvent;
82
import java.beans.PropertyChangeEvent;
82
import java.beans.PropertyChangeListener;
83
import java.beans.PropertyChangeListener;
83
import java.beans.PropertyChangeListenerProxy;
84
import java.beans.PropertyChangeListenerProxy;
84
import java.beans.PropertyChangeSupport;
85
import java.beans.PropertyChangeSupport;
85
import java.io.File;
86
import java.io.File;
86
import java.io.IOException;
87
import java.io.IOException;
87
import java.sql.Clob;
88
import java.sql.Clob;
88
import java.sql.Time;
89
import java.sql.Time;
89
import java.sql.Timestamp;
90
import java.sql.Timestamp;
90
import java.text.DateFormat;
91
import java.text.DateFormat;
91
import java.text.Format;
92
import java.text.Format;
92
import java.util.ArrayList;
93
import java.util.ArrayList;
93
import java.util.Calendar;
94
import java.util.Calendar;
94
import java.util.Collection;
95
import java.util.Collection;
95
import java.util.Collections;
96
import java.util.Collections;
96
import java.util.Date;
97
import java.util.Date;
97
import java.util.EventObject;
98
import java.util.EventObject;
98
import java.util.HashMap;
99
import java.util.HashMap;
99
import java.util.HashSet;
100
import java.util.HashSet;
100
import java.util.LinkedHashMap;
101
import java.util.LinkedHashMap;
101
import java.util.List;
102
import java.util.List;
102
import java.util.Locale;
103
import java.util.Locale;
103
import java.util.Map;
104
import java.util.Map;
104
import java.util.Map.Entry;
105
import java.util.Map.Entry;
105
import java.util.Set;
106
import java.util.Set;
106
import java.util.concurrent.ExecutionException;
107
import java.util.concurrent.ExecutionException;
107
 
108
 
108
import javax.swing.AbstractAction;
109
import javax.swing.AbstractAction;
109
import javax.swing.Action;
110
import javax.swing.Action;
110
import javax.swing.DropMode;
111
import javax.swing.DropMode;
111
import javax.swing.InputMap;
112
import javax.swing.InputMap;
112
import javax.swing.JButton;
113
import javax.swing.JButton;
113
import javax.swing.JCheckBoxMenuItem;
114
import javax.swing.JCheckBoxMenuItem;
114
import javax.swing.JComponent;
115
import javax.swing.JComponent;
115
import javax.swing.JMenuItem;
116
import javax.swing.JMenuItem;
116
import javax.swing.JPanel;
117
import javax.swing.JPanel;
117
import javax.swing.JPopupMenu;
118
import javax.swing.JPopupMenu;
118
import javax.swing.JScrollPane;
119
import javax.swing.JScrollPane;
119
import javax.swing.JTable;
120
import javax.swing.JTable;
120
import javax.swing.JTextField;
121
import javax.swing.JTextField;
121
import javax.swing.KeyStroke;
122
import javax.swing.KeyStroke;
122
import javax.swing.ListSelectionModel;
123
import javax.swing.ListSelectionModel;
123
import javax.swing.SwingUtilities;
124
import javax.swing.SwingUtilities;
124
import javax.swing.SwingWorker;
125
import javax.swing.SwingWorker;
125
import javax.swing.event.AncestorEvent;
126
import javax.swing.event.AncestorEvent;
126
import javax.swing.event.AncestorListener;
127
import javax.swing.event.AncestorListener;
127
import javax.swing.event.ListSelectionEvent;
128
import javax.swing.event.ListSelectionEvent;
128
import javax.swing.event.ListSelectionListener;
129
import javax.swing.event.ListSelectionListener;
129
import javax.swing.event.TableColumnModelEvent;
130
import javax.swing.event.TableColumnModelEvent;
130
import javax.swing.event.TableModelEvent;
131
import javax.swing.event.TableModelEvent;
131
import javax.swing.event.TableModelListener;
132
import javax.swing.event.TableModelListener;
132
import javax.swing.table.DefaultTableCellRenderer;
133
import javax.swing.table.DefaultTableCellRenderer;
133
import javax.swing.table.TableCellRenderer;
134
import javax.swing.table.TableCellRenderer;
134
import javax.swing.table.TableColumn;
135
import javax.swing.table.TableColumn;
135
import javax.swing.table.TableColumnModel;
136
import javax.swing.table.TableColumnModel;
136
import javax.swing.table.TableModel;
137
import javax.swing.table.TableModel;
137
 
138
 
138
import net.jcip.annotations.GuardedBy;
139
import net.jcip.annotations.GuardedBy;
139
 
140
 
140
/**
141
/**
141
 * Une liste de lignes correspondant à une ListSQLRequest. Diagramme pour la sélection :
142
 * Une liste de lignes correspondant à une ListSQLRequest. Diagramme pour la sélection :
142
 * <img src="doc-files/listSelection.png"/><br/>
143
 * <img src="doc-files/listSelection.png"/><br/>
143
 * 
144
 * 
144
 * @author ILM Informatique
145
 * @author ILM Informatique
145
 */
146
 */
146
public final class IListe extends JPanel {
147
public final class IListe extends JPanel {
147
 
148
 
148
    static private final class LockAction extends RowAction {
149
    static private final class LockAction extends RowAction {
-
 
150
        private final boolean lock;
-
 
151
 
149
        public LockAction(final boolean lock) {
152
        public LockAction(final boolean lock) {
150
            super(new AbstractAction(TM.tr(lock ? "ilist.lockRows" : "ilist.unlockRows")) {
153
            super(new AbstractAction(TM.tr(lock ? "ilist.lockRows" : "ilist.unlockRows")) {
151
                @Override
154
                @Override
152
                public void actionPerformed(ActionEvent e) {
155
                public void actionPerformed(ActionEvent e) {
153
                    final IListe list = IListe.get(e);
156
                    final IListe list = IListe.get(e);
154
                    final List<Integer> ids = list.getSelection().getSelectedIDs();
157
                    final List<Integer> ids = list.getSelection().getSelectedIDs();
155
                    final SQLTable t = list.getSource().getPrimaryTable();
158
                    final SQLTable t = list.getSource().getPrimaryTable();
156
                    final UpdateBuilder update = new UpdateBuilder(t);
159
                    final UpdateBuilder update = new UpdateBuilder(t);
157
                    update.setObject(SQLComponent.READ_ONLY_FIELD, lock ? SQLComponent.READ_ONLY_VALUE : SQLComponent.READ_WRITE_VALUE);
160
                    update.setObject(SQLComponent.READ_ONLY_FIELD, lock ? SQLComponent.READ_ONLY_VALUE : SQLComponent.READ_WRITE_VALUE);
158
                    final User user = UserManager.getUser();
161
                    final User user = UserManager.getUser();
159
                    if (user != null)
162
                    if (user != null)
160
                        update.setObject(SQLComponent.READ_ONLY_USER_FIELD, user.getId());
163
                        update.setObject(SQLComponent.READ_ONLY_USER_FIELD, user.getId());
161
                    update.setWhere(new Where(t.getKey(), ids));
164
                    update.setWhere(new Where(t.getKey(), ids));
162
                    t.getDBSystemRoot().getDataSource().execute(update.asString());
165
                    t.getDBSystemRoot().getDataSource().execute(update.asString());
163
                    // don't fire too many times, as each one will cause UpdateQueue to issue a
166
                    // don't fire too many times, as each one will cause UpdateQueue to issue a
164
                    // request
167
                    // request
165
                    final Collection<? extends Number> fireIDs = ids.size() < 12 ? ids : Collections.singleton(SQLRow.NONEXISTANT_ID);
168
                    final Collection<? extends Number> fireIDs = ids.size() < 12 ? ids : Collections.singleton(SQLRow.NONEXISTANT_ID);
166
                    for (final Number fireID : fireIDs)
169
                    for (final Number fireID : fireIDs)
167
                        t.fireTableModified(fireID.intValue(), update.getFieldsNames());
170
                        t.fireTableModified(fireID.intValue(), update.getFieldsNames());
168
                }
171
                }
169
            }, false, true);
172
            }, false, true);
-
 
173
            this.lock = lock;
170
        }
174
        }
171
 
175
 
172
        @Override
176
        @Override
173
        public boolean enabledFor(IListeEvent evt) {
177
        public boolean enabledFor(IListeEvent evt) {
174
            // TODO use right
178
            boolean hasRight = TableAllRights.currentUserHasRight(this.lock ? TableAllRights.USER_UI_LOCK_ROW : TableAllRights.USER_UI_UNLOCK_ROW, evt.getTable());
175
            return evt.getSelectedRows().size() > 0;
179
            return !evt.getSelectedRows().isEmpty() && hasRight;
176
        }
180
        }
177
    }
181
    }
178
 
182
 
179
    private static LockAction LOCK_ACTION;
183
    private static LockAction LOCK_ACTION;
180
    private static LockAction UNLOCK_ACTION;
184
    private static LockAction UNLOCK_ACTION;
181
 
185
 
182
    private static final LockAction getLockAction() {
186
    private static final LockAction getLockAction() {
183
        assert SwingUtilities.isEventDispatchThread();
187
        assert SwingUtilities.isEventDispatchThread();
184
        // don't create too early as we might not have the localisation available. Further some
188
        // don't create too early as we might not have the localisation available. Further some
185
        // applications will never use it.
189
        // applications will never use it.
186
        if (LOCK_ACTION == null)
190
        if (LOCK_ACTION == null)
187
            LOCK_ACTION = new LockAction(true);
191
            LOCK_ACTION = new LockAction(true);
188
        return LOCK_ACTION;
192
        return LOCK_ACTION;
189
    }
193
    }
190
 
194
 
191
    private static final LockAction getUnlockAction() {
195
    private static final LockAction getUnlockAction() {
192
        assert SwingUtilities.isEventDispatchThread();
196
        assert SwingUtilities.isEventDispatchThread();
193
        if (UNLOCK_ACTION == null)
197
        if (UNLOCK_ACTION == null)
194
            UNLOCK_ACTION = new LockAction(false);
198
            UNLOCK_ACTION = new LockAction(false);
195
        return UNLOCK_ACTION;
199
        return UNLOCK_ACTION;
196
    }
200
    }
197
 
201
 
198
    /**
202
    /**
199
     * When this system property is set, table {@link JTableStateManager state} is never read nor
203
     * When this system property is set, table {@link JTableStateManager state} is never read nor
200
     * written. I.e. the user can change the table state but it will be reset at each launch.
204
     * written. I.e. the user can change the table state but it will be reset at each launch.
201
     */
205
     */
202
    public static final String STATELESS_TABLE_PROP = "org.openconcerto.sql.list.statelessTable";
206
    public static final String STATELESS_TABLE_PROP = "org.openconcerto.sql.list.statelessTable";
203
    private static final String SELECTION_DATA_PROPNAME = "selectionData";
207
    private static final String SELECTION_DATA_PROPNAME = "selectionData";
204
 
208
 
205
    static private final class FormatRenderer extends DefaultTableCellRenderer {
209
    static private final class FormatRenderer extends DefaultTableCellRenderer {
206
        private final Format fmt;
210
        private final Format fmt;
207
 
211
 
208
        private FormatRenderer(Format fmt) {
212
        private FormatRenderer(Format fmt) {
209
            super();
213
            super();
210
            this.fmt = fmt;
214
            this.fmt = fmt;
211
        }
215
        }
212
 
216
 
213
        @Override
217
        @Override
214
        protected void setValue(Object value) {
218
        protected void setValue(Object value) {
215
            this.setText(value == null ? "" : this.fmt.format(value));
219
            this.setText(value == null ? "" : this.fmt.format(value));
216
        }
220
        }
217
    }
221
    }
218
 
222
 
219
    private static boolean FORCE_ALT_CELL_RENDERER = false;
223
    private static boolean FORCE_ALT_CELL_RENDERER = false;
220
    static final String SEP = " ► ";
224
    static final String SEP = " ► ";
221
 
225
 
222
    // DefaultTableCellRenderer is stateful, so safer to not share (JTable also has private
226
    // DefaultTableCellRenderer is stateful, so safer to not share (JTable also has private
223
    // instances, see createDefaultRenderers())
227
    // instances, see createDefaultRenderers())
224
    public static final TableCellRenderer createDateRenderer() {
228
    public static final TableCellRenderer createDateRenderer() {
225
        return new FormatRenderer(DateFormat.getDateInstance(DateFormat.MEDIUM));
229
        return new FormatRenderer(DateFormat.getDateInstance(DateFormat.MEDIUM));
226
    }
230
    }
227
 
231
 
228
    public static final TableCellRenderer createTimeRenderer() {
232
    public static final TableCellRenderer createTimeRenderer() {
229
        return new FormatRenderer(DateFormat.getTimeInstance(DateFormat.SHORT));
233
        return new FormatRenderer(DateFormat.getTimeInstance(DateFormat.SHORT));
230
    }
234
    }
231
 
235
 
232
    public static final TableCellRenderer createDateTimeRenderer() {
236
    public static final TableCellRenderer createDateTimeRenderer() {
233
        return new FormatRenderer(DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT));
237
        return new FormatRenderer(DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT));
234
    }
238
    }
235
 
239
 
236
    private static final Map<Class<?>, FormatGroup> FORMATS;
240
    private static final Map<Class<?>, FormatGroup> FORMATS;
237
    static {
241
    static {
238
        FORMATS = new HashMap<Class<?>, FormatGroup>();
242
        FORMATS = new HashMap<Class<?>, FormatGroup>();
239
        FORMATS.put(Date.class, new FormatGroup(DateFormat.getDateInstance(DateFormat.SHORT), DateFormat.getDateInstance(DateFormat.MEDIUM), DateFormat.getDateInstance(DateFormat.LONG)));
243
        FORMATS.put(Date.class, new FormatGroup(DateFormat.getDateInstance(DateFormat.SHORT), DateFormat.getDateInstance(DateFormat.MEDIUM), DateFormat.getDateInstance(DateFormat.LONG)));
240
        // longer first otherwise seconds are not displayed by the cell editor and will be lost
244
        // longer first otherwise seconds are not displayed by the cell editor and will be lost
241
        FORMATS.put(Time.class, new FormatGroup(DateFormat.getTimeInstance(DateFormat.MEDIUM), DateFormat.getTimeInstance(DateFormat.SHORT)));
245
        FORMATS.put(Time.class, new FormatGroup(DateFormat.getTimeInstance(DateFormat.MEDIUM), DateFormat.getTimeInstance(DateFormat.SHORT)));
242
        FORMATS.put(Timestamp.class, new FormatGroup(DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM), DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT),
246
        FORMATS.put(Timestamp.class, new FormatGroup(DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM), DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT),
243
                DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM), DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT)));
247
                DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM), DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT)));
244
 
248
 
245
    }
249
    }
246
 
250
 
247
    public static final void remove(InputMap m, KeyStroke key) {
251
    public static final void remove(InputMap m, KeyStroke key) {
248
        InputMap current = m;
252
        InputMap current = m;
249
        while (current != null) {
253
        while (current != null) {
250
            current.remove(key);
254
            current.remove(key);
251
            current = current.getParent();
255
            current = current.getParent();
252
        }
256
        }
253
    }
257
    }
254
 
258
 
255
    /**
259
    /**
256
     * Whether to force table cell renderers to always be alternate. I.e. even after the list
260
     * Whether to force table cell renderers to always be alternate. I.e. even after the list
257
     * creation, if the renderer of a cell is changed, a listener will wrap it in an
261
     * creation, if the renderer of a cell is changed, a listener will wrap it in an
258
     * {@link AlternateTableCellRenderer} if necessary.
262
     * {@link AlternateTableCellRenderer} if necessary.
259
     * 
263
     * 
260
     * @param force <code>true</code> to listen to renderer change, and wrap it in an
264
     * @param force <code>true</code> to listen to renderer change, and wrap it in an
261
     *        {@link AlternateTableCellRenderer}.
265
     *        {@link AlternateTableCellRenderer}.
262
     */
266
     */
263
    public static void setForceAlternateCellRenderer(boolean force) {
267
    public static void setForceAlternateCellRenderer(boolean force) {
264
        FORCE_ALT_CELL_RENDERER = force;
268
        FORCE_ALT_CELL_RENDERER = force;
265
    }
269
    }
266
 
270
 
267
    public static final IListe get(EventObject evt) {
271
    public static final IListe get(EventObject evt) {
268
        return SwingThreadUtils.getAncestorOrSelf(IListe.class, (Component) evt.getSource());
272
        return SwingThreadUtils.getAncestorOrSelf(IListe.class, (Component) evt.getSource());
269
    }
273
    }
270
 
274
 
271
    // *** instance
275
    // *** instance
272
 
276
 
273
    private final JTable jTable;
277
    private final JTable jTable;
274
    private final JTextField filter;
278
    private final JTextField filter;
275
    private boolean debugFilter;
279
    private boolean debugFilter;
276
    @GuardedBy("this")
280
    @GuardedBy("this")
277
    private FilterWorker filterWorker;
281
    private FilterWorker filterWorker;
278
    // optional popup on the table
282
    // optional popup on the table
279
    private final JPopupMenu popup;
283
    private final JPopupMenu popup;
280
    private final TableSorter sorter;
284
    private final TableSorter sorter;
281
    @GuardedBy("this")
285
    @GuardedBy("this")
282
    // record the source when non-displayable (ie getModel() == null), also allow to be read outside
286
    // record the source when non-displayable (ie getModel() == null), also allow to be read outside
283
    // of the EDT
287
    // of the EDT
284
    private SQLTableModelSource src;
288
    private SQLTableModelSource src;
285
    private boolean adjustVisible;
289
    private boolean adjustVisible;
286
    private ColumnSizeAdjustor tcsa;
290
    private ColumnSizeAdjustor tcsa;
287
 
291
 
288
    private final Map<IListeAction, ButtonsBuilder> rowActions;
292
    private final Map<IListeAction, ButtonsBuilder> rowActions;
289
    // double-click
293
    // double-click
290
    private IListeAction defaultRowAction;
294
    private IListeAction defaultRowAction;
291
    private final JPanel btnPanel;
295
    private final JPanel btnPanel;
292
    private final Map<Class<?>, FormatGroup> searchFormats;
296
    private final Map<Class<?>, FormatGroup> searchFormats;
293
 
297
 
294
    // * selection
298
    // * selection
295
    private final List<IListener> listeners;
299
    private final List<IListener> listeners;
296
    private final List<IListener> naListeners;
300
    private final List<IListener> naListeners;
297
 
301
 
298
    // * listeners
302
    // * listeners
299
    private final PropertyChangeSupport supp;
303
    private final PropertyChangeSupport supp;
300
    // for not adjusting listeners
304
    // for not adjusting listeners
301
    private final ListSelectionListener selectionListener;
305
    private final ListSelectionListener selectionListener;
302
    private final TableModelListener selectionDataListener;
306
    private final TableModelListener selectionDataListener;
303
    // filter
307
    // filter
304
    private final PropertyChangeListener filterListener;
308
    private final PropertyChangeListener filterListener;
305
    // listen on model's properties
309
    // listen on model's properties
306
    private final List<PropertyChangeListener> modelPCListeners;
310
    private final List<PropertyChangeListener> modelPCListeners;
307
 
311
 
308
    private final ListSelectionState state;
312
    private final ListSelectionState state;
309
    private final JTableStateManager tableStateManager;
313
    private final JTableStateManager tableStateManager;
310
 
314
 
311
    private int retainCount = 0;
315
    private int retainCount = 0;
312
 
316
 
313
    private boolean cellModificationAllowed = ITableModel.isDefaultCellsEditable();
317
    private boolean cellModificationAllowed = ITableModel.isDefaultCellsEditable();
314
    private boolean orderModificationAllowed = ITableModel.isDefaultOrderEditable();
318
    private boolean orderModificationAllowed = ITableModel.isDefaultOrderEditable();
315
 
319
 
316
    public IListe(final SQLTableModelSource req) {
320
    public IListe(final SQLTableModelSource req) {
317
        this(req, null);
321
        this(req, null);
318
    }
322
    }
319
 
323
 
320
    public IListe(final SQLTableModelSource req, final File configFile) {
324
    public IListe(final SQLTableModelSource req, final File configFile) {
321
        if (req == null)
325
        if (req == null)
322
            throw new NullPointerException("Création d'une IListe avec une requete null");
326
            throw new NullPointerException("Création d'une IListe avec une requete null");
323
 
327
 
324
        this.rowActions = new LinkedHashMap<IListeAction, ButtonsBuilder>();
328
        this.rowActions = new LinkedHashMap<IListeAction, ButtonsBuilder>();
325
        this.supp = new PropertyChangeSupport(this);
329
        this.supp = new PropertyChangeSupport(this);
326
        this.listeners = new ArrayList<IListener>();
330
        this.listeners = new ArrayList<IListener>();
327
        this.naListeners = new ArrayList<IListener>();
331
        this.naListeners = new ArrayList<IListener>();
328
        this.modelPCListeners = new ArrayList<PropertyChangeListener>();
332
        this.modelPCListeners = new ArrayList<PropertyChangeListener>();
329
 
333
 
330
        this.sorter = new TableSorter();
334
        this.sorter = new TableSorter();
331
        this.jTable = new JTable(this.sorter) {
335
        this.jTable = new JTable(this.sorter) {
332
            @Override
336
            @Override
333
            public String getToolTipText(MouseEvent event) {
337
            public String getToolTipText(MouseEvent event) {
334
                final String original = super.getToolTipText(event);
338
                final String original = super.getToolTipText(event);
335
 
339
 
336
                // Locate the row under the event location
340
                // Locate the row under the event location
337
                final int rowIndex = rowAtPoint(event.getPoint());
341
                final int rowIndex = rowAtPoint(event.getPoint());
338
                // has already happened on M3 (not sure how)
342
                // has already happened on M3 (not sure how)
339
                if (rowIndex < 0)
343
                if (rowIndex < 0)
340
                    return original;
344
                    return original;
341
 
345
 
342
                final List<String> infoL = new ArrayList<String>();
346
                final List<String> infoL = new ArrayList<String>();
343
                if (original != null) {
347
                if (original != null) {
344
                    final String html = "<html>";
348
                    final String html = "<html>";
345
                    if (original.startsWith(html))
349
                    if (original.startsWith(html))
346
                        // -1 since the closing tag is </html>
350
                        // -1 since the closing tag is </html>
347
                        infoL.add(original.substring(html.length(), original.length() - html.length() - 1));
351
                        infoL.add(original.substring(html.length(), original.length() - html.length() - 1));
348
                    else
352
                    else
349
                        infoL.add(original);
353
                        infoL.add(original);
350
                }
354
                }
351
 
355
 
352
                final SQLRowValues row = ITableModel.getLine(this.getModel(), rowIndex).getRow();
356
                final SQLRowValues row = ITableModel.getLine(this.getModel(), rowIndex).getRow();
353
 
357
 
354
                final String create = getLine(true, row, getSource().getPrimaryTable().getCreationUserField(), getSource().getPrimaryTable().getCreationDateField());
358
                final String create = getLine(true, row, getSource().getPrimaryTable().getCreationUserField(), getSource().getPrimaryTable().getCreationDateField());
355
                if (create != null)
359
                if (create != null)
356
                    infoL.add(create);
360
                    infoL.add(create);
357
                final String modif = getLine(false, row, getSource().getPrimaryTable().getModifUserField(), getSource().getPrimaryTable().getModifDateField());
361
                final String modif = getLine(false, row, getSource().getPrimaryTable().getModifUserField(), getSource().getPrimaryTable().getModifDateField());
358
                if (modif != null)
362
                if (modif != null)
359
                    infoL.add(modif);
363
                    infoL.add(modif);
360
                // TODO locked by
364
                // TODO locked by
361
 
365
 
362
                final String info;
366
                final String info;
363
                if (infoL.size() == 0)
367
                if (infoL.size() == 0)
364
                    info = null;
368
                    info = null;
365
                else
369
                else
366
                    info = "<html>" + CollectionUtils.join(infoL, "<br/>") + "</html>";
370
                    info = "<html>" + CollectionUtils.join(infoL, "<br/>") + "</html>";
367
                // ATTN doesn't follow the mouse if info remains the same, MAYBE add an identifier
371
                // ATTN doesn't follow the mouse if info remains the same, MAYBE add an identifier
368
                return info;
372
                return info;
369
            }
373
            }
370
 
374
 
371
            public String getLine(final boolean created, final SQLRowValues row, final SQLField userF, final SQLField dateF) {
375
            public String getLine(final boolean created, final SQLRowValues row, final SQLField userF, final SQLField dateF) {
372
                final Calendar date = dateF == null ? null : row.getDate(dateF.getName());
376
                final Calendar date = dateF == null ? null : row.getDate(dateF.getName());
373
                final SQLRowAccessor user = userF == null || row.getObject(userF.getName()) == null || row.isForeignEmpty(userF.getName()) ? null : row.getForeign(userF.getName());
377
                final SQLRowAccessor user = userF == null || row.getObject(userF.getName()) == null || row.isForeignEmpty(userF.getName()) ? null : row.getForeign(userF.getName());
374
                if (user == null && date == null)
378
                if (user == null && date == null)
375
                    return null;
379
                    return null;
376
 
380
 
377
                final int userParam;
381
                final int userParam;
378
                final String firstName, lastName;
382
                final String firstName, lastName;
379
                if (user != null) {
383
                if (user != null) {
380
                    userParam = 1;
384
                    userParam = 1;
381
                    firstName = user.getString("PRENOM");
385
                    firstName = user.getString("PRENOM");
382
                    lastName = user.getString("NOM");
386
                    lastName = user.getString("NOM");
383
                } else {
387
                } else {
384
                    userParam = 0;
388
                    userParam = 0;
385
                    firstName = null;
389
                    firstName = null;
386
                    lastName = null;
390
                    lastName = null;
387
                }
391
                }
388
 
392
 
389
                return TM.tr("ilist.metadata", created ? 1 : 0, userParam, firstName, lastName, date == null ? 0 : 1, date == null ? null : date.getTime());
393
                return TM.tr("ilist.metadata", created ? 1 : 0, userParam, firstName, lastName, date == null ? 0 : 1, date == null ? null : date.getTime());
390
            }
394
            }
391
 
395
 
392
            @Override
396
            @Override
393
            protected TableColumnModel createDefaultColumnModel() {
397
            protected TableColumnModel createDefaultColumnModel() {
394
                // allow to hide columns
398
                // allow to hide columns
395
                return new XTableColumnModel();
399
                return new XTableColumnModel();
396
            }
400
            }
397
 
401
 
398
            // only load from XML once
402
            // only load from XML once
399
            private boolean stateLoaded = false;
403
            private boolean stateLoaded = false;
400
 
404
 
401
            @Override
405
            @Override
402
            public void createDefaultColumnsFromModel() {
406
            public void createDefaultColumnsFromModel() {
403
                final XTableColumnModel cm = getColumnModel() instanceof XTableColumnModel ? (XTableColumnModel) getColumnModel() : null;
407
                final XTableColumnModel cm = getColumnModel() instanceof XTableColumnModel ? (XTableColumnModel) getColumnModel() : null;
404
                final Set<Object> invisibleCols = new HashSet<Object>();
408
                final Set<Object> invisibleCols = new HashSet<Object>();
405
                if (cm != null) {
409
                if (cm != null) {
406
                    // Remove any current columns, including invisible ones
410
                    // Remove any current columns, including invisible ones
407
                    while (cm.getColumnCount(false) > 0) {
411
                    while (cm.getColumnCount(false) > 0) {
408
                        final TableColumn col = cm.getColumn(0, false);
412
                        final TableColumn col = cm.getColumn(0, false);
409
                        if (!cm.isColumnVisible(col)) {
413
                        if (!cm.isColumnVisible(col)) {
410
                            if (!invisibleCols.add(col.getIdentifier()))
414
                            if (!invisibleCols.add(col.getIdentifier()))
411
                                throw new IllegalStateException("Duplicate identifier " + col.getIdentifier());
415
                                throw new IllegalStateException("Duplicate identifier " + col.getIdentifier());
412
                        }
416
                        }
413
                        cm.removeColumn(col);
417
                        cm.removeColumn(col);
414
                    }
418
                    }
415
                }
419
                }
416
                super.createDefaultColumnsFromModel();
420
                super.createDefaultColumnsFromModel();
417
                final boolean stateLoadedByThisMethod;
421
                final boolean stateLoadedByThisMethod;
418
                // don't try to load state from XML, when e.g. the list is switching to debug
422
                // don't try to load state from XML, when e.g. the list is switching to debug
419
                if (this.stateLoaded) {
423
                if (this.stateLoaded) {
420
                    stateLoadedByThisMethod = false;
424
                    stateLoadedByThisMethod = false;
421
                } else {
425
                } else {
422
                    // only load when all columns are created
426
                    // only load when all columns are created
423
                    stateLoadedByThisMethod = loadTableState();
427
                    stateLoadedByThisMethod = loadTableState();
424
                    this.stateLoaded = stateLoadedByThisMethod;
428
                    this.stateLoaded = stateLoadedByThisMethod;
425
                }
429
                }
426
                // don't overwrite state loaded by XML
430
                // don't overwrite state loaded by XML
427
                if (!stateLoadedByThisMethod && cm != null) {
431
                if (!stateLoadedByThisMethod && cm != null) {
428
                    for (final TableColumn col : new ArrayList<TableColumn>(cm.getColumns(false))) {
432
                    for (final TableColumn col : new ArrayList<TableColumn>(cm.getColumns(false))) {
429
                        cm.setColumnVisible(col, !invisibleCols.contains(col.getIdentifier()));
433
                        cm.setColumnVisible(col, !invisibleCols.contains(col.getIdentifier()));
430
                    }
434
                    }
431
                }
435
                }
432
            };
436
            };
433
        };
437
        };
434
        this.adjustVisible = true;
438
        this.adjustVisible = true;
435
        this.tcsa = null;
439
        this.tcsa = null;
436
        this.filter = new JTextField();
440
        this.filter = new JTextField();
437
        this.filter.setEditable(false);
441
        this.filter.setEditable(false);
438
        this.debugFilter = false;
442
        this.debugFilter = false;
439
 
443
 
440
        // do not handle F2, let our application use it :
444
        // do not handle F2, let our application use it :
441
        // remove F2 keybinding, use space
445
        // remove F2 keybinding, use space
442
        final InputMap tm = this.jTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
446
        final InputMap tm = this.jTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
443
        remove(tm, KeyStroke.getKeyStroke("F2"));
447
        remove(tm, KeyStroke.getKeyStroke("F2"));
444
        tm.put(KeyStroke.getKeyStroke(' '), "startEditing");
448
        tm.put(KeyStroke.getKeyStroke(' '), "startEditing");
445
        // don't auto start, otherwise F2 will trigger the edition
449
        // don't auto start, otherwise F2 will trigger the edition
446
        this.jTable.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);
450
        this.jTable.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);
447
 
451
 
448
        // Better look
452
        // Better look
449
        this.jTable.setShowHorizontalLines(false);
453
        this.jTable.setShowHorizontalLines(false);
450
        this.jTable.setGridColor(new Color(230, 230, 230));
454
        this.jTable.setGridColor(new Color(230, 230, 230));
451
        this.jTable.setRowHeight(FontUtils.getPreferredRowHeight(this.jTable));
455
        this.jTable.setRowHeight(FontUtils.getPreferredRowHeight(this.jTable));
452
 
456
 
453
        this.popup = new JPopupMenu();
457
        this.popup = new JPopupMenu();
454
        TablePopupMouseListener.add(this.jTable, new ITransformer<MouseEvent, JPopupMenu>() {
458
        TablePopupMouseListener.add(this.jTable, new ITransformer<MouseEvent, JPopupMenu>() {
455
            @Override
459
            @Override
456
            public JPopupMenu transformChecked(MouseEvent input) {
460
            public JPopupMenu transformChecked(MouseEvent input) {
457
                return updatePopupMenu(true);
461
                return updatePopupMenu(true);
458
            }
462
            }
459
        });
463
        });
460
        this.jTable.addMouseListener(new MouseAdapter() {
464
        this.jTable.addMouseListener(new MouseAdapter() {
461
            @Override
465
            @Override
462
            public void mouseClicked(MouseEvent e) {
466
            public void mouseClicked(MouseEvent e) {
463
                if (e.getClickCount() == 2)
467
                if (e.getClickCount() == 2)
464
                    performDefaultAction(e);
468
                    performDefaultAction(e);
465
            }
469
            }
466
        });
470
        });
467
 
471
 
468
        this.selectionListener = new ListSelectionListener() {
472
        this.selectionListener = new ListSelectionListener() {
469
            public void valueChanged(ListSelectionEvent e) {
473
            public void valueChanged(ListSelectionEvent e) {
470
                if (!e.getValueIsAdjusting()) {
474
                if (!e.getValueIsAdjusting()) {
471
                    fireNASelectionId();
475
                    fireNASelectionId();
472
                    updateButtons();
476
                    updateButtons();
473
                }
477
                }
474
            }
478
            }
475
        };
479
        };
476
        this.selectionDataListener = new TableModelListener() {
480
        this.selectionDataListener = new TableModelListener() {
477
            @Override
481
            @Override
478
            public void tableChanged(TableModelEvent e) {
482
            public void tableChanged(TableModelEvent e) {
479
                // assert we're not listening to the sorter since we're interested in data change
483
                // assert we're not listening to the sorter since we're interested in data change
480
                // not sort order
484
                // not sort order
481
                assert e.getSource() instanceof ITableModel;
485
                assert e.getSource() instanceof ITableModel;
482
                boolean fire = false;
486
                boolean fire = false;
483
                // insert or delete don't change the content of current selection (e.g. if the
487
                // insert or delete don't change the content of current selection (e.g. if the
484
                // deleted row was part of the selection "selectedIDs" will change)
488
                // deleted row was part of the selection "selectedIDs" will change)
485
                // a change in the name or order of columns doesn't mean the SQL values are updated
489
                // a change in the name or order of columns doesn't mean the SQL values are updated
486
                if (e.getType() == TableModelEvent.UPDATE && e.getFirstRow() != TableModelEvent.HEADER_ROW) {
490
                if (e.getType() == TableModelEvent.UPDATE && e.getFirstRow() != TableModelEvent.HEADER_ROW) {
487
                    // see TableModelEvent(TableModel) constructor
491
                    // see TableModelEvent(TableModel) constructor
488
                    if (e.getLastRow() == Integer.MAX_VALUE) {
492
                    if (e.getLastRow() == Integer.MAX_VALUE) {
489
                        // since JTable uses a regular listener to update its selection and the
493
                        // since JTable uses a regular listener to update its selection and the
490
                        // listeners are called in reverse order, the selection isn't yet cleared by
494
                        // listeners are called in reverse order, the selection isn't yet cleared by
491
                        // JTable.tableChanged(). Thus if the table was just shrunk, the selection
495
                        // JTable.tableChanged(). Thus if the table was just shrunk, the selection
492
                        // might be out of bounds. So don't fire now, let
496
                        // might be out of bounds. So don't fire now, let
493
                        // JTable.clearSelectionAndLeadAnchor() do it.
497
                        // JTable.clearSelectionAndLeadAnchor() do it.
494
                        fire = false;
498
                        fire = false;
495
                    } else {
499
                    } else {
496
                        // do fire if only some rows were updated as in this case, no selection
500
                        // do fire if only some rows were updated as in this case, no selection
497
                        // change will occur.
501
                        // change will occur.
498
                        for (int i = e.getFirstRow(); !fire && i <= e.getLastRow(); i++) {
502
                        for (int i = e.getFirstRow(); !fire && i <= e.getLastRow(); i++) {
499
                            if (getJTable().getSelectionModel().isSelectedIndex(IListe.this.sorter.viewIndex(i)))
503
                            if (getJTable().getSelectionModel().isSelectedIndex(IListe.this.sorter.viewIndex(i)))
500
                                fire = true;
504
                                fire = true;
501
                        }
505
                        }
502
                    }
506
                    }
503
                }
507
                }
504
                if (fire)
508
                if (fire)
505
                    IListe.this.supp.firePropertyChange(SELECTION_DATA_PROPNAME, null, null);
509
                    IListe.this.supp.firePropertyChange(SELECTION_DATA_PROPNAME, null, null);
506
            }
510
            }
507
        };
511
        };
508
        this.filterListener = new PropertyChangeListener() {
512
        this.filterListener = new PropertyChangeListener() {
509
            public void propertyChange(PropertyChangeEvent evt) {
513
            public void propertyChange(PropertyChangeEvent evt) {
510
                updateFilter();
514
                updateFilter();
511
            }
515
            }
512
        };
516
        };
513
        this.jTable.getColumnModel().addColumnModelListener(new TableColumnModelAdapter() {
517
        this.jTable.getColumnModel().addColumnModelListener(new TableColumnModelAdapter() {
514
            // invoked by toggleAutoAdjust(), ITableModel.setDebug() or updateColNames()
518
            // invoked by toggleAutoAdjust(), ITableModel.setDebug() or updateColNames()
515
            @Override
519
            @Override
516
            public void columnAdded(TableColumnModelEvent e) {
520
            public void columnAdded(TableColumnModelEvent e) {
517
                updateCols(e.getToIndex());
521
                updateCols(e.getToIndex());
518
            }
522
            }
519
        });
523
        });
520
        this.tableStateManager = new JTableStateManager(this.jTable);
524
        this.tableStateManager = new JTableStateManager(this.jTable);
521
        this.setConfigFile(configFile);
525
        this.setConfigFile(configFile);
522
 
526
 
523
        // MAYBE only set this.src and let the model be null so that the mere creation of an IListe
527
        // MAYBE only set this.src and let the model be null so that the mere creation of an IListe
524
        // does not spawn several threads and access the db. But a lot of code assumes there's
528
        // does not spawn several threads and access the db. But a lot of code assumes there's
525
        // immediately a model.
529
        // immediately a model.
526
        this.setSource(req);
530
        this.setSource(req);
527
        this.state = ListSelectionState.manage(this.jTable.getSelectionModel(), new TableListStateModel(this.sorter));
531
        this.state = ListSelectionState.manage(this.jTable.getSelectionModel(), new TableListStateModel(this.sorter));
528
        this.state.addPropertyChangeListener("selectedIndex", new PropertyChangeListener() {
532
        this.state.addPropertyChangeListener("selectedIndex", new PropertyChangeListener() {
529
            public void propertyChange(PropertyChangeEvent evt) {
533
            public void propertyChange(PropertyChangeEvent evt) {
530
                final Number newValue = (Number) evt.getNewValue();
534
                final Number newValue = (Number) evt.getNewValue();
531
                // if there's no selection (eg some lines were removed)
535
                // if there's no selection (eg some lines were removed)
532
                // don't try to scroll (it will go to the top)
536
                // don't try to scroll (it will go to the top)
533
                if (newValue.intValue() >= 0)
537
                if (newValue.intValue() >= 0)
534
                    IListe.this.jTable.scrollRectToVisible(IListe.this.jTable.getCellRect(newValue.intValue(), 0, true));
538
                    IListe.this.jTable.scrollRectToVisible(IListe.this.jTable.getCellRect(newValue.intValue(), 0, true));
535
            }
539
            }
536
        });
540
        });
537
        this.state.addPropertyChangeListener("selectedID", new PropertyChangeListener() {
541
        this.state.addPropertyChangeListener("selectedID", new PropertyChangeListener() {
538
            public void propertyChange(PropertyChangeEvent evt) {
542
            public void propertyChange(PropertyChangeEvent evt) {
539
                fireSelectionId(((Number) evt.getNewValue()).intValue(), IListe.this.jTable.getSelectedColumn());
543
                fireSelectionId(((Number) evt.getNewValue()).intValue(), IListe.this.jTable.getSelectedColumn());
540
            }
544
            }
541
        });
545
        });
542
        // don't use userSelectedIDs as we need to fire when the whole list is changed, see
546
        // don't use userSelectedIDs as we need to fire when the whole list is changed, see
543
        // this.selectionDataListener
547
        // this.selectionDataListener
544
        this.state.addPropertyChangeListener("selectedIDs", new PropertyChangeListener() {
548
        this.state.addPropertyChangeListener("selectedIDs", new PropertyChangeListener() {
545
            public void propertyChange(PropertyChangeEvent evt) {
549
            public void propertyChange(PropertyChangeEvent evt) {
546
                IListe.this.supp.firePropertyChange(SELECTION_DATA_PROPNAME, null, null);
550
                IListe.this.supp.firePropertyChange(SELECTION_DATA_PROPNAME, null, null);
547
            }
551
            }
548
        });
552
        });
549
        // this.jTable.setEnabled(!updating) ne sert à rien
553
        // this.jTable.setEnabled(!updating) ne sert à rien
550
        // car les updates du ITableModel se font de manière synchrone dans la EDT
554
        // car les updates du ITableModel se font de manière synchrone dans la EDT
551
        // donc on ne peut faire aucune action pendant les maj
555
        // donc on ne peut faire aucune action pendant les maj
552
 
556
 
553
        this.btnPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
557
        this.btnPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
554
        this.addListenerOnModel(new PropertyChangeListener() {
558
        this.addListenerOnModel(new PropertyChangeListener() {
555
            @Override
559
            @Override
556
            public void propertyChange(PropertyChangeEvent evt) {
560
            public void propertyChange(PropertyChangeEvent evt) {
557
                // let the header buttons know that the rows have changed
561
                // let the header buttons know that the rows have changed
558
                final boolean doneUpdating = "updating".equals(evt.getPropertyName()) && Boolean.FALSE.equals(evt.getNewValue());
562
                final boolean doneUpdating = "updating".equals(evt.getPropertyName()) && Boolean.FALSE.equals(evt.getNewValue());
559
                if (doneUpdating || "cellsEditable".equals(evt.getPropertyName()))
563
                if (doneUpdating || "cellsEditable".equals(evt.getPropertyName()))
560
                    updateButtons();
564
                    updateButtons();
561
            }
565
            }
562
        });
566
        });
563
        this.searchFormats = new HashMap<Class<?>, FormatGroup>(this.getFormats());
567
        this.searchFormats = new HashMap<Class<?>, FormatGroup>(this.getFormats());
564
        // localized boolean search
568
        // localized boolean search
565
        this.searchFormats.put(Boolean.class, new FormatGroup(new BooleanFormat(), BooleanFormat.getNumberInstance(), BooleanFormat.createYesNo(Locale.getDefault())));
569
        this.searchFormats.put(Boolean.class, new FormatGroup(new BooleanFormat(), BooleanFormat.getNumberInstance(), BooleanFormat.createYesNo(Locale.getDefault())));
566
        // on edition we want to force the user to enter a time, so it doesn't blindly paste a date
570
        // on edition we want to force the user to enter a time, so it doesn't blindly paste a date
567
        // and erase the time part. But on search it's quicker to filter with > 25/12/99
571
        // and erase the time part. But on search it's quicker to filter with > 25/12/99
568
        final List<Format> wAndwoTime = new ArrayList<Format>();
572
        final List<Format> wAndwoTime = new ArrayList<Format>();
569
        wAndwoTime.addAll(this.searchFormats.get(Timestamp.class).getFormats());
573
        wAndwoTime.addAll(this.searchFormats.get(Timestamp.class).getFormats());
570
        wAndwoTime.addAll(this.searchFormats.get(Date.class).getFormats());
574
        wAndwoTime.addAll(this.searchFormats.get(Date.class).getFormats());
571
        this.searchFormats.put(Timestamp.class, new FormatGroup(wAndwoTime));
575
        this.searchFormats.put(Timestamp.class, new FormatGroup(wAndwoTime));
572
 
576
 
573
        uiInit();
577
        uiInit();
574
    }
578
    }
575
 
579
 
576
    /**
580
    /**
577
     * Formats used for editing cells.
581
     * Formats used for editing cells.
578
     * 
582
     * 
579
     * @return a mapping between cell value's class and its format.
583
     * @return a mapping between cell value's class and its format.
580
     */
584
     */
581
    public final Map<Class<?>, FormatGroup> getFormats() {
585
    public final Map<Class<?>, FormatGroup> getFormats() {
582
        return FORMATS;
586
        return FORMATS;
583
    }
587
    }
584
 
588
 
585
    public final Map<Class<?>, FormatGroup> getSearchFormats() {
589
    public final Map<Class<?>, FormatGroup> getSearchFormats() {
586
        return this.searchFormats;
590
        return this.searchFormats;
587
    }
591
    }
588
 
592
 
589
    public final RowAction addRowAction(Action action) {
593
    public final RowAction addRowAction(Action action) {
590
        return this.addRowAction(action, null);
594
        return this.addRowAction(action, null);
591
    }
595
    }
592
 
596
 
593
    public final RowAction addRowAction(Action action, String id) {
597
    public final RowAction addRowAction(Action action, String id) {
594
        // for backward compatibility don't put in header
598
        // for backward compatibility don't put in header
595
        final RowAction res = new PredicateRowAction(action, false, true, id).setPredicate(IListeEvent.getSingleSelectionPredicate());
599
        final RowAction res = new PredicateRowAction(action, false, true, id).setPredicate(IListeEvent.getSingleSelectionPredicate());
596
        this.addIListeAction(res);
600
        this.addIListeAction(res);
597
        return res;
601
        return res;
598
    }
602
    }
599
 
603
 
600
    public final void addIListeActions(Collection<? extends IListeAction> actions) {
604
    public final void addIListeActions(Collection<? extends IListeAction> actions) {
601
        for (final IListeAction a : actions)
605
        for (final IListeAction a : actions)
602
            this.addIListeAction(a);
606
            this.addIListeAction(a);
603
    }
607
    }
604
 
608
 
605
    private final int findGroupIndex(final String groupName) {
609
    private final int findGroupIndex(final String groupName) {
606
        if (groupName != null) {
610
        if (groupName != null) {
607
            final Component[] components = this.btnPanel.getComponents();
611
            final Component[] components = this.btnPanel.getComponents();
608
            for (int i = components.length - 1; i >= 0; i--) {
612
            for (int i = components.length - 1; i >= 0; i--) {
609
                final JComponent comp = (JComponent) components[i];
613
                final JComponent comp = (JComponent) components[i];
610
                if (groupName.equals(comp.getClientProperty(ButtonsBuilder.GROUPNAME_PROPNAME))) {
614
                if (groupName.equals(comp.getClientProperty(ButtonsBuilder.GROUPNAME_PROPNAME))) {
611
                    return i + 1;
615
                    return i + 1;
612
                }
616
                }
613
            }
617
            }
614
        }
618
        }
615
        return -1;
619
        return -1;
616
    }
620
    }
617
 
621
 
618
    public final void addIListeAction(IListeAction action) {
622
    public final void addIListeAction(IListeAction action) {
619
        // we need to handle addition of an already added action at least for setDefaultRowAction()
623
        // we need to handle addition of an already added action at least for setDefaultRowAction()
620
        if (this.rowActions.containsKey(action))
624
        if (this.rowActions.containsKey(action))
621
            return;
625
            return;
622
        final ButtonsBuilder headerBtns = action.getHeaderButtons();
626
        final ButtonsBuilder headerBtns = action.getHeaderButtons();
623
        this.rowActions.put(action, headerBtns);
627
        this.rowActions.put(action, headerBtns);
624
        if (headerBtns.getContent().size() > 0) {
628
        if (headerBtns.getContent().size() > 0) {
625
            updateButton(headerBtns, new IListeEvent(this));
629
            updateButton(headerBtns, new IListeEvent(this));
626
            for (final JButton headerBtn : headerBtns.getContent().keySet()) {
630
            for (final JButton headerBtn : headerBtns.getContent().keySet()) {
627
                headerBtn.setOpaque(false);
631
                headerBtn.setOpaque(false);
628
                this.btnPanel.add(headerBtn, findGroupIndex((String) headerBtn.getClientProperty(ButtonsBuilder.GROUPNAME_PROPNAME)));
632
                this.btnPanel.add(headerBtn, findGroupIndex((String) headerBtn.getClientProperty(ButtonsBuilder.GROUPNAME_PROPNAME)));
629
            }
633
            }
630
            this.btnPanel.setVisible(true);
634
            this.btnPanel.setVisible(true);
631
        }
635
        }
632
    }
636
    }
633
 
637
 
634
    public final void removeIListeActions(Collection<? extends IListeAction> actions) {
638
    public final void removeIListeActions(Collection<? extends IListeAction> actions) {
635
        for (final IListeAction a : actions)
639
        for (final IListeAction a : actions)
636
            this.removeIListeAction(a);
640
            this.removeIListeAction(a);
637
    }
641
    }
638
 
642
 
639
    public final void removeIListeAction(IListeAction action) {
643
    public final void removeIListeAction(IListeAction action) {
640
        final ButtonsBuilder headerBtns = this.rowActions.remove(action);
644
        final ButtonsBuilder headerBtns = this.rowActions.remove(action);
641
        // handle the removal of inexistent action (ButtonsBuilder can not be null)
645
        // handle the removal of inexistent action (ButtonsBuilder can not be null)
642
        if (headerBtns == null)
646
        if (headerBtns == null)
643
            return;
647
            return;
644
        for (final JButton headerBtn : headerBtns.getContent().keySet()) {
648
        for (final JButton headerBtn : headerBtns.getContent().keySet()) {
645
            this.btnPanel.remove(headerBtn);
649
            this.btnPanel.remove(headerBtn);
646
            if (this.btnPanel.getComponentCount() == 0)
650
            if (this.btnPanel.getComponentCount() == 0)
647
                this.btnPanel.setVisible(false);
651
                this.btnPanel.setVisible(false);
648
            this.btnPanel.revalidate();
652
            this.btnPanel.revalidate();
649
        }
653
        }
650
        if (action.equals(this.defaultRowAction))
654
        if (action.equals(this.defaultRowAction))
651
            this.setDefaultRowAction(null);
655
            this.setDefaultRowAction(null);
652
    }
656
    }
653
 
657
 
654
    private void updateButtons() {
658
    private void updateButtons() {
655
        final IListeEvent evt = new IListeEvent(this);
659
        final IListeEvent evt = new IListeEvent(this);
656
        for (final ButtonsBuilder btns : this.rowActions.values()) {
660
        for (final ButtonsBuilder btns : this.rowActions.values()) {
657
            this.updateButton(btns, evt);
661
            this.updateButton(btns, evt);
658
        }
662
        }
659
    }
663
    }
660
 
664
 
661
    private void updateButton(final ButtonsBuilder btns, final IListeEvent evt) {
665
    private void updateButton(final ButtonsBuilder btns, final IListeEvent evt) {
662
        for (final Entry<JButton, IPredicate<IListeEvent>> e : btns.getContent().entrySet()) {
666
        for (final Entry<JButton, IPredicate<IListeEvent>> e : btns.getContent().entrySet()) {
663
            e.getKey().setEnabled(e.getValue().evaluateChecked(evt));
667
            e.getKey().setEnabled(e.getValue().evaluateChecked(evt));
664
        }
668
        }
665
    }
669
    }
666
 
670
 
667
    private JPopupMenu updatePopupMenu(final boolean onRows) {
671
    private JPopupMenu updatePopupMenu(final boolean onRows) {
668
        this.popup.removeAll();
672
        this.popup.removeAll();
669
        final PopupEvent evt = new PopupEvent(this, onRows);
673
        final PopupEvent evt = new PopupEvent(this, onRows);
670
        final Action defaultAction = this.defaultRowAction != null ? this.defaultRowAction.getDefaultAction(evt) : null;
674
        final Action defaultAction = this.defaultRowAction != null ? this.defaultRowAction.getDefaultAction(evt) : null;
671
        final VirtualMenu menu = VirtualMenu.createRoot(null);
675
        final VirtualMenu menu = VirtualMenu.createRoot(null);
672
        for (final IListeAction a : this.rowActions.keySet()) {
676
        for (final IListeAction a : this.rowActions.keySet()) {
673
            final PopupBuilder popupContent = a.getPopupContent(evt);
677
            final PopupBuilder popupContent = a.getPopupContent(evt);
674
            if (defaultAction != null && a == this.defaultRowAction) {
678
            if (defaultAction != null && a == this.defaultRowAction) {
675
                /**
679
                /**
676
                 * If popup actions are ["Dial 03", "Dial 06"] then getDefaultAction() must not
680
                 * If popup actions are ["Dial 03", "Dial 06"] then getDefaultAction() must not
677
                 * always return the same instance "if land line is default then Dial 03 else Dial
681
                 * always return the same instance "if land line is default then Dial 03 else Dial
678
                 * 06" otherwise we can't find its matching menu item in the popup. IOW the check
682
                 * 06" otherwise we can't find its matching menu item in the popup. IOW the check
679
                 * should be done in getDefaultAction(), which should then return one of the popup
683
                 * should be done in getDefaultAction(), which should then return one of the popup
680
                 * actions.
684
                 * actions.
681
                 * <p>
685
                 * <p>
682
                 * Also, the IListeAction can just choose not to return the default action in its
686
                 * Also, the IListeAction can just choose not to return the default action in its
683
                 * menu.
687
                 * menu.
684
                 */
688
                 */
685
                final JMenuItem defaultMI = popupContent.getRootMenuItem(defaultAction);
689
                final JMenuItem defaultMI = popupContent.getRootMenuItem(defaultAction);
686
                if (defaultMI == null)
690
                if (defaultMI == null)
687
                    Log.get().info("Default action not found at the root level of popup for " + this);
691
                    Log.get().info("Default action not found at the root level of popup for " + this);
688
                else
692
                else
689
                    defaultMI.setFont(defaultMI.getFont().deriveFont(Font.BOLD));
693
                    defaultMI.setFont(defaultMI.getFont().deriveFont(Font.BOLD));
690
            }
694
            }
691
            menu.merge(popupContent.getMenu());
695
            menu.merge(popupContent.getMenu());
692
        }
696
        }
693
 
697
 
694
        for (final Entry<JMenuItem, List<String>> e : menu.getContent().entrySet()) {
698
        for (final Entry<JMenuItem, List<String>> e : menu.getContent().entrySet()) {
695
            MenuUtils.addMenuItem(e.getKey(), this.popup, e.getValue());
699
            MenuUtils.addMenuItem(e.getKey(), this.popup, e.getValue());
696
        }
700
        }
697
 
701
 
698
        return this.popup;
702
        return this.popup;
699
    }
703
    }
700
 
704
 
701
    /**
705
    /**
702
     * Set the action performed when double-clicking a row.
706
     * Set the action performed when double-clicking a row.
703
     * 
707
     * 
704
     * @param action the default action, can be <code>null</code>.
708
     * @param action the default action, can be <code>null</code>.
705
     */
709
     */
706
    public final void setDefaultRowAction(final IListeAction action) {
710
    public final void setDefaultRowAction(final IListeAction action) {
707
        this.defaultRowAction = action;
711
        this.defaultRowAction = action;
708
        if (action != null)
712
        if (action != null)
709
            this.addIListeAction(action);
713
            this.addIListeAction(action);
710
    }
714
    }
711
 
715
 
712
    public final IListeAction getDefaultRowAction() {
716
    public final IListeAction getDefaultRowAction() {
713
        return this.defaultRowAction;
717
        return this.defaultRowAction;
714
    }
718
    }
715
 
719
 
716
    private void performDefaultAction(MouseEvent e) {
720
    private void performDefaultAction(MouseEvent e) {
717
        // special method needed since sometimes getPopupContent() can access the DB (optionally
721
        // special method needed since sometimes getPopupContent() can access the DB (optionally
718
        // creating threads) or be slow
722
        // creating threads) or be slow
719
        if (this.defaultRowAction != null) {
723
        if (this.defaultRowAction != null) {
720
            final Action defaultAction = this.defaultRowAction.getDefaultAction(new IListeEvent(this));
724
            final Action defaultAction = this.defaultRowAction.getDefaultAction(new IListeEvent(this));
721
            if (defaultAction != null)
725
            if (defaultAction != null)
722
                defaultAction.actionPerformed(new ActionEvent(e.getSource(), e.getID(), null, e.getWhen(), e.getModifiers()));
726
                defaultAction.actionPerformed(new ActionEvent(e.getSource(), e.getID(), null, e.getWhen(), e.getModifiers()));
723
        }
727
        }
724
    }
728
    }
725
 
729
 
726
    private void uiInit() {
730
    private void uiInit() {
727
        // * filter
731
        // * filter
728
        this.filter.addMouseListener(new MouseAdapter() {
732
        this.filter.addMouseListener(new MouseAdapter() {
729
            @Override
733
            @Override
730
            public void mouseClicked(MouseEvent e) {
734
            public void mouseClicked(MouseEvent e) {
731
                if (e.isAltDown()) {
735
                if (e.isAltDown()) {
732
                    invertDebug();
736
                    invertDebug();
733
                }
737
                }
734
            }
738
            }
735
        });
739
        });
736
        FontUtils.setFontFor(this.filter, SEP);
740
        FontUtils.setFontFor(this.filter, SEP);
737
        // initially hide to limit modifications for instances which don't need the filter, see
741
        // initially hide to limit modifications for instances which don't need the filter, see
738
        // setFilter() comment
742
        // setFilter() comment
739
        this.setFilter(null);
743
        this.setFilter(null);
740
        this.updateFilter();
744
        this.updateFilter();
741
 
745
 
742
        // * JTable
746
        // * JTable
743
 
747
 
744
        // active/désactive le mode DEBUG du tableModel en ALT-clickant sur les entêtes des colonnes
748
        // active/désactive le mode DEBUG du tableModel en ALT-clickant sur les entêtes des colonnes
745
        this.jTable.getTableHeader().addMouseListener(new MouseAdapter() {
749
        this.jTable.getTableHeader().addMouseListener(new MouseAdapter() {
746
 
750
 
747
            @Override
751
            @Override
748
            public void mouseClicked(MouseEvent e) {
752
            public void mouseClicked(MouseEvent e) {
749
                if (e.isAltDown()) {
753
                if (e.isAltDown()) {
750
                    final boolean debug = IListe.this.getModel().isDebug();
754
                    final boolean debug = IListe.this.getModel().isDebug();
751
                    IListe.this.getModel().setDebug(!debug);
755
                    IListe.this.getModel().setDebug(!debug);
752
                    setDebug(!debug);
756
                    setDebug(!debug);
753
                }
757
                }
754
            }
758
            }
755
 
759
 
756
            static private final String COL_INDEX_KEY = "tableColIndex";
760
            static private final String COL_INDEX_KEY = "tableColIndex";
757
 
761
 
758
            private final JPopupMenu popupMenu;
762
            private final JPopupMenu popupMenu;
759
            private final Action toggleWidth;
763
            private final Action toggleWidth;
760
            private final Action toggleVisibility;
764
            private final Action toggleVisibility;
761
            private final Action setAllVisible;
765
            private final Action setAllVisible;
762
 
766
 
763
            {
767
            {
764
                this.popupMenu = new JPopupMenu();
768
                this.popupMenu = new JPopupMenu();
765
                this.toggleWidth = new AbstractAction(TM.tr("ilist.setColumnsWidth")) {
769
                this.toggleWidth = new AbstractAction(TM.tr("ilist.setColumnsWidth")) {
766
                    @Override
770
                    @Override
767
                    public void actionPerformed(ActionEvent e) {
771
                    public void actionPerformed(ActionEvent e) {
768
                        toggleAutoAdjust();
772
                        toggleAutoAdjust();
769
                    }
773
                    }
770
                };
774
                };
771
                this.toggleVisibility = new AbstractAction() {
775
                this.toggleVisibility = new AbstractAction() {
772
                    @Override
776
                    @Override
773
                    public void actionPerformed(ActionEvent e) {
777
                    public void actionPerformed(ActionEvent e) {
774
                        final XTableColumnModel colModel = (XTableColumnModel) getJTable().getColumnModel();
778
                        final XTableColumnModel colModel = (XTableColumnModel) getJTable().getColumnModel();
775
                        final JComponent cb = (JComponent) e.getSource();
779
                        final JComponent cb = (JComponent) e.getSource();
776
                        final int columnIndex = ((Number) cb.getClientProperty(COL_INDEX_KEY)).intValue();
780
                        final int columnIndex = ((Number) cb.getClientProperty(COL_INDEX_KEY)).intValue();
777
                        final TableColumn col = colModel.getColumn(columnIndex, false);
781
                        final TableColumn col = colModel.getColumn(columnIndex, false);
778
                        final boolean newValue = !colModel.isColumnVisible(col);
782
                        final boolean newValue = !colModel.isColumnVisible(col);
779
                        // don't remove last column
783
                        // don't remove last column
780
                        if (newValue || colModel.getColumnCount(true) > 1)
784
                        if (newValue || colModel.getColumnCount(true) > 1)
781
                            colModel.setColumnVisible(col, newValue);
785
                            colModel.setColumnVisible(col, newValue);
782
                    }
786
                    }
783
                };
787
                };
784
                this.setAllVisible = new AbstractAction() {
788
                this.setAllVisible = new AbstractAction() {
785
                    @Override
789
                    @Override
786
                    public void actionPerformed(ActionEvent e) {
790
                    public void actionPerformed(ActionEvent e) {
787
                        final XTableColumnModel colModel = (XTableColumnModel) getJTable().getColumnModel();
791
                        final XTableColumnModel colModel = (XTableColumnModel) getJTable().getColumnModel();
788
                        colModel.setAllColumnsVisible();
792
                        colModel.setAllColumnsVisible();
789
                    }
793
                    }
790
                };
794
                };
791
            }
795
            }
792
 
796
 
793
            @Override
797
            @Override
794
            public void mousePressed(MouseEvent e) {
798
            public void mousePressed(MouseEvent e) {
795
                maybeShowPopup(e);
799
                maybeShowPopup(e);
796
            }
800
            }
797
 
801
 
798
            @Override
802
            @Override
799
            public void mouseReleased(MouseEvent e) {
803
            public void mouseReleased(MouseEvent e) {
800
                maybeShowPopup(e);
804
                maybeShowPopup(e);
801
            }
805
            }
802
 
806
 
803
            private void maybeShowPopup(MouseEvent e) {
807
            private void maybeShowPopup(MouseEvent e) {
804
                if (e.isPopupTrigger()) {
808
                if (e.isPopupTrigger()) {
805
                    this.popupMenu.removeAll();
809
                    this.popupMenu.removeAll();
806
 
810
 
807
                    if (IListe.this.adjustVisible) {
811
                    if (IListe.this.adjustVisible) {
808
                        final JCheckBoxMenuItem cb = new JCheckBoxMenuItem(this.toggleWidth);
812
                        final JCheckBoxMenuItem cb = new JCheckBoxMenuItem(this.toggleWidth);
809
                        cb.setSelected(isAutoAdjusting());
813
                        cb.setSelected(isAutoAdjusting());
810
                        this.popupMenu.add(cb);
814
                        this.popupMenu.add(cb);
811
                    }
815
                    }
812
 
816
 
813
                    if (getJTable().getColumnModel() instanceof XTableColumnModel) {
817
                    if (getJTable().getColumnModel() instanceof XTableColumnModel) {
814
                        if (this.popupMenu.getComponentCount() > 0)
818
                        if (this.popupMenu.getComponentCount() > 0)
815
                            this.popupMenu.addSeparator();
819
                            this.popupMenu.addSeparator();
816
                        this.setAllVisible.putValue(Action.NAME, TM.tr("ilist.showAllColumns"));
820
                        this.setAllVisible.putValue(Action.NAME, TM.tr("ilist.showAllColumns"));
817
                        this.popupMenu.add(this.setAllVisible);
821
                        this.popupMenu.add(this.setAllVisible);
818
 
822
 
819
                        final XTableColumnModel colModel = (XTableColumnModel) getJTable().getColumnModel();
823
                        final XTableColumnModel colModel = (XTableColumnModel) getJTable().getColumnModel();
820
                        int i = 0;
824
                        int i = 0;
821
                        final boolean disableLastCol = colModel.getColumnCount(true) == 1;
825
                        final boolean disableLastCol = colModel.getColumnCount(true) == 1;
822
                        for (final TableColumn c : colModel.getColumns(false)) {
826
                        for (final TableColumn c : colModel.getColumns(false)) {
823
                            final JCheckBoxMenuItem cb = new JCheckBoxMenuItem(this.toggleVisibility);
827
                            final JCheckBoxMenuItem cb = new JCheckBoxMenuItem(this.toggleVisibility);
824
                            // speed up display of menu
828
                            // speed up display of menu
825
                            cb.setText(StringUtils.Shortener.Ellipsis.getBoundedLengthString(String.valueOf(c.getHeaderValue()), 200));
829
                            cb.setText(StringUtils.Shortener.Ellipsis.getBoundedLengthString(String.valueOf(c.getHeaderValue()), 200));
826
                            final boolean isVisible = colModel.isColumnVisible(c);
830
                            final boolean isVisible = colModel.isColumnVisible(c);
827
                            cb.setSelected(isVisible);
831
                            cb.setSelected(isVisible);
828
                            cb.setEnabled(!isVisible || !disableLastCol);
832
                            cb.setEnabled(!isVisible || !disableLastCol);
829
                            if (!cb.isEnabled())
833
                            if (!cb.isEnabled())
830
                                cb.setToolTipText(TM.tr("ilist.lastCol"));
834
                                cb.setToolTipText(TM.tr("ilist.lastCol"));
831
                            cb.putClientProperty(COL_INDEX_KEY, i++);
835
                            cb.putClientProperty(COL_INDEX_KEY, i++);
832
                            this.popupMenu.add(cb);
836
                            this.popupMenu.add(cb);
833
                        }
837
                        }
834
                    }
838
                    }
835
 
839
 
836
                    if (this.popupMenu.getComponentCount() > 0)
840
                    if (this.popupMenu.getComponentCount() > 0)
837
                        this.popupMenu.show((Component) e.getSource(), e.getX(), e.getY());
841
                        this.popupMenu.show((Component) e.getSource(), e.getX(), e.getY());
838
                }
842
                }
839
            }
843
            }
840
 
844
 
841
        });
845
        });
842
        // use SQLTableModelColumn.getToolTip()
846
        // use SQLTableModelColumn.getToolTip()
843
        this.jTable.getTableHeader().setDefaultRenderer(new TableCellRenderer() {
847
        this.jTable.getTableHeader().setDefaultRenderer(new TableCellRenderer() {
844
            private final TableCellRenderer orig = IListe.this.jTable.getTableHeader().getDefaultRenderer();
848
            private final TableCellRenderer orig = IListe.this.jTable.getTableHeader().getDefaultRenderer();
845
 
849
 
846
            @Override
850
            @Override
847
            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
851
            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
848
                final Component res = this.orig.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
852
                final Component res = this.orig.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
849
                if (res instanceof JComponent) {
853
                if (res instanceof JComponent) {
850
                    // column is the view index
854
                    // column is the view index
851
                    final SQLTableModelColumn col = getSource().getColumn(table.convertColumnIndexToModel(column));
855
                    final SQLTableModelColumn col = getSource().getColumn(table.convertColumnIndexToModel(column));
852
                    ((JComponent) res).setToolTipText(col.getToolTip());
856
                    ((JComponent) res).setToolTipText(col.getToolTip());
853
                }
857
                }
854
                return res;
858
                return res;
855
            }
859
            }
856
        });
860
        });
857
        this.jTable.setDefaultRenderer(Clob.class, new DefaultTableCellRenderer() {
861
        this.jTable.setDefaultRenderer(Clob.class, new DefaultTableCellRenderer() {
858
            @Override
862
            @Override
859
            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
863
            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
860
                return super.getTableCellRendererComponent(table, StringClobConvertor.INSTANCE.unconvert((Clob) value), isSelected, hasFocus, row, column);
864
                return super.getTableCellRendererComponent(table, StringClobConvertor.INSTANCE.unconvert((Clob) value), isSelected, hasFocus, row, column);
861
            }
865
            }
862
        });
866
        });
863
        this.jTable.setDefaultRenderer(Date.class, createDateRenderer());
867
        this.jTable.setDefaultRenderer(Date.class, createDateRenderer());
864
        this.jTable.setDefaultRenderer(Time.class, createTimeRenderer());
868
        this.jTable.setDefaultRenderer(Time.class, createTimeRenderer());
865
        this.jTable.setDefaultRenderer(Timestamp.class, createDateTimeRenderer());
869
        this.jTable.setDefaultRenderer(Timestamp.class, createDateTimeRenderer());
866
        for (final Map.Entry<Class<?>, FormatGroup> e : this.getFormats().entrySet())
870
        for (final Map.Entry<Class<?>, FormatGroup> e : this.getFormats().entrySet())
867
            this.jTable.setDefaultEditor(e.getKey(), new FormatEditor(e.getValue()));
871
            this.jTable.setDefaultEditor(e.getKey(), new FormatEditor(e.getValue()));
868
        this.sorter.setTableHeader(this.jTable.getTableHeader());
872
        this.sorter.setTableHeader(this.jTable.getTableHeader());
869
        this.addAncestorListener(new AncestorListener() {
873
        this.addAncestorListener(new AncestorListener() {
870
 
874
 
871
            // these callbacks are called later than the change, and by that time the visibility
875
            // these callbacks are called later than the change, and by that time the visibility
872
            // might have changed several times thus use isShowing() to avoid flip-flopping for
876
            // might have changed several times thus use isShowing() to avoid flip-flopping for
873
            // nothing
877
            // nothing
874
 
878
 
875
            @Override
879
            @Override
876
            public void ancestorRemoved(AncestorEvent event) {
880
            public void ancestorRemoved(AncestorEvent event) {
877
                visibilityChanged();
881
                visibilityChanged();
878
            }
882
            }
879
 
883
 
880
            @Override
884
            @Override
881
            public void ancestorAdded(AncestorEvent event) {
885
            public void ancestorAdded(AncestorEvent event) {
882
                visibilityChanged();
886
                visibilityChanged();
883
            }
887
            }
884
 
888
 
885
            @Override
889
            @Override
886
            public void ancestorMoved(AncestorEvent event) {
890
            public void ancestorMoved(AncestorEvent event) {
887
                // nothing to do
891
                // nothing to do
888
            }
892
            }
889
        });
893
        });
890
        // we used to rm this listener, possibly to avoid events once dead, but this doesn't seem
894
        // we used to rm this listener, possibly to avoid events once dead, but this doesn't seem
891
        // necessary anymore
895
        // necessary anymore
892
        this.jTable.getSelectionModel().addListSelectionListener(this.selectionListener);
896
        this.jTable.getSelectionModel().addListSelectionListener(this.selectionListener);
893
 
897
 
894
        // TODO speed up like IListPanel buttons
898
        // TODO speed up like IListPanel buttons
895
        // works because "JTable.autoStartsEdit" is false
899
        // works because "JTable.autoStartsEdit" is false
896
        // otherwise mets un + a la fin de la cellule courante
900
        // otherwise mets un + a la fin de la cellule courante
897
        if (this.getSource().getReq().isTableOrder()) {
901
        if (this.getSource().getReq().isTableOrder()) {
898
            this.jTable.addKeyListener(new KeyAdapter() {
902
            this.jTable.addKeyListener(new KeyAdapter() {
899
                public void keyTyped(KeyEvent e) {
903
                public void keyTyped(KeyEvent e) {
900
                    if (e.getKeyChar() == '+') {
904
                    if (e.getKeyChar() == '+') {
901
                        deplacerDe(1);
905
                        deplacerDe(1);
902
                    } else if (e.getKeyChar() == '-') {
906
                    } else if (e.getKeyChar() == '-') {
903
                        deplacerDe(-1);
907
                        deplacerDe(-1);
904
                    }
908
                    }
905
                }
909
                }
906
            });
910
            });
907
 
911
 
908
            // DnD
912
            // DnD
909
            this.jTable.setDragEnabled(true);
913
            this.jTable.setDragEnabled(true);
910
            this.jTable.setDropMode(DropMode.INSERT_ROWS);
914
            this.jTable.setDropMode(DropMode.INSERT_ROWS);
911
            this.jTable.setTransferHandler(new IListeTransferHandler());
915
            this.jTable.setTransferHandler(new IListeTransferHandler());
912
        }
916
        }
913
 
917
 
914
        final JScrollPane scrollPane = new JScrollPane(this.jTable);
918
        final JScrollPane scrollPane = new JScrollPane(this.jTable);
915
        scrollPane.setFocusable(false);
919
        scrollPane.setFocusable(false);
916
        scrollPane.addMouseListener(new PopupMouseListener() {
920
        scrollPane.addMouseListener(new PopupMouseListener() {
917
            @Override
921
            @Override
918
            protected JPopupMenu createPopup(MouseEvent e) {
922
            protected JPopupMenu createPopup(MouseEvent e) {
919
                return updatePopupMenu(false);
923
                return updatePopupMenu(false);
920
            }
924
            }
921
        });
925
        });
922
 
926
 
923
        this.setLayout(new GridBagLayout());
927
        this.setLayout(new GridBagLayout());
924
        final GridBagConstraints c = new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0);
928
        final GridBagConstraints c = new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0);
925
        this.add(this.filter, c);
929
        this.add(this.filter, c);
926
 
930
 
927
        c.gridy++;
931
        c.gridy++;
928
        this.btnPanel.setVisible(false);
932
        this.btnPanel.setVisible(false);
929
        this.btnPanel.setOpaque(false);
933
        this.btnPanel.setOpaque(false);
930
        this.add(this.btnPanel, c);
934
        this.add(this.btnPanel, c);
931
 
935
 
932
        c.weighty = 1;
936
        c.weighty = 1;
933
        c.gridy++;
937
        c.gridy++;
934
        this.add(scrollPane, c);
938
        this.add(scrollPane, c);
935
 
939
 
936
        // destroy if non displayable
940
        // destroy if non displayable
937
        this.addHierarchyListener(new HierarchyListener() {
941
        this.addHierarchyListener(new HierarchyListener() {
938
            public void hierarchyChanged(HierarchyEvent e) {
942
            public void hierarchyChanged(HierarchyEvent e) {
939
                if ((e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0)
943
                if ((e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0)
940
                    dispChanged();
944
                    dispChanged();
941
            }
945
            }
942
        });
946
        });
943
 
947
 
944
        this.setOpaque(false);
948
        this.setOpaque(false);
945
        this.setTransferHandler(new FileTransfertHandler(getSource().getPrimaryTable()));
949
        this.setTransferHandler(new FileTransfertHandler(getSource().getPrimaryTable()));
946
 
950
 
947
        if (this.getSource().getPrimaryTable().getFieldRaw(SQLComponent.READ_ONLY_FIELD) != null) {
951
        if (this.getSource().getPrimaryTable().getFieldRaw(SQLComponent.READ_ONLY_FIELD) != null) {
948
            this.addIListeAction(getUnlockAction());
952
            this.addIListeAction(getUnlockAction());
949
            this.addIListeAction(getLockAction());
953
            this.addIListeAction(getLockAction());
950
        }
954
        }
951
    }
955
    }
952
 
956
 
953
    protected synchronized final void invertDebug() {
957
    protected synchronized final void invertDebug() {
954
        this.setDebug(!this.debugFilter);
958
        this.setDebug(!this.debugFilter);
955
    }
959
    }
956
 
960
 
957
    protected synchronized final void setDebug(boolean b) {
961
    protected synchronized final void setDebug(boolean b) {
958
        this.debugFilter = b;
962
        this.debugFilter = b;
959
        updateFilter();
963
        updateFilter();
960
    }
964
    }
961
 
965
 
962
    // thread-safe
966
    // thread-safe
963
    private synchronized void updateFilter() {
967
    private synchronized void updateFilter() {
964
        if (this.filterWorker != null) {
968
        if (this.filterWorker != null) {
965
            this.filterWorker.cancel(true);
969
            this.filterWorker.cancel(true);
966
        }
970
        }
967
        final FilterWorker worker;
971
        final FilterWorker worker;
968
        if (!this.hasRequest()) {
972
        if (!this.hasRequest()) {
969
            worker = new RowFilterWorker(null);
973
            worker = new RowFilterWorker(null);
970
        } else if (this.debugFilter) {
974
        } else if (this.debugFilter) {
971
            worker = new WhereFilterWorker(this.getRequest().getInstanceWhere());
975
            worker = new WhereFilterWorker(this.getRequest().getInstanceWhere());
972
        } else {
976
        } else {
973
            worker = new RowFilterWorker(this.getRequest().getFilterRows());
977
            worker = new RowFilterWorker(this.getRequest().getFilterRows());
974
        }
978
        }
975
        this.filterWorker = worker;
979
        this.filterWorker = worker;
976
        this.filterWorker.execute();
980
        this.filterWorker.execute();
977
    }
981
    }
978
 
982
 
979
    /**
983
    /**
980
     * Sets the filter label.
984
     * Sets the filter label.
981
     * 
985
     * 
982
     * @param text the text to display, <code>null</code> to hide the label.
986
     * @param text the text to display, <code>null</code> to hide the label.
983
     */
987
     */
984
    private void setFilter(String text) {
988
    private void setFilter(String text) {
985
        final boolean currentVisible = this.filter.isVisible();
989
        final boolean currentVisible = this.filter.isVisible();
986
        final boolean newVisible = text != null;
990
        final boolean newVisible = text != null;
987
        // limit modifications due to a bug in Swing that can bring the frame to the back.
991
        // limit modifications due to a bug in Swing that can bring the frame to the back.
988
        if (newVisible || currentVisible) {
992
        if (newVisible || currentVisible) {
989
            this.filter.setText(text == null ? "" : text);
993
            this.filter.setText(text == null ? "" : text);
990
            this.filter.setVisible(newVisible);
994
            this.filter.setVisible(newVisible);
991
            this.revalidate();
995
            this.revalidate();
992
        }
996
        }
993
    }
997
    }
994
 
998
 
995
    public void selectID(final int id) {
999
    public void selectID(final int id) {
996
        this.selectIDs(Collections.singleton(id));
1000
        this.selectIDs(Collections.singleton(id));
997
    }
1001
    }
998
 
1002
 
999
    public void selectIDs(final Collection<Integer> ids) {
1003
    public void selectIDs(final Collection<Integer> ids) {
1000
        if (!SwingUtilities.isEventDispatchThread())
1004
        if (!SwingUtilities.isEventDispatchThread())
1001
            throw new IllegalStateException("not in EDT");
1005
            throw new IllegalStateException("not in EDT");
1002
        // no need to put a runnable in the model queue to wait for an inserted ID to actually
1006
        // no need to put a runnable in the model queue to wait for an inserted ID to actually
1003
        // show up in the list, the ListSelectionState will record the userID and select it after
1007
        // show up in the list, the ListSelectionState will record the userID and select it after
1004
        // the update
1008
        // the update
1005
        if (!isDead())
1009
        if (!isDead())
1006
            this.state.selectIDs(ids);
1010
            this.state.selectIDs(ids);
1007
    }
1011
    }
1008
 
1012
 
1009
    // retourne l'ID de la ligne rowIndex à l'écran.
1013
    // retourne l'ID de la ligne rowIndex à l'écran.
1010
    public int idFromIndex(int rowIndex) {
1014
    public int idFromIndex(int rowIndex) {
1011
        return this.state.idFromIndex(rowIndex);
1015
        return this.state.idFromIndex(rowIndex);
1012
    }
1016
    }
1013
 
1017
 
1014
    /**
1018
    /**
1015
     * Cherche une chaîne de caractères dans la liste et reclasse les éléments trouvés au début
1019
     * Cherche une chaîne de caractères dans la liste et reclasse les éléments trouvés au début
1016
     * 
1020
     * 
1017
     * @param s la chaîne de caractères recherchées
1021
     * @param s la chaîne de caractères recherchées
1018
     * @param column la colonne dans laquelle chercher, <code>null</code> pour toutes.
1022
     * @param column la colonne dans laquelle chercher, <code>null</code> pour toutes.
1019
     */
1023
     */
1020
    public void search(String s, String column) {
1024
    public void search(String s, String column) {
1021
        this.search(s, column, null);
1025
        this.search(s, column, null);
1022
    }
1026
    }
1023
 
1027
 
1024
    public void search(String s, String column, Runnable r) {
1028
    public void search(String s, String column, Runnable r) {
1025
        // Determine sur quelle colonne on cherche
1029
        // Determine sur quelle colonne on cherche
1026
        this.getModel().searchContains(s, this.getModel().getColumnNames().indexOf(column), r);
1030
        this.getModel().searchContains(s, this.getModel().getColumnNames().indexOf(column), r);
1027
    }
1031
    }
1028
 
1032
 
1029
    // Export en tableau OpenOffice
1033
    // Export en tableau OpenOffice
1030
    public void exporter(File file) throws IOException {
1034
    public void exporter(File file) throws IOException {
1031
        exporter(file, false, XMLFormatVersion.getDefault());
1035
        exporter(file, false, XMLFormatVersion.getDefault());
1032
    }
1036
    }
1033
 
1037
 
1034
    public File exporter(File file, final boolean onlySelection, final XMLFormatVersion version) throws IOException {
1038
    public File exporter(File file, final boolean onlySelection, final XMLFormatVersion version) throws IOException {
1035
        return SpreadSheet.export(getExportModel(onlySelection), file, version);
1039
        return SpreadSheet.export(getExportModel(onlySelection), file, version);
1036
    }
1040
    }
1037
 
1041
 
1038
    protected TableModel getExportModel(final boolean onlySelection) {
1042
    protected TableModel getExportModel(final boolean onlySelection) {
1039
        final ViewTableModel res = new ViewTableModel(this.jTable);
1043
        final ViewTableModel res = new ViewTableModel(this.jTable);
1040
        return onlySelection ? new TableModelSelectionAdapter(res, this.jTable.getSelectedRows()) : res;
1044
        return onlySelection ? new TableModelSelectionAdapter(res, this.jTable.getSelectedRows()) : res;
1041
    }
1045
    }
1042
 
1046
 
1043
    public void update() {
1047
    public void update() {
1044
        this.getModel().updateAll();
1048
        this.getModel().updateAll();
1045
    }
1049
    }
1046
 
1050
 
1047
    /**
1051
    /**
1048
     * Retourne le nombre de ligne de cette liste.
1052
     * Retourne le nombre de ligne de cette liste.
1049
     * 
1053
     * 
1050
     * @return le nombre de ligne de cette liste, -1 si {@link #isDead()}.
1054
     * @return le nombre de ligne de cette liste, -1 si {@link #isDead()}.
1051
     */
1055
     */
1052
    public int getRowCount() {
1056
    public int getRowCount() {
1053
        if (isDead()) {
1057
        if (isDead()) {
1054
            return -1;
1058
            return -1;
1055
        }
1059
        }
1056
        return this.getTableModel().getRowCount();
1060
        return this.getTableModel().getRowCount();
1057
    }
1061
    }
1058
 
1062
 
1059
    public int getTotalRowCount() {
1063
    public int getTotalRowCount() {
1060
        if (isDead()) {
1064
        if (isDead()) {
1061
            return -1;
1065
            return -1;
1062
        }
1066
        }
1063
        return this.getModel().getTotalRowCount();
1067
        return this.getModel().getTotalRowCount();
1064
    }
1068
    }
1065
 
1069
 
1066
    public final boolean isDead() {
1070
    public final boolean isDead() {
1067
        return this.getTableModel() == null;
1071
        return this.getTableModel() == null;
1068
    }
1072
    }
1069
 
1073
 
1070
    /**
1074
    /**
1071
     * Retourne le nombre d'éléments contenu dans cette liste. C'est à dire la somme du champs
1075
     * Retourne le nombre d'éléments contenu dans cette liste. C'est à dire la somme du champs
1072
     * 'quantité' ou 'nombre d'essai DDR'.
1076
     * 'quantité' ou 'nombre d'essai DDR'.
1073
     * 
1077
     * 
1074
     * @return la somme ou -1 s'il n'y a pas de champs quantité.
1078
     * @return la somme ou -1 s'il n'y a pas de champs quantité.
1075
     */
1079
     */
1076
    public int getItemCount() {
1080
    public int getItemCount() {
1077
        int count = -1;
1081
        int count = -1;
1078
        if (!this.isDead()) {
1082
        if (!this.isDead()) {
1079
            int fieldIndex = -1;
1083
            int fieldIndex = -1;
1080
            // ATTN ne marche que si qte est dans les listFields, donc dans le tableModel
1084
            // ATTN ne marche que si qte est dans les listFields, donc dans le tableModel
1081
            // sinon on pourrait faire un SUM(QUANTITE)
1085
            // sinon on pourrait faire un SUM(QUANTITE)
1082
            final SQLField qte;
1086
            final SQLField qte;
1083
            final SQLTable t = this.getModel().getTable();
1087
            final SQLTable t = this.getModel().getTable();
1084
            if (t.contains("QUANTITE"))
1088
            if (t.contains("QUANTITE"))
1085
                qte = t.getField("QUANTITE");
1089
                qte = t.getField("QUANTITE");
1086
            else
1090
            else
1087
                qte = t.getFieldRaw("NB_ESSAI_DDR");
1091
                qte = t.getFieldRaw("NB_ESSAI_DDR");
1088
 
1092
 
1089
            if (qte != null) {
1093
            if (qte != null) {
1090
                int i = 0;
1094
                int i = 0;
1091
                for (final SQLTableModelColumn col : this.getModel().getCols()) {
1095
                for (final SQLTableModelColumn col : this.getModel().getCols()) {
1092
                    if (CollectionUtils.getSole(col.getFields()) == qte)
1096
                    if (CollectionUtils.getSole(col.getFields()) == qte)
1093
                        fieldIndex = i;
1097
                        fieldIndex = i;
1094
                    i++;
1098
                    i++;
1095
                }
1099
                }
1096
            }
1100
            }
1097
            if (fieldIndex > 0) {
1101
            if (fieldIndex > 0) {
1098
                count = 0;
1102
                count = 0;
1099
                for (int j = 0; j < this.getTableModel().getRowCount(); j++) {
1103
                for (int j = 0; j < this.getTableModel().getRowCount(); j++) {
1100
                    count += ((Number) this.getTableModel().getValueAt(j, fieldIndex)).intValue();
1104
                    count += ((Number) this.getTableModel().getValueAt(j, fieldIndex)).intValue();
1101
                }
1105
                }
1102
            }
1106
            }
1103
        }
1107
        }
1104
        return count;
1108
        return count;
1105
    }
1109
    }
1106
 
1110
 
1107
    public void deplacerDe(final int inc) {
1111
    public void deplacerDe(final int inc) {
1108
        if (isSorted())
1112
        if (isSorted())
1109
            return;
1113
            return;
1110
        this.getModel().moveBy(this.getSelectedRows(), inc, true);
1114
        this.getModel().moveBy(this.getSelectedRows(), inc, true);
1111
    }
1115
    }
1112
 
1116
 
1113
    /**
1117
    /**
1114
     * The currently selected id.
1118
     * The currently selected id.
1115
     * 
1119
     * 
1116
     * @return the currently selected id or -1 if no selection.
1120
     * @return the currently selected id or -1 if no selection.
1117
     */
1121
     */
1118
    public int getSelectedId() {
1122
    public int getSelectedId() {
1119
        return this.state.getSelectedID();
1123
        return this.state.getSelectedID();
1120
    }
1124
    }
1121
 
1125
 
1122
    public final boolean hasSelection() {
1126
    public final boolean hasSelection() {
1123
        return this.jTable.getSelectedRow() >= 0;
1127
        return this.jTable.getSelectedRow() >= 0;
1124
    }
1128
    }
1125
 
1129
 
1126
    public final ListSelection getSelection() {
1130
    public final ListSelection getSelection() {
1127
        return this.state;
1131
        return this.state;
1128
    }
1132
    }
1129
 
1133
 
1130
    /**
1134
    /**
1131
     * Return the line at the passed index.
1135
     * Return the line at the passed index.
1132
     * 
1136
     * 
1133
     * @param viewIndex the index in the JTable.
1137
     * @param viewIndex the index in the JTable.
1134
     * @return the line at the passed index.
1138
     * @return the line at the passed index.
1135
     * @see ITableModel#getLine(TableModel, int)
1139
     * @see ITableModel#getLine(TableModel, int)
1136
     */
1140
     */
1137
    public final ListSQLLine getLine(int viewIndex) {
1141
    public final ListSQLLine getLine(int viewIndex) {
1138
        return ITableModel.getLine(this.getJTable().getModel(), viewIndex);
1142
        return ITableModel.getLine(this.getJTable().getModel(), viewIndex);
1139
    }
1143
    }
1140
 
1144
 
1141
    // protect our internal values
1145
    // protect our internal values
1142
    private <R> R getRow(int index, final Class<R> clazz) {
1146
    private <R> R getRow(int index, final Class<R> clazz) {
1143
        final ListSQLLine line = this.getLine(index);
1147
        final ListSQLLine line = this.getLine(index);
1144
        final Object toCast;
1148
        final Object toCast;
1145
        if (clazz == SQLRowValues.class) {
1149
        if (clazz == SQLRowValues.class) {
1146
            toCast = line.getRow().toImmutable();
1150
            toCast = line.getRow().toImmutable();
1147
        } else if (clazz == SQLRow.class) {
1151
        } else if (clazz == SQLRow.class) {
1148
            toCast = line.getRow().asRow();
1152
            toCast = line.getRow().asRow();
1149
        } else if (clazz == ListSQLLine.class) {
1153
        } else if (clazz == ListSQLLine.class) {
1150
            toCast = line;
1154
            toCast = line;
1151
        } else {
1155
        } else {
1152
            throw new IllegalArgumentException("Not implemented : " + clazz);
1156
            throw new IllegalArgumentException("Not implemented : " + clazz);
1153
        }
1157
        }
1154
        return clazz.cast(toCast);
1158
        return clazz.cast(toCast);
1155
    }
1159
    }
1156
 
1160
 
1157
    private SQLRow fetchRow(int id) {
1161
    private SQLRow fetchRow(int id) {
1158
        if (id < SQLRow.MIN_VALID_ID) {
1162
        if (id < SQLRow.MIN_VALID_ID) {
1159
            return null;
1163
            return null;
1160
        } else
1164
        } else
1161
            return this.getSource().getPrimaryTable().getRow(id);
1165
            return this.getSource().getPrimaryTable().getRow(id);
1162
    }
1166
    }
1163
 
1167
 
1164
    public SQLRow fetchSelectedRow() {
1168
    public SQLRow fetchSelectedRow() {
1165
        return this.fetchRow(this.getSelectedId());
1169
        return this.fetchRow(this.getSelectedId());
1166
    }
1170
    }
1167
 
1171
 
1168
    public SQLRowValues getSelectedRow() {
1172
    public SQLRowValues getSelectedRow() {
1169
        return this.getSelectedRow(SQLRowValues.class);
1173
        return this.getSelectedRow(SQLRowValues.class);
1170
    }
1174
    }
1171
 
1175
 
1172
    // selected row cannot be inferred from iterateSelectedRows() since the user might have selected
1176
    // selected row cannot be inferred from iterateSelectedRows() since the user might have selected
1173
    // the last row anywhere in the selection
1177
    // the last row anywhere in the selection
1174
    private final <R extends SQLRowAccessor> R getSelectedRow(final Class<R> clazz) {
1178
    private final <R extends SQLRowAccessor> R getSelectedRow(final Class<R> clazz) {
1175
        final int selectedIndex = this.state.getSelectedIndex().intValue();
1179
        final int selectedIndex = this.state.getSelectedIndex().intValue();
1176
        if (selectedIndex == BaseListStateModel.INVALID_INDEX)
1180
        if (selectedIndex == BaseListStateModel.INVALID_INDEX)
1177
            return null;
1181
            return null;
1178
        else
1182
        else
1179
            return this.getRow(selectedIndex, clazz);
1183
            return this.getRow(selectedIndex, clazz);
1180
    }
1184
    }
1181
 
1185
 
1182
    public final SQLRow getDesiredRow() {
1186
    public final SQLRow getDesiredRow() {
1183
        return this.fetchRow(this.getSelection().getUserSelectedID());
1187
        return this.fetchRow(this.getSelection().getUserSelectedID());
1184
    }
1188
    }
1185
 
1189
 
1186
    public final List<SQLRowValues> getSelectedRows() {
1190
    public final List<SQLRowValues> getSelectedRows() {
1187
        return iterateSelectedRows(SQLRowValues.class);
1191
        return iterateSelectedRows(SQLRowValues.class);
1188
    }
1192
    }
1189
 
1193
 
1190
    public final List<ListSQLLine> getSelectedLines() {
1194
    public final List<ListSQLLine> getSelectedLines() {
1191
        return iterateSelectedRows(ListSQLLine.class);
1195
        return iterateSelectedRows(ListSQLLine.class);
1192
    }
1196
    }
1193
 
1197
 
1194
    private final <R> List<R> iterateSelectedRows(final Class<R> clazz) {
1198
    private final <R> List<R> iterateSelectedRows(final Class<R> clazz) {
1195
        final ListSelectionModel selectionModel = this.getJTable().getSelectionModel();
1199
        final ListSelectionModel selectionModel = this.getJTable().getSelectionModel();
1196
        if (selectionModel.isSelectionEmpty())
1200
        if (selectionModel.isSelectionEmpty())
1197
            return Collections.emptyList();
1201
            return Collections.emptyList();
1198
 
1202
 
1199
        final int start = selectionModel.getMinSelectionIndex();
1203
        final int start = selectionModel.getMinSelectionIndex();
1200
        final int stop = selectionModel.getMaxSelectionIndex();
1204
        final int stop = selectionModel.getMaxSelectionIndex();
1201
        final List<R> res = new ArrayList<R>();
1205
        final List<R> res = new ArrayList<R>();
1202
        for (int i = start; i <= stop; i++) {
1206
        for (int i = start; i <= stop; i++) {
1203
            if (selectionModel.isSelectedIndex(i)) {
1207
            if (selectionModel.isSelectedIndex(i)) {
1204
                try {
1208
                try {
1205
                    res.add(getRow(i, clazz));
1209
                    res.add(getRow(i, clazz));
1206
                } catch (IndexOutOfBoundsException e) {
1210
                } catch (IndexOutOfBoundsException e) {
1207
                    throw new IllegalStateException(
1211
                    throw new IllegalStateException("The selected row at " + i
1208
                            "The selected row at " + i
-
 
1209
                                    + " is not in the model : it has been changed before Swing could update the selection. E.g. the DB was changed on mousePressed and Swing updated the selection on mouseReleased.",
1212
                            + " is not in the model : it has been changed before Swing could update the selection. E.g. the DB was changed on mousePressed and Swing updated the selection on mouseReleased.",
1210
                            e);
1213
                            e);
1211
                }
1214
                }
1212
            }
1215
            }
1213
        }
1216
        }
1214
        return res;
1217
        return res;
1215
    }
1218
    }
1216
 
1219
 
1217
    public final void setAdjustVisible(boolean b) {
1220
    public final void setAdjustVisible(boolean b) {
1218
        this.adjustVisible = b;
1221
        this.adjustVisible = b;
1219
    }
1222
    }
1220
 
1223
 
1221
    protected final void toggleAutoAdjust() {
1224
    protected final void toggleAutoAdjust() {
1222
        if (this.tcsa == null) {
1225
        if (this.tcsa == null) {
1223
            this.tcsa = new ColumnSizeAdjustor(this.jTable);
1226
            this.tcsa = new ColumnSizeAdjustor(this.jTable);
1224
        } else {
1227
        } else {
1225
            this.tcsa.setInstalled(!this.tcsa.isInstalled());
1228
            this.tcsa.setInstalled(!this.tcsa.isInstalled());
1226
        }
1229
        }
1227
    }
1230
    }
1228
 
1231
 
1229
    public final boolean isAutoAdjusting() {
1232
    public final boolean isAutoAdjusting() {
1230
        if (this.tcsa == null) {
1233
        if (this.tcsa == null) {
1231
            return false;
1234
            return false;
1232
        } else
1235
        } else
1233
            return this.tcsa.isInstalled();
1236
            return this.tcsa.isInstalled();
1234
    }
1237
    }
1235
 
1238
 
1236
    // *** Listeners ***//
1239
    // *** Listeners ***//
1237
 
1240
 
1238
    public void addIListener(IListener l) {
1241
    public void addIListener(IListener l) {
1239
        this.listeners.add(l);
1242
        this.listeners.add(l);
1240
    }
1243
    }
1241
 
1244
 
1242
    public void addNonAdjustingIListener(IListener l) {
1245
    public void addNonAdjustingIListener(IListener l) {
1243
        this.naListeners.add(l);
1246
        this.naListeners.add(l);
1244
    }
1247
    }
1245
 
1248
 
1246
    /**
1249
    /**
1247
     * Adds a listener to the list that's notified each time a change to the data model occurs. This
1250
     * Adds a listener to the list that's notified each time a change to the data model occurs. This
1248
     * includes when this is not displayable and the model becomes empty.
1251
     * includes when this is not displayable and the model becomes empty.
1249
     * 
1252
     * 
1250
     * @param l the listener.
1253
     * @param l the listener.
1251
     * @see #retain()
1254
     * @see #retain()
1252
     */
1255
     */
1253
    public void addListener(TableModelListener l) {
1256
    public void addListener(TableModelListener l) {
1254
        // sorter is final, only its own model (ITableModel) changes
1257
        // sorter is final, only its own model (ITableModel) changes
1255
        this.sorter.addTableModelListener(l);
1258
        this.sorter.addTableModelListener(l);
1256
    }
1259
    }
1257
 
1260
 
1258
    public void removeListener(TableModelListener l) {
1261
    public void removeListener(TableModelListener l) {
1259
        this.sorter.removeTableModelListener(l);
1262
        this.sorter.removeTableModelListener(l);
1260
    }
1263
    }
1261
 
1264
 
1262
    /**
1265
    /**
1263
     * To be notified when the table is being sorted. Each time a sort is requested you'll be
1266
     * To be notified when the table is being sorted. Each time a sort is requested you'll be
1264
     * notified twice to indicate the beginning and end of the sort. Don't confuse it with the
1267
     * notified twice to indicate the beginning and end of the sort. Don't confuse it with the
1265
     * sortED status.
1268
     * sortED status.
1266
     * 
1269
     * 
1267
     * @param l the listener.
1270
     * @param l the listener.
1268
     * @see #isSorted()
1271
     * @see #isSorted()
1269
     */
1272
     */
1270
    public void addSortListener(PropertyChangeListener l) {
1273
    public void addSortListener(PropertyChangeListener l) {
1271
        this.sorter.addPropertyChangeListener(new PropertyChangeListenerProxy("sorting", l));
1274
        this.sorter.addPropertyChangeListener(new PropertyChangeListenerProxy("sorting", l));
1272
    }
1275
    }
1273
 
1276
 
1274
    /**
1277
    /**
1275
     * Whether this list is sorted by a column.
1278
     * Whether this list is sorted by a column.
1276
     * 
1279
     * 
1277
     * @return true if this list is sorted.
1280
     * @return true if this list is sorted.
1278
     */
1281
     */
1279
    public boolean isSorted() {
1282
    public boolean isSorted() {
1280
        return this.sorter.isSorting();
1283
        return this.sorter.isSorting();
1281
    }
1284
    }
1282
 
1285
 
1283
    public final void setSortingEnabled(final boolean b) {
1286
    public final void setSortingEnabled(final boolean b) {
1284
        this.sorter.setSortingEnabled(b);
1287
        this.sorter.setSortingEnabled(b);
1285
    }
1288
    }
1286
 
1289
 
1287
    public final boolean isSortingEnabled() {
1290
    public final boolean isSortingEnabled() {
1288
        return this.sorter.isSortingEnabled();
1291
        return this.sorter.isSortingEnabled();
1289
    }
1292
    }
1290
 
1293
 
1291
    private void fireSelectionId(int id, int selectedColumn) {
1294
    private void fireSelectionId(int id, int selectedColumn) {
1292
        for (IListener l : this.listeners) {
1295
        for (IListener l : this.listeners) {
1293
            l.selectionId(id, selectedColumn);
1296
            l.selectionId(id, selectedColumn);
1294
        }
1297
        }
1295
    }
1298
    }
1296
 
1299
 
1297
    protected final void fireNASelectionId() {
1300
    protected final void fireNASelectionId() {
1298
        final int id = this.getSelectedId();
1301
        final int id = this.getSelectedId();
1299
        for (IListener l : this.naListeners) {
1302
        for (IListener l : this.naListeners) {
1300
            l.selectionId(id, -1);
1303
            l.selectionId(id, -1);
1301
        }
1304
        }
1302
    }
1305
    }
1303
 
1306
 
1304
    public final void addModelListener(final PropertyChangeListener l) {
1307
    public final void addModelListener(final PropertyChangeListener l) {
1305
        this.supp.addPropertyChangeListener("model", l);
1308
        this.supp.addPropertyChangeListener("model", l);
1306
    }
1309
    }
1307
 
1310
 
1308
    public final void rmModelListener(final PropertyChangeListener l) {
1311
    public final void rmModelListener(final PropertyChangeListener l) {
1309
        this.supp.removePropertyChangeListener("model", l);
1312
        this.supp.removePropertyChangeListener("model", l);
1310
    }
1313
    }
1311
 
1314
 
1312
    /**
1315
    /**
1313
     * Ensure that the passed listener will always listen on our current {@link #getModel() model}
1316
     * Ensure that the passed listener will always listen on our current {@link #getModel() model}
1314
     * even if it changes. Warning: to signal model change
1317
     * even if it changes. Warning: to signal model change
1315
     * {@link PropertyChangeListener#propertyChange(PropertyChangeEvent)} will be called with a
1318
     * {@link PropertyChangeListener#propertyChange(PropertyChangeEvent)} will be called with a
1316
     * <code>null</code> name.
1319
     * <code>null</code> name.
1317
     * 
1320
     * 
1318
     * @param l the listener.
1321
     * @param l the listener.
1319
     */
1322
     */
1320
    public final void addListenerOnModel(final PropertyChangeListener l) {
1323
    public final void addListenerOnModel(final PropertyChangeListener l) {
1321
        this.modelPCListeners.add(l);
1324
        this.modelPCListeners.add(l);
1322
        if (getModel() != null)
1325
        if (getModel() != null)
1323
            getModel().addPropertyChangeListener(l);
1326
            getModel().addPropertyChangeListener(l);
1324
    }
1327
    }
1325
 
1328
 
1326
    public final void rmListenerOnModel(final PropertyChangeListener l) {
1329
    public final void rmListenerOnModel(final PropertyChangeListener l) {
1327
        this.modelPCListeners.remove(l);
1330
        this.modelPCListeners.remove(l);
1328
        if (getModel() != null)
1331
        if (getModel() != null)
1329
            getModel().rmPropertyChangeListener(l);
1332
            getModel().rmPropertyChangeListener(l);
1330
    }
1333
    }
1331
 
1334
 
1332
    /**
1335
    /**
1333
     * Listen to the content of the selection, i.e. both selection ID change and data change of the
1336
     * Listen to the content of the selection, i.e. both selection ID change and data change of the
1334
     * current selection. Note: <code>l</code> is called for each selection change, even when
1337
     * current selection. Note: <code>l</code> is called for each selection change, even when
1335
     * {@link ListSelectionEvent#getValueIsAdjusting()} is <code>true</code>.
1338
     * {@link ListSelectionEvent#getValueIsAdjusting()} is <code>true</code>.
1336
     * 
1339
     * 
1337
     * @param l the listener.
1340
     * @param l the listener.
1338
     */
1341
     */
1339
    public final void addSelectionDataListener(final PropertyChangeListener l) {
1342
    public final void addSelectionDataListener(final PropertyChangeListener l) {
1340
        this.supp.addPropertyChangeListener(SELECTION_DATA_PROPNAME, l);
1343
        this.supp.addPropertyChangeListener(SELECTION_DATA_PROPNAME, l);
1341
    }
1344
    }
1342
 
1345
 
1343
    public final void removeSelectionDataListener(final PropertyChangeListener l) {
1346
    public final void removeSelectionDataListener(final PropertyChangeListener l) {
1344
        this.supp.removePropertyChangeListener(SELECTION_DATA_PROPNAME, l);
1347
        this.supp.removePropertyChangeListener(SELECTION_DATA_PROPNAME, l);
1345
    }
1348
    }
1346
 
1349
 
1347
    protected final void visibilityChanged() {
1350
    protected final void visibilityChanged() {
1348
        // test isDead() since in JComponent.removeNotify() first setDisplayable(false) (in super)
1351
        // test isDead() since in JComponent.removeNotify() first setDisplayable(false) (in super)
1349
        // then firePropertyChange("ancestor", null).
1352
        // then firePropertyChange("ancestor", null).
1350
        // thus we can still be visible while not displayable anymore
1353
        // thus we can still be visible while not displayable anymore
1351
        if (!this.isDead())
1354
        if (!this.isDead())
1352
            // we used to call isVisible() but that was incorrect : a component can be visible and
1355
            // we used to call isVisible() but that was incorrect : a component can be visible and
1353
            // not on screen. E.g. the frame would be made invisible, so this method was called but
1356
            // not on screen. E.g. the frame would be made invisible, so this method was called but
1354
            // isVisible() hadn't changed (so still true) thus the model never slept (hence never
1357
            // isVisible() hadn't changed (so still true) thus the model never slept (hence never
1355
            // hibernated, hence never was emptied).
1358
            // hibernated, hence never was emptied).
1356
            this.getModel().setSleeping(!this.isShowing());
1359
            this.getModel().setSleeping(!this.isShowing());
1357
    }
1360
    }
1358
 
1361
 
1359
    /**
1362
    /**
1360
     * The {@link ITableModel} of this list.
1363
     * The {@link ITableModel} of this list.
1361
     * 
1364
     * 
1362
     * @return the model, <code>null</code> if destroyed.
1365
     * @return the model, <code>null</code> if destroyed.
1363
     */
1366
     */
1364
    public ITableModel getModel() {
1367
    public ITableModel getModel() {
1365
        return (ITableModel) this.getTableModel();
1368
        return (ITableModel) this.getTableModel();
1366
    }
1369
    }
1367
 
1370
 
1368
    public TableModel getTableModel() {
1371
    public TableModel getTableModel() {
1369
        assert SwingUtilities.isEventDispatchThread();
1372
        assert SwingUtilities.isEventDispatchThread();
1370
        return this.sorter.getTableModel();
1373
        return this.sorter.getTableModel();
1371
    }
1374
    }
1372
 
1375
 
1373
    private final void setTableModel(ITableModel t) {
1376
    private final void setTableModel(ITableModel t) {
1374
        final ITableModel old = this.getModel();
1377
        final ITableModel old = this.getModel();
1375
        if (t == old)
1378
        if (t == old)
1376
            return;
1379
            return;
1377
 
1380
 
1378
        if (old != null) {
1381
        if (old != null) {
1379
            for (final PropertyChangeListener l : this.modelPCListeners)
1382
            for (final PropertyChangeListener l : this.modelPCListeners)
1380
                old.rmPropertyChangeListener(l);
1383
                old.rmPropertyChangeListener(l);
1381
            old.removeTableModelListener(this.selectionDataListener);
1384
            old.removeTableModelListener(this.selectionDataListener);
1382
            if (this.hasRequest())
1385
            if (this.hasRequest())
1383
                this.getRequest().rmWhereListener(this.filterListener);
1386
                this.getRequest().rmWhereListener(this.filterListener);
1384
        }
1387
        }
1385
        this.sorter.setTableModel(t);
1388
        this.sorter.setTableModel(t);
1386
        if (t != null) {
1389
        if (t != null) {
1387
            updateModelEditable();
1390
            updateModelEditable();
1388
            // no need to listen to source columns since our ITableModel does, then it
1391
            // no need to listen to source columns since our ITableModel does, then it
1389
            // fireTableStructureChanged() and our JTable createDefaultColumnsFromModel() so
1392
            // fireTableStructureChanged() and our JTable createDefaultColumnsFromModel() so
1390
            // columnAdded() and thus updateCols() are called. Note: we might want to listen to
1393
            // columnAdded() and thus updateCols() are called. Note: we might want to listen to
1391
            // SQLTableModelColumn themselves (and not their list), e.g. if their renderers change.
1394
            // SQLTableModelColumn themselves (and not their list), e.g. if their renderers change.
1392
            for (final PropertyChangeListener l : this.modelPCListeners) {
1395
            for (final PropertyChangeListener l : this.modelPCListeners) {
1393
                t.addPropertyChangeListener(l);
1396
                t.addPropertyChangeListener(l);
1394
                // signal to the listeners that the model has changed (ie all of its properties)
1397
                // signal to the listeners that the model has changed (ie all of its properties)
1395
                l.propertyChange(new PropertyChangeEvent(t, null, null, null));
1398
                l.propertyChange(new PropertyChangeEvent(t, null, null, null));
1396
            }
1399
            }
1397
            // listen to the SQL model and not this.sorter since change in sorting doesn't change
1400
            // listen to the SQL model and not this.sorter since change in sorting doesn't change
1398
            // the selection nor its data. Full listener since not all values are displayed.
1401
            // the selection nor its data. Full listener since not all values are displayed.
1399
            t.addTableModelListener(this.selectionDataListener, true);
1402
            t.addTableModelListener(this.selectionDataListener, true);
1400
            if (this.hasRequest()) {
1403
            if (this.hasRequest()) {
1401
                this.getRequest().addWhereListener(this.filterListener);
1404
                this.getRequest().addWhereListener(this.filterListener);
1402
                // the where might have changed since we last listened
1405
                // the where might have changed since we last listened
1403
                this.filterListener.propertyChange(null);
1406
                this.filterListener.propertyChange(null);
1404
            }
1407
            }
1405
        }
1408
        }
1406
        this.supp.firePropertyChange("model", old, t);
1409
        this.supp.firePropertyChange("model", old, t);
1407
    }
1410
    }
1408
 
1411
 
1409
    // must be called when columnModel or getSource() changes
1412
    // must be called when columnModel or getSource() changes
1410
    private void updateCols(final int index) {
1413
    private void updateCols(final int index) {
1411
        final TableColumnModel columnModel = this.jTable.getColumnModel();
1414
        final TableColumnModel columnModel = this.jTable.getColumnModel();
1412
        final int start = index < 0 ? 0 : index;
1415
        final int start = index < 0 ? 0 : index;
1413
        final int stop = index < 0 ? columnModel.getColumnCount() : index + 1;
1416
        final int stop = index < 0 ? columnModel.getColumnCount() : index + 1;
1414
        for (int i = start; i < stop; i++) {
1417
        for (int i = start; i < stop; i++) {
1415
            final TableColumn col = columnModel.getColumn(i);
1418
            final TableColumn col = columnModel.getColumn(i);
1416
            final SQLTableModelColumn srcCol = this.getSource().getColumn(col.getModelIndex());
1419
            final SQLTableModelColumn srcCol = this.getSource().getColumn(col.getModelIndex());
1417
            srcCol.install(col);
1420
            srcCol.install(col);
1418
            col.setIdentifier(srcCol.getIdentifier());
1421
            col.setIdentifier(srcCol.getIdentifier());
1419
            if (FORCE_ALT_CELL_RENDERER)
1422
            if (FORCE_ALT_CELL_RENDERER)
1420
                AlternateTableCellRenderer.setRendererAndListen(col);
1423
                AlternateTableCellRenderer.setRendererAndListen(col);
1421
            else
1424
            else
1422
                AlternateTableCellRenderer.setRenderer(col);
1425
                AlternateTableCellRenderer.setRenderer(col);
1423
        }
1426
        }
1424
    }
1427
    }
1425
 
1428
 
1426
    public final boolean hasRequest() {
1429
    public final boolean hasRequest() {
1427
        return this.getSource() instanceof SQLTableModelSourceOnline;
1430
        return this.getSource() instanceof SQLTableModelSourceOnline;
1428
    }
1431
    }
1429
 
1432
 
1430
    public final ListSQLRequest getRequest() {
1433
    public final ListSQLRequest getRequest() {
1431
        return this.getSource().getReq();
1434
        return this.getSource().getReq();
1432
    }
1435
    }
1433
 
1436
 
1434
    public final void setSource(SQLTableModelSource src) {
1437
    public final void setSource(SQLTableModelSource src) {
1435
        if (src == null)
1438
        if (src == null)
1436
            throw new NullPointerException();
1439
            throw new NullPointerException();
1437
        synchronized (this) {
1440
        synchronized (this) {
1438
            // necessary to limit table model changes, since it recreates columns (and thus forget
1441
            // necessary to limit table model changes, since it recreates columns (and thus forget
1439
            // about customizations, eg renderers)
1442
            // about customizations, eg renderers)
1440
            if (this.src == src)
1443
            if (this.src == src)
1441
                return;
1444
                return;
1442
 
1445
 
1443
            this.src = src;
1446
            this.src = src;
1444
        }
1447
        }
1445
        this.setTableModel(new ITableModel(src));
1448
        this.setTableModel(new ITableModel(src));
1446
    }
1449
    }
1447
 
1450
 
1448
    public synchronized final SQLTableModelSource getSource() {
1451
    public synchronized final SQLTableModelSource getSource() {
1449
        return this.src;
1452
        return this.src;
1450
    }
1453
    }
1451
 
1454
 
1452
    public final File getConfigFile() {
1455
    public final File getConfigFile() {
1453
        // can be null if this is called before the end of the constructor
1456
        // can be null if this is called before the end of the constructor
1454
        return this.tableStateManager == null ? null : this.tableStateManager.getConfigFile();
1457
        return this.tableStateManager == null ? null : this.tableStateManager.getConfigFile();
1455
    }
1458
    }
1456
 
1459
 
1457
    public final void setConfigFile(File configFile) {
1460
    public final void setConfigFile(File configFile) {
1458
        if (Boolean.getBoolean(STATELESS_TABLE_PROP))
1461
        if (Boolean.getBoolean(STATELESS_TABLE_PROP))
1459
            configFile = null;
1462
            configFile = null;
1460
        final File oldFile = this.getConfigFile();
1463
        final File oldFile = this.getConfigFile();
1461
        if (!CompareUtils.equals(oldFile, configFile)) {
1464
        if (!CompareUtils.equals(oldFile, configFile)) {
1462
            if (configFile == null)
1465
            if (configFile == null)
1463
                this.tableStateManager.endAutoSave();
1466
                this.tableStateManager.endAutoSave();
1464
            this.tableStateManager.setConfigFile(configFile);
1467
            this.tableStateManager.setConfigFile(configFile);
1465
            if (oldFile == null)
1468
            if (oldFile == null)
1466
                this.tableStateManager.beginAutoSave();
1469
                this.tableStateManager.beginAutoSave();
1467
            loadTableState();
1470
            loadTableState();
1468
        }
1471
        }
1469
    }
1472
    }
1470
 
1473
 
1471
    private boolean loadTableState() {
1474
    private boolean loadTableState() {
1472
        // - if configFile changes setConfigFile() calls us
1475
        // - if configFile changes setConfigFile() calls us
1473
        // - if the model changes, fireTableStructureChanged() is called and thus
1476
        // - if the model changes, fireTableStructureChanged() is called and thus
1474
        // JTable.createDefaultColumnsFromModel() which calls us
1477
        // JTable.createDefaultColumnsFromModel() which calls us
1475
        if (this.getConfigFile() != null && this.getModel() != null)
1478
        if (this.getConfigFile() != null && this.getModel() != null)
1476
            return this.tableStateManager.loadState();
1479
            return this.tableStateManager.loadState();
1477
        else
1480
        else
1478
            return false;
1481
            return false;
1479
    }
1482
    }
1480
 
1483
 
1481
    /**
1484
    /**
1482
     * Allow this list to be garbage collected. This method is necessary since this instance is
1485
     * Allow this list to be garbage collected. This method is necessary since this instance is
1483
     * listener of SQLTable which will never be gc'd.
1486
     * listener of SQLTable which will never be gc'd.
1484
     */
1487
     */
1485
    private final void dispChanged() {
1488
    private final void dispChanged() {
1486
        final boolean requiredToLive = this.isDisplayable() || this.retainCount > 0;
1489
        final boolean requiredToLive = this.isDisplayable() || this.retainCount > 0;
1487
        if (!requiredToLive && !this.isDead()) {
1490
        if (!requiredToLive && !this.isDead()) {
1488
            this.setTableModel(null);
1491
            this.setTableModel(null);
1489
        } else if (requiredToLive && this.isDead()) {
1492
        } else if (requiredToLive && this.isDead()) {
1490
            this.setTableModel(new ITableModel(this.getSource()));
1493
            this.setTableModel(new ITableModel(this.getSource()));
1491
        }
1494
        }
1492
    }
1495
    }
1493
 
1496
 
1494
    /**
1497
    /**
1495
     * Allow this to stay alive even if undisplayable. Attention, you must call {@link #release()}
1498
     * Allow this to stay alive even if undisplayable. Attention, you must call {@link #release()}
1496
     * for each {@link #retain()} otherwise this instance will never be garbage collected.
1499
     * for each {@link #retain()} otherwise this instance will never be garbage collected.
1497
     */
1500
     */
1498
    public final void retain() {
1501
    public final void retain() {
1499
        this.retainCount++;
1502
        this.retainCount++;
1500
        this.dispChanged();
1503
        this.dispChanged();
1501
    }
1504
    }
1502
 
1505
 
1503
    public final void release() {
1506
    public final void release() {
1504
        if (this.retainCount == 0)
1507
        if (this.retainCount == 0)
1505
            throw new IllegalStateException("Unbalanced release");
1508
            throw new IllegalStateException("Unbalanced release");
1506
        this.retainCount--;
1509
        this.retainCount--;
1507
        this.dispChanged();
1510
        this.dispChanged();
1508
    }
1511
    }
1509
 
1512
 
1510
    public JTable getJTable() {
1513
    public JTable getJTable() {
1511
        return this.jTable;
1514
        return this.jTable;
1512
    }
1515
    }
1513
 
1516
 
1514
    private void updateModelEditable() {
1517
    private void updateModelEditable() {
1515
        final ITableModel m = this.getModel();
1518
        final ITableModel m = this.getModel();
1516
        if (m != null) {
1519
        if (m != null) {
1517
            m.setCellsEditable(this.isCellModificationAllowed() && !getSource().getElem().isPrivate());
1520
            m.setCellsEditable(this.isCellModificationAllowed() && !getSource().getElem().isPrivate());
1518
            m.setOrderEditable(this.isOrderModificationAllowed() && (!getSource().getElem().isPrivate()));
1521
            m.setOrderEditable(this.isOrderModificationAllowed() && (!getSource().getElem().isPrivate()));
1519
        }
1522
        }
1520
    }
1523
    }
1521
 
1524
 
1522
    public void setModificationAllowed(boolean b) {
1525
    public void setModificationAllowed(boolean b) {
1523
        this.setCellModificationAllowed(b);
1526
        this.setCellModificationAllowed(b);
1524
        this.setOrderModificationAllowed(b);
1527
        this.setOrderModificationAllowed(b);
1525
    }
1528
    }
1526
 
1529
 
1527
    public void setCellModificationAllowed(boolean b) {
1530
    public void setCellModificationAllowed(boolean b) {
1528
        this.cellModificationAllowed = b;
1531
        this.cellModificationAllowed = b;
1529
        updateModelEditable();
1532
        updateModelEditable();
1530
    }
1533
    }
1531
 
1534
 
1532
    public boolean isCellModificationAllowed() {
1535
    public boolean isCellModificationAllowed() {
1533
        return this.cellModificationAllowed;
1536
        return this.cellModificationAllowed;
1534
    }
1537
    }
1535
 
1538
 
1536
    public void setOrderModificationAllowed(boolean b) {
1539
    public void setOrderModificationAllowed(boolean b) {
1537
        this.orderModificationAllowed = b;
1540
        this.orderModificationAllowed = b;
1538
        updateModelEditable();
1541
        updateModelEditable();
1539
    }
1542
    }
1540
 
1543
 
1541
    public boolean isOrderModificationAllowed() {
1544
    public boolean isOrderModificationAllowed() {
1542
        return this.orderModificationAllowed;
1545
        return this.orderModificationAllowed;
1543
    }
1546
    }
1544
 
1547
 
1545
    @Override
1548
    @Override
1546
    public void grabFocus() {
1549
    public void grabFocus() {
1547
        this.jTable.grabFocus();
1550
        this.jTable.grabFocus();
1548
    }
1551
    }
1549
 
1552
 
1550
    // *** workers
1553
    // *** workers
1551
 
1554
 
1552
    private abstract class FilterWorker extends SwingWorker<String, Object> {
1555
    private abstract class FilterWorker extends SwingWorker<String, Object> {
1553
 
1556
 
1554
        @Override
1557
        @Override
1555
        protected final void done() {
1558
        protected final void done() {
1556
            if (!this.isCancelled()) {
1559
            if (!this.isCancelled()) {
1557
                // if doInBackground() wasn't cancelled, display our result
1560
                // if doInBackground() wasn't cancelled, display our result
1558
                try {
1561
                try {
1559
                    setFilter(this.get());
1562
                    setFilter(this.get());
1560
                } catch (Exception e) {
1563
                } catch (Exception e) {
1561
                    if (e instanceof ExecutionException && ((ExecutionException) e).getCause() instanceof InterruptedException) {
1564
                    if (e instanceof ExecutionException && ((ExecutionException) e).getCause() instanceof InterruptedException) {
1562
                        final String msg = this.getClass() + " interruped";
1565
                        final String msg = this.getClass() + " interruped";
1563
                        Log.get().fine(msg);
1566
                        Log.get().fine(msg);
1564
                        setFilter(msg);
1567
                        setFilter(msg);
1565
                    } else {
1568
                    } else {
1566
                        e.printStackTrace();
1569
                        e.printStackTrace();
1567
                        setFilter(e.getLocalizedMessage());
1570
                        setFilter(e.getLocalizedMessage());
1568
                    }
1571
                    }
1569
                }
1572
                }
1570
                synchronized (IListe.this) {
1573
                synchronized (IListe.this) {
1571
                    // only doInBackground() can be cancelled, so this might have received cancel()
1574
                    // only doInBackground() can be cancelled, so this might have received cancel()
1572
                    // after doInBackground() had completed but before done() had been called
1575
                    // after doInBackground() had completed but before done() had been called
1573
                    // thus filterWorker is not always this instance
1576
                    // thus filterWorker is not always this instance
1574
                    if (IListe.this.filterWorker == this) {
1577
                    if (IListe.this.filterWorker == this) {
1575
                        IListe.this.filterWorker = null;
1578
                        IListe.this.filterWorker = null;
1576
                    }
1579
                    }
1577
                }
1580
                }
1578
            }
1581
            }
1579
        }
1582
        }
1580
 
1583
 
1581
    }
1584
    }
1582
 
1585
 
1583
    private final class WhereFilterWorker extends FilterWorker {
1586
    private final class WhereFilterWorker extends FilterWorker {
1584
        private final Where w;
1587
        private final Where w;
1585
 
1588
 
1586
        private WhereFilterWorker(Where r) {
1589
        private WhereFilterWorker(Where r) {
1587
            this.w = r;
1590
            this.w = r;
1588
        }
1591
        }
1589
 
1592
 
1590
        @Override
1593
        @Override
1591
        protected String doInBackground() throws InterruptedException {
1594
        protected String doInBackground() throws InterruptedException {
1592
            return this.w == null ? "No where" : this.w.getClause();
1595
            return this.w == null ? "No where" : this.w.getClause();
1593
        }
1596
        }
1594
 
1597
 
1595
    }
1598
    }
1596
 
1599
 
1597
    private final class RowFilterWorker extends FilterWorker {
1600
    private final class RowFilterWorker extends FilterWorker {
1598
        private final Collection<SQLRow> rows;
1601
        private final Collection<SQLRow> rows;
1599
 
1602
 
1600
        private RowFilterWorker(Collection<SQLRow> r) {
1603
        private RowFilterWorker(Collection<SQLRow> r) {
1601
            this.rows = r;
1604
            this.rows = r;
1602
        }
1605
        }
1603
 
1606
 
1604
        @Override
1607
        @Override
1605
        protected String doInBackground() throws InterruptedException {
1608
        protected String doInBackground() throws InterruptedException {
1606
            if (this.getRows() == null)
1609
            if (this.getRows() == null)
1607
                return null;
1610
                return null;
1608
 
1611
 
1609
            // attend 1 peu avant de faire des requetes, comme ca si le filtre change
1612
            // attend 1 peu avant de faire des requetes, comme ca si le filtre change
1610
            // tout le temps, on ne commence meme pas (sleep jette InterruptedExn)
1613
            // tout le temps, on ne commence meme pas (sleep jette InterruptedExn)
1611
            Thread.sleep(60);
1614
            Thread.sleep(60);
1612
 
1615
 
1613
            final List<String> ancestors = new ArrayList<String>();
1616
            final List<String> ancestors = new ArrayList<String>();
1614
            final SQLElementDirectory dir = getSource().getElem().getDirectory();
1617
            final SQLElementDirectory dir = getSource().getElem().getDirectory();
1615
            // always put the description of getRows(), but only put their ancestor if they all have
1618
            // always put the description of getRows(), but only put their ancestor if they all have
1616
            // the same parent
1619
            // the same parent
1617
            Tuple2<SQLRow, String> parentAndDesc = getParent(this.getRows(), dir);
1620
            Tuple2<SQLRow, String> parentAndDesc = getParent(this.getRows(), dir);
1618
            ancestors.add(parentAndDesc.get1());
1621
            ancestors.add(parentAndDesc.get1());
1619
            SQLRow current = parentAndDesc.get0();
1622
            SQLRow current = parentAndDesc.get0();
1620
            while (current != null) {
1623
            while (current != null) {
1621
                if (Thread.interrupted()) {
1624
                if (Thread.interrupted()) {
1622
                    throw new InterruptedException();
1625
                    throw new InterruptedException();
1623
                }
1626
                }
1624
                final SQLElement elem = dir.getElement(current.getTable());
1627
                final SQLElement elem = dir.getElement(current.getTable());
1625
                ancestors.add(0, elem.getDescription(current));
1628
                ancestors.add(0, elem.getDescription(current));
1626
                current = elem.getForeignParent(current);
1629
                current = elem.getForeignParent(current);
1627
            }
1630
            }
1628
 
1631
 
1629
            return CollectionUtils.join(ancestors, SEP);
1632
            return CollectionUtils.join(ancestors, SEP);
1630
        }
1633
        }
1631
 
1634
 
1632
        private Tuple2<SQLRow, String> getParent(Collection<SQLRow> rows, final SQLElementDirectory dir) throws InterruptedException {
1635
        private Tuple2<SQLRow, String> getParent(Collection<SQLRow> rows, final SQLElementDirectory dir) throws InterruptedException {
1633
            SQLRow parent = null;
1636
            SQLRow parent = null;
1634
            boolean sameParent = true;
1637
            boolean sameParent = true;
1635
            final List<String> desc = new ArrayList<String>(rows.size());
1638
            final List<String> desc = new ArrayList<String>(rows.size());
1636
 
1639
 
1637
            for (final SQLRow current : rows) {
1640
            for (final SQLRow current : rows) {
1638
                if (Thread.interrupted()) {
1641
                if (Thread.interrupted()) {
1639
                    throw new InterruptedException();
1642
                    throw new InterruptedException();
1640
                }
1643
                }
1641
                final SQLElement elem = dir.getElement(current.getTable());
1644
                final SQLElement elem = dir.getElement(current.getTable());
1642
                if (parent == null || sameParent) {
1645
                if (parent == null || sameParent) {
1643
                    final SQLRow currentParent = elem.getForeignParent(current);
1646
                    final SQLRow currentParent = elem.getForeignParent(current);
1644
                    if (parent == null)
1647
                    if (parent == null)
1645
                        parent = currentParent;
1648
                        parent = currentParent;
1646
                    else if (!parent.equals(currentParent))
1649
                    else if (!parent.equals(currentParent))
1647
                        sameParent = false;
1650
                        sameParent = false;
1648
                }
1651
                }
1649
                desc.add(elem.getDescription(current));
1652
                desc.add(elem.getDescription(current));
1650
            }
1653
            }
1651
 
1654
 
1652
            return Tuple2.create(sameParent ? parent : null, CollectionUtils.join(desc, " ●"));
1655
            return Tuple2.create(sameParent ? parent : null, CollectionUtils.join(desc, " ●"));
1653
        }
1656
        }
1654
 
1657
 
1655
        private final Collection<SQLRow> getRows() {
1658
        private final Collection<SQLRow> getRows() {
1656
            return this.rows;
1659
            return this.rows;
1657
        }
1660
        }
1658
 
1661
 
1659
        @Override
1662
        @Override
1660
        public String toString() {
1663
        public String toString() {
1661
            return super.toString() + " on " + this.getRows();
1664
            return super.toString() + " on " + this.getRows();
1662
        }
1665
        }
1663
    }
1666
    }
1664
}
1667
}