| 1 | // License: GPL. For details, see LICENSE file. |
|---|
| 2 | package org.openstreetmap.josm.gui.actionsupport; |
|---|
| 3 | |
|---|
| 4 | import static org.openstreetmap.josm.gui.help.HelpUtil.ht; |
|---|
| 5 | import static org.openstreetmap.josm.tools.I18n.tr; |
|---|
| 6 | import static org.openstreetmap.josm.tools.I18n.trn; |
|---|
| 7 | |
|---|
| 8 | import java.awt.BorderLayout; |
|---|
| 9 | import java.awt.Dimension; |
|---|
| 10 | import java.awt.FlowLayout; |
|---|
| 11 | import java.awt.Font; |
|---|
| 12 | import java.awt.event.ActionEvent; |
|---|
| 13 | import java.awt.event.WindowAdapter; |
|---|
| 14 | import java.awt.event.WindowEvent; |
|---|
| 15 | import java.text.MessageFormat; |
|---|
| 16 | import java.util.ArrayList; |
|---|
| 17 | import java.util.Collection; |
|---|
| 18 | import java.util.Collections; |
|---|
| 19 | import java.util.Comparator; |
|---|
| 20 | import java.util.HashSet; |
|---|
| 21 | import java.util.Set; |
|---|
| 22 | |
|---|
| 23 | import javax.swing.AbstractAction; |
|---|
| 24 | import javax.swing.BorderFactory; |
|---|
| 25 | import javax.swing.JDialog; |
|---|
| 26 | import javax.swing.JEditorPane; |
|---|
| 27 | import javax.swing.JOptionPane; |
|---|
| 28 | import javax.swing.JPanel; |
|---|
| 29 | import javax.swing.JScrollPane; |
|---|
| 30 | import javax.swing.JTable; |
|---|
| 31 | import javax.swing.UIManager; |
|---|
| 32 | import javax.swing.event.TableModelEvent; |
|---|
| 33 | import javax.swing.event.TableModelListener; |
|---|
| 34 | import javax.swing.table.DefaultTableColumnModel; |
|---|
| 35 | import javax.swing.table.DefaultTableModel; |
|---|
| 36 | import javax.swing.table.TableColumn; |
|---|
| 37 | import javax.swing.text.html.HTMLEditorKit; |
|---|
| 38 | import javax.swing.text.html.StyleSheet; |
|---|
| 39 | |
|---|
| 40 | import org.openstreetmap.josm.Main; |
|---|
| 41 | import org.openstreetmap.josm.data.osm.NameFormatter; |
|---|
| 42 | import org.openstreetmap.josm.data.osm.OsmPrimitive; |
|---|
| 43 | import org.openstreetmap.josm.data.osm.RelationToChildReference; |
|---|
| 44 | import org.openstreetmap.josm.gui.DefaultNameFormatter; |
|---|
| 45 | import org.openstreetmap.josm.gui.OsmPrimitivRenderer; |
|---|
| 46 | import org.openstreetmap.josm.gui.SideButton; |
|---|
| 47 | import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; |
|---|
| 48 | import org.openstreetmap.josm.gui.help.HelpUtil; |
|---|
| 49 | import org.openstreetmap.josm.tools.ImageProvider; |
|---|
| 50 | import org.openstreetmap.josm.tools.WindowGeometry; |
|---|
| 51 | |
|---|
| 52 | /** |
|---|
| 53 | * This dialog is used to get a user confirmation that a collection of primitives can be removed |
|---|
| 54 | * from their parent relations. |
|---|
| 55 | * |
|---|
| 56 | */ |
|---|
| 57 | public class DeleteFromRelationConfirmationDialog extends JDialog implements TableModelListener { |
|---|
| 58 | /** the unique instance of this dialog */ |
|---|
| 59 | static private DeleteFromRelationConfirmationDialog instance; |
|---|
| 60 | |
|---|
| 61 | /** |
|---|
| 62 | * Replies the unique instance of this dialog |
|---|
| 63 | * |
|---|
| 64 | * @return |
|---|
| 65 | */ |
|---|
| 66 | static public DeleteFromRelationConfirmationDialog getInstance() { |
|---|
| 67 | if (instance == null) { |
|---|
| 68 | instance = new DeleteFromRelationConfirmationDialog(); |
|---|
| 69 | } |
|---|
| 70 | return instance; |
|---|
| 71 | } |
|---|
| 72 | |
|---|
| 73 | /** the data model */ |
|---|
| 74 | private RelationMemberTableModel model; |
|---|
| 75 | private JEditorPane jepMessage; |
|---|
| 76 | private boolean canceled; |
|---|
| 77 | private SideButton btnOK; |
|---|
| 78 | |
|---|
| 79 | protected JPanel buildMessagePanel() { |
|---|
| 80 | JPanel pnl = new JPanel(new BorderLayout()); |
|---|
| 81 | jepMessage = new JEditorPane("text/html", ""); |
|---|
| 82 | jepMessage.setOpaque(false); |
|---|
| 83 | jepMessage.setEditable(false); |
|---|
| 84 | Font f = UIManager.getFont("Label.font"); |
|---|
| 85 | StyleSheet ss = new StyleSheet(); |
|---|
| 86 | String rule = MessageFormat.format( |
|---|
| 87 | "font-family: ''{0}'';font-size: {1,number}pt; font-weight: {2}; font-style: {3}", |
|---|
| 88 | f.getName(), |
|---|
| 89 | f.getSize(), |
|---|
| 90 | f.isBold() ? "bold" : "normal", |
|---|
| 91 | f.isItalic() ? "italic" : "normal" |
|---|
| 92 | ); |
|---|
| 93 | rule = "body {" + rule + "}"; |
|---|
| 94 | rule = MessageFormat.format( |
|---|
| 95 | "font-family: ''{0}'';font-size: {1,number}pt; font-weight: {2}; font-style: {3}", |
|---|
| 96 | f.getName(), |
|---|
| 97 | f.getSize(), |
|---|
| 98 | "bold", |
|---|
| 99 | 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 | } |
|---|