OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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

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