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 |
1
1. isCellEditable : replaced boolean return with true for com/jsql/view/swing/table/PanelTable$1::isCellEditable → NO_COVERAGE |
return false; |
64 | } | |
65 | }; | |
66 |
1
1. <init> : removed call to javax/swing/JTable::setAutoResizeMode → NO_COVERAGE |
this.tableValues.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); |
67 |
1
1. <init> : removed call to javax/swing/JTable::setSelectionMode → NO_COVERAGE |
this.tableValues.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); |
68 |
1
1. <init> : removed call to javax/swing/JTable::setColumnSelectionAllowed → NO_COVERAGE |
this.tableValues.setColumnSelectionAllowed(true); |
69 |
1
1. <init> : removed call to javax/swing/JTable::setRowHeight → NO_COVERAGE |
this.tableValues.setRowHeight(20); |
70 |
1
1. <init> : removed call to javax/swing/JTable::setRowSelectionAllowed → NO_COVERAGE |
this.tableValues.setRowSelectionAllowed(true); |
71 |
1
1. <init> : removed call to javax/swing/JTable::setCellSelectionEnabled → NO_COVERAGE |
this.tableValues.setCellSelectionEnabled(true); |
72 | ||
73 |
1
1. <init> : removed call to com/jsql/view/swing/table/PanelTable::initRenderer → NO_COVERAGE |
this.initRenderer(); |
74 | ||
75 |
1
1. <init> : removed call to javax/swing/table/JTableHeader::setReorderingAllowed → NO_COVERAGE |
this.tableValues.getTableHeader().setReorderingAllowed(false); |
76 | ||
77 |
1
1. <init> : removed call to com/jsql/view/swing/table/PanelTable::initMouseEvent → NO_COVERAGE |
this.initMouseEvent(); |
78 |
1
1. <init> : removed call to com/jsql/view/swing/table/PanelTable::initTabShortcut → NO_COVERAGE |
this.initTabShortcut(); |
79 | ||
80 | var columnAdjuster = new AdjusterTableColumn(this.tableValues); | |
81 |
1
1. <init> : removed call to com/jsql/view/swing/table/AdjusterTableColumn::adjustColumns → NO_COVERAGE |
columnAdjuster.adjustColumns(); |
82 | ||
83 | final TableRowSorter<TableModel> rowSorter = new TableRowSorter<>(this.tableValues.getModel()); | |
84 |
1
1. <init> : removed call to javax/swing/JTable::setRowSorter → NO_COVERAGE |
this.tableValues.setRowSorter(rowSorter); |
85 |
1
1. <init> : removed call to com/jsql/view/swing/table/PanelTable::initTableScroller → NO_COVERAGE |
this.initTableScroller(); |
86 |
1
1. <init> : removed call to com/jsql/view/swing/table/PanelTable::initPanelSearch → NO_COVERAGE |
this.initPanelSearch(rowSorter); |
87 | ||
88 | Comparator<Object> comparatorNumeric = new ComparatorColumn<>(); | |
89 |
2
1. <init> : negated conditional → NO_COVERAGE 2. <init> : changed conditional boundary → NO_COVERAGE |
for (var i = 0 ; i < this.tableValues.getColumnCount() ; i++) { |
90 |
1
1. <init> : removed call to javax/swing/table/TableRowSorter::setComparator → NO_COVERAGE |
rowSorter.setComparator(i, comparatorNumeric); |
91 | } | |
92 | } | |
93 | ||
94 | private void initMouseEvent() { | |
95 |
1
1. initMouseEvent : removed call to javax/swing/JTable::setDragEnabled → NO_COVERAGE |
this.tableValues.setDragEnabled(true); |
96 |
1
1. initMouseEvent : removed call to javax/swing/JTable::addMouseListener → NO_COVERAGE |
this.tableValues.addMouseListener(new MouseAdapter() { |
97 | @Override | |
98 | public void mousePressed(MouseEvent e) { | |
99 | PanelTable.this.tableValues.requestFocusInWindow(); | |
100 |
1
1. mousePressed : negated conditional → NO_COVERAGE |
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 |
1
1. mousePressed : removed call to javax/swing/DefaultListSelectionModel::moveLeadSelectionIndex → NO_COVERAGE |
modelRow.moveLeadSelectionIndex(rowNumber); |
109 |
1
1. mousePressed : removed call to javax/swing/DefaultListSelectionModel::moveLeadSelectionIndex → NO_COVERAGE |
modelColumn.moveLeadSelectionIndex(colNumber); |
110 | } | |
111 | } | |
112 | }); | |
113 | } | |
114 | ||
115 | private void initRenderer() { | |
116 | final TableCellRenderer cellRendererHeader = this.tableValues.getTableHeader().getDefaultRenderer(); | |
117 |
1
1. initRenderer : removed call to javax/swing/table/JTableHeader::setDefaultRenderer → NO_COVERAGE |
this.tableValues.getTableHeader().setDefaultRenderer( |
118 |
1
1. lambda$initRenderer$0 : replaced return value with null for com/jsql/view/swing/table/PanelTable::lambda$initRenderer$0 → NO_COVERAGE |
(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 |
1
1. initRenderer : removed call to javax/swing/JTable::setDefaultRenderer → NO_COVERAGE |
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 |
1
1. lambda$initRenderer$1 : negated conditional → NO_COVERAGE |
String cellValue = value != null ? value.toString() : StringUtils.EMPTY; |
134 | // Fix #90481: NullPointerException on getTableCellRendererComponent() | |
135 | // Fix #96072: IllegalArgumentException on getTableCellRendererComponent() | |
136 | try { | |
137 |
1
1. lambda$initRenderer$1 : replaced return value with null for com/jsql/view/swing/table/PanelTable::lambda$initRenderer$1 → NO_COVERAGE |
return cellRendererDefault.getTableCellRendererComponent( |
138 | table, UiStringUtil.detectUtf8HtmlNoWrap(cellValue), isSelected, hasFocus, row, column | |
139 | ); | |
140 | } catch (IllegalArgumentException | NullPointerException e) { | |
141 | LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e); | |
142 | return null; | |
143 | } | |
144 | } | |
145 | ); | |
146 | } | |
147 | ||
148 | private void initTableScroller() { | |
149 | var scroller = new JScrollPane(this.tableValues); | |
150 | var tableFixedColumn = new FixedColumnTable(); | |
151 |
1
1. initTableScroller : removed call to com/jsql/view/swing/table/FixedColumnTable::fixColumnSize → NO_COVERAGE |
tableFixedColumn.fixColumnSize(2, scroller); |
152 |
1
1. initTableScroller : removed call to com/jsql/view/swing/table/PanelTable::add → NO_COVERAGE |
this.add(scroller, BorderLayout.CENTER); |
153 | } | |
154 | ||
155 | private void initPanelSearch(final TableRowSorter<TableModel> rowSorter) { | |
156 | final var panelSearch = new JPanel(new BorderLayout()); | |
157 | ||
158 | final JTextField textFilter = new JTextFieldPlaceholder("Find in table"); | |
159 |
1
1. initPanelSearch : removed call to javax/swing/JPanel::add → NO_COVERAGE |
panelSearch.add(textFilter, BorderLayout.CENTER); |
160 | ||
161 | Action actionShowSearchTable = new ActionShowSearch(panelSearch, textFilter); | |
162 | String keySearch = "search"; | |
163 |
1
1. initPanelSearch : removed call to javax/swing/ActionMap::put → NO_COVERAGE |
this.tableValues.getActionMap().put(keySearch, actionShowSearchTable); |
164 |
1
1. initPanelSearch : removed call to javax/swing/InputMap::put → NO_COVERAGE |
this.tableValues.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK), keySearch); |
165 | ||
166 | Action actionCloseSearch = new ActionCloseSearch(textFilter, panelSearch, this); | |
167 | String keyClose = "close"; | |
168 |
1
1. initPanelSearch : removed call to javax/swing/ActionMap::put → NO_COVERAGE |
textFilter.getActionMap().put(keyClose, actionCloseSearch); |
169 |
1
1. initPanelSearch : removed call to javax/swing/InputMap::put → NO_COVERAGE |
textFilter.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), keyClose); |
170 | ||
171 | // Fix #43974: PatternSyntaxException on regexFilter() => Pattern.quote() | |
172 |
1
1. initPanelSearch : removed call to javax/swing/text/Document::addDocumentListener → NO_COVERAGE |
textFilter.getDocument().addDocumentListener(new DocumentListener() { |
173 | private void insertUpdateFixed() { | |
174 | String text = textFilter.getText(); | |
175 |
1
1. insertUpdateFixed : negated conditional → NO_COVERAGE |
if (text.trim().isEmpty()) { |
176 |
1
1. insertUpdateFixed : removed call to javax/swing/table/TableRowSorter::setRowFilter → NO_COVERAGE |
rowSorter.setRowFilter(null); |
177 | } else { | |
178 |
1
1. insertUpdateFixed : removed call to javax/swing/table/TableRowSorter::setRowFilter → NO_COVERAGE |
rowSorter.setRowFilter(RowFilter.regexFilter("(?i)" + Pattern.quote(text))); |
179 | } | |
180 | } | |
181 | @Override | |
182 | public void insertUpdate(DocumentEvent e) { | |
183 |
1
1. insertUpdate : removed call to com/jsql/view/swing/table/PanelTable$3::insertUpdateFixed → NO_COVERAGE |
this.insertUpdateFixed(); |
184 | } | |
185 | @Override | |
186 | public void removeUpdate(DocumentEvent e) { | |
187 |
1
1. removeUpdate : removed call to com/jsql/view/swing/table/PanelTable$3::insertUpdateFixed → NO_COVERAGE |
this.insertUpdateFixed(); |
188 | } | |
189 | @Override | |
190 | public void changedUpdate(DocumentEvent e) { | |
191 | throw new UnsupportedOperationException("Not supported yet."); | |
192 | } | |
193 | }); | |
194 | ||
195 |
1
1. initPanelSearch : removed call to javax/swing/JTable::setComponentPopupMenu → NO_COVERAGE |
this.tableValues.setComponentPopupMenu(new JPopupMenuTable(this.tableValues, actionShowSearchTable)); |
196 | | |
197 | JButton buttonCloseSearch = new ButtonClose(); | |
198 |
1
1. initPanelSearch : removed call to javax/swing/JButton::addActionListener → NO_COVERAGE |
buttonCloseSearch.addActionListener(actionCloseSearch); |
199 |
1
1. initPanelSearch : removed call to javax/swing/JPanel::add → NO_COVERAGE |
panelSearch.add(buttonCloseSearch, BorderLayout.EAST); |
200 |
1
1. initPanelSearch : removed call to javax/swing/JPanel::setVisible → NO_COVERAGE |
panelSearch.setVisible(false); |
201 |
1
1. initPanelSearch : removed call to com/jsql/view/swing/table/PanelTable::add → NO_COVERAGE |
this.add(panelSearch, BorderLayout.SOUTH); |
202 | } | |
203 | ||
204 | private void initTabShortcut() { | |
205 |
1
1. initTabShortcut : removed call to javax/swing/InputMap::put → NO_COVERAGE |
this.tableValues.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put( |
206 | KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), | |
207 | null | |
208 | ); | |
209 |
1
1. initTabShortcut : removed call to javax/swing/InputMap::put → NO_COVERAGE |
this.tableValues.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put( |
210 | KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_DOWN_MASK), | |
211 | null | |
212 | ); | |
213 | ||
214 | Set<AWTKeyStroke> forward = new HashSet<>( | |
215 | this.tableValues.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS) | |
216 | ); | |
217 | forward.add(KeyStroke.getKeyStroke("TAB")); | |
218 |
1
1. initTabShortcut : removed call to javax/swing/JTable::setFocusTraversalKeys → NO_COVERAGE |
this.tableValues.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forward); |
219 | | |
220 | Set<AWTKeyStroke> backward = new HashSet<>( | |
221 | this.tableValues.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS) | |
222 | ); | |
223 | backward.add(KeyStroke.getKeyStroke("shift TAB")); | |
224 |
1
1. initTabShortcut : removed call to javax/swing/JTable::setFocusTraversalKeys → NO_COVERAGE |
this.tableValues.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, backward); |
225 | } | |
226 | ||
227 | /** | |
228 | * Select every cell. | |
229 | */ | |
230 | public void selectTable() { | |
231 |
1
1. selectTable : removed call to javax/swing/JTable::selectAll → NO_COVERAGE |
this.tableValues.selectAll(); |
232 | } | |
233 | ||
234 | /** | |
235 | * Perform copy event on current table. | |
236 | */ | |
237 | public void copyTable() { | |
238 | var actionEvent = new ActionEvent(this.tableValues, ActionEvent.ACTION_PERFORMED, "copy"); | |
239 |
1
1. copyTable : removed call to javax/swing/Action::actionPerformed → NO_COVERAGE |
this.tableValues.getActionMap().get(actionEvent.getActionCommand()).actionPerformed(actionEvent); |
240 | } | |
241 | ||
242 | | |
243 | // Getter and setter | |
244 | | |
245 | public JTable getTableValues() { | |
246 |
1
1. getTableValues : replaced return value with null for com/jsql/view/swing/table/PanelTable::getTableValues → NO_COVERAGE |
return this.tableValues; |
247 | } | |
248 | } | |
Mutations | ||
63 |
1.1 |
|
66 |
1.1 |
|
67 |
1.1 |
|
68 |
1.1 |
|
69 |
1.1 |
|
70 |
1.1 |
|
71 |
1.1 |
|
73 |
1.1 |
|
75 |
1.1 |
|
77 |
1.1 |
|
78 |
1.1 |
|
81 |
1.1 |
|
84 |
1.1 |
|
85 |
1.1 |
|
86 |
1.1 |
|
89 |
1.1 2.2 |
|
90 |
1.1 |
|
95 |
1.1 |
|
96 |
1.1 |
|
100 |
1.1 |
|
108 |
1.1 |
|
109 |
1.1 |
|
117 |
1.1 |
|
118 |
1.1 |
|
129 |
1.1 |
|
133 |
1.1 |
|
137 |
1.1 |
|
151 |
1.1 |
|
152 |
1.1 |
|
159 |
1.1 |
|
163 |
1.1 |
|
164 |
1.1 |
|
168 |
1.1 |
|
169 |
1.1 |
|
172 |
1.1 |
|
175 |
1.1 |
|
176 |
1.1 |
|
178 |
1.1 |
|
183 |
1.1 |
|
187 |
1.1 |
|
195 |
1.1 |
|
198 |
1.1 |
|
199 |
1.1 |
|
200 |
1.1 |
|
201 |
1.1 |
|
205 |
1.1 |
|
209 |
1.1 |
|
218 |
1.1 |
|
224 |
1.1 |
|
231 |
1.1 |
|
239 |
1.1 |
|
246 |
1.1 |