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

Last change on this file since 17440 was 17440, checked in by GerdP, 3 years ago

see #17184: Memory leaks

  • remove more listeners in destroy()
  • reset fields which might reference OSM objects
  • call data.clearSelection when data layer is destroyed so that fewer actions keep references on OSM elements in that layer

My goal here: When a layer is closed JOSM should really allow to GC all OSM data and GPX data that was loaded before.

  • Property svn:eol-style set to native
File size: 12.3 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.event.ActionEvent;
12import java.awt.event.WindowAdapter;
13import java.awt.event.WindowEvent;
14import java.io.Serializable;
15import java.util.ArrayList;
16import java.util.Collection;
17import java.util.Comparator;
18import java.util.List;
19import java.util.Set;
20import java.util.stream.Collectors;
21
22import javax.swing.AbstractAction;
23import javax.swing.JButton;
24import javax.swing.JDialog;
25import javax.swing.JPanel;
26import javax.swing.JScrollPane;
27import javax.swing.JTable;
28import javax.swing.event.TableModelEvent;
29import javax.swing.event.TableModelListener;
30import javax.swing.table.DefaultTableColumnModel;
31import javax.swing.table.DefaultTableModel;
32import javax.swing.table.TableColumn;
33
34import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
35import org.openstreetmap.josm.data.osm.NameFormatter;
36import org.openstreetmap.josm.data.osm.OsmPrimitive;
37import org.openstreetmap.josm.data.osm.RelationToChildReference;
38import org.openstreetmap.josm.gui.MainApplication;
39import org.openstreetmap.josm.gui.PrimitiveRenderer;
40import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
41import org.openstreetmap.josm.gui.help.HelpUtil;
42import org.openstreetmap.josm.gui.util.GuiHelper;
43import org.openstreetmap.josm.gui.util.WindowGeometry;
44import org.openstreetmap.josm.gui.widgets.HtmlPanel;
45import org.openstreetmap.josm.tools.I18n;
46import org.openstreetmap.josm.tools.ImageProvider;
47
48/**
49 * This dialog is used to get a user confirmation that a collection of primitives can be removed
50 * from their parent relations.
51 * @since 2308
52 */
53public class DeleteFromRelationConfirmationDialog extends JDialog implements TableModelListener {
54 /** the unique instance of this dialog */
55 private static DeleteFromRelationConfirmationDialog instance;
56
57 /**
58 * Replies the unique instance of this dialog
59 *
60 * @return The unique instance of this dialog
61 */
62 public static synchronized DeleteFromRelationConfirmationDialog getInstance() {
63 if (instance == null) {
64 instance = new DeleteFromRelationConfirmationDialog();
65 }
66 return instance;
67 }
68
69 /** the data model */
70 private RelationMemberTableModel model;
71 private final HtmlPanel htmlPanel = new HtmlPanel();
72 private boolean canceled;
73 private final JButton btnOK = new JButton(new OKAction());
74
75 protected JPanel buildRelationMemberTablePanel() {
76 JTable table = new JTable(model, new RelationMemberTableColumnModel());
77 JPanel pnl = new JPanel(new BorderLayout());
78 pnl.add(new JScrollPane(table));
79 return pnl;
80 }
81
82 protected JPanel buildButtonPanel() {
83 JPanel pnl = new JPanel(new FlowLayout());
84 pnl.add(btnOK);
85 btnOK.setFocusable(true);
86 pnl.add(new JButton(new CancelAction()));
87 pnl.add(new JButton(new ContextSensitiveHelpAction(ht("/Action/Delete#DeleteFromRelations"))));
88 return pnl;
89 }
90
91 protected final void build() {
92 model = new RelationMemberTableModel();
93 model.addTableModelListener(this);
94 getContentPane().setLayout(new BorderLayout());
95 getContentPane().add(htmlPanel, BorderLayout.NORTH);
96 getContentPane().add(buildRelationMemberTablePanel(), BorderLayout.CENTER);
97 getContentPane().add(buildButtonPanel(), BorderLayout.SOUTH);
98
99 HelpUtil.setHelpContext(this.getRootPane(), ht("/Action/Delete#DeleteFromRelations"));
100
101 addWindowListener(new WindowEventHandler());
102 }
103
104 protected void updateMessage() {
105 int numObjectsToDelete = model.getNumObjectsToDelete();
106 int numParentRelations = model.getNumParentRelations();
107 final String msg1 = trn(
108 "Please confirm to remove <strong>{0} object</strong>.",
109 "Please confirm to remove <strong>{0} objects</strong>.",
110 numObjectsToDelete, numObjectsToDelete);
111 final String msg2 = trn(
112 "{0} relation is affected.",
113 "{0} relations are affected.",
114 numParentRelations, numParentRelations);
115 @I18n.QuirkyPluralString
116 final String msg = "<html>" + msg1 + ' ' + msg2 + "</html>";
117 htmlPanel.getEditorPane().setText(msg);
118 invalidate();
119 }
120
121 protected void updateTitle() {
122 int numObjectsToDelete = model.getNumObjectsToDelete();
123 if (numObjectsToDelete > 0) {
124 setTitle(trn("Deleting {0} object", "Deleting {0} objects", numObjectsToDelete, numObjectsToDelete));
125 } else {
126 setTitle(tr("Delete objects"));
127 }
128 }
129
130 /**
131 * Constructs a new {@code DeleteFromRelationConfirmationDialog}.
132 */
133 public DeleteFromRelationConfirmationDialog() {
134 super(GuiHelper.getFrameForComponent(MainApplication.getMainFrame()), "", ModalityType.DOCUMENT_MODAL);
135 build();
136 }
137
138 /**
139 * Replies the data model used in this dialog
140 *
141 * @return the data model
142 */
143 public RelationMemberTableModel getModel() {
144 return model;
145 }
146
147 /**
148 * Replies true if the dialog was canceled
149 *
150 * @return true if the dialog was canceled
151 */
152 public boolean isCanceled() {
153 return canceled;
154 }
155
156 protected void setCanceled(boolean canceled) {
157 this.canceled = canceled;
158 }
159
160 @Override
161 public void setVisible(boolean visible) {
162 if (visible) {
163 new WindowGeometry(
164 getClass().getName() + ".geometry",
165 WindowGeometry.centerInWindow(
166 MainApplication.getMainFrame(),
167 new Dimension(400, 200)
168 )
169 ).applySafe(this);
170 setCanceled(false);
171 } else {
172 if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
173 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
174 }
175 model.data.clear();
176 }
177 super.setVisible(visible);
178 }
179
180 @Override
181 public void tableChanged(TableModelEvent e) {
182 updateMessage();
183 updateTitle();
184 }
185
186 /**
187 * The table model which manages the list of relation-to-child references
188 */
189 public static class RelationMemberTableModel extends DefaultTableModel {
190 private static class RelationToChildReferenceComparator implements Comparator<RelationToChildReference>, Serializable {
191 private static final long serialVersionUID = 1L;
192 @Override
193 public int compare(RelationToChildReference o1, RelationToChildReference o2) {
194 NameFormatter nf = DefaultNameFormatter.getInstance();
195 int cmp = o1.getChild().getDisplayName(nf).compareTo(o2.getChild().getDisplayName(nf));
196 if (cmp != 0) return cmp;
197 cmp = o1.getParent().getDisplayName(nf).compareTo(o2.getParent().getDisplayName(nf));
198 if (cmp != 0) return cmp;
199 return Integer.compare(o1.getPosition(), o2.getPosition());
200 }
201 }
202
203 private final transient List<RelationToChildReference> data;
204
205 /**
206 * Constructs a new {@code RelationMemberTableModel}.
207 */
208 public RelationMemberTableModel() {
209 data = new ArrayList<>();
210 }
211
212 @Override
213 public int getRowCount() {
214 if (data == null) return 0;
215 return data.size();
216 }
217
218 /**
219 * Sets the data that should be displayed in the list.
220 * @param references A list of references to display
221 */
222 public void populate(Collection<RelationToChildReference> references) {
223 data.clear();
224 if (references != null) {
225 data.addAll(references);
226 }
227 data.sort(new RelationToChildReferenceComparator());
228 fireTableDataChanged();
229 }
230
231 /**
232 * Gets the list of children that are currently displayed.
233 * @return The children.
234 */
235 public Set<OsmPrimitive> getObjectsToDelete() {
236 return data.stream().map(RelationToChildReference::getChild).collect(Collectors.toSet());
237 }
238
239 /**
240 * Gets the number of elements {@link #getObjectsToDelete()} would return.
241 * @return That number.
242 */
243 public int getNumObjectsToDelete() {
244 return getObjectsToDelete().size();
245 }
246
247 /**
248 * Gets the set of parent relations
249 * @return All parent relations of the references
250 */
251 public Set<OsmPrimitive> getParentRelations() {
252 return data.stream().map(RelationToChildReference::getParent).collect(Collectors.toSet());
253 }
254
255 /**
256 * Gets the number of elements {@link #getParentRelations()} would return.
257 * @return That number.
258 */
259 public int getNumParentRelations() {
260 return getParentRelations().size();
261 }
262
263 @Override
264 public Object getValueAt(int rowIndex, int columnIndex) {
265 if (data == null) return null;
266 RelationToChildReference ref = data.get(rowIndex);
267 switch(columnIndex) {
268 case 0: return ref.getChild();
269 case 1: return ref.getParent();
270 case 2: return ref.getPosition()+1;
271 case 3: return ref.getRole();
272 default:
273 assert false : "Illegal column index";
274 }
275 return null;
276 }
277
278 @Override
279 public boolean isCellEditable(int row, int column) {
280 return false;
281 }
282 }
283
284 private static class RelationMemberTableColumnModel extends DefaultTableColumnModel {
285
286 protected final void createColumns() {
287
288 // column 0 - To Delete
289 TableColumn col = new TableColumn(0);
290 col.setHeaderValue(tr("To delete"));
291 col.setResizable(true);
292 col.setWidth(100);
293 col.setPreferredWidth(100);
294 col.setCellRenderer(new PrimitiveRenderer());
295 addColumn(col);
296
297 // column 0 - From Relation
298 col = new TableColumn(1);
299 col.setHeaderValue(tr("From Relation"));
300 col.setResizable(true);
301 col.setWidth(100);
302 col.setPreferredWidth(100);
303 col.setCellRenderer(new PrimitiveRenderer());
304 addColumn(col);
305
306 // column 1 - Pos.
307 col = new TableColumn(2);
308 col.setHeaderValue(tr("Pos."));
309 col.setResizable(true);
310 col.setWidth(30);
311 col.setPreferredWidth(30);
312 addColumn(col);
313
314 // column 2 - Role
315 col = new TableColumn(3);
316 col.setHeaderValue(tr("Role"));
317 col.setResizable(true);
318 col.setWidth(50);
319 col.setPreferredWidth(50);
320 addColumn(col);
321 }
322
323 RelationMemberTableColumnModel() {
324 createColumns();
325 }
326 }
327
328 class OKAction extends AbstractAction {
329 OKAction() {
330 putValue(NAME, tr("OK"));
331 new ImageProvider("ok").getResource().attachImageIcon(this);
332 putValue(SHORT_DESCRIPTION, tr("Click to close the dialog and remove the object from the relations"));
333 }
334
335 @Override
336 public void actionPerformed(ActionEvent e) {
337 setCanceled(false);
338 setVisible(false);
339 }
340 }
341
342 class CancelAction extends AbstractAction {
343 CancelAction() {
344 putValue(NAME, tr("Cancel"));
345 new ImageProvider("cancel").getResource().attachImageIcon(this);
346 putValue(SHORT_DESCRIPTION, tr("Click to close the dialog and to abort deleting the objects"));
347 }
348
349 @Override
350 public void actionPerformed(ActionEvent e) {
351 setCanceled(true);
352 setVisible(false);
353 }
354 }
355
356 class WindowEventHandler extends WindowAdapter {
357
358 @Override
359 public void windowClosing(WindowEvent e) {
360 setCanceled(true);
361 }
362
363 @Override
364 public void windowOpened(WindowEvent e) {
365 btnOK.requestFocusInWindow();
366 }
367 }
368}
Note: See TracBrowser for help on using the repository browser.