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