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