1 package fr.ifremer.tutti.ui.swing.util.table;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import com.google.common.base.Preconditions;
26 import com.google.common.collect.Sets;
27 import fr.ifremer.tutti.ui.swing.content.operation.catches.species.edit.SampleCategoryComponent.SampleCategoryEditor;
28 import fr.ifremer.tutti.ui.swing.util.AbstractTuttiBeanUIModel;
29 import fr.ifremer.tutti.ui.swing.util.AbstractTuttiUIHandler;
30 import fr.ifremer.tutti.ui.swing.util.TuttiBeanMonitor;
31 import fr.ifremer.tutti.ui.swing.util.TuttiUI;
32 import fr.ifremer.tutti.ui.swing.util.computable.ComputableDataTableCell.TuttiComputedOrNotDataTableCellEditor;
33 import jaxx.runtime.SwingUtil;
34 import jaxx.runtime.swing.editor.cell.NumberCellEditor;
35 import org.apache.commons.collections4.CollectionUtils;
36 import org.apache.commons.lang3.ArrayUtils;
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39 import org.jdesktop.swingx.JXTable;
40 import org.nuiton.jaxx.application.swing.table.AbstractApplicationTableModel;
41 import org.nuiton.jaxx.application.swing.table.MoveToNextEditableCellAction;
42 import org.nuiton.jaxx.application.swing.table.MoveToNextEditableRowAction;
43 import org.nuiton.jaxx.application.swing.table.MoveToPreviousEditableCellAction;
44 import org.nuiton.jaxx.application.swing.table.MoveToPreviousEditableRowAction;
45
46 import javax.swing.AbstractAction;
47 import javax.swing.JTable;
48 import javax.swing.ListSelectionModel;
49 import javax.swing.event.ListSelectionEvent;
50 import javax.swing.event.ListSelectionListener;
51 import javax.swing.table.TableCellEditor;
52 import javax.swing.table.TableColumn;
53 import javax.swing.table.TableColumnModel;
54 import java.awt.event.KeyAdapter;
55 import java.awt.event.KeyEvent;
56 import java.beans.PropertyChangeEvent;
57 import java.beans.PropertyChangeListener;
58 import java.util.Enumeration;
59 import java.util.HashMap;
60 import java.util.List;
61 import java.util.Map;
62 import java.util.Set;
63
64
65
66
67
68
69
70 public abstract class AbstractTuttiTableUIHandler<R extends AbstractTuttiBeanUIModel, M extends AbstractTuttiTableUIModel<?, R, M>, UI extends TuttiUI<M, ?>> extends AbstractTuttiUIHandler<M, UI> {
71
72
73 private static final Log log =
74 LogFactory.getLog(AbstractTuttiTableUIHandler.class);
75
76
77
78
79
80 public abstract AbstractApplicationTableModel<R> getTableModel();
81
82
83
84
85
86 public abstract JXTable getTable();
87
88
89
90
91
92
93
94
95 protected abstract boolean isRowValid(R row);
96
97
98
99
100
101
102
103
104
105
106
107
108 protected void onRowModified(int rowIndex,
109 R row,
110 String propertyName,
111 Object oldValue,
112 Object newValue) {
113 getModel().setModify(true);
114 }
115
116
117
118
119
120
121
122
123
124
125 protected abstract void saveSelectedRowIfRequired(TuttiBeanMonitor<R> rowMonitor, R row);
126
127
128
129
130
131
132 private final TuttiBeanMonitor<R> rowMonitor;
133
134 protected AbstractTuttiTableUIHandler(String... properties) {
135
136 rowMonitor = new TuttiBeanMonitor<>(properties);
137
138
139 rowMonitor.addPropertyChangeListener(TuttiBeanMonitor.PROPERTY_BEAN, new PropertyChangeListener() {
140
141 final Set<String> propertiesToSkip =
142 Sets.newHashSet(getRowPropertiesToIgnore());
143
144 final PropertyChangeListener l = new PropertyChangeListener() {
145 @Override
146 public void propertyChange(PropertyChangeEvent evt) {
147 String propertyName = evt.getPropertyName();
148
149 R row = (R) evt.getSource();
150
151 Object oldValue = evt.getOldValue();
152 Object newValue = evt.getNewValue();
153
154 int rowIndex = getTableModel().getRowIndex(row);
155
156 if (AbstractTuttiBeanUIModel.PROPERTY_VALID.equals(propertyName)) {
157 onRowValidStateChanged(rowIndex, row,
158 (Boolean) oldValue,
159 (Boolean) newValue);
160 } else if (AbstractTuttiBeanUIModel.PROPERTY_MODIFY.equals(propertyName)) {
161 onRowModifyStateChanged(rowIndex, row,
162 (Boolean) oldValue,
163 (Boolean) newValue);
164 } else if (!propertiesToSkip.contains(propertyName)) {
165
166 if (log.isDebugEnabled()) {
167 log.debug("row [" + rowIndex + "] property " +
168 propertyName + " changed from " + oldValue +
169 " to " + newValue);
170 }
171 onRowModified(rowIndex, row,
172 propertyName,
173 oldValue,
174 newValue);
175 }
176 }
177 };
178
179 @Override
180 public void propertyChange(PropertyChangeEvent evt) {
181 R oldValue = (R) evt.getOldValue();
182 R newValue = (R) evt.getNewValue();
183 if (log.isDebugEnabled()) {
184 log.debug("Monitor row changed from " +
185 oldValue + " to " + newValue);
186 }
187 if (oldValue != null) {
188 oldValue.removePropertyChangeListener(l);
189 }
190 if (newValue != null) {
191 newValue.addPropertyChangeListener(l);
192 }
193 }
194 });
195 }
196
197
198
199
200
201 protected String[] getRowPropertiesToIgnore() {
202 return ArrayUtils.EMPTY_STRING_ARRAY;
203 }
204
205 protected void onModelRowsChanged(List<R> rows) {
206 if (log.isDebugEnabled()) {
207 log.debug("Will set " + (rows == null ? 0 : rows.size()) +
208 " rows on model.");
209 }
210 if (CollectionUtils.isNotEmpty(rows)) {
211
212
213 for (R row : rows) {
214 recomputeRowValidState(row);
215 }
216 }
217 getTableModel().setRows(rows);
218 }
219
220 protected void onRowModifyStateChanged(int rowIndex,
221 R row,
222 Boolean oldValue,
223 Boolean newValue) {
224 if (log.isDebugEnabled()) {
225 log.debug("row [" + rowIndex + "] modify state changed from " +
226 oldValue + " to " + newValue);
227 }
228 }
229
230 protected void onRowValidStateChanged(int rowIndex,
231 R row,
232 Boolean oldValue,
233 Boolean newValue) {
234
235 if (log.isDebugEnabled()) {
236 log.debug("row [" + rowIndex + "] valid state changed from " +
237 oldValue + " to " + newValue);
238 }
239
240 if (rowIndex > -1) {
241 getTableModel().fireTableRowsUpdated(rowIndex, rowIndex);
242 }
243 }
244
245 protected void onAfterSelectedRowChanged(int oldRowIndex,
246 R oldRow,
247 int newRowIndex,
248 R newRow) {
249 if (log.isDebugEnabled()) {
250 log.debug("Selected row changed from [" + oldRowIndex + "] to [" +
251 newRowIndex + "]");
252 }
253 }
254
255
256
257
258
259 protected void initTable(JXTable table) {
260
261
262 table.getTableHeader().setReorderingAllowed(false);
263
264 addHighlighters(table);
265
266
267 getModel().addPropertyChangeListener(AbstractTuttiTableUIModel.PROPERTY_ROWS, evt -> onModelRowsChanged((List<R>) evt.getNewValue()));
268
269
270 SwingUtil.scrollToTableSelection(getTable());
271
272
273 uninstallTableSaveOnRowChangedSelectionListener();
274
275
276 installTableSaveOnRowChangedSelectionListener();
277 }
278
279
280
281
282
283 private ListSelectionListener tableSelectionListener;
284
285 private Map<JTable, KeyAdapter> keyAdapters = new HashMap<>();
286
287 protected void installTableSaveOnRowChangedSelectionListener() {
288
289 Preconditions.checkState(
290 tableSelectionListener == null,
291 "There is already a tableSelectionListener registred, " +
292 "remove it before invoking this method.");
293
294
295
296
297 tableSelectionListener = new ListSelectionListener() {
298
299
300
301
302
303 protected int selectedRowIndex;
304
305 @Override
306 public void valueChanged(ListSelectionEvent e) {
307
308 if (log.isDebugEnabled()) {
309 log.debug("Selection changed: " + e);
310 }
311
312
313
314
315 ListSelectionModel source = (ListSelectionModel) e.getSource();
316
317 int oldRowIndex = selectedRowIndex;
318 int newRowIndex = source.getLeadSelectionIndex();
319
320 R oldRow = rowMonitor.getBean();
321
322 if (oldRow == null || oldRowIndex != newRowIndex) {
323
324 R newRow;
325
326 if (source.isSelectionEmpty()) {
327
328 newRow = null;
329 } else {
330 newRow = getTableModel().getEntry(newRowIndex);
331 }
332
333 if (log.isDebugEnabled()) {
334 log.debug("Will monitor entry: " + newRowIndex);
335 }
336 rowMonitor.setBean(newRow);
337
338 selectedRowIndex = newRowIndex;
339
340 onAfterSelectedRowChanged(oldRowIndex,
341 oldRow,
342 selectedRowIndex,
343 rowMonitor.getBean());
344 }
345 }
346 };
347
348 if (log.isDebugEnabled()) {
349 log.debug("Intall " + tableSelectionListener + " on tableModel " + getTableModel());
350 }
351
352 getTable().getSelectionModel().addListSelectionListener(tableSelectionListener);
353 }
354
355 protected void uninstallTableSaveOnRowChangedSelectionListener() {
356
357 if (tableSelectionListener != null) {
358
359 if (log.isDebugEnabled()) {
360 log.debug("Desintall " + tableSelectionListener);
361 }
362
363
364 getTable().getSelectionModel().removeListSelectionListener(tableSelectionListener);
365 tableSelectionListener = null;
366 }
367 }
368
369 protected void installTableKeyListener(TableColumnModel columnModel, final JTable table) {
370 installTableKeyListener(columnModel, table, true);
371 }
372
373 protected void installTableKeyListener(TableColumnModel columnModel, JTable table, boolean enterToChangeRow) {
374
375 Preconditions.checkState(
376 keyAdapters.get(table) == null,
377 "There is already a tableSelectionListener registred, " +
378 "remove it before invoking this method.");
379
380 AbstractApplicationTableModel model = (AbstractApplicationTableModel) table.getModel();
381
382 MoveToNextEditableCellAction nextCellAction = MoveToNextEditableCellAction.newAction(model, table);
383 MoveToPreviousEditableCellAction previousCellAction = MoveToPreviousEditableCellAction.newAction(model, table);
384 MoveToNextEditableRowAction nextRowAction = MoveToNextEditableRowAction.newAction(model, table);
385 MoveToPreviousEditableRowAction previousRowAction = MoveToPreviousEditableRowAction.newAction(model, table);
386
387 KeyAdapter keyAdapter = new KeyAdapter() {
388
389 @Override
390 public void keyPressed(KeyEvent e) {
391 TableCellEditor editor = table.getCellEditor();
392
393 int keyCode = e.getKeyCode();
394 boolean shiftDown = e.isShiftDown();
395
396 if (gotoPreviousCell(keyCode, shiftDown)) {
397
398 consumeAction(e, editor, previousCellAction);
399
400 } else if (gotoNextCell(keyCode)) {
401 consumeAction(e, editor, nextCellAction);
402
403 } else if (gotoPreviousRow(keyCode, shiftDown)) {
404
405 consumeAction(e, editor, previousRowAction);
406
407 } else if (gotoNextRow(keyCode)) {
408
409 consumeAction(e, editor, nextRowAction);
410
411 }
412 }
413
414 protected void consumeAction(KeyEvent e, TableCellEditor editor, AbstractAction action) {
415 e.consume();
416 if (editor != null) {
417 editor.stopCellEditing();
418 }
419 action.actionPerformed(null);
420 }
421
422 protected boolean gotoPreviousCell(int keyCode, boolean shiftDown) {
423 return keyCode == KeyEvent.VK_LEFT
424 || (keyCode == KeyEvent.VK_TAB && shiftDown)
425 || (!enterToChangeRow && keyCode == KeyEvent.VK_ENTER && shiftDown);
426 }
427
428 protected boolean gotoNextCell(int keyCode) {
429 return keyCode == KeyEvent.VK_RIGHT
430 || keyCode == KeyEvent.VK_TAB
431 || (!enterToChangeRow && keyCode == KeyEvent.VK_ENTER);
432 }
433
434 protected boolean gotoPreviousRow(int keyCode, boolean shiftDown) {
435 return keyCode == KeyEvent.VK_UP
436 || (enterToChangeRow && keyCode == KeyEvent.VK_ENTER && shiftDown);
437 }
438
439 protected boolean gotoNextRow(int keyCode) {
440 return keyCode == KeyEvent.VK_DOWN
441 || (enterToChangeRow && keyCode == KeyEvent.VK_ENTER);
442 }
443
444 };
445 keyAdapters.put(table, keyAdapter);
446
447 if (log.isDebugEnabled()) {
448 log.debug("Intall " + keyAdapter);
449 }
450
451 table.addKeyListener(keyAdapter);
452
453 Enumeration<TableColumn> columns = columnModel.getColumns();
454 while (columns.hasMoreElements()) {
455 TableColumn tableColumn = columns.nextElement();
456 TableCellEditor cellEditor = tableColumn.getCellEditor();
457 if (cellEditor instanceof NumberCellEditor) {
458 NumberCellEditor editor = (NumberCellEditor) cellEditor;
459 editor.getNumberEditor().getTextField().addKeyListener(keyAdapter);
460
461 } else if (cellEditor instanceof TuttiComputedOrNotDataTableCellEditor) {
462 TuttiComputedOrNotDataTableCellEditor editor =
463 (TuttiComputedOrNotDataTableCellEditor) cellEditor;
464 editor.getNumberEditor().getTextField().addKeyListener(keyAdapter);
465
466 } else if (cellEditor instanceof SampleCategoryEditor) {
467 SampleCategoryEditor editor = (SampleCategoryEditor) cellEditor;
468 editor.getNumberEditor().getTextField().addKeyListener(keyAdapter);
469 }
470 }
471 }
472
473 protected void uninstallTableKeyListener(JTable table) {
474
475 KeyAdapter keyAdapter = keyAdapters.get(table);
476 if (keyAdapter != null) {
477
478 if (log.isDebugEnabled()) {
479 log.debug("Desintall " + keyAdapter);
480 }
481
482 table.removeKeyListener(keyAdapter);
483
484 TableColumnModel columnModel = table.getColumnModel();
485 Enumeration<TableColumn> columns = columnModel.getColumns();
486 while (columns.hasMoreElements()) {
487 TableColumn tableColumn = columns.nextElement();
488 TableCellEditor cellEditor = tableColumn.getCellEditor();
489 if (cellEditor instanceof NumberCellEditor) {
490 NumberCellEditor editor = (NumberCellEditor) cellEditor;
491 editor.getNumberEditor().getTextField().removeKeyListener(keyAdapter);
492 }
493 }
494 keyAdapters.remove(table);
495 }
496 }
497
498 protected final void saveSelectedRowIfNeeded() {
499
500 R row = rowMonitor.getBean();
501
502 if (row != null) {
503 saveSelectedRowIfRequired(rowMonitor, row);
504 }
505 }
506
507
508 protected void cleanrRowMonitor() {
509 rowMonitor.clearModified();
510 }
511
512 protected final void recomputeRowValidState(R row) {
513
514
515 boolean valid = isRowValid(row);
516
517
518 row.setValid(valid);
519
520 if (valid) {
521 getModel().removeRowInError(row);
522 } else {
523 getModel().addRowInError(row);
524 }
525 }
526
527 }