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