source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/DeleteFromRelationConfirmationDialog.java

Last change on this file was 19050, checked in by taylor.smock, 3 days ago

Revert most var changes from r19048, fix most new compile warnings and checkstyle issues

Also, document why various ErrorProne checks were originally disabled and fix
generic SonarLint issues.

  • Property svn:eol-style set to native
File size: 18.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.BorderLayout;
9import java.awt.Dimension;
10import java.awt.FlowLayout;
11import java.awt.GridBagLayout;
12import java.awt.event.ActionEvent;
13import java.awt.event.WindowAdapter;
14import java.awt.event.WindowEvent;
15import java.io.Serializable;
16import java.util.ArrayList;
17import java.util.Collection;
18import java.util.Comparator;
19import java.util.List;
20import java.util.Set;
21import java.util.stream.Collectors;
22
23import javax.swing.AbstractAction;
24import javax.swing.JButton;
25import javax.swing.JDialog;
26import javax.swing.JPanel;
27import javax.swing.JScrollPane;
28import javax.swing.JTable;
29import javax.swing.event.TableModelEvent;
30import javax.swing.event.TableModelListener;
31import javax.swing.table.DefaultTableColumnModel;
32import javax.swing.table.DefaultTableModel;
33import javax.swing.table.TableColumn;
34
35import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
36import org.openstreetmap.josm.data.osm.NameFormatter;
37import org.openstreetmap.josm.data.osm.OsmPrimitive;
38import org.openstreetmap.josm.data.osm.Relation;
39import org.openstreetmap.josm.data.osm.RelationToChildReference;
40import org.openstreetmap.josm.gui.MainApplication;
41import org.openstreetmap.josm.gui.PrimitiveRenderer;
42import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
43import org.openstreetmap.josm.gui.help.HelpUtil;
44import org.openstreetmap.josm.gui.util.GuiHelper;
45import org.openstreetmap.josm.gui.util.WindowGeometry;
46import org.openstreetmap.josm.gui.widgets.HtmlPanel;
47import org.openstreetmap.josm.tools.GBC;
48import org.openstreetmap.josm.tools.I18n;
49import org.openstreetmap.josm.tools.ImageProvider;
50import org.openstreetmap.josm.tools.Pair;
51import org.openstreetmap.josm.tools.Utils;
52
53/**
54 * This dialog is used to get a user confirmation that a collection of primitives can be removed
55 * from their parent relations.
56 * @since 2308
57 */
58public class DeleteFromRelationConfirmationDialog extends JDialog implements TableModelListener {
59 /** the unique instance of this dialog */
60 private static DeleteFromRelationConfirmationDialog instance;
61
62 /**
63 * Replies the unique instance of this dialog
64 *
65 * @return The unique instance of this dialog
66 */
67 public static synchronized DeleteFromRelationConfirmationDialog getInstance() {
68 if (instance == null) {
69 instance = new DeleteFromRelationConfirmationDialog();
70 }
71 return instance;
72 }
73
74 /** the data model */
75 private RelationMemberTableModel model;
76 /** The data model for deleting relations */
77 private RelationDeleteModel deletedRelationsModel;
78 /** The table to hide/show if the relations to delete are not empty*/
79 private final HtmlPanel htmlPanel = new HtmlPanel();
80 private boolean canceled;
81 private final JButton btnOK = new JButton(new OKAction());
82
83 protected JPanel buildRelationMemberTablePanel() {
84 JTable table = new JTable(model, new RelationMemberTableColumnModel());
85 JPanel pnl = new JPanel(new GridBagLayout());
86 pnl.add(new JScrollPane(table), GBC.eol().fill());
87 JTable deletedRelationsTable = new JTable(this.deletedRelationsModel, new RelationDeleteTableColumnModel());
88 JScrollPane deletedRelationsModelTableScrollPane = new JScrollPane(deletedRelationsTable);
89 this.deletedRelationsModel.addTableModelListener(
90 e -> deletedRelationsModelTableScrollPane.setVisible(this.deletedRelationsModel.getRowCount() > 0));
91 // Default to not visible
92 deletedRelationsModelTableScrollPane.setVisible(false);
93 pnl.add(deletedRelationsModelTableScrollPane, GBC.eol().fill());
94 return pnl;
95 }
96
97 protected JPanel buildButtonPanel() {
98 JPanel pnl = new JPanel(new FlowLayout());
99 pnl.add(btnOK);
100 btnOK.setFocusable(true);
101 pnl.add(new JButton(new CancelAction()));
102 pnl.add(new JButton(new ContextSensitiveHelpAction(ht("/Action/Delete#DeleteFromRelations"))));
103 return pnl;
104 }
105
106 protected final void build() {
107 model = new RelationMemberTableModel();
108 model.addTableModelListener(this);
109 this.deletedRelationsModel = new RelationDeleteModel();
110 this.deletedRelationsModel.addTableModelListener(this);
111 getContentPane().setLayout(new BorderLayout());
112 getContentPane().add(htmlPanel, BorderLayout.NORTH);
113 getContentPane().add(buildRelationMemberTablePanel(), BorderLayout.CENTER);
114 getContentPane().add(buildButtonPanel(), BorderLayout.SOUTH);
115
116 HelpUtil.setHelpContext(this.getRootPane(), ht("/Action/Delete#DeleteFromRelations"));
117
118 addWindowListener(new WindowEventHandler());
119 }
120
121 protected void updateMessage() {
122 int numObjectsToDelete = this.model.getNumObjectsToDelete() + this.deletedRelationsModel.getNumObjectsToDelete();
123 int numParentRelations = this.model.getNumParentRelations() + this.deletedRelationsModel.getNumParentRelations();
124 final String msg1 = trn(
125 "Please confirm to remove <strong>{0} object</strong>.",
126 "Please confirm to remove <strong>{0} objects</strong>.",
127 numObjectsToDelete, numObjectsToDelete);
128 final String msg2 = trn(
129 "{0} relation is affected.",
130 "{0} relations are affected.",
131 numParentRelations, numParentRelations);
132 @I18n.QuirkyPluralString
133 final String msg = "<html>" + msg1 + ' ' + msg2 + "</html>";
134 htmlPanel.getEditorPane().setText(msg);
135 invalidate();
136 }
137
138 protected void updateTitle() {
139 int numObjectsToDelete = this.model.getNumObjectsToDelete() + this.deletedRelationsModel.getNumObjectsToDelete();
140 if (numObjectsToDelete > 0) {
141 setTitle(trn("Deleting {0} object", "Deleting {0} objects", numObjectsToDelete, numObjectsToDelete));
142 } else {
143 setTitle(tr("Delete objects"));
144 }
145 }
146
147 /**
148 * Constructs a new {@code DeleteFromRelationConfirmationDialog}.
149 */
150 public DeleteFromRelationConfirmationDialog() {
151 super(GuiHelper.getFrameForComponent(MainApplication.getMainFrame()), "", ModalityType.DOCUMENT_MODAL);
152 build();
153 }
154
155 /**
156 * Replies the data model used in this dialog
157 *
158 * @return the data model
159 */
160 public RelationMemberTableModel getModel() {
161 return model;
162 }
163
164 /**
165 * Replies the data model used for relations that should probably be deleted.
166 * @return the data model
167 * @since 18395
168 */
169 public RelationDeleteModel getDeletedRelationsModel() {
170 return this.deletedRelationsModel;
171 }
172
173 /**
174 * Replies true if the dialog was canceled
175 *
176 * @return true if the dialog was canceled
177 */
178 public boolean isCanceled() {
179 return canceled;
180 }
181
182 protected void setCanceled(boolean canceled) {
183 this.canceled = canceled;
184 }
185
186 @Override
187 public void setVisible(boolean visible) {
188 if (visible) {
189 new WindowGeometry(
190 getClass().getName() + ".geometry",
191 WindowGeometry.centerInWindow(
192 MainApplication.getMainFrame(),
193 new Dimension(400, 200)
194 )
195 ).applySafe(this);
196 setCanceled(false);
197 } else {
198 if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
199 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
200 }
201 model.data.clear();
202 this.deletedRelationsModel.data.clear();
203 }
204 super.setVisible(visible);
205 }
206
207 @Override
208 public void tableChanged(TableModelEvent e) {
209 updateMessage();
210 updateTitle();
211 }
212
213 /**
214 * The table model which manages the list of relation-to-child references
215 */
216 public static class RelationMemberTableModel extends DefaultTableModel {
217 private static final class RelationToChildReferenceComparator implements Comparator<RelationToChildReference>, Serializable {
218 private static final long serialVersionUID = 1L;
219 @Override
220 public int compare(RelationToChildReference o1, RelationToChildReference o2) {
221 NameFormatter nf = DefaultNameFormatter.getInstance();
222 int cmp = o1.getChild().getDisplayName(nf).compareTo(o2.getChild().getDisplayName(nf));
223 if (cmp != 0) return cmp;
224 cmp = o1.getParent().getDisplayName(nf).compareTo(o2.getParent().getDisplayName(nf));
225 if (cmp != 0) return cmp;
226 return Integer.compare(o1.getPosition(), o2.getPosition());
227 }
228 }
229
230 private final transient List<RelationToChildReference> data;
231
232 /**
233 * Constructs a new {@code RelationMemberTableModel}.
234 */
235 public RelationMemberTableModel() {
236 data = new ArrayList<>();
237 }
238
239 @Override
240 public int getRowCount() {
241 if (data == null) return 0;
242 return data.size();
243 }
244
245 /**
246 * Sets the data that should be displayed in the list.
247 * @param references A list of references to display
248 */
249 public void populate(Collection<RelationToChildReference> references) {
250 data.clear();
251 if (references != null) {
252 data.addAll(references);
253 }
254 data.sort(new RelationToChildReferenceComparator());
255 fireTableDataChanged();
256 }
257
258 /**
259 * Gets the list of children that are currently displayed.
260 * @return The children.
261 */
262 public Set<OsmPrimitive> getObjectsToDelete() {
263 return data.stream().map(RelationToChildReference::getChild).collect(Collectors.toSet());
264 }
265
266 /**
267 * Gets the number of elements {@link #getObjectsToDelete()} would return.
268 * @return That number.
269 */
270 public int getNumObjectsToDelete() {
271 return getObjectsToDelete().size();
272 }
273
274 /**
275 * Gets the set of parent relations
276 * @return All parent relations of the references
277 */
278 public Set<OsmPrimitive> getParentRelations() {
279 return data.stream().map(RelationToChildReference::getParent).collect(Collectors.toSet());
280 }
281
282 /**
283 * Gets the number of elements {@link #getParentRelations()} would return.
284 * @return That number.
285 */
286 public int getNumParentRelations() {
287 return getParentRelations().size();
288 }
289
290 @Override
291 public Object getValueAt(int rowIndex, int columnIndex) {
292 if (data == null) return null;
293 RelationToChildReference ref = data.get(rowIndex);
294 switch (columnIndex) {
295 case 0: return ref.getChild();
296 case 1: return ref.getParent();
297 case 2: return ref.getPosition()+1;
298 case 3: return ref.getRole();
299 default:
300 assert false : "Illegal column index";
301 }
302 return null;
303 }
304
305 @Override
306 public boolean isCellEditable(int row, int column) {
307 return false;
308 }
309 }
310
311 private static class RelationMemberTableColumnModel extends DefaultTableColumnModel {
312
313 protected final void createColumns() {
314
315 // column 0 - To Delete
316 TableColumn col = new TableColumn(0);
317 col.setHeaderValue(tr("To delete"));
318 col.setResizable(true);
319 col.setWidth(100);
320 col.setPreferredWidth(100);
321 col.setCellRenderer(new PrimitiveRenderer());
322 addColumn(col);
323
324 // column 0 - From Relation
325 col = new TableColumn(1);
326 col.setHeaderValue(tr("From Relation"));
327 col.setResizable(true);
328 col.setWidth(100);
329 col.setPreferredWidth(100);
330 col.setCellRenderer(new PrimitiveRenderer());
331 addColumn(col);
332
333 // column 1 - Pos.
334 col = new TableColumn(2);
335 col.setHeaderValue(tr("Pos."));
336 col.setResizable(true);
337 col.setWidth(30);
338 col.setPreferredWidth(30);
339 addColumn(col);
340
341 // column 2 - Role
342 col = new TableColumn(3);
343 col.setHeaderValue(tr("Role"));
344 col.setResizable(true);
345 col.setWidth(50);
346 col.setPreferredWidth(50);
347 addColumn(col);
348 }
349
350 RelationMemberTableColumnModel() {
351 createColumns();
352 }
353 }
354
355 /**
356 * The table model which manages relations that will be deleted, if their children are deleted.
357 * @since 18395
358 */
359 public static class RelationDeleteModel extends DefaultTableModel {
360 private final transient List<Pair<Relation, Boolean>> data = new ArrayList<>();
361
362 @Override
363 public int getRowCount() {
364 // This is called in the super constructor. Before we have instantiated the list. Removing the null check
365 // WILL LEAD TO A SILENT NPE!
366 if (this.data == null) {
367 return 0;
368 }
369 return this.data.size();
370 }
371
372 /**
373 * Sets the data that should be displayed in the list.
374 * @param references A list of references to display
375 */
376 public void populate(Collection<Pair<Relation, Boolean>> references) {
377 this.data.clear();
378 if (references != null) {
379 this.data.addAll(references);
380 }
381 this.data.sort(Comparator.comparing(pair -> pair.a));
382 fireTableDataChanged();
383 }
384
385 /**
386 * Gets the list of children that are currently displayed.
387 * @return The children.
388 */
389 public Set<Relation> getObjectsToDelete() {
390 return this.data.stream().filter(relation -> relation.b).map(relation -> relation.a).collect(Collectors.toSet());
391 }
392
393 /**
394 * Gets the number of elements {@link #getObjectsToDelete()} would return.
395 * @return That number.
396 */
397 public int getNumObjectsToDelete() {
398 return getObjectsToDelete().size();
399 }
400
401 /**
402 * Gets the set of parent relations
403 * @return All parent relations of the references
404 */
405 public Set<OsmPrimitive> getParentRelations() {
406 return this.data.stream()
407 .flatMap(pair -> Utils.filteredCollection(pair.a.getReferrers(), Relation.class).stream())
408 .collect(Collectors.toSet());
409 }
410
411 /**
412 * Gets the number of elements {@link #getParentRelations()} would return.
413 * @return That number.
414 */
415 public int getNumParentRelations() {
416 return getParentRelations().size();
417 }
418
419 @Override
420 public Object getValueAt(int rowIndex, int columnIndex) {
421 if (this.data.isEmpty()) {
422 return null;
423 }
424 Pair<Relation, Boolean> ref = this.data.get(rowIndex);
425 switch (columnIndex) {
426 case 0: return ref.a;
427 case 1: return ref.b;
428 default:
429 assert false : "Illegal column index";
430 }
431 return null;
432 }
433
434 @Override
435 public boolean isCellEditable(int row, int column) {
436 return !this.data.isEmpty() && column == 1;
437 }
438
439 @Override
440 public void setValueAt(Object aValue, int row, int column) {
441 if (this.data.size() > row && column == 1 && aValue instanceof Boolean) {
442 this.data.get(row).b = ((Boolean) aValue);
443 }
444 }
445
446 @Override
447 public Class<?> getColumnClass(int columnIndex) {
448 switch (columnIndex) {
449 case 0:
450 return Relation.class;
451 case 1:
452 return Boolean.class;
453 default:
454 return super.getColumnClass(columnIndex);
455 }
456 }
457 }
458
459 private static class RelationDeleteTableColumnModel extends DefaultTableColumnModel {
460 protected final void createColumns() {
461 // column 0 - To Delete
462 TableColumn col = new TableColumn(0);
463 col.setHeaderValue(tr("Relation"));
464 col.setResizable(true);
465 col.setWidth(100);
466 col.setPreferredWidth(100);
467 col.setCellRenderer(new PrimitiveRenderer());
468 addColumn(col);
469
470 // column 0 - From Relation
471 col = new TableColumn(1);
472 final String toDelete = tr("To delete");
473 col.setHeaderValue(toDelete);
474 col.setResizable(true);
475 col.setPreferredWidth(toDelete.length());
476 addColumn(col);
477 }
478
479 RelationDeleteTableColumnModel() {
480 createColumns();
481 }
482 }
483
484 class OKAction extends AbstractAction {
485 OKAction() {
486 putValue(NAME, tr("OK"));
487 new ImageProvider("ok").getResource().attachImageIcon(this);
488 putValue(SHORT_DESCRIPTION, tr("Click to close the dialog and remove the object from the relations"));
489 }
490
491 @Override
492 public void actionPerformed(ActionEvent e) {
493 setCanceled(false);
494 setVisible(false);
495 }
496 }
497
498 class CancelAction extends AbstractAction {
499 CancelAction() {
500 putValue(NAME, tr("Cancel"));
501 new ImageProvider("cancel").getResource().attachImageIcon(this);
502 putValue(SHORT_DESCRIPTION, tr("Click to close the dialog and to abort deleting the objects"));
503 }
504
505 @Override
506 public void actionPerformed(ActionEvent e) {
507 setCanceled(true);
508 setVisible(false);
509 }
510 }
511
512 class WindowEventHandler extends WindowAdapter {
513
514 @Override
515 public void windowClosing(WindowEvent e) {
516 setCanceled(true);
517 }
518
519 @Override
520 public void windowOpened(WindowEvent e) {
521 btnOK.requestFocusInWindow();
522 }
523 }
524}
Note: See TracBrowser for help on using the repository browser.