View Javadoc
1   /*******************************************************************************
2    * Copyhacked (H) 2012-2025.
3    * This program and the accompanying materials
4    * are made available under no term at all, use it like
5    * you want, but share and discuss it
6    * every time possible with every body.
7    * 
8    * Contributors:
9    *      ron190 at ymail dot com - initial implementation
10   ******************************************************************************/
11  package com.jsql.view.swing.table;
12  
13  import com.jsql.util.LogLevelUtil;
14  import com.jsql.view.swing.popupmenu.JPopupMenuTable;
15  import com.jsql.view.swing.text.JTextFieldPlaceholder;
16  import com.jsql.view.swing.util.UiStringUtil;
17  import org.apache.commons.lang3.StringUtils;
18  import org.apache.logging.log4j.LogManager;
19  import org.apache.logging.log4j.Logger;
20  
21  import javax.swing.*;
22  import javax.swing.event.DocumentEvent;
23  import javax.swing.event.DocumentListener;
24  import javax.swing.table.DefaultTableCellRenderer;
25  import javax.swing.table.TableCellRenderer;
26  import javax.swing.table.TableModel;
27  import javax.swing.table.TableRowSorter;
28  import java.awt.*;
29  import java.awt.event.*;
30  import java.util.Comparator;
31  import java.util.HashSet;
32  import java.util.Set;
33  import java.util.regex.Pattern;
34  
35  /**
36   * Display a table for database values. Add keyboard shortcut, mouse icon, text
37   * and header formatting.
38   */
39  public class PanelTable extends JPanel {
40      
41      /**
42       * Log4j logger sent to view.
43       */
44      private static final Logger LOGGER = LogManager.getRootLogger();
45      
46      /**
47       * Table to display in the panel.
48       */
49      private final JTable tableValues;
50  
51      /**
52       * Create a panel containing a table to display injection values.
53       * 
54       * @param data Array 2D with injection table data
55       * @param columnNames Names of columns from database
56       */
57      public PanelTable(String[][] data, String[] columnNames) {
58          super(new BorderLayout());
59  
60          this.tableValues = new JTable(data, columnNames) {
61              @Override
62              public boolean isCellEditable(int row, int column) {
63                  return false;
64              }
65          };
66          this.tableValues.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
67          this.tableValues.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
68          this.tableValues.setColumnSelectionAllowed(true);
69          this.tableValues.setRowHeight(20);
70          this.tableValues.setRowSelectionAllowed(true);
71          this.tableValues.setCellSelectionEnabled(true);
72  
73          this.initRenderer();
74  
75          this.tableValues.getTableHeader().setReorderingAllowed(false);
76  
77          this.initMouseEvent();
78          this.initTabShortcut();
79  
80          var columnAdjuster = new AdjusterTableColumn(this.tableValues);
81          columnAdjuster.adjustColumns();
82  
83          final TableRowSorter<TableModel> rowSorter = new TableRowSorter<>(this.tableValues.getModel());
84          this.tableValues.setRowSorter(rowSorter);
85          this.initTableScroller();
86          this.initPanelSearch(rowSorter);
87  
88          Comparator<Object> comparatorNumeric = new ComparatorColumn<>();
89          for (var i = 0 ; i < this.tableValues.getColumnCount() ; i++) {
90              rowSorter.setComparator(i, comparatorNumeric);
91          }
92      }
93  
94      private void initMouseEvent() {
95          this.tableValues.setDragEnabled(true);
96          this.tableValues.addMouseListener(new MouseAdapter() {
97              @Override
98              public void mousePressed(MouseEvent e) {
99                  PanelTable.this.tableValues.requestFocusInWindow();
100                 if (SwingUtilities.isRightMouseButton(e)) {
101                     // Keep selection when multiple cells are selected,
102                     // move focus only
103                     var p = e.getPoint();
104                     var rowNumber = PanelTable.this.tableValues.rowAtPoint(p);
105                     var colNumber = PanelTable.this.tableValues.columnAtPoint(p);
106                     DefaultListSelectionModel modelRow = (DefaultListSelectionModel) PanelTable.this.tableValues.getSelectionModel();
107                     DefaultListSelectionModel modelColumn = (DefaultListSelectionModel) PanelTable.this.tableValues.getColumnModel().getSelectionModel();
108                     modelRow.moveLeadSelectionIndex(rowNumber);
109                     modelColumn.moveLeadSelectionIndex(colNumber);
110                 }
111             }
112         });
113     }
114 
115     private void initRenderer() {
116         final TableCellRenderer cellRendererHeader = this.tableValues.getTableHeader().getDefaultRenderer();
117         this.tableValues.getTableHeader().setDefaultRenderer(
118             (JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) -> cellRendererHeader.getTableCellRendererComponent(
119                 table,
120                 UiStringUtil.detectUtf8HtmlNoWrap(StringUtils.SPACE + value + StringUtils.SPACE),
121                 isSelected,
122                 hasFocus,
123                 row,
124                 column
125             )
126         );
127 
128         final var cellRendererDefault = new DefaultTableCellRenderer();
129         this.tableValues.setDefaultRenderer(
130             this.tableValues.getColumnClass(2),
131             (JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) -> {
132                 // Prepare cell value to be utf8 inspected
133                 String cellValue = value != null ? value.toString() : StringUtils.EMPTY;
134                 // Fix #90481: NullPointerException on getTableCellRendererComponent()
135                 try {
136                     return cellRendererDefault.getTableCellRendererComponent(
137                         table, UiStringUtil.detectUtf8HtmlNoWrap(cellValue), isSelected, hasFocus, row, column
138                     );
139                 } catch (NullPointerException e) {
140                     LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
141                     return null;
142                 }
143             }
144         );
145     }
146 
147     private void initTableScroller() {
148         var scroller = new JScrollPane(this.tableValues);
149         var tableFixedColumn = new FixedColumnTable();
150         tableFixedColumn.fixColumnSize(2, scroller);
151         this.add(scroller, BorderLayout.CENTER);
152     }
153 
154     private void initPanelSearch(final TableRowSorter<TableModel> rowSorter) {
155         final var panelSearch = new JPanel(new BorderLayout());
156 
157         final JTextField textFilter = new JTextFieldPlaceholder("Find in table");
158         panelSearch.add(textFilter, BorderLayout.CENTER);
159 
160         Action actionShowSearchTable = new ActionShowSearch(panelSearch, textFilter);
161         String keySearch = "search";
162         this.tableValues.getActionMap().put(keySearch, actionShowSearchTable);
163         this.tableValues.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK), keySearch);
164 
165         Action actionCloseSearch = new ActionCloseSearch(textFilter, panelSearch, this);
166         String keyClose = "close";
167         textFilter.getActionMap().put(keyClose, actionCloseSearch);
168         textFilter.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), keyClose);
169 
170         // Fix #43974: PatternSyntaxException on regexFilter() => Pattern.quote()
171         textFilter.getDocument().addDocumentListener(new DocumentListener() {
172             private void insertUpdateFixed() {
173                 String text = textFilter.getText();
174                 if (text.trim().isEmpty()) {
175                     rowSorter.setRowFilter(null);
176                 } else {
177                     rowSorter.setRowFilter(RowFilter.regexFilter("(?i)" + Pattern.quote(text)));
178                 }
179             }
180             @Override
181             public void insertUpdate(DocumentEvent e) {
182                 this.insertUpdateFixed();
183             }
184             @Override
185             public void removeUpdate(DocumentEvent e) {
186                 this.insertUpdateFixed();
187             }
188             @Override
189             public void changedUpdate(DocumentEvent e) {
190                 throw new UnsupportedOperationException("Not supported yet.");
191             }
192         });
193 
194         this.tableValues.setComponentPopupMenu(new JPopupMenuTable(this.tableValues, actionShowSearchTable));
195         
196         JButton buttonCloseSearch = new ButtonClose();
197         buttonCloseSearch.addActionListener(actionCloseSearch);
198         panelSearch.add(buttonCloseSearch, BorderLayout.EAST);
199         panelSearch.setVisible(false);
200         this.add(panelSearch, BorderLayout.SOUTH);
201     }
202 
203     private void initTabShortcut() {
204         this.tableValues.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
205             KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0),
206             null
207         );
208         this.tableValues.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
209             KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_DOWN_MASK),
210             null
211         );
212 
213         Set<AWTKeyStroke> forward = new HashSet<>(
214             this.tableValues.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS)
215         );
216         forward.add(KeyStroke.getKeyStroke("TAB"));
217         this.tableValues.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forward);
218         
219         Set<AWTKeyStroke> backward = new HashSet<>(
220             this.tableValues.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS)
221         );
222         backward.add(KeyStroke.getKeyStroke("shift TAB"));
223         this.tableValues.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, backward);
224     }
225 
226     /**
227      * Select every cell.
228      */
229     public void selectTable() {
230         this.tableValues.selectAll();
231     }
232 
233     /**
234      * Perform copy event on current table.
235      */
236     public void copyTable() {
237         var actionEvent = new ActionEvent(this.tableValues, ActionEvent.ACTION_PERFORMED, "copy");
238         this.tableValues.getActionMap().get(actionEvent.getActionCommand()).actionPerformed(actionEvent);
239     }
240 
241     
242     // Getter and setter
243     
244     public JTable getTableValues() {
245         return this.tableValues;
246     }
247 }