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.util.ArrayList; 015import java.util.Collection; 016import java.util.Collections; 017import java.util.Comparator; 018import java.util.HashSet; 019import java.util.List; 020import java.util.Set; 021 022import javax.swing.AbstractAction; 023import javax.swing.JDialog; 024import javax.swing.JOptionPane; 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.Main; 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.DefaultNameFormatter; 039import org.openstreetmap.josm.gui.OsmPrimitivRenderer; 040import org.openstreetmap.josm.gui.SideButton; 041import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; 042import org.openstreetmap.josm.gui.help.HelpUtil; 043import org.openstreetmap.josm.gui.widgets.HtmlPanel; 044import org.openstreetmap.josm.tools.I18n; 045import org.openstreetmap.josm.tools.ImageProvider; 046import org.openstreetmap.josm.tools.WindowGeometry; 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 HtmlPanel htmlPanel; 072 private boolean canceled; 073 private SideButton btnOK; 074 075 protected JPanel buildRelationMemberTablePanel() { 076 JTable table = new JTable(model, new RelationMemberTableColumnModel()); 077 JPanel pnl = new JPanel(); 078 pnl.setLayout(new BorderLayout()); 079 pnl.add(new JScrollPane(table)); 080 return pnl; 081 } 082 083 protected JPanel buildButtonPanel() { 084 JPanel pnl = new JPanel(); 085 pnl.setLayout(new FlowLayout()); 086 pnl.add(btnOK = new SideButton(new OKAction())); 087 btnOK.setFocusable(true); 088 pnl.add(new SideButton(new CancelAction())); 089 pnl.add(new SideButton(new ContextSensitiveHelpAction(ht("/Action/Delete#DeleteFromRelations")))); 090 return pnl; 091 } 092 093 protected final void build() { 094 model = new RelationMemberTableModel(); 095 model.addTableModelListener(this); 096 getContentPane().setLayout(new BorderLayout()); 097 getContentPane().add(htmlPanel = new HtmlPanel(), BorderLayout.NORTH); 098 getContentPane().add(buildRelationMemberTablePanel(), BorderLayout.CENTER); 099 getContentPane().add(buildButtonPanel(), BorderLayout.SOUTH); 100 101 HelpUtil.setHelpContext(this.getRootPane(), ht("/Action/Delete#DeleteFromRelations")); 102 103 addWindowListener(new WindowEventHandler()); 104 } 105 106 protected void updateMessage() { 107 int numObjectsToDelete = model.getNumObjectsToDelete(); 108 int numParentRelations = model.getNumParentRelations(); 109 final String msg1 = trn( 110 "Please confirm to remove <strong>{0} object</strong>.", 111 "Please confirm to remove <strong>{0} objects</strong>.", 112 numObjectsToDelete, numObjectsToDelete); 113 final String msg2 = trn( 114 "{0} relation is affected.", 115 "{0} relations are affected.", 116 numParentRelations, numParentRelations); 117 @I18n.QuirkyPluralString 118 final String msg = "<html>" + msg1 + " " + msg2 + "</html>"; 119 htmlPanel.getEditorPane().setText(msg); 120 invalidate(); 121 } 122 123 protected void updateTitle() { 124 int numObjectsToDelete = model.getNumObjectsToDelete(); 125 if (numObjectsToDelete > 0) { 126 setTitle(trn("Deleting {0} object", "Deleting {0} objects", numObjectsToDelete, numObjectsToDelete)); 127 } else { 128 setTitle(tr("Delete objects")); 129 } 130 } 131 132 /** 133 * Constructs a new {@code DeleteFromRelationConfirmationDialog}. 134 */ 135 public DeleteFromRelationConfirmationDialog() { 136 super(JOptionPane.getFrameForComponent(Main.parent), "", ModalityType.DOCUMENT_MODAL); 137 build(); 138 } 139 140 /** 141 * Replies the data model used in this dialog 142 * 143 * @return the data model 144 */ 145 public RelationMemberTableModel getModel() { 146 return model; 147 } 148 149 /** 150 * Replies true if the dialog was canceled 151 * 152 * @return true if the dialog was canceled 153 */ 154 public boolean isCanceled() { 155 return canceled; 156 } 157 158 protected void setCanceled(boolean canceled) { 159 this.canceled = canceled; 160 } 161 162 @Override 163 public void setVisible(boolean visible) { 164 if (visible) { 165 new WindowGeometry( 166 getClass().getName() + ".geometry", 167 WindowGeometry.centerInWindow( 168 Main.parent, 169 new Dimension(400, 200) 170 ) 171 ).applySafe(this); 172 setCanceled(false); 173 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775 174 new WindowGeometry(this).remember(getClass().getName() + ".geometry"); 175 } 176 super.setVisible(visible); 177 } 178 179 @Override 180 public void tableChanged(TableModelEvent e) { 181 updateMessage(); 182 updateTitle(); 183 } 184 185 /** 186 * The table model which manages the list of relation-to-child references 187 * 188 */ 189 public static class RelationMemberTableModel extends DefaultTableModel { 190 private transient List<RelationToChildReference> data; 191 192 /** 193 * Constructs a new {@code RelationMemberTableModel}. 194 */ 195 public RelationMemberTableModel() { 196 data = new ArrayList<>(); 197 } 198 199 @Override 200 public int getRowCount() { 201 if (data == null) return 0; 202 return data.size(); 203 } 204 205 protected void sort() { 206 Collections.sort( 207 data, 208 new Comparator<RelationToChildReference>() { 209 private NameFormatter nf = DefaultNameFormatter.getInstance(); 210 @Override 211 public int compare(RelationToChildReference o1, RelationToChildReference o2) { 212 int cmp = o1.getChild().getDisplayName(nf).compareTo(o2.getChild().getDisplayName(nf)); 213 if (cmp != 0) return cmp; 214 cmp = o1.getParent().getDisplayName(nf).compareTo(o2.getParent().getDisplayName(nf)); 215 if (cmp != 0) return cmp; 216 return Integer.compare(o1.getPosition(), o2.getPosition()); 217 } 218 } 219 ); 220 } 221 222 public void populate(Collection<RelationToChildReference> references) { 223 data.clear(); 224 if (references != null) { 225 data.addAll(references); 226 } 227 sort(); 228 fireTableDataChanged(); 229 } 230 231 public Set<OsmPrimitive> getObjectsToDelete() { 232 Set<OsmPrimitive> ret = new HashSet<>(); 233 for (RelationToChildReference ref: data) { 234 ret.add(ref.getChild()); 235 } 236 return ret; 237 } 238 239 public int getNumObjectsToDelete() { 240 return getObjectsToDelete().size(); 241 } 242 243 public Set<OsmPrimitive> getParentRelations() { 244 Set<OsmPrimitive> ret = new HashSet<>(); 245 for (RelationToChildReference ref: data) { 246 ret.add(ref.getParent()); 247 } 248 return ret; 249 } 250 251 public int getNumParentRelations() { 252 return getParentRelations().size(); 253 } 254 255 @Override 256 public Object getValueAt(int rowIndex, int columnIndex) { 257 if (data == null) return null; 258 RelationToChildReference ref = data.get(rowIndex); 259 switch(columnIndex) { 260 case 0: return ref.getChild(); 261 case 1: return ref.getParent(); 262 case 2: return ref.getPosition()+1; 263 case 3: return ref.getRole(); 264 default: 265 assert false : "Illegal column index"; 266 } 267 return null; 268 } 269 270 @Override 271 public boolean isCellEditable(int row, int column) { 272 return false; 273 } 274 } 275 276 private static class RelationMemberTableColumnModel extends DefaultTableColumnModel { 277 278 protected final void createColumns() { 279 TableColumn col = null; 280 281 // column 0 - To Delete 282 col = new TableColumn(0); 283 col.setHeaderValue(tr("To delete")); 284 col.setResizable(true); 285 col.setWidth(100); 286 col.setPreferredWidth(100); 287 col.setCellRenderer(new OsmPrimitivRenderer()); 288 addColumn(col); 289 290 // column 0 - From Relation 291 col = new TableColumn(1); 292 col.setHeaderValue(tr("From Relation")); 293 col.setResizable(true); 294 col.setWidth(100); 295 col.setPreferredWidth(100); 296 col.setCellRenderer(new OsmPrimitivRenderer()); 297 addColumn(col); 298 299 // column 1 - Pos. 300 col = new TableColumn(2); 301 col.setHeaderValue(tr("Pos.")); 302 col.setResizable(true); 303 col.setWidth(30); 304 col.setPreferredWidth(30); 305 addColumn(col); 306 307 // column 2 - Role 308 col = new TableColumn(3); 309 col.setHeaderValue(tr("Role")); 310 col.setResizable(true); 311 col.setWidth(50); 312 col.setPreferredWidth(50); 313 addColumn(col); 314 } 315 316 RelationMemberTableColumnModel() { 317 createColumns(); 318 } 319 } 320 321 class OKAction extends AbstractAction { 322 OKAction() { 323 putValue(NAME, tr("OK")); 324 putValue(SMALL_ICON, ImageProvider.get("ok")); 325 putValue(SHORT_DESCRIPTION, tr("Click to close the dialog and remove the object from the relations")); 326 } 327 328 @Override 329 public void actionPerformed(ActionEvent e) { 330 setCanceled(false); 331 setVisible(false); 332 } 333 } 334 335 class CancelAction extends AbstractAction { 336 CancelAction() { 337 putValue(NAME, tr("Cancel")); 338 putValue(SMALL_ICON, ImageProvider.get("cancel")); 339 putValue(SHORT_DESCRIPTION, tr("Click to close the dialog and to abort deleting the objects")); 340 } 341 342 @Override 343 public void actionPerformed(ActionEvent e) { 344 setCanceled(true); 345 setVisible(false); 346 } 347 } 348 349 class WindowEventHandler extends WindowAdapter { 350 351 @Override 352 public void windowClosing(WindowEvent e) { 353 setCanceled(true); 354 } 355 356 @Override 357 public void windowOpened(WindowEvent e) { 358 btnOK.requestFocusInWindow(); 359 } 360 } 361}