source: josm/trunk/src/org/openstreetmap/josm/gui/actionsupport/DeleteFromRelationConfirmationDialog.java @ 5241

Revision 4879, 13.1 KB checked in by stoecker, 4 months ago (diff)

fix #7339 - wrong relation member position in delete confirmation

  • Property svn:eol-style set to native
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.actionsupport;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.BorderLayout;
9import java.awt.Dimension;
10import java.awt.FlowLayout;
11import java.awt.Font;
12import java.awt.event.ActionEvent;
13import java.awt.event.WindowAdapter;
14import java.awt.event.WindowEvent;
15import java.text.MessageFormat;
16import java.util.ArrayList;
17import java.util.Collection;
18import java.util.Collections;
19import java.util.Comparator;
20import java.util.HashSet;
21import java.util.Set;
22
23import javax.swing.AbstractAction;
24import javax.swing.BorderFactory;
25import javax.swing.JDialog;
26import javax.swing.JEditorPane;
27import javax.swing.JOptionPane;
28import javax.swing.JPanel;
29import javax.swing.JScrollPane;
30import javax.swing.JTable;
31import javax.swing.UIManager;
32import javax.swing.event.TableModelEvent;
33import javax.swing.event.TableModelListener;
34import javax.swing.table.DefaultTableColumnModel;
35import javax.swing.table.DefaultTableModel;
36import javax.swing.table.TableColumn;
37import javax.swing.text.html.HTMLEditorKit;
38import javax.swing.text.html.StyleSheet;
39
40import org.openstreetmap.josm.Main;
41import org.openstreetmap.josm.data.osm.NameFormatter;
42import org.openstreetmap.josm.data.osm.OsmPrimitive;
43import org.openstreetmap.josm.data.osm.RelationToChildReference;
44import org.openstreetmap.josm.gui.DefaultNameFormatter;
45import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
46import org.openstreetmap.josm.gui.SideButton;
47import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
48import org.openstreetmap.josm.gui.help.HelpUtil;
49import org.openstreetmap.josm.tools.ImageProvider;
50import 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 */
57public 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}
Note: See TracBrowser for help on using the repository browser.