001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs; 003 004import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005import static org.openstreetmap.josm.tools.I18n.tr; 006import static org.openstreetmap.josm.tools.I18n.trn; 007 008import java.awt.BorderLayout; 009import java.awt.Dimension; 010import java.awt.FlowLayout; 011import java.awt.event.ActionEvent; 012import java.awt.event.WindowAdapter; 013import java.awt.event.WindowEvent; 014import java.io.Serializable; 015import java.util.ArrayList; 016import java.util.Collection; 017import java.util.Comparator; 018import java.util.HashSet; 019import java.util.List; 020import java.util.Set; 021 022import javax.swing.AbstractAction; 023import javax.swing.JButton; 024import javax.swing.JDialog; 025import javax.swing.JPanel; 026import javax.swing.JScrollPane; 027import javax.swing.JTable; 028import javax.swing.event.TableModelEvent; 029import javax.swing.event.TableModelListener; 030import javax.swing.table.DefaultTableColumnModel; 031import javax.swing.table.DefaultTableModel; 032import javax.swing.table.TableColumn; 033 034import org.openstreetmap.josm.data.osm.DefaultNameFormatter; 035import org.openstreetmap.josm.data.osm.NameFormatter; 036import org.openstreetmap.josm.data.osm.OsmPrimitive; 037import org.openstreetmap.josm.data.osm.RelationToChildReference; 038import org.openstreetmap.josm.gui.MainApplication; 039import org.openstreetmap.josm.gui.PrimitiveRenderer; 040import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; 041import org.openstreetmap.josm.gui.help.HelpUtil; 042import org.openstreetmap.josm.gui.util.GuiHelper; 043import org.openstreetmap.josm.gui.util.WindowGeometry; 044import org.openstreetmap.josm.gui.widgets.HtmlPanel; 045import org.openstreetmap.josm.tools.I18n; 046import org.openstreetmap.josm.tools.ImageProvider; 047 048/** 049 * This dialog is used to get a user confirmation that a collection of primitives can be removed 050 * from their parent relations. 051 * @since 2308 052 */ 053public class DeleteFromRelationConfirmationDialog extends JDialog implements TableModelListener { 054 /** the unique instance of this dialog */ 055 private static DeleteFromRelationConfirmationDialog instance; 056 057 /** 058 * Replies the unique instance of this dialog 059 * 060 * @return The unique instance of this dialog 061 */ 062 public static synchronized DeleteFromRelationConfirmationDialog getInstance() { 063 if (instance == null) { 064 instance = new DeleteFromRelationConfirmationDialog(); 065 } 066 return instance; 067 } 068 069 /** the data model */ 070 private RelationMemberTableModel model; 071 private final HtmlPanel htmlPanel = new HtmlPanel(); 072 private boolean canceled; 073 private final JButton btnOK = new JButton(new OKAction()); 074 075 protected JPanel buildRelationMemberTablePanel() { 076 JTable table = new JTable(model, new RelationMemberTableColumnModel()); 077 JPanel pnl = new JPanel(new BorderLayout()); 078 pnl.add(new JScrollPane(table)); 079 return pnl; 080 } 081 082 protected JPanel buildButtonPanel() { 083 JPanel pnl = new JPanel(new FlowLayout()); 084 pnl.add(btnOK); 085 btnOK.setFocusable(true); 086 pnl.add(new JButton(new CancelAction())); 087 pnl.add(new JButton(new ContextSensitiveHelpAction(ht("/Action/Delete#DeleteFromRelations")))); 088 return pnl; 089 } 090 091 protected final void build() { 092 model = new RelationMemberTableModel(); 093 model.addTableModelListener(this); 094 getContentPane().setLayout(new BorderLayout()); 095 getContentPane().add(htmlPanel, BorderLayout.NORTH); 096 getContentPane().add(buildRelationMemberTablePanel(), BorderLayout.CENTER); 097 getContentPane().add(buildButtonPanel(), BorderLayout.SOUTH); 098 099 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 if (isShowing()) { // Avoid IllegalComponentStateException like in #8775 172 new WindowGeometry(this).remember(getClass().getName() + ".geometry"); 173 } 174 super.setVisible(visible); 175 } 176 177 @Override 178 public void tableChanged(TableModelEvent e) { 179 updateMessage(); 180 updateTitle(); 181 } 182 183 /** 184 * The table model which manages the list of relation-to-child references 185 */ 186 public static class RelationMemberTableModel extends DefaultTableModel { 187 private static class RelationToChildReferenceComparator implements Comparator<RelationToChildReference>, Serializable { 188 private static final long serialVersionUID = 1L; 189 @Override 190 public int compare(RelationToChildReference o1, RelationToChildReference o2) { 191 NameFormatter nf = DefaultNameFormatter.getInstance(); 192 int cmp = o1.getChild().getDisplayName(nf).compareTo(o2.getChild().getDisplayName(nf)); 193 if (cmp != 0) return cmp; 194 cmp = o1.getParent().getDisplayName(nf).compareTo(o2.getParent().getDisplayName(nf)); 195 if (cmp != 0) return cmp; 196 return Integer.compare(o1.getPosition(), o2.getPosition()); 197 } 198 } 199 200 private final transient List<RelationToChildReference> data; 201 202 /** 203 * Constructs a new {@code RelationMemberTableModel}. 204 */ 205 public RelationMemberTableModel() { 206 data = new ArrayList<>(); 207 } 208 209 @Override 210 public int getRowCount() { 211 if (data == null) return 0; 212 return data.size(); 213 } 214 215 /** 216 * Sets the data that should be displayed in the list. 217 * @param references A list of references to display 218 */ 219 public void populate(Collection<RelationToChildReference> references) { 220 data.clear(); 221 if (references != null) { 222 data.addAll(references); 223 } 224 data.sort(new RelationToChildReferenceComparator()); 225 fireTableDataChanged(); 226 } 227 228 /** 229 * Gets the list of children that are currently displayed. 230 * @return The children. 231 */ 232 public Set<OsmPrimitive> getObjectsToDelete() { 233 Set<OsmPrimitive> ret = new HashSet<>(); 234 for (RelationToChildReference ref: data) { 235 ret.add(ref.getChild()); 236 } 237 return ret; 238 } 239 240 /** 241 * Gets the number of elements {@link #getObjectsToDelete()} would return. 242 * @return That number. 243 */ 244 public int getNumObjectsToDelete() { 245 return getObjectsToDelete().size(); 246 } 247 248 /** 249 * Gets the set of parent relations 250 * @return All parent relations of the references 251 */ 252 public Set<OsmPrimitive> getParentRelations() { 253 Set<OsmPrimitive> ret = new HashSet<>(); 254 for (RelationToChildReference ref: data) { 255 ret.add(ref.getParent()); 256 } 257 return ret; 258 } 259 260 /** 261 * Gets the number of elements {@link #getParentRelations()} would return. 262 * @return That number. 263 */ 264 public int getNumParentRelations() { 265 return getParentRelations().size(); 266 } 267 268 @Override 269 public Object getValueAt(int rowIndex, int columnIndex) { 270 if (data == null) return null; 271 RelationToChildReference ref = data.get(rowIndex); 272 switch(columnIndex) { 273 case 0: return ref.getChild(); 274 case 1: return ref.getParent(); 275 case 2: return ref.getPosition()+1; 276 case 3: return ref.getRole(); 277 default: 278 assert false : "Illegal column index"; 279 } 280 return null; 281 } 282 283 @Override 284 public boolean isCellEditable(int row, int column) { 285 return false; 286 } 287 } 288 289 private static class RelationMemberTableColumnModel extends DefaultTableColumnModel { 290 291 protected final void createColumns() { 292 293 // column 0 - To Delete 294 TableColumn col = new TableColumn(0); 295 col.setHeaderValue(tr("To delete")); 296 col.setResizable(true); 297 col.setWidth(100); 298 col.setPreferredWidth(100); 299 col.setCellRenderer(new PrimitiveRenderer()); 300 addColumn(col); 301 302 // column 0 - From Relation 303 col = new TableColumn(1); 304 col.setHeaderValue(tr("From Relation")); 305 col.setResizable(true); 306 col.setWidth(100); 307 col.setPreferredWidth(100); 308 col.setCellRenderer(new PrimitiveRenderer()); 309 addColumn(col); 310 311 // column 1 - Pos. 312 col = new TableColumn(2); 313 col.setHeaderValue(tr("Pos.")); 314 col.setResizable(true); 315 col.setWidth(30); 316 col.setPreferredWidth(30); 317 addColumn(col); 318 319 // column 2 - Role 320 col = new TableColumn(3); 321 col.setHeaderValue(tr("Role")); 322 col.setResizable(true); 323 col.setWidth(50); 324 col.setPreferredWidth(50); 325 addColumn(col); 326 } 327 328 RelationMemberTableColumnModel() { 329 createColumns(); 330 } 331 } 332 333 class OKAction extends AbstractAction { 334 OKAction() { 335 putValue(NAME, tr("OK")); 336 new ImageProvider("ok").getResource().attachImageIcon(this); 337 putValue(SHORT_DESCRIPTION, tr("Click to close the dialog and remove the object from the relations")); 338 } 339 340 @Override 341 public void actionPerformed(ActionEvent e) { 342 setCanceled(false); 343 setVisible(false); 344 } 345 } 346 347 class CancelAction extends AbstractAction { 348 CancelAction() { 349 putValue(NAME, tr("Cancel")); 350 new ImageProvider("cancel").getResource().attachImageIcon(this); 351 putValue(SHORT_DESCRIPTION, tr("Click to close the dialog and to abort deleting the objects")); 352 } 353 354 @Override 355 public void actionPerformed(ActionEvent e) { 356 setCanceled(true); 357 setVisible(false); 358 } 359 } 360 361 class WindowEventHandler extends WindowAdapter { 362 363 @Override 364 public void windowClosing(WindowEvent e) { 365 setCanceled(true); 366 } 367 368 @Override 369 public void windowOpened(WindowEvent e) { 370 btnOK.requestFocusInWindow(); 371 } 372 } 373}