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