1 | /******************************************************************************* | |
2 | * Copyhacked (H) 2012-2020. | |
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 about 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.list; | |
12 | ||
13 | import com.jsql.util.I18nUtil; | |
14 | import com.jsql.util.LogLevelUtil; | |
15 | import com.jsql.view.swing.util.UiUtil; | |
16 | import org.apache.commons.io.FilenameUtils; | |
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 java.awt.event.*; | |
23 | import java.io.BufferedReader; | |
24 | import java.io.File; | |
25 | import java.io.FileReader; | |
26 | import java.io.IOException; | |
27 | import java.nio.charset.StandardCharsets; | |
28 | import java.util.ArrayList; | |
29 | import java.util.List; | |
30 | ||
31 | /** | |
32 | * A list supporting drag and drop. | |
33 | */ | |
34 | public class DnDList extends JList<ItemList> { | |
35 | | |
36 | /** | |
37 | * Log4j logger sent to view. | |
38 | */ | |
39 | private static final Logger LOGGER = LogManager.getRootLogger(); | |
40 | | |
41 | /** | |
42 | * Model for the JList. | |
43 | */ | |
44 | protected final DefaultListModel<ItemList> listModel; | |
45 | | |
46 | /** | |
47 | * List of default items. | |
48 | */ | |
49 | private final transient List<ItemList> defaultList; | |
50 | | |
51 | /** | |
52 | * Create a JList decorated with drag/drop features. | |
53 | * @param newList List to decorate | |
54 | */ | |
55 | public DnDList(List<ItemList> newList) { | |
56 | | |
57 | this.defaultList = newList; | |
58 | ||
59 | this.listModel = new DefaultListModel<>(); | |
60 | ||
61 | for (ItemList path: newList) { | |
62 |
1
1. <init> : removed call to javax/swing/DefaultListModel::addElement → NO_COVERAGE |
this.listModel.addElement(path); |
63 | } | |
64 | ||
65 |
1
1. <init> : removed call to com/jsql/view/swing/list/DnDList::setModel → NO_COVERAGE |
this.setModel(this.listModel); |
66 | ||
67 | ListCellRenderer<ItemList> renderer = new RendererComplexCell(); | |
68 |
1
1. <init> : removed call to com/jsql/view/swing/list/DnDList::setCellRenderer → NO_COVERAGE |
this.setCellRenderer(renderer); |
69 | | |
70 |
1
1. <init> : removed call to com/jsql/view/swing/list/DnDList::initializeActionMap → NO_COVERAGE |
this.initializeActionMap(); |
71 |
1
1. <init> : removed call to com/jsql/view/swing/list/DnDList::initializeListener → NO_COVERAGE |
this.initializeListener(); |
72 | | |
73 |
1
1. <init> : removed call to com/jsql/view/swing/list/DnDList::setDragEnabled → NO_COVERAGE |
this.setDragEnabled(true); |
74 |
1
1. <init> : removed call to com/jsql/view/swing/list/DnDList::setDropMode → NO_COVERAGE |
this.setDropMode(DropMode.INSERT); |
75 | ||
76 | // Set Drag and Drop | |
77 |
1
1. <init> : removed call to com/jsql/view/swing/list/DnDList::setTransferHandler → NO_COVERAGE |
this.setTransferHandler(new ListTransfertHandler()); |
78 | } | |
79 | ||
80 | private void initializeListener() { | |
81 | | |
82 |
1
1. initializeListener : removed call to com/jsql/view/swing/list/DnDList::addMouseListener → NO_COVERAGE |
this.addMouseListener(new MouseAdapterMenuAction(this)); |
83 | ||
84 | // Allows color change when list loses/gains focus | |
85 |
1
1. initializeListener : removed call to com/jsql/view/swing/list/DnDList::addFocusListener → NO_COVERAGE |
this.addFocusListener(new FocusListener() { |
86 | | |
87 | @Override | |
88 | public void focusLost(FocusEvent arg0) { | |
89 |
1
1. focusLost : removed call to com/jsql/view/swing/list/DnDList::repaint → NO_COVERAGE |
DnDList.this.repaint(); |
90 | } | |
91 | | |
92 | @Override | |
93 | public void focusGained(FocusEvent arg0) { | |
94 |
1
1. focusGained : removed call to com/jsql/view/swing/list/DnDList::repaint → NO_COVERAGE |
DnDList.this.repaint(); |
95 | } | |
96 | }); | |
97 | | |
98 | // Allows deleting values | |
99 |
1
1. initializeListener : removed call to com/jsql/view/swing/list/DnDList::addKeyListener → NO_COVERAGE |
this.addKeyListener(new KeyAdapter() { |
100 | @Override | |
101 | public void keyPressed(KeyEvent arg0) { | |
102 |
1
1. keyPressed : negated conditional → NO_COVERAGE |
if (arg0.getKeyCode() == KeyEvent.VK_DELETE) { |
103 |
1
1. keyPressed : removed call to com/jsql/view/swing/list/DnDList::removeSelectedItem → NO_COVERAGE |
DnDList.this.removeSelectedItem(); |
104 | } | |
105 | } | |
106 | }); | |
107 | } | |
108 | ||
109 | private void initializeActionMap() { | |
110 | | |
111 | // Transform Cut, selects next value | |
112 | var listActionMap = this.getActionMap(); | |
113 | | |
114 |
1
1. initializeActionMap : removed call to javax/swing/ActionMap::put → NO_COVERAGE |
listActionMap.put(TransferHandler.getCutAction().getValue(Action.NAME), new AbstractAction() { |
115 | @Override | |
116 | public void actionPerformed(ActionEvent e) { | |
117 | | |
118 |
1
1. actionPerformed : negated conditional → NO_COVERAGE |
if (DnDList.this.getSelectedValuesList().isEmpty()) { |
119 | return; | |
120 | } | |
121 | | |
122 | List<ItemList> selectedValues = DnDList.this.getSelectedValuesList(); | |
123 | List<ItemList> siblings = new ArrayList<>(); | |
124 | | |
125 | for (ItemList value: selectedValues) { | |
126 | | |
127 | int valueIndex = DnDList.this.listModel.indexOf(value); | |
128 | ||
129 |
3
1. actionPerformed : changed conditional boundary → NO_COVERAGE 2. actionPerformed : negated conditional → NO_COVERAGE 3. actionPerformed : Replaced integer subtraction with addition → NO_COVERAGE |
if (valueIndex < DnDList.this.listModel.size() - 1) { |
130 |
1
1. actionPerformed : Replaced integer addition with subtraction → NO_COVERAGE |
siblings.add(DnDList.this.listModel.get(valueIndex + 1)); |
131 |
2
1. actionPerformed : negated conditional → NO_COVERAGE 2. actionPerformed : changed conditional boundary → NO_COVERAGE |
} else if (valueIndex > 0) { |
132 |
1
1. actionPerformed : Replaced integer subtraction with addition → NO_COVERAGE |
siblings.add(DnDList.this.listModel.get(valueIndex - 1)); |
133 | } | |
134 | } | |
135 | ||
136 |
1
1. actionPerformed : removed call to javax/swing/Action::actionPerformed → NO_COVERAGE |
TransferHandler.getCutAction().actionPerformed(e); |
137 | | |
138 | for (ItemList sibling: siblings) { | |
139 |
1
1. actionPerformed : removed call to com/jsql/view/swing/list/DnDList::setSelectedValue → NO_COVERAGE |
DnDList.this.setSelectedValue(sibling, true); |
140 | } | |
141 | } | |
142 | }); | |
143 | ||
144 |
1
1. initializeActionMap : removed call to javax/swing/ActionMap::put → NO_COVERAGE |
listActionMap.put( |
145 | TransferHandler.getCopyAction().getValue(Action.NAME), | |
146 | TransferHandler.getCopyAction() | |
147 | ); | |
148 | | |
149 |
1
1. initializeActionMap : removed call to javax/swing/ActionMap::put → NO_COVERAGE |
listActionMap.put( |
150 | TransferHandler.getPasteAction().getValue(Action.NAME), | |
151 | TransferHandler.getPasteAction() | |
152 | ); | |
153 | } | |
154 | ||
155 | /** | |
156 | * Delete selected items from the list. | |
157 | */ | |
158 | public void removeSelectedItem() { | |
159 | | |
160 |
1
1. removeSelectedItem : negated conditional → NO_COVERAGE |
if (this.getSelectedValuesList().isEmpty()) { |
161 | return; | |
162 | } | |
163 | ||
164 | List<ItemList> selectedValues = this.getSelectedValuesList(); | |
165 | | |
166 | for (ItemList itemSelected: selectedValues) { | |
167 | | |
168 | int indexOfItemSelected = this.listModel.indexOf(itemSelected); | |
169 | this.listModel.removeElement(itemSelected); | |
170 | | |
171 |
1
1. removeSelectedItem : negated conditional → NO_COVERAGE |
if (indexOfItemSelected == this.listModel.getSize()) { |
172 |
2
1. removeSelectedItem : Replaced integer subtraction with addition → NO_COVERAGE 2. removeSelectedItem : removed call to com/jsql/view/swing/list/DnDList::setSelectedIndex → NO_COVERAGE |
this.setSelectedIndex(indexOfItemSelected - 1); |
173 | } else { | |
174 |
1
1. removeSelectedItem : removed call to com/jsql/view/swing/list/DnDList::setSelectedIndex → NO_COVERAGE |
this.setSelectedIndex(indexOfItemSelected); |
175 | } | |
176 | } | |
177 | | |
178 | try { | |
179 | var rectangle = this.getCellBounds( | |
180 | this.getMinSelectionIndex(), | |
181 | this.getMaxSelectionIndex() | |
182 | ); | |
183 | | |
184 |
1
1. removeSelectedItem : negated conditional → NO_COVERAGE |
if (rectangle != null) { |
185 |
1
1. removeSelectedItem : removed call to com/jsql/view/swing/list/DnDList::scrollRectToVisible → NO_COVERAGE |
this.scrollRectToVisible( |
186 | this.getCellBounds( | |
187 | this.getMinSelectionIndex(), | |
188 | this.getMaxSelectionIndex() | |
189 | ) | |
190 | ); | |
191 | } | |
192 | } catch (NullPointerException e) { | |
193 | // Report NullPointerException #1571 : manual scroll elsewhere then run action | |
194 | LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e); | |
195 | } | |
196 | } | |
197 | ||
198 | /** | |
199 | * Load a file into the list (drag/drop or copy/paste). | |
200 | * @param filesToImport | |
201 | * @param position | |
202 | */ | |
203 | public void dropPasteFile(final List<File> filesToImport, int position) { | |
204 | | |
205 |
1
1. dropPasteFile : negated conditional → NO_COVERAGE |
if (filesToImport.isEmpty()) { |
206 | return; | |
207 | } | |
208 | | |
209 | for (File fileToImport : filesToImport) { | |
210 | | |
211 | // Report NoSuchMethodError #1617 | |
212 | if ( | |
213 | !FilenameUtils | |
214 | .getExtension(fileToImport.getPath()) | |
215 |
1
1. dropPasteFile : negated conditional → NO_COVERAGE |
.matches("txt|csv|ini") |
216 | ) { | |
217 | | |
218 | // Fix #42832: ClassCastException on showMessageDialog() | |
219 | try { | |
220 |
1
1. dropPasteFile : removed call to javax/swing/JOptionPane::showMessageDialog → NO_COVERAGE |
JOptionPane.showMessageDialog( |
221 | this.getTopLevelAncestor(), | |
222 | I18nUtil.valueByKey("LIST_IMPORT_ERROR_LABEL"), | |
223 | I18nUtil.valueByKey("LIST_IMPORT_ERROR_TITLE"), | |
224 | JOptionPane.ERROR_MESSAGE, | |
225 | UiUtil.ICON_ERROR | |
226 | ); | |
227 | } catch (ClassCastException e) { | |
228 | LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e.getMessage(), e); | |
229 | } | |
230 | | |
231 | return; | |
232 | } | |
233 | } | |
234 | ||
235 | var options = new String[] { | |
236 | I18nUtil.valueByKey("LIST_IMPORT_CONFIRM_REPLACE"), | |
237 | I18nUtil.valueByKey("LIST_IMPORT_CONFIRM_ADD"), | |
238 | I18nUtil.valueByKey("LIST_ADD_VALUE_CANCEL") | |
239 | }; | |
240 | | |
241 | int answer = JOptionPane.showOptionDialog( | |
242 | this.getTopLevelAncestor(), | |
243 | I18nUtil.valueByKey("LIST_IMPORT_CONFIRM_LABEL"), | |
244 | I18nUtil.valueByKey("LIST_IMPORT_CONFIRM_TITLE"), | |
245 | JOptionPane.YES_NO_CANCEL_OPTION, | |
246 | JOptionPane.QUESTION_MESSAGE, | |
247 | null, | |
248 | options, | |
249 | options[2] | |
250 | ); | |
251 | | |
252 | int startPosition = position; | |
253 | ||
254 |
2
1. dropPasteFile : negated conditional → NO_COVERAGE 2. dropPasteFile : negated conditional → NO_COVERAGE |
if (answer != JOptionPane.YES_OPTION && answer != JOptionPane.NO_OPTION) { |
255 | return; | |
256 | } | |
257 | | |
258 |
1
1. dropPasteFile : negated conditional → NO_COVERAGE |
if (answer == JOptionPane.YES_OPTION) { |
259 | | |
260 |
1
1. dropPasteFile : removed call to javax/swing/DefaultListModel::clear → NO_COVERAGE |
this.listModel.clear(); |
261 | startPosition = 0; | |
262 | } | |
263 | | |
264 | int startPositionFinal = startPosition; | |
265 | | |
266 |
2
1. lambda$dropPasteFile$0 : removed call to com/jsql/view/swing/list/DnDList::addItems → NO_COVERAGE 2. dropPasteFile : removed call to javax/swing/SwingUtilities::invokeLater → NO_COVERAGE |
SwingUtilities.invokeLater(() -> this.addItems(filesToImport, startPositionFinal)); |
267 | } | |
268 | ||
269 | private void addItems(final List<File> filesToImport, int startPosition) { | |
270 | | |
271 | int endPosition = startPosition; | |
272 | | |
273 | for (File file: filesToImport) { | |
274 | endPosition = this.initializeItems(endPosition, file); | |
275 | } | |
276 | ||
277 |
1
1. addItems : negated conditional → NO_COVERAGE |
if (!this.listModel.isEmpty()) { |
278 |
2
1. addItems : Replaced integer subtraction with addition → NO_COVERAGE 2. addItems : removed call to com/jsql/view/swing/list/DnDList::setSelectionInterval → NO_COVERAGE |
DnDList.this.setSelectionInterval(startPosition, endPosition - 1); |
279 | } | |
280 | | |
281 | try { | |
282 |
1
1. addItems : removed call to com/jsql/view/swing/list/DnDList::scrollRectToVisible → NO_COVERAGE |
DnDList.this.scrollRectToVisible( |
283 | DnDList.this.getCellBounds( | |
284 | DnDList.this.getMinSelectionIndex(), | |
285 | DnDList.this.getMaxSelectionIndex() | |
286 | ) | |
287 | ); | |
288 | } catch (NullPointerException e) { | |
289 | // Report NullPointerException #1571 : manual scroll elsewhere then run action | |
290 | LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e.getMessage(), e); | |
291 | } | |
292 | } | |
293 | ||
294 | private int initializeItems(int startPosition, File file) { | |
295 | | |
296 | int endPosition = startPosition; | |
297 | | |
298 | try ( | |
299 | var fileReader = new FileReader(file, StandardCharsets.UTF_8); | |
300 | var bufferedReader = new BufferedReader(fileReader) | |
301 | ) { | |
302 | | |
303 | String line; | |
304 |
1
1. initializeItems : negated conditional → NO_COVERAGE |
while ((line = bufferedReader.readLine()) != null) { |
305 | if ( | |
306 |
3
1. initializeItems : changed conditional boundary → NO_COVERAGE 2. initializeItems : negated conditional → NO_COVERAGE 3. initializeItems : negated conditional → NO_COVERAGE |
StringUtils.isNotEmpty(line) |
307 | // Fix Report #60 | |
308 | && 0 <= endPosition | |
309 |
2
1. initializeItems : changed conditional boundary → NO_COVERAGE 2. initializeItems : negated conditional → NO_COVERAGE |
&& endPosition <= this.listModel.size() |
310 | ) { | |
311 |
2
1. initializeItems : removed call to com/jsql/view/swing/list/DnDList::addItem → NO_COVERAGE 2. initializeItems : Changed increment from 1 to -1 → NO_COVERAGE |
this.addItem(endPosition++, line); |
312 | } | |
313 | } | |
314 | } catch (IOException e) { | |
315 | LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e.getMessage(), e); | |
316 | } | |
317 | | |
318 |
1
1. initializeItems : replaced int return with 0 for com/jsql/view/swing/list/DnDList::initializeItems → NO_COVERAGE |
return endPosition; |
319 | } | |
320 | | |
321 | public void restore() { | |
322 | | |
323 |
1
1. restore : removed call to javax/swing/DefaultListModel::clear → NO_COVERAGE |
this.listModel.clear(); |
324 | | |
325 | for (ItemList path: this.defaultList) { | |
326 |
1
1. restore : removed call to javax/swing/DefaultListModel::addElement → NO_COVERAGE |
this.listModel.addElement(path); |
327 | } | |
328 | } | |
329 | | |
330 | public void addItem(int endPosition, String line) { | |
331 |
1
1. addItem : removed call to javax/swing/DefaultListModel::add → NO_COVERAGE |
this.listModel.add(endPosition, new ItemList(line.replace("\\", "/"))); |
332 | } | |
333 | } | |
Mutations | ||
62 |
1.1 |
|
65 |
1.1 |
|
68 |
1.1 |
|
70 |
1.1 |
|
71 |
1.1 |
|
73 |
1.1 |
|
74 |
1.1 |
|
77 |
1.1 |
|
82 |
1.1 |
|
85 |
1.1 |
|
89 |
1.1 |
|
94 |
1.1 |
|
99 |
1.1 |
|
102 |
1.1 |
|
103 |
1.1 |
|
114 |
1.1 |
|
118 |
1.1 |
|
129 |
1.1 2.2 3.3 |
|
130 |
1.1 |
|
131 |
1.1 2.2 |
|
132 |
1.1 |
|
136 |
1.1 |
|
139 |
1.1 |
|
144 |
1.1 |
|
149 |
1.1 |
|
160 |
1.1 |
|
171 |
1.1 |
|
172 |
1.1 2.2 |
|
174 |
1.1 |
|
184 |
1.1 |
|
185 |
1.1 |
|
205 |
1.1 |
|
215 |
1.1 |
|
220 |
1.1 |
|
254 |
1.1 2.2 |
|
258 |
1.1 |
|
260 |
1.1 |
|
266 |
1.1 2.2 |
|
277 |
1.1 |
|
278 |
1.1 2.2 |
|
282 |
1.1 |
|
304 |
1.1 |
|
306 |
1.1 2.2 3.3 |
|
309 |
1.1 2.2 |
|
311 |
1.1 2.2 |
|
318 |
1.1 |
|
323 |
1.1 |
|
326 |
1.1 |
|
331 |
1.1 |