OSDN Git Service

Fix #31981
[stew/Stew4.git] / src / net / argius / stew / ui / window / ResultSetTable.java
1 package net.argius.stew.ui.window;
2
3 import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;
4 import static java.awt.event.KeyEvent.*;
5 import static java.awt.event.MouseEvent.MOUSE_DRAGGED;
6 import static java.awt.event.MouseEvent.MOUSE_PRESSED;
7 import static javax.swing.KeyStroke.getKeyStroke;
8 import static net.argius.stew.ui.window.AnyActionKey.*;
9 import static net.argius.stew.ui.window.ResultSetTable.ActionKey.*;
10
11 import java.awt.*;
12 import java.awt.event.*;
13 import java.beans.*;
14 import java.io.*;
15 import java.sql.*;
16 import java.util.*;
17 import java.util.List;
18
19 import javax.swing.*;
20 import javax.swing.event.*;
21 import javax.swing.table.*;
22 import javax.swing.text.*;
23
24 import net.argius.stew.*;
25 import net.argius.stew.io.*;
26 import net.argius.stew.text.*;
27
28 /**
29  * Table for Result Set.
30  */
31 final class ResultSetTable extends JTable implements AnyActionListener, TextSearch {
32
33     enum ActionKey {
34         copyWithEscape,
35         clearSelectedCellValue,
36         setCurrentTimeValue,
37         copyColumnName,
38         findColumnName,
39         addEmptyRow,
40         insertFromClipboard,
41         duplicateRows,
42         linkRowsToDatabase,
43         deleteRows,
44         sort,
45         jumpToColumn,
46         doNothing,
47     }
48
49     static final String TAB = "\t";
50
51     private static final Logger log = Logger.getLogger(ResultSetTable.class);
52     private static final TableCellRenderer nullRenderer = new NullValueRenderer();
53     private static final boolean[] booleans = {false, true};
54
55     private final AnyActionListener anyActionListener;
56     private final ColumnHeaderCellRenderer columnHeaderRenderer;
57     private final RowHeader rowHeader;
58     private final Point mousePositionForColumnHeader = new Point();
59
60     private int lastSortedIndex;
61     private boolean lastSortedIsReverse;
62     private String autoAdjustMode;
63
64     // It is used by the process that has no key-event.
65     private volatile KeyEvent lastKeyEvent;
66
67     /**
68      * Constructor.
69      */
70     ResultSetTable(AnyActionListener anyActionListener) {
71         this.anyActionListener = anyActionListener;
72         JTableHeader columnHeader = getTableHeader();
73         TableCellRenderer columnHeaderDefaultRenderer = columnHeader.getDefaultRenderer();
74         final RowHeader rowHeader = new RowHeader(this);
75         this.columnHeaderRenderer = new ColumnHeaderCellRenderer(columnHeaderDefaultRenderer);
76         this.rowHeader = rowHeader;
77         setColumnSelectionAllowed(true);
78         setAutoResizeMode(AUTO_RESIZE_OFF);
79         columnHeader.setDefaultRenderer(columnHeaderRenderer);
80         columnHeader.setReorderingAllowed(false);
81         // [Events]
82         // column header
83         MouseInputListener colHeaderMouseListener = new ColumnHeaderMouseInputListener();
84         columnHeader.addMouseListener(colHeaderMouseListener);
85         columnHeader.addMouseMotionListener(colHeaderMouseListener);
86         // row header
87         MouseInputListener rowHeaderMouseListener = new RowHeaderMouseInputListener(rowHeader);
88         rowHeader.addMouseListener(rowHeaderMouseListener);
89         rowHeader.addMouseMotionListener(rowHeaderMouseListener);
90         // cursor
91         for (final boolean withSelect : booleans) {
92             bindJumpAction("home", VK_HOME, withSelect);
93             bindJumpAction("end", VK_END, withSelect);
94             bindJumpAction("top", VK_UP, withSelect);
95             bindJumpAction("bottom", VK_DOWN, withSelect);
96             bindJumpAction("leftmost", VK_LEFT, withSelect);
97             bindJumpAction("rightmost", VK_RIGHT, withSelect);
98         }
99         // key binds
100         final int shortcutKey = Utilities.getMenuShortcutKeyMask();
101         AnyAction aa = new AnyAction(this);
102         aa.bindSelf(copyWithEscape, getKeyStroke(VK_C, shortcutKey | InputEvent.SHIFT_DOWN_MASK));
103         aa.bindSelf(paste, getKeyStroke(VK_V, shortcutKey));
104         aa.bindSelf(clearSelectedCellValue, getKeyStroke(VK_DELETE, 0));
105         aa.bindSelf(deleteRows, getKeyStroke(VK_MINUS, shortcutKey | InputEvent.SHIFT_DOWN_MASK));
106         aa.bindKeyStroke(true, adjustColumnWidth, getKeyStroke(VK_SLASH, shortcutKey));
107         aa.bindKeyStroke(false, doNothing, getKeyStroke(VK_ESCAPE, 0));
108     }
109
110     private final class RowHeaderMouseInputListener extends MouseInputAdapter {
111     
112         @SuppressWarnings("hiding")
113         private final RowHeader rowHeader;
114         private int dragStartRow;
115     
116         RowHeaderMouseInputListener(RowHeader rowHeader) {
117             this.rowHeader = rowHeader;
118         }
119     
120         @Override
121         public void mousePressed(MouseEvent e) {
122             changeSelection(e);
123         }
124     
125         @Override
126         public void mouseDragged(MouseEvent e) {
127             changeSelection(e);
128         }
129     
130         private void changeSelection(MouseEvent e) {
131             Point p = new Point(e.getX(), e.getY());
132             if (SwingUtilities.isLeftMouseButton(e)) {
133                 int id = e.getID();
134                 boolean isMousePressed = (id == MOUSE_PRESSED);
135                 boolean isMouseDragged = (id == MOUSE_DRAGGED);
136                 if (isMousePressed || isMouseDragged) {
137                     if (p.y >= rowHeader.getBounds().height) {
138                         return;
139                     }
140                     if (!e.isControlDown() && !e.isShiftDown()) {
141                         clearSelection();
142                     }
143                     int rowIndex = rowAtPoint(p);
144                     if (rowIndex < 0 || getRowCount() < rowIndex) {
145                         return;
146                     }
147                     final int index0;
148                     final int index1;
149                     if (isMousePressed) {
150                         if (e.isShiftDown()) {
151                             index0 = dragStartRow;
152                             index1 = rowIndex;
153                         } else {
154                             dragStartRow = rowIndex;
155                             index0 = rowIndex;
156                             index1 = rowIndex;
157                         }
158                     } else if (isMouseDragged) {
159                         index0 = dragStartRow;
160                         index1 = rowIndex;
161                     } else {
162                         return;
163                     }
164                     addRowSelectionInterval(index0, index1);
165                     addColumnSelectionInterval(getColumnCount() - 1, 0);
166                     requestFocus();
167                     // justify a position between table and its row header
168                     JViewport tableView = (JViewport)getParent();
169                     Point viewPosition = tableView.getViewPosition();
170                     viewPosition.y = ((JViewport)rowHeader.getParent()).getViewPosition().y;
171                     tableView.setViewPosition(viewPosition);
172                 }
173             }
174         }
175     }
176
177     private final class ColumnHeaderMouseInputListener extends MouseInputAdapter {
178     
179         private int dragStartColumn;
180     
181         ColumnHeaderMouseInputListener() {
182         } // empty
183
184         @SuppressWarnings("synthetic-access")
185         @Override
186         public void mousePressed(MouseEvent e) {
187             if (SwingUtilities.isLeftMouseButton(e)) {
188                 changeSelection(e);
189             }
190             mousePositionForColumnHeader.setLocation(e.getPoint());
191         }
192     
193         @Override
194         public void mouseDragged(MouseEvent e) {
195             if (SwingUtilities.isLeftMouseButton(e)) {
196                 changeSelection(e);
197             }
198         }
199     
200         private void changeSelection(MouseEvent e) {
201             final Point p = e.getPoint();
202             int id = e.getID();
203             boolean isMousePressed = (id == MOUSE_PRESSED);
204             boolean isMouseDragged = (id == MOUSE_DRAGGED);
205             if (isMousePressed || isMouseDragged) {
206                 if (!e.isControlDown() && !e.isShiftDown()) {
207                     clearSelection();
208                 }
209                 int columnIndex = columnAtPoint(p);
210                 if (columnIndex < 0 || getColumnCount() <= columnIndex) {
211                     return;
212                 }
213                 final int index0;
214                 final int index1;
215                 if (isMousePressed) {
216                     if (e.isShiftDown()) {
217                         index0 = dragStartColumn;
218                         index1 = columnIndex;
219                     } else {
220                         dragStartColumn = columnIndex;
221                         index0 = columnIndex;
222                         index1 = columnIndex;
223                     }
224                 } else if (isMouseDragged) {
225                     index0 = dragStartColumn;
226                     index1 = columnIndex;
227                 } else {
228                     return;
229                 }
230                 selectColumn(index0, index1);
231                 requestFocus();
232             }
233         }
234     }
235
236     @Override
237     protected void processKeyEvent(KeyEvent e) {
238         super.processKeyEvent(e);
239         lastKeyEvent = e;
240     }
241
242     @Override
243     public void anyActionPerformed(AnyActionEvent ev) {
244         try {
245             processAnyActionEvent(ev);
246         } catch (Exception ex) {
247             WindowOutputProcessor.showErrorDialog(this, ex);
248         }
249     }
250
251     public void processAnyActionEvent(AnyActionEvent ev) throws Exception {
252         if (ev.isAnyOf(copy, selectAll)) {
253             final String cmd = ev.getActionCommand();
254             getActionMap().get(cmd).actionPerformed(new ActionEvent(this, 0, cmd));
255         } else if (ev.isAnyOf(copyWithEscape)) {
256             List<String> rows = new ArrayList<String>();
257             for (int rowIndex : getSelectedRows()) {
258                 List<Object> row = new ArrayList<Object>();
259                 for (int columnIndex : getSelectedColumns()) {
260                     final Object o = getValueAt(rowIndex, columnIndex);
261                     row.add(CsvFormatter.AUTO.format(o == null ? "" : String.valueOf(o)));
262                 }
263                 rows.add(TextUtilities.join(TAB, row));
264             }
265             ClipboardHelper.setStrings(rows);
266         } else if (ev.isAnyOf(paste)) {
267             try {
268                 InputStream is = new ByteArrayInputStream(ClipboardHelper.getString().getBytes());
269                 Importer importer = new SmartImporter(is, TAB);
270                 try {
271                     int[] selectedColumns = getSelectedColumns();
272                     for (int rowIndex : getSelectedRows()) {
273                         Object[] values = importer.nextRow();
274                         final int limit = Math.min(selectedColumns.length, values.length);
275                         for (int x = 0; x < limit; x++) {
276                             setValueAt(values[x], rowIndex, selectedColumns[x]);
277                         }
278                     }
279                 } finally {
280                     importer.close();
281                 }
282                 repaint();
283             } finally {
284                 editingCanceled(new ChangeEvent(ev.getSource()));
285             }
286         } else if (ev.isAnyOf(clearSelectedCellValue)) {
287             try {
288                 setValueAtSelectedCells(null);
289                 repaint();
290             } finally {
291                 editingCanceled(new ChangeEvent(ev.getSource()));
292             }
293         } else if (ev.isAnyOf(setCurrentTimeValue)) {
294             try {
295                 setValueAtSelectedCells(new Timestamp(System.currentTimeMillis()));
296                 repaint();
297             } finally {
298                 editingCanceled(new ChangeEvent(ev.getSource()));
299             }
300         } else if (ev.isAnyOf(copyColumnName)) {
301             List<String> a = new ArrayList<String>();
302             ResultSetTableModel m = getResultSetTableModel();
303             if (ev.getModifiers() == 0) {
304                 for (int i = 0, n = m.getColumnCount(); i < n; i++) {
305                     a.add(m.getColumnName(i));
306                 }
307             } else {
308                 for (final int i : getSelectedColumns()) {
309                     a.add(m.getColumnName(i));
310                 }   
311             }
312             ClipboardHelper.setString(TextUtilities.join(TAB, a));
313         } else if (ev.isAnyOf(findColumnName)) {
314             anyActionListener.anyActionPerformed(ev);
315         } else if (ev.isAnyOf(addEmptyRow)) {
316             ResultSetTableModel m = getResultSetTableModel();
317             int[] selectedRows = getSelectedRows();
318             if (selectedRows.length > 0) {
319                 final int nextRow = selectedRows[selectedRows.length - 1] + 1;
320                 for (int i = 0; i < selectedRows.length; i++) {
321                     m.insertUnlinkedRow(nextRow, new Object[m.getColumnCount()]);
322                 }
323             } else {
324                 m.addUnlinkedRow(new Object[m.getColumnCount()]);
325             }
326         } else if (ev.isAnyOf(insertFromClipboard)) {
327             try {
328                 Importer importer = new SmartImporter(ClipboardHelper.getReaderForText(), TAB);
329                 try {
330                     ResultSetTableModel m = getResultSetTableModel();
331                     while (true) {
332                         Object[] row = importer.nextRow();
333                         if (row.length == 0) {
334                             break;
335                         }
336                         m.addUnlinkedRow(row);
337                         m.linkRow(m.getRowCount() - 1);
338                     }
339                     repaintRowHeader("model");
340                 } finally {
341                     importer.close();
342                 }
343             } finally {
344                 editingCanceled(new ChangeEvent(ev.getSource()));
345             }
346         } else if (ev.isAnyOf(duplicateRows)) {
347             ResultSetTableModel m = getResultSetTableModel();
348             List<?> rows = m.getDataVector();
349             int[] selectedRows = getSelectedRows();
350             int index = selectedRows[selectedRows.length - 1];
351             for (int rowIndex : selectedRows) {
352                 m.insertUnlinkedRow(++index, (Vector<?>)((Vector<?>)rows.get(rowIndex)).clone());
353             }
354             repaint();
355             repaintRowHeader("model");
356         } else if (ev.isAnyOf(linkRowsToDatabase)) {
357             ResultSetTableModel m = getResultSetTableModel();
358             try {
359                 for (int rowIndex : getSelectedRows()) {
360                     m.linkRow(rowIndex);
361                 }
362             } finally {
363                 repaintRowHeader("unlinkedRowStatus");
364             }
365         } else if (ev.isAnyOf(deleteRows)) {
366             try {
367                 ResultSetTableModel m = getResultSetTableModel();
368                 while (true) {
369                     final int selectedRow = getSelectedRow();
370                     if (selectedRow < 0) {
371                         break;
372                     }
373                     if (m.isLinkedRow(selectedRow)) {
374                         final boolean removed = m.removeLinkedRow(selectedRow);
375                         assert removed;
376                     } else {
377                         m.removeRow(selectedRow);
378                     }
379                 }
380             } finally {
381                 repaintRowHeader("model");
382             }
383         } else if (ev.isAnyOf(adjustColumnWidth)) {
384             adjustColumnWidth();
385         } else if (ev.isAnyOf(widenColumnWidth)) {
386             changeTableColumnWidth(1.5f);
387         } else if (ev.isAnyOf(narrowColumnWidth)) {
388             changeTableColumnWidth(1 / 1.5f);
389         } else if (ev.isAnyOf(sort)) {
390             doSort(getTableHeader().columnAtPoint(mousePositionForColumnHeader));
391         } else if (ev.isAnyOf(jumpToColumn)) {
392             Object[] args = ev.getArgs();
393             if (args != null && args.length > 0) {
394                 jumpToColumn(String.valueOf(args[0]));
395             }
396         } else if (ev.isAnyOf(showColumnNumber)) {
397             setShowColumnNumber(!columnHeaderRenderer.fixesColumnNumber);
398             updateUI();
399         } else {
400             log.warn("not expected: Event=%s", ev);
401         }
402     }
403
404     @Override
405     public void editingStopped(ChangeEvent e) {
406         try {
407             super.editingStopped(e);
408         } catch (Exception ex) {
409             WindowOutputProcessor.showErrorDialog(getParent(), ex);
410         }
411     }
412
413     @Override
414     public boolean editCellAt(int row, int column, EventObject e) {
415         boolean succeeded = super.editCellAt(row, column, e);
416         if (succeeded) {
417             if (editorComp instanceof JTextField) {
418                 // make it selected when starting edit-mode
419                 if (lastKeyEvent != null && lastKeyEvent.getKeyCode() != VK_F2) {
420                     JTextField editor = (JTextField)editorComp;
421                     initializeEditorComponent(editor);
422                     editor.requestFocus();
423                     editor.selectAll();
424                 }
425             }
426         }
427         return succeeded;
428     }
429
430     @Override
431     public TableCellEditor getCellEditor() {
432         TableCellEditor editor = super.getCellEditor();
433         if (editor instanceof DefaultCellEditor) {
434             DefaultCellEditor d = (DefaultCellEditor)editor;
435             initializeEditorComponent(d.getComponent());
436         }
437         return editor;
438     }
439
440     private void initializeEditorComponent(Component c) {
441         final Color bgColor = Color.ORANGE;
442         if (c != null && c.getBackground() != bgColor) {
443             // determines initialized state by bgcolor
444             if (!c.isEnabled()) {
445                 c.setEnabled(true);
446             }
447             c.setFont(getFont());
448             c.setBackground(bgColor);
449             if (c instanceof JTextComponent) {
450                 final JTextComponent text = (JTextComponent)c;
451                 AnyAction aa = new AnyAction(text);
452                 aa.setUndoAction();
453                 c.addFocusListener(new FocusAdapter() {
454                     @Override
455                     public void focusLost(FocusEvent e) {
456                         editingCanceled(new ChangeEvent(e.getSource()));
457                     }
458                 });
459             }
460         }
461     }
462
463     @Override
464     public TableCellRenderer getCellRenderer(int row, int column) {
465         final Object v = getValueAt(row, column);
466         if (v == null) {
467             return nullRenderer;
468         }
469         return super.getCellRenderer(row, column);
470     }
471
472     @Override
473     public void updateUI() {
474         super.updateUI();
475         adjustRowHeight(this);
476     }
477
478     static void adjustRowHeight(JTable table) {
479         Component c = new JLabel("0");
480         final int height = c.getPreferredSize().height;
481         if (height > 0) {
482             table.setRowHeight(height);
483         }
484     }
485
486     @Override
487     protected void configureEnclosingScrollPane() {
488         super.configureEnclosingScrollPane();
489         Container p = getParent();
490         if (p instanceof JViewport) {
491             Container gp = p.getParent();
492             if (gp instanceof JScrollPane) {
493                 JScrollPane scrollPane = (JScrollPane)gp;
494                 JViewport viewport = scrollPane.getViewport();
495                 if (viewport == null || viewport.getView() != this) {
496                     return;
497                 }
498                 scrollPane.setRowHeaderView(rowHeader);
499             }
500         }
501     }
502
503     @Override
504     public void setModel(TableModel dataModel) {
505         dataModel.addTableModelListener(rowHeader);
506         super.setModel(dataModel);
507     }
508
509     /**
510      * Selects colomns.
511      * @param index0 index of start
512      * @param index1 index of end
513      */
514     void selectColumn(int index0, int index1) {
515         if (getRowCount() > 0) {
516             addColumnSelectionInterval(index0, index1);
517             addRowSelectionInterval(getRowCount() - 1, 0);
518         }
519     }
520
521     /**
522      * Jumps to specified column.
523      * @param index
524      * @return whether the column exists or not
525      */
526     boolean jumpToColumn(int index) {
527         final int columnCount = getColumnCount();
528         if (0 <= index && index < columnCount) {
529             if (getSelectedRowCount() == 0) {
530                 changeSelection(-1, index, false, false);
531             } else {
532                 int[] selectedRows = getSelectedRows();
533                 changeSelection(getSelectedRow(), index, false, false);
534                 for (final int selectedRow : selectedRows) {
535                     changeSelection(selectedRow, index, false, true);
536                 }
537             }
538             return true;
539         }
540         return false;
541     }
542
543     /**
544      * Jumps to specified column.
545      * @param name
546      * @return whether the column exists or not
547      */
548     boolean jumpToColumn(String name) {
549         TableColumnModel columnModel = getColumnModel();
550         for (int i = 0, n = columnModel.getColumnCount(); i < n; i++) {
551             if (name.equals(String.valueOf(columnModel.getColumn(i).getHeaderValue()))) {
552                 jumpToColumn(i);
553                 return true;
554             }
555         }
556         return false;
557     }
558
559     RowHeader getRowHeader() {
560         return rowHeader;
561     }
562
563     ResultSetTableModel getResultSetTableModel() {
564         return (ResultSetTableModel)getModel();
565     }
566
567     boolean isShowColumnNumber() {
568         return columnHeaderRenderer.fixesColumnNumber;
569     }
570
571     void setShowColumnNumber(boolean showColumnNumber) {
572         final boolean oldValue = this.columnHeaderRenderer.fixesColumnNumber;
573         this.columnHeaderRenderer.fixesColumnNumber = showColumnNumber;
574         firePropertyChange("showNumber", oldValue, showColumnNumber);
575     }
576
577     void repaintRowHeader(String propName) {
578         if (rowHeader != null) {
579             rowHeader.propertyChange(new PropertyChangeEvent(this, propName, null, null));
580         }
581     }
582
583     String getAutoAdjustMode() {
584         return autoAdjustMode;
585     }
586
587     void setAutoAdjustMode(String autoAdjustMode) {
588         final String oldValue = this.autoAdjustMode;
589         this.autoAdjustMode = autoAdjustMode;
590         firePropertyChange("autoAdjustMode", oldValue, autoAdjustMode);
591     }
592
593     private void bindJumpAction(String suffix, int key, boolean withSelect) {
594         final String actionKey = String.format("%s-to-%s", (withSelect) ? "select" : "jump", suffix);
595         final CellCursor c = new CellCursor(this, withSelect);
596         final int modifiers = Utilities.getMenuShortcutKeyMask()
597                               | (withSelect ? SHIFT_DOWN_MASK : 0);
598         KeyStroke[] keyStrokes = {getKeyStroke(key, modifiers)};
599         c.putValue(Action.ACTION_COMMAND_KEY, actionKey);
600         InputMap im = this.getInputMap();
601         for (KeyStroke ks : keyStrokes) {
602             im.put(ks, actionKey);
603         }
604         this.getActionMap().put(actionKey, c);
605     }
606
607     private static final class CellCursor extends AbstractAction {
608
609         private final JTable table;
610         private final boolean extend;
611
612         CellCursor(JTable table, boolean extend) {
613             this.table = table;
614             this.extend = extend;
615         }
616
617         int getColumnPosition() {
618             int[] a = table.getSelectedColumns();
619             if (a == null || a.length == 0) {
620                 return -1;
621             }
622             ListSelectionModel csm = table.getColumnModel().getSelectionModel();
623             return (a[0] == csm.getAnchorSelectionIndex()) ? a[a.length - 1] : a[0];
624         }
625
626         int getRowPosition() {
627             int[] a = table.getSelectedRows();
628             if (a == null || a.length == 0) {
629                 return -1;
630             }
631             ListSelectionModel rsm = table.getSelectionModel();
632             return (a[0] == rsm.getAnchorSelectionIndex()) ? a[a.length - 1] : a[0];
633         }
634
635         @Override
636         public void actionPerformed(ActionEvent e) {
637             final String cmd = e.getActionCommand();
638             final int ri;
639             final int ci;
640             if (cmd == null) {
641                 assert false : "command is null";
642                 return;
643             } else if (cmd.endsWith("-to-home")) {
644                 ri = 0;
645                 ci = 0;
646             } else if (cmd.endsWith("-to-end")) {
647                 ri = table.getRowCount() - 1;
648                 ci = table.getColumnCount() - 1;
649             } else if (cmd.endsWith("-to-top")) {
650                 ri = 0;
651                 ci = getColumnPosition();
652             } else if (cmd.endsWith("-to-bottom")) {
653                 ri = table.getRowCount() - 1;
654                 ci = getColumnPosition();
655             } else if (cmd.endsWith("-to-leftmost")) {
656                 ri = getRowPosition();
657                 ci = 0;
658             } else if (cmd.endsWith("-to-rightmost")) {
659                 ri = getRowPosition();
660                 ci = table.getColumnCount() - 1;
661             } else {
662                 assert false : "unknown command: " + cmd;
663                 return;
664             }
665             table.changeSelection(ri, ci, false, extend);
666         }
667         
668     }
669
670     private static final class NullValueRenderer extends DefaultTableCellRenderer {
671
672         NullValueRenderer() {
673         } // empty
674
675         @Override
676         public Component getTableCellRendererComponent(JTable table,
677                                                        Object value,
678                                                        boolean isSelected,
679                                                        boolean hasFocus,
680                                                        int row,
681                                                        int column) {
682             Component c = super.getTableCellRendererComponent(table,
683                                                               "NULL",
684                                                               isSelected,
685                                                               hasFocus,
686                                                               row,
687                                                               column);
688             c.setForeground(new Color(63, 63, 192, 192));
689             Font font = c.getFont();
690             c.setFont(font.deriveFont(font.getSize() * 0.8f));
691             return c;
692         }
693
694     }
695
696     private static final class ColumnHeaderCellRenderer implements TableCellRenderer {
697
698         TableCellRenderer renderer;
699         boolean fixesColumnNumber;
700
701         ColumnHeaderCellRenderer(TableCellRenderer renderer) {
702             this.renderer = renderer;
703             this.fixesColumnNumber = false;
704         }
705
706         @Override
707         public Component getTableCellRendererComponent(JTable table,
708                                                        Object value,
709                                                        boolean isSelected,
710                                                        boolean hasFocus,
711                                                        int row,
712                                                        int column) {
713             Object o = fixesColumnNumber ? String.format("%d %s", column + 1, value) : value;
714             return renderer.getTableCellRendererComponent(table,
715                                                           o,
716                                                           isSelected,
717                                                           hasFocus,
718                                                           row,
719                                                           column);
720         }
721
722     }
723
724     private static final class RowHeader extends JTable implements PropertyChangeListener {
725
726         private static final int DEFAULT_WIDTH = 40;
727
728         private DefaultTableModel model;
729         private TableCellRenderer renderer;
730
731         RowHeader(JTable table) {
732             this.model = new DefaultTableModel(0, 1) {
733                 @Override
734                 public boolean isCellEditable(int row, int column) {
735                     return false;
736                 }
737             };
738             final ResultSetTable t = (ResultSetTable)table;
739             this.renderer = new DefaultTableCellRenderer() {
740                 @Override
741                 public Component getTableCellRendererComponent(JTable table,
742                                                                Object value,
743                                                                boolean isSelected,
744                                                                boolean hasFocus,
745                                                                int row,
746                                                                int column) {
747                     assert t.getModel() instanceof ResultSetTableModel;
748                     final boolean rowLinked = t.getResultSetTableModel().isLinkedRow(row);
749                     JLabel label = new JLabel(String.format("%s ", rowLinked ? row + 1 : "+"));
750                     label.setHorizontalAlignment(RIGHT);
751                     label.setFont(table.getFont());
752                     label.setOpaque(true);
753                     return label;
754                 }
755             };
756             setModel(model);
757             setWidth(table);
758             setFocusable(false);
759             table.addPropertyChangeListener(this);
760         }
761
762         @Override
763         public void propertyChange(PropertyChangeEvent e) {
764             JTable table = (JTable)e.getSource();
765             if (table == null) {
766                 return;
767             }
768             String propertyName = e.getPropertyName();
769             if (propertyName.equals("enabled")) {
770                 boolean isEnabled = table.isEnabled();
771                 setVisible(isEnabled);
772                 if (isEnabled) {
773                     setWidth(table);
774                     resetViewPosition(table);
775                 }
776             } else if (propertyName.equals("font")) {
777                 setFont(table.getFont());
778             } else if (propertyName.equals("rowHeight")) {
779                 setRowHeight(table.getRowHeight());
780             } else if (propertyName.equals("model")) {
781                 model.setRowCount(table.getRowCount());
782             } else if (propertyName.equals("unlinkedRowStatus")) {
783                 repaint();
784             } else if (propertyName.equals("ancestor")) {
785                 // empty
786             } else {
787                 // empty
788             }
789             validate();
790         }
791
792         @Override
793         public void tableChanged(TableModelEvent e) {
794             Object src = e.getSource();
795             if (model != null && src != null) {
796                 model.setRowCount(((TableModel)src).getRowCount());
797             }
798             super.tableChanged(e);
799         }
800
801         @Override
802         public void updateUI() {
803             super.updateUI();
804             adjustRowHeight(this);
805         }
806
807         void setWidth(JTable table) {
808             // XXX unstable
809             final int rowCount = table.getRowCount();
810             model.setRowCount(rowCount);
811             JLabel label = new JLabel(String.valueOf(rowCount * 1000L));
812             Dimension d = getSize();
813             d.width = Math.max(label.getPreferredSize().width, DEFAULT_WIDTH);
814             setPreferredScrollableViewportSize(d);
815         }
816
817         private void resetViewPosition(JTable table) {
818             // forces to reset its view position to table's view position
819             Container p1 = table.getParent();
820             Container p2 = getParent();
821             if (p1 instanceof JViewport && p2 instanceof JViewport) {
822                 JViewport v1 = (JViewport)p1;
823                 JViewport v2 = (JViewport)p2;
824                 v2.setViewPosition(v1.getViewPosition());
825             }
826         }
827
828         @Override
829         public boolean isCellEditable(int row, int column) {
830             return false;
831         }
832
833         @Override
834         public TableCellRenderer getCellRenderer(int row, int column) {
835             return renderer;
836         }
837
838     }
839
840     // text search
841
842     @Override
843     public boolean search(Matcher matcher) {
844         final int rowCount = getRowCount();
845         if (rowCount <= 0) {
846             return false;
847         }
848         final int columnCount = getColumnCount();
849         final boolean backward = matcher.isBackward();
850         final int amount = backward ? -1 : 1;
851         final int rowStart = backward ? rowCount - 1 : 0;
852         final int rowEnd = backward ? 0 : rowCount - 1;
853         final int columnStart = backward ? columnCount - 1 : 0;
854         final int columnEnd = backward ? 0 : columnCount - 1;
855         int row = rowStart;
856         int column = columnStart;
857         if (getSelectedColumnCount() > 0) {
858             column = getSelectedColumn();
859             row = getSelectedRow() + amount;
860             if (backward) {
861                 if (row < 0) {
862                     --column;
863                     if (column < 0) {
864                         return false;
865                     }
866                     row = rowStart;
867                 }
868             } else {
869                 if (row >= rowCount) {
870                     ++column;
871                     if (column >= columnCount) {
872                         return false;
873                     }
874                     row = rowStart;
875                 }
876             }
877         }
878         final TableModel m = getModel();
879         for (; backward ? column >= columnEnd : column <= columnEnd; column += amount) {
880             for (; backward ? row >= rowEnd : row <= rowEnd; row += amount) {
881                 if (matcher.find(String.valueOf(m.getValueAt(row, column)))) {
882                     changeSelection(row, column, false, false);
883                     return true;
884                 }
885             }
886             row = rowStart;
887         }
888         return false;
889     }
890
891     @Override
892     public void reset() {
893         ((DefaultTableModel)getModel()).setRowCount(0);
894         lastSortedIndex = -1;
895         lastSortedIsReverse = false;
896     }
897
898     static final class TableHeaderTextSearch implements TextSearch {
899         private ResultSetTable rstable;
900         private JTableHeader tableHeader;
901         TableHeaderTextSearch(ResultSetTable rstable, JTableHeader tableHeader) {
902             this.rstable = rstable;
903             this.tableHeader = tableHeader;
904         }
905         @Override
906         public boolean search(Matcher matcher) {
907             TableColumnModel m = tableHeader.getColumnModel();
908             int columnCount = m.getColumnCount();
909             if (columnCount < 1) {
910                 return false;
911             }
912             final boolean backward = matcher.isBackward();
913             final int amount = backward ? -1 : 1;
914             final int columnStart = backward ? columnCount - 1 : 0;
915             final int columnEnd = backward ? 0 : columnCount - 1;
916             int column = columnStart;
917             if (rstable.getSelectedColumnCount() > 0) {
918                 column = rstable.getSelectedColumn() + amount;
919             }
920             for (; backward ? column >= columnEnd : column <= columnEnd; column += amount) {
921                 if (matcher.find(String.valueOf(m.getColumn(column).getHeaderValue()))) {
922                     rstable.jumpToColumn(column);
923                     return true;
924                 }
925             }
926             return false;
927         }
928         @Override
929         public void reset() {
930             // empty
931         }
932     }
933
934     // event-handlers
935
936     private void setValueAtSelectedCells(Object value) {
937         int[] selectedColumns = getSelectedColumns();
938         for (int rowIndex : getSelectedRows()) {
939             for (int colIndex : selectedColumns) {
940                 setValueAt(value, rowIndex, colIndex);
941             }
942         }
943     }
944
945     private void adjustColumnWidth() {
946         final int rowCount = getRowCount();
947         final boolean byHeader;
948         final boolean byValue;
949         switch (AnyActionKey.of(autoAdjustMode)) {
950             case autoAdjustModeNone:
951                 byHeader = false;
952                 byValue = false;
953                 break;
954             case autoAdjustModeHeader:
955                 byHeader = true;
956                 byValue = false;
957                 break;
958             case autoAdjustModeValue:
959                 byHeader = false;
960                 byValue = true;
961                 break;
962             case autoAdjustModeHeaderAndValue:
963                 byHeader = true;
964                 byValue = true;
965                 break;
966             default:
967                 log.warn("autoAdjustMode=%s", autoAdjustMode);
968                 return;
969         }
970         if (!byHeader && rowCount == 0) {
971             return;
972         }
973         final float max = getParent().getWidth() * 0.8f;
974         TableColumnModel columnModel = getColumnModel();
975         JTableHeader header = getTableHeader();
976         for (int columnIndex = 0, n = getColumnCount(); columnIndex < n; columnIndex++) {
977             float size = 0f;
978             if (byHeader) {
979                 TableColumn column = columnModel.getColumn(columnIndex);
980                 TableCellRenderer renderer = column.getHeaderRenderer();
981                 if (renderer == null) {
982                     renderer = header.getDefaultRenderer();
983                 }
984                 if (renderer != null) {
985                     Component c = renderer.getTableCellRendererComponent(this,
986                                                                          column.getHeaderValue(),
987                                                                          false,
988                                                                          false,
989                                                                          0,
990                                                                          columnIndex);
991                     size = c.getPreferredSize().width * 1.5f;
992                 }
993             }
994             if (byValue) {
995                 for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
996                     TableCellRenderer renderer = getCellRenderer(rowIndex,
997                                                                                 columnIndex);
998                     if (renderer == null) {
999                         continue;
1000                     }
1001                     Object value = getValueAt(rowIndex, columnIndex);
1002                     Component c = renderer.getTableCellRendererComponent(this,
1003                                                                          value,
1004                                                                          false,
1005                                                                          false,
1006                                                                          rowIndex,
1007                                                                          columnIndex);
1008                     size = Math.max(size, c.getPreferredSize().width);
1009                     if (size >= max) {
1010                         break;
1011                     }
1012                 }
1013             }
1014             int width = Math.round(size > max ? max : size) + 1;
1015             columnModel.getColumn(columnIndex).setPreferredWidth(width);
1016         }
1017     }
1018
1019     void doSort(int columnIndex) {
1020         if (getColumnCount() == 0) {
1021             return;
1022         }
1023         final boolean reverse;
1024         if (lastSortedIndex == columnIndex) {
1025             reverse = (lastSortedIsReverse == false);
1026         } else {
1027             lastSortedIndex = columnIndex;
1028             reverse = false;
1029         }
1030         lastSortedIsReverse = reverse;
1031         getResultSetTableModel().sort(columnIndex, reverse);
1032         repaint();
1033         repaintRowHeader("unlinkedRowStatus");
1034     }
1035
1036     void changeTableColumnWidth(double rate) {
1037         for (TableColumn column : Collections.list(getColumnModel().getColumns())) {
1038             column.setPreferredWidth((int)(column.getWidth() * rate));
1039         }
1040     }
1041
1042
1043 }