source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java @ 12933

Last change on this file since 12933 was 12933, checked in by Don-vip, 2 weeks ago

fix #15368 - Button that selects the relation from relation editor

  • Property svn:eol-style set to native
File size: 41.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.relation;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.BorderLayout;
8import java.awt.Dimension;
9import java.awt.FlowLayout;
10import java.awt.GraphicsEnvironment;
11import java.awt.GridBagConstraints;
12import java.awt.GridBagLayout;
13import java.awt.Window;
14import java.awt.datatransfer.Clipboard;
15import java.awt.datatransfer.FlavorListener;
16import java.awt.event.ActionEvent;
17import java.awt.event.FocusAdapter;
18import java.awt.event.FocusEvent;
19import java.awt.event.InputEvent;
20import java.awt.event.KeyEvent;
21import java.awt.event.MouseAdapter;
22import java.awt.event.MouseEvent;
23import java.awt.event.WindowAdapter;
24import java.awt.event.WindowEvent;
25import java.util.ArrayList;
26import java.util.Collection;
27import java.util.Collections;
28import java.util.EnumSet;
29import java.util.HashSet;
30import java.util.List;
31import java.util.Set;
32
33import javax.swing.AbstractAction;
34import javax.swing.BorderFactory;
35import javax.swing.InputMap;
36import javax.swing.JButton;
37import javax.swing.JComponent;
38import javax.swing.JLabel;
39import javax.swing.JMenuItem;
40import javax.swing.JOptionPane;
41import javax.swing.JPanel;
42import javax.swing.JRootPane;
43import javax.swing.JScrollPane;
44import javax.swing.JSplitPane;
45import javax.swing.JTabbedPane;
46import javax.swing.JTable;
47import javax.swing.JToolBar;
48import javax.swing.KeyStroke;
49
50import org.openstreetmap.josm.Main;
51import org.openstreetmap.josm.actions.ExpertToggleAction;
52import org.openstreetmap.josm.actions.JosmAction;
53import org.openstreetmap.josm.command.ChangeCommand;
54import org.openstreetmap.josm.command.Command;
55import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
56import org.openstreetmap.josm.data.osm.OsmPrimitive;
57import org.openstreetmap.josm.data.osm.Relation;
58import org.openstreetmap.josm.data.osm.RelationMember;
59import org.openstreetmap.josm.data.osm.Tag;
60import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
61import org.openstreetmap.josm.gui.MainApplication;
62import org.openstreetmap.josm.gui.MainMenu;
63import org.openstreetmap.josm.gui.ScrollViewport;
64import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
65import org.openstreetmap.josm.gui.dialogs.relation.actions.AbstractRelationEditorAction;
66import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAfterSelection;
67import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAtEndAction;
68import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAtStartAction;
69import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedBeforeSelection;
70import org.openstreetmap.josm.gui.dialogs.relation.actions.ApplyAction;
71import org.openstreetmap.josm.gui.dialogs.relation.actions.CancelAction;
72import org.openstreetmap.josm.gui.dialogs.relation.actions.CopyMembersAction;
73import org.openstreetmap.josm.gui.dialogs.relation.actions.DeleteCurrentRelationAction;
74import org.openstreetmap.josm.gui.dialogs.relation.actions.DownloadIncompleteMembersAction;
75import org.openstreetmap.josm.gui.dialogs.relation.actions.DownloadSelectedIncompleteMembersAction;
76import org.openstreetmap.josm.gui.dialogs.relation.actions.DuplicateRelationAction;
77import org.openstreetmap.josm.gui.dialogs.relation.actions.EditAction;
78import org.openstreetmap.josm.gui.dialogs.relation.actions.MoveDownAction;
79import org.openstreetmap.josm.gui.dialogs.relation.actions.MoveUpAction;
80import org.openstreetmap.josm.gui.dialogs.relation.actions.OKAction;
81import org.openstreetmap.josm.gui.dialogs.relation.actions.PasteMembersAction;
82import org.openstreetmap.josm.gui.dialogs.relation.actions.RefreshAction;
83import org.openstreetmap.josm.gui.dialogs.relation.actions.RemoveAction;
84import org.openstreetmap.josm.gui.dialogs.relation.actions.RemoveSelectedAction;
85import org.openstreetmap.josm.gui.dialogs.relation.actions.ReverseAction;
86import org.openstreetmap.josm.gui.dialogs.relation.actions.SelectAction;
87import org.openstreetmap.josm.gui.dialogs.relation.actions.SelectPrimitivesForSelectedMembersAction;
88import org.openstreetmap.josm.gui.dialogs.relation.actions.SelectedMembersForSelectionAction;
89import org.openstreetmap.josm.gui.dialogs.relation.actions.SetRoleAction;
90import org.openstreetmap.josm.gui.dialogs.relation.actions.SortAction;
91import org.openstreetmap.josm.gui.dialogs.relation.actions.SortBelowAction;
92import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
93import org.openstreetmap.josm.gui.help.HelpUtil;
94import org.openstreetmap.josm.gui.layer.OsmDataLayer;
95import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
96import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
97import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
98import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
99import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
100import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
101import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
102import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
103import org.openstreetmap.josm.gui.util.WindowGeometry;
104import org.openstreetmap.josm.spi.preferences.Config;
105import org.openstreetmap.josm.tools.CheckParameterUtil;
106import org.openstreetmap.josm.tools.Logging;
107import org.openstreetmap.josm.tools.Shortcut;
108import org.openstreetmap.josm.tools.Utils;
109
110/**
111 * This dialog is for editing relations.
112 * @since 343
113 */
114public class GenericRelationEditor extends RelationEditor {
115    /** the tag table and its model */
116    private final TagEditorPanel tagEditorPanel;
117    private final ReferringRelationsBrowser referrerBrowser;
118    private final ReferringRelationsBrowserModel referrerModel;
119
120    /** the member table and its model */
121    private final MemberTable memberTable;
122    private final MemberTableModel memberTableModel;
123
124    /** the selection table and its model */
125    private final SelectionTable selectionTable;
126    private final SelectionTableModel selectionTableModel;
127
128    private final AutoCompletingTextField tfRole;
129
130    /**
131     * the menu item in the windows menu. Required to properly hide on dialog close.
132     */
133    private JMenuItem windowMenuItem;
134    /**
135     * The toolbar with the buttons on the left
136     */
137    private final LeftButtonToolbar leftButtonToolbar;
138    /**
139     * Action for performing the {@link RefreshAction}
140     */
141    private final RefreshAction refreshAction;
142    /**
143     * Action for performing the {@link ApplyAction}
144     */
145    private final ApplyAction applyAction;
146    /**
147     * Action for performing the {@link SelectAction}
148     */
149    private final SelectAction selectAction;
150    /**
151     * Action for performing the {@link DuplicateRelationAction}
152     */
153    private final DuplicateRelationAction duplicateAction;
154    /**
155     * Action for performing the {@link DeleteCurrentRelationAction}
156     */
157    private final DeleteCurrentRelationAction deleteAction;
158    /**
159     * Action for performing the {@link OKAction}
160     */
161    private final OKAction okAction;
162    /**
163     * Action for performing the {@link CancelAction}
164     */
165    private final CancelAction cancelAction;
166    /**
167     * A list of listeners that need to be notified on clipboard content changes.
168     */
169    private final ArrayList<FlavorListener> clipboardListeners = new ArrayList<>();
170
171    /**
172     * Creates a new relation editor for the given relation. The relation will be saved if the user
173     * selects "ok" in the editor.
174     *
175     * If no relation is given, will create an editor for a new relation.
176     *
177     * @param layer the {@link OsmDataLayer} the new or edited relation belongs to
178     * @param relation relation to edit, or null to create a new one.
179     * @param selectedMembers a collection of members which shall be selected initially
180     */
181    public GenericRelationEditor(OsmDataLayer layer, Relation relation, Collection<RelationMember> selectedMembers) {
182        super(layer, relation);
183
184        setRememberWindowGeometry(getClass().getName() + ".geometry",
185                WindowGeometry.centerInWindow(Main.parent, new Dimension(700, 650)));
186
187        final TaggingPresetHandler presetHandler = new TaggingPresetHandler() {
188
189            @Override
190            public void updateTags(List<Tag> tags) {
191                tagEditorPanel.getModel().updateTags(tags);
192            }
193
194            @Override
195            public Collection<OsmPrimitive> getSelection() {
196                Relation relation = new Relation();
197                tagEditorPanel.getModel().applyToPrimitive(relation);
198                return Collections.<OsmPrimitive>singletonList(relation);
199            }
200        };
201
202        // init the various models
203        //
204        memberTableModel = new MemberTableModel(relation, getLayer(), presetHandler);
205        memberTableModel.register();
206        selectionTableModel = new SelectionTableModel(getLayer());
207        selectionTableModel.register();
208        referrerModel = new ReferringRelationsBrowserModel(relation);
209
210        tagEditorPanel = new TagEditorPanel(relation, presetHandler);
211        populateModels(relation);
212        tagEditorPanel.getModel().ensureOneTag();
213
214        // setting up the member table
215        memberTable = new MemberTable(getLayer(), getRelation(), memberTableModel);
216        memberTable.addMouseListener(new MemberTableDblClickAdapter());
217        memberTableModel.addMemberModelListener(memberTable);
218
219        MemberRoleCellEditor ce = (MemberRoleCellEditor) memberTable.getColumnModel().getColumn(0).getCellEditor();
220        selectionTable = new SelectionTable(selectionTableModel, memberTableModel);
221        selectionTable.setRowHeight(ce.getEditor().getPreferredSize().height);
222
223        leftButtonToolbar = new LeftButtonToolbar(memberTable, memberTableModel, this);
224        tfRole = buildRoleTextField(this);
225
226        JSplitPane pane = buildSplitPane(
227                buildTagEditorPanel(tagEditorPanel),
228                buildMemberEditorPanel(memberTable, memberTableModel, selectionTable, selectionTableModel, this, leftButtonToolbar, tfRole),
229                this);
230        pane.setPreferredSize(new Dimension(100, 100));
231
232        JPanel pnl = new JPanel(new BorderLayout());
233        pnl.add(pane, BorderLayout.CENTER);
234        pnl.setBorder(BorderFactory.createRaisedBevelBorder());
235
236        getContentPane().setLayout(new BorderLayout());
237        JTabbedPane tabbedPane = new JTabbedPane();
238        tabbedPane.add(tr("Tags and Members"), pnl);
239        referrerBrowser = new ReferringRelationsBrowser(getLayer(), referrerModel);
240        tabbedPane.add(tr("Parent Relations"), referrerBrowser);
241        tabbedPane.add(tr("Child Relations"), new ChildRelationBrowser(getLayer(), relation));
242        tabbedPane.addChangeListener(e -> {
243            JTabbedPane sourceTabbedPane = (JTabbedPane) e.getSource();
244            int index = sourceTabbedPane.getSelectedIndex();
245            String title = sourceTabbedPane.getTitleAt(index);
246            if (title.equals(tr("Parent Relations"))) {
247                referrerBrowser.init();
248            }
249        });
250
251        refreshAction = new RefreshAction(memberTable, memberTableModel, tagEditorPanel.getModel(), getLayer(), this);
252        applyAction = new ApplyAction(memberTable, memberTableModel, tagEditorPanel.getModel(), getLayer(), this);
253        selectAction = new SelectAction(getLayer(), this);
254        duplicateAction = new DuplicateRelationAction(memberTableModel, tagEditorPanel.getModel(), getLayer());
255        deleteAction = new DeleteCurrentRelationAction(getLayer(), this);
256        addPropertyChangeListener(deleteAction);
257
258        okAction = new OKAction(memberTable, memberTableModel, tagEditorPanel.getModel(), getLayer(), this, tfRole);
259        cancelAction = new CancelAction(memberTable, memberTableModel, tagEditorPanel.getModel(), getLayer(), this, tfRole);
260
261        getContentPane().add(buildToolBar(refreshAction, applyAction, selectAction, duplicateAction, deleteAction), BorderLayout.NORTH);
262        getContentPane().add(tabbedPane, BorderLayout.CENTER);
263        getContentPane().add(buildOkCancelButtonPanel(okAction, cancelAction), BorderLayout.SOUTH);
264
265        setSize(findMaxDialogSize());
266
267        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
268        addWindowListener(
269                new WindowAdapter() {
270                    @Override
271                    public void windowOpened(WindowEvent e) {
272                        cleanSelfReferences(memberTableModel, getRelation());
273                    }
274
275                    @Override
276                    public void windowClosing(WindowEvent e) {
277                        cancel();
278                    }
279                }
280        );
281        // CHECKSTYLE.OFF: LineLength
282        registerCopyPasteAction(tagEditorPanel.getPasteAction(), "PASTE_TAGS",
283                Shortcut.registerShortcut("system:pastestyle", tr("Edit: {0}", tr("Paste Tags")), KeyEvent.VK_V, Shortcut.CTRL_SHIFT).getKeyStroke(),
284                getRootPane(), memberTable, selectionTable);
285        // CHECKSTYLE.ON: LineLength
286
287        KeyStroke key = Shortcut.getPasteKeyStroke();
288        if (key != null) {
289            // handle uncommon situation, that user has no keystroke assigned to paste
290            registerCopyPasteAction(new PasteMembersAction(memberTable, getLayer(), this) {
291                @Override
292                public void actionPerformed(ActionEvent e) {
293                    super.actionPerformed(e);
294                    tfRole.requestFocusInWindow();
295                }
296            }, "PASTE_MEMBERS", key, getRootPane(), memberTable, selectionTable);
297        }
298        key = Shortcut.getCopyKeyStroke();
299        if (key != null) {
300            // handle uncommon situation, that user has no keystroke assigned to copy
301            registerCopyPasteAction(new CopyMembersAction(memberTableModel, getLayer(), this),
302                    "COPY_MEMBERS", key, getRootPane(), memberTable, selectionTable);
303        }
304        tagEditorPanel.setNextFocusComponent(memberTable);
305        selectionTable.setFocusable(false);
306        memberTableModel.setSelectedMembers(selectedMembers);
307        HelpUtil.setHelpContext(getRootPane(), ht("/Dialog/RelationEditor"));
308    }
309
310    @Override
311    public void reloadDataFromRelation() {
312        setRelation(getRelation());
313        populateModels(getRelation());
314        refreshAction.updateEnabledState();
315    }
316
317    private void populateModels(Relation relation) {
318        if (relation != null) {
319            tagEditorPanel.getModel().initFromPrimitive(relation);
320            memberTableModel.populate(relation);
321            if (!getLayer().data.getRelations().contains(relation)) {
322                // treat it as a new relation if it doesn't exist in the data set yet.
323                setRelation(null);
324            }
325        } else {
326            tagEditorPanel.getModel().clear();
327            memberTableModel.populate(null);
328        }
329    }
330
331    /**
332     * Apply changes.
333     * @see ApplyAction
334     */
335    public void apply() {
336        applyAction.actionPerformed(null);
337    }
338
339    /**
340     * Select relation.
341     * @see SelectAction
342     * @since 12933
343     */
344    public void select() {
345        selectAction.actionPerformed(null);
346    }
347
348    /**
349     * Cancel changes.
350     * @see CancelAction
351     */
352    public void cancel() {
353        cancelAction.actionPerformed(null);
354    }
355
356    /**
357     * Creates the toolbar
358     * @param actions relation toolbar actions
359     * @return the toolbar
360     * @since 12933
361     */
362    protected static JToolBar buildToolBar(AbstractRelationEditorAction... actions) {
363        JToolBar tb = new JToolBar();
364        tb.setFloatable(false);
365        for (AbstractRelationEditorAction action : actions) {
366            tb.add(action);
367        }
368        return tb;
369    }
370
371    /**
372     * builds the panel with the OK and the Cancel button
373     * @param okAction OK action
374     * @param cancelAction Cancel action
375     *
376     * @return the panel with the OK and the Cancel button
377     */
378    protected static JPanel buildOkCancelButtonPanel(OKAction okAction, CancelAction cancelAction) {
379        JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
380        pnl.add(new JButton(okAction));
381        pnl.add(new JButton(cancelAction));
382        pnl.add(new JButton(new ContextSensitiveHelpAction(ht("/Dialog/RelationEditor"))));
383        return pnl;
384    }
385
386    /**
387     * builds the panel with the tag editor
388     * @param tagEditorPanel tag editor panel
389     *
390     * @return the panel with the tag editor
391     */
392    protected static JPanel buildTagEditorPanel(TagEditorPanel tagEditorPanel) {
393        JPanel pnl = new JPanel(new GridBagLayout());
394
395        GridBagConstraints gc = new GridBagConstraints();
396        gc.gridx = 0;
397        gc.gridy = 0;
398        gc.gridheight = 1;
399        gc.gridwidth = 1;
400        gc.fill = GridBagConstraints.HORIZONTAL;
401        gc.anchor = GridBagConstraints.FIRST_LINE_START;
402        gc.weightx = 1.0;
403        gc.weighty = 0.0;
404        pnl.add(new JLabel(tr("Tags")), gc);
405
406        gc.gridx = 0;
407        gc.gridy = 1;
408        gc.fill = GridBagConstraints.BOTH;
409        gc.anchor = GridBagConstraints.CENTER;
410        gc.weightx = 1.0;
411        gc.weighty = 1.0;
412        pnl.add(tagEditorPanel, gc);
413        return pnl;
414    }
415
416    /**
417     * builds the role text field
418     * @param re relation editor
419     * @return the role text field
420     */
421    protected static AutoCompletingTextField buildRoleTextField(final IRelationEditor re) {
422        final AutoCompletingTextField tfRole = new AutoCompletingTextField(10);
423        tfRole.setToolTipText(tr("Enter a role and apply it to the selected relation members"));
424        tfRole.addFocusListener(new FocusAdapter() {
425            @Override
426            public void focusGained(FocusEvent e) {
427                tfRole.selectAll();
428            }
429        });
430        tfRole.setAutoCompletionList(new AutoCompletionList());
431        tfRole.addFocusListener(
432                new FocusAdapter() {
433                    @Override
434                    public void focusGained(FocusEvent e) {
435                        AutoCompletionList list = tfRole.getAutoCompletionList();
436                        if (list != null) {
437                            list.clear();
438                            AutoCompletionManager.of(re.getLayer().data).populateWithMemberRoles(list, re.getRelation());
439                        }
440                    }
441                }
442        );
443        tfRole.setText(Config.getPref().get("relation.editor.generic.lastrole", ""));
444        return tfRole;
445    }
446
447    /**
448     * builds the panel for the relation member editor
449     * @param memberTable member table
450     * @param memberTableModel member table model
451     * @param selectionTable selection table
452     * @param selectionTableModel selection table model
453     * @param re relation editor
454     * @param leftButtonToolbar left button toolbar
455     * @param tfRole role text field
456     *
457     * @return the panel for the relation member editor
458     */
459    protected static JPanel buildMemberEditorPanel(final MemberTable memberTable, MemberTableModel memberTableModel,
460            SelectionTable selectionTable, SelectionTableModel selectionTableModel, IRelationEditor re,
461            LeftButtonToolbar leftButtonToolbar, final AutoCompletingTextField tfRole) {
462        final JPanel pnl = new JPanel(new GridBagLayout());
463        final JScrollPane scrollPane = new JScrollPane(memberTable);
464
465        GridBagConstraints gc = new GridBagConstraints();
466        gc.gridx = 0;
467        gc.gridy = 0;
468        gc.gridwidth = 2;
469        gc.fill = GridBagConstraints.HORIZONTAL;
470        gc.anchor = GridBagConstraints.FIRST_LINE_START;
471        gc.weightx = 1.0;
472        gc.weighty = 0.0;
473        pnl.add(new JLabel(tr("Members")), gc);
474
475        gc.gridx = 0;
476        gc.gridy = 1;
477        gc.gridheight = 2;
478        gc.gridwidth = 1;
479        gc.fill = GridBagConstraints.VERTICAL;
480        gc.anchor = GridBagConstraints.NORTHWEST;
481        gc.weightx = 0.0;
482        gc.weighty = 1.0;
483        pnl.add(new ScrollViewport(leftButtonToolbar, ScrollViewport.VERTICAL_DIRECTION), gc);
484
485        gc.gridx = 1;
486        gc.gridy = 1;
487        gc.gridheight = 1;
488        gc.fill = GridBagConstraints.BOTH;
489        gc.anchor = GridBagConstraints.CENTER;
490        gc.weightx = 0.6;
491        gc.weighty = 1.0;
492        pnl.add(scrollPane, gc);
493
494        // --- role editing
495        JPanel p3 = new JPanel(new FlowLayout(FlowLayout.LEFT));
496        p3.add(new JLabel(tr("Apply Role:")));
497        p3.add(tfRole);
498        SetRoleAction setRoleAction = new SetRoleAction(memberTable, memberTableModel, tfRole);
499        memberTableModel.getSelectionModel().addListSelectionListener(setRoleAction);
500        tfRole.getDocument().addDocumentListener(setRoleAction);
501        tfRole.addActionListener(setRoleAction);
502        memberTableModel.getSelectionModel().addListSelectionListener(
503                e -> tfRole.setEnabled(memberTable.getSelectedRowCount() > 0)
504        );
505        tfRole.setEnabled(memberTable.getSelectedRowCount() > 0);
506        JButton btnApply = new JButton(setRoleAction);
507        btnApply.setPreferredSize(new Dimension(20, 20));
508        btnApply.setText("");
509        p3.add(btnApply);
510
511        gc.gridx = 1;
512        gc.gridy = 2;
513        gc.fill = GridBagConstraints.HORIZONTAL;
514        gc.anchor = GridBagConstraints.LAST_LINE_START;
515        gc.weightx = 1.0;
516        gc.weighty = 0.0;
517        pnl.add(p3, gc);
518
519        JPanel pnl2 = new JPanel(new GridBagLayout());
520
521        gc.gridx = 0;
522        gc.gridy = 0;
523        gc.gridheight = 1;
524        gc.gridwidth = 3;
525        gc.fill = GridBagConstraints.HORIZONTAL;
526        gc.anchor = GridBagConstraints.FIRST_LINE_START;
527        gc.weightx = 1.0;
528        gc.weighty = 0.0;
529        pnl2.add(new JLabel(tr("Selection")), gc);
530
531        gc.gridx = 0;
532        gc.gridy = 1;
533        gc.gridheight = 1;
534        gc.gridwidth = 1;
535        gc.fill = GridBagConstraints.VERTICAL;
536        gc.anchor = GridBagConstraints.NORTHWEST;
537        gc.weightx = 0.0;
538        gc.weighty = 1.0;
539        pnl2.add(new ScrollViewport(buildSelectionControlButtonToolbar(memberTable, memberTableModel, selectionTableModel, re),
540                ScrollViewport.VERTICAL_DIRECTION), gc);
541
542        gc.gridx = 1;
543        gc.gridy = 1;
544        gc.weightx = 1.0;
545        gc.weighty = 1.0;
546        gc.fill = GridBagConstraints.BOTH;
547        pnl2.add(buildSelectionTablePanel(selectionTable), gc);
548
549        final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
550        splitPane.setLeftComponent(pnl);
551        splitPane.setRightComponent(pnl2);
552        splitPane.setOneTouchExpandable(false);
553        if (re instanceof Window) {
554            ((Window) re).addWindowListener(new WindowAdapter() {
555                @Override
556                public void windowOpened(WindowEvent e) {
557                    // has to be called when the window is visible, otherwise no effect
558                    splitPane.setDividerLocation(0.6);
559                }
560            });
561        }
562
563        JPanel pnl3 = new JPanel(new BorderLayout());
564        pnl3.add(splitPane, BorderLayout.CENTER);
565
566        return pnl3;
567    }
568
569    /**
570     * builds the panel with the table displaying the currently selected primitives
571     * @param selectionTable selection table
572     *
573     * @return panel with current selection
574     */
575    protected static JPanel buildSelectionTablePanel(SelectionTable selectionTable) {
576        JPanel pnl = new JPanel(new BorderLayout());
577        pnl.add(new JScrollPane(selectionTable), BorderLayout.CENTER);
578        return pnl;
579    }
580
581    /**
582     * builds the {@link JSplitPane} which divides the editor in an upper and a lower half
583     * @param top top panel
584     * @param bottom bottom panel
585     * @param re relation editor
586     *
587     * @return the split panel
588     */
589    protected static JSplitPane buildSplitPane(JPanel top, JPanel bottom, IRelationEditor re) {
590        final JSplitPane pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
591        pane.setTopComponent(top);
592        pane.setBottomComponent(bottom);
593        pane.setOneTouchExpandable(true);
594        if (re instanceof Window) {
595            ((Window) re).addWindowListener(new WindowAdapter() {
596                @Override
597                public void windowOpened(WindowEvent e) {
598                    // has to be called when the window is visible, otherwise no effect
599                    pane.setDividerLocation(0.3);
600                }
601            });
602        }
603        return pane;
604    }
605
606    /**
607     * The toolbar with the buttons on the left
608     */
609    static class LeftButtonToolbar extends JToolBar {
610
611        /**
612         * Button for performing the {@link org.openstreetmap.josm.gui.dialogs.relation.actions.SortBelowAction}.
613         */
614        final JButton sortBelowButton;
615
616        /**
617         * Constructs a new {@code LeftButtonToolbar}.
618         * @param memberTable member table
619         * @param memberTableModel member table model
620         * @param re relation editor
621         */
622        LeftButtonToolbar(MemberTable memberTable, MemberTableModel memberTableModel, IRelationEditor re) {
623            setOrientation(JToolBar.VERTICAL);
624            setFloatable(false);
625
626            // -- move up action
627            MoveUpAction moveUpAction = new MoveUpAction(memberTable, memberTableModel, "moveUp");
628            memberTableModel.getSelectionModel().addListSelectionListener(moveUpAction);
629            add(moveUpAction);
630
631            // -- move down action
632            MoveDownAction moveDownAction = new MoveDownAction(memberTable, memberTableModel, "moveDown");
633            memberTableModel.getSelectionModel().addListSelectionListener(moveDownAction);
634            add(moveDownAction);
635
636            addSeparator();
637
638            // -- edit action
639            EditAction editAction = new EditAction(memberTable, memberTableModel, re.getLayer());
640            memberTableModel.getSelectionModel().addListSelectionListener(editAction);
641            add(editAction);
642
643            // -- delete action
644            RemoveAction removeSelectedAction = new RemoveAction(memberTable, memberTableModel, "removeSelected");
645            memberTable.getSelectionModel().addListSelectionListener(removeSelectedAction);
646            add(removeSelectedAction);
647
648            addSeparator();
649            // -- sort action
650            SortAction sortAction = new SortAction(memberTable, memberTableModel);
651            memberTableModel.addTableModelListener(sortAction);
652            add(sortAction);
653            final SortBelowAction sortBelowAction = new SortBelowAction(memberTable, memberTableModel);
654            memberTableModel.addTableModelListener(sortBelowAction);
655            memberTableModel.getSelectionModel().addListSelectionListener(sortBelowAction);
656            sortBelowButton = add(sortBelowAction);
657
658            // -- reverse action
659            ReverseAction reverseAction = new ReverseAction(memberTable, memberTableModel);
660            memberTableModel.addTableModelListener(reverseAction);
661            add(reverseAction);
662
663            addSeparator();
664
665            // -- download action
666            DownloadIncompleteMembersAction downloadIncompleteMembersAction = new DownloadIncompleteMembersAction(
667                    memberTable, memberTableModel, "downloadIncomplete", re.getLayer(), re);
668            memberTable.getModel().addTableModelListener(downloadIncompleteMembersAction);
669            add(downloadIncompleteMembersAction);
670
671            // -- download selected action
672            DownloadSelectedIncompleteMembersAction downloadSelectedIncompleteMembersAction = new DownloadSelectedIncompleteMembersAction(
673                    memberTable, memberTableModel, null, re.getLayer(), re);
674            memberTable.getModel().addTableModelListener(downloadSelectedIncompleteMembersAction);
675            memberTable.getSelectionModel().addListSelectionListener(downloadSelectedIncompleteMembersAction);
676            add(downloadSelectedIncompleteMembersAction);
677
678            InputMap inputMap = memberTable.getInputMap(MemberTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
679            inputMap.put((KeyStroke) removeSelectedAction.getValue(AbstractAction.ACCELERATOR_KEY), "removeSelected");
680            inputMap.put((KeyStroke) moveUpAction.getValue(AbstractAction.ACCELERATOR_KEY), "moveUp");
681            inputMap.put((KeyStroke) moveDownAction.getValue(AbstractAction.ACCELERATOR_KEY), "moveDown");
682            inputMap.put((KeyStroke) downloadIncompleteMembersAction.getValue(AbstractAction.ACCELERATOR_KEY), "downloadIncomplete");
683        }
684    }
685
686    /**
687     * build the toolbar with the buttons for adding or removing the current selection
688     * @param memberTable member table
689     * @param memberTableModel member table model
690     * @param selectionTableModel selection table model
691     * @param re relation editor
692     *
693     * @return control buttons panel for selection/members
694     */
695    protected static JToolBar buildSelectionControlButtonToolbar(MemberTable memberTable,
696            MemberTableModel memberTableModel, SelectionTableModel selectionTableModel, IRelationEditor re) {
697        JToolBar tb = new JToolBar(JToolBar.VERTICAL);
698        tb.setFloatable(false);
699
700        // -- add at start action
701        AddSelectedAtStartAction addSelectionAction = new AddSelectedAtStartAction(
702                memberTableModel, selectionTableModel, re);
703        selectionTableModel.addTableModelListener(addSelectionAction);
704        tb.add(addSelectionAction);
705
706        // -- add before selected action
707        AddSelectedBeforeSelection addSelectedBeforeSelectionAction = new AddSelectedBeforeSelection(
708                memberTableModel, selectionTableModel, re);
709        selectionTableModel.addTableModelListener(addSelectedBeforeSelectionAction);
710        memberTableModel.getSelectionModel().addListSelectionListener(addSelectedBeforeSelectionAction);
711        tb.add(addSelectedBeforeSelectionAction);
712
713        // -- add after selected action
714        AddSelectedAfterSelection addSelectedAfterSelectionAction = new AddSelectedAfterSelection(
715                memberTableModel, selectionTableModel, re);
716        selectionTableModel.addTableModelListener(addSelectedAfterSelectionAction);
717        memberTableModel.getSelectionModel().addListSelectionListener(addSelectedAfterSelectionAction);
718        tb.add(addSelectedAfterSelectionAction);
719
720        // -- add at end action
721        AddSelectedAtEndAction addSelectedAtEndAction = new AddSelectedAtEndAction(
722                memberTableModel, selectionTableModel, re);
723        selectionTableModel.addTableModelListener(addSelectedAtEndAction);
724        tb.add(addSelectedAtEndAction);
725
726        tb.addSeparator();
727
728        // -- select members action
729        SelectedMembersForSelectionAction selectMembersForSelectionAction = new SelectedMembersForSelectionAction(
730                memberTableModel, selectionTableModel, re.getLayer());
731        selectionTableModel.addTableModelListener(selectMembersForSelectionAction);
732        memberTableModel.addTableModelListener(selectMembersForSelectionAction);
733        tb.add(selectMembersForSelectionAction);
734
735        // -- select action
736        SelectPrimitivesForSelectedMembersAction selectAction = new SelectPrimitivesForSelectedMembersAction(
737                memberTable, memberTableModel, re.getLayer());
738        memberTable.getSelectionModel().addListSelectionListener(selectAction);
739        tb.add(selectAction);
740
741        tb.addSeparator();
742
743        // -- remove selected action
744        RemoveSelectedAction removeSelectedAction = new RemoveSelectedAction(memberTableModel, selectionTableModel, re.getLayer());
745        selectionTableModel.addTableModelListener(removeSelectedAction);
746        tb.add(removeSelectedAction);
747
748        return tb;
749    }
750
751    @Override
752    protected Dimension findMaxDialogSize() {
753        return new Dimension(700, 650);
754    }
755
756    @Override
757    public void setVisible(boolean visible) {
758        if (isVisible() == visible) {
759            return;
760        }
761        if (visible) {
762            tagEditorPanel.initAutoCompletion(getLayer());
763        }
764        super.setVisible(visible);
765        Clipboard clipboard = ClipboardUtils.getClipboard();
766        if (visible) {
767            leftButtonToolbar.sortBelowButton.setVisible(ExpertToggleAction.isExpert());
768            RelationDialogManager.getRelationDialogManager().positionOnScreen(this);
769            if (windowMenuItem == null) {
770                windowMenuItem = addToWindowMenu(this, getLayer().getName());
771            }
772            tagEditorPanel.requestFocusInWindow();
773            for (FlavorListener listener : clipboardListeners) {
774                clipboard.addFlavorListener(listener);
775            }
776        } else {
777            // make sure all registered listeners are unregistered
778            //
779            memberTable.stopHighlighting();
780            selectionTableModel.unregister();
781            memberTableModel.unregister();
782            memberTable.unregisterListeners();
783            if (windowMenuItem != null) {
784                MainApplication.getMenu().windowMenu.remove(windowMenuItem);
785                windowMenuItem = null;
786            }
787            for (FlavorListener listener : clipboardListeners) {
788                clipboard.removeFlavorListener(listener);
789            }
790            dispose();
791        }
792    }
793
794    /**
795     * Adds current relation editor to the windows menu (in the "volatile" group)
796     * @param re relation editor
797     * @param layerName layer name
798     * @return created menu item
799     */
800    protected static JMenuItem addToWindowMenu(IRelationEditor re, String layerName) {
801        Relation r = re.getRelation();
802        String name = r == null ? tr("New Relation") : r.getLocalName();
803        JosmAction focusAction = new JosmAction(
804                tr("Relation Editor: {0}", name == null && r != null ? r.getId() : name),
805                "dialogs/relationlist",
806                tr("Focus Relation Editor with relation ''{0}'' in layer ''{1}''", name, layerName),
807                null, false, false) {
808            @Override
809            public void actionPerformed(ActionEvent e) {
810                ((RelationEditor) getValue("relationEditor")).setVisible(true);
811            }
812        };
813        focusAction.putValue("relationEditor", re);
814        return MainMenu.add(MainApplication.getMenu().windowMenu, focusAction, MainMenu.WINDOW_MENU_GROUP.VOLATILE);
815    }
816
817    /**
818     * checks whether the current relation has members referring to itself. If so,
819     * warns the users and provides an option for removing these members.
820     * @param memberTableModel member table model
821     * @param relation relation
822     */
823    protected static void cleanSelfReferences(MemberTableModel memberTableModel, Relation relation) {
824        List<OsmPrimitive> toCheck = new ArrayList<>();
825        toCheck.add(relation);
826        if (memberTableModel.hasMembersReferringTo(toCheck)) {
827            int ret = ConditionalOptionPaneUtil.showOptionDialog(
828                    "clean_relation_self_references",
829                    Main.parent,
830                    tr("<html>There is at least one member in this relation referring<br>"
831                            + "to the relation itself.<br>"
832                            + "This creates circular dependencies and is discouraged.<br>"
833                            + "How do you want to proceed with circular dependencies?</html>"),
834                            tr("Warning"),
835                            JOptionPane.YES_NO_OPTION,
836                            JOptionPane.WARNING_MESSAGE,
837                            new String[]{tr("Remove them, clean up relation"), tr("Ignore them, leave relation as is")},
838                            tr("Remove them, clean up relation")
839            );
840            switch(ret) {
841            case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION:
842            case JOptionPane.CLOSED_OPTION:
843            case JOptionPane.NO_OPTION:
844                return;
845            case JOptionPane.YES_OPTION:
846                memberTableModel.removeMembersReferringTo(toCheck);
847                break;
848            default: // Do nothing
849            }
850        }
851    }
852
853    private void registerCopyPasteAction(AbstractAction action, Object actionName, KeyStroke shortcut,
854            JRootPane rootPane, JTable... tables) {
855        int mods = shortcut.getModifiers();
856        int code = shortcut.getKeyCode();
857        if (code != KeyEvent.VK_INSERT && (mods == 0 || mods == InputEvent.SHIFT_DOWN_MASK)) {
858            Logging.info(tr("Sorry, shortcut \"{0}\" can not be enabled in Relation editor dialog"), shortcut);
859            return;
860        }
861        rootPane.getActionMap().put(actionName, action);
862        rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcut, actionName);
863        // Assign also to JTables because they have their own Copy&Paste implementation
864        // (which is disabled in this case but eats key shortcuts anyway)
865        for (JTable table : tables) {
866            table.getInputMap(JComponent.WHEN_FOCUSED).put(shortcut, actionName);
867            table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(shortcut, actionName);
868            table.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcut, actionName);
869        }
870        if (action instanceof FlavorListener) {
871            clipboardListeners.add((FlavorListener) action);
872        }
873    }
874
875    /**
876     * Exception thrown when user aborts add operation.
877     */
878    public static class AddAbortException extends Exception {
879    }
880
881    /**
882     * Asks confirmationbefore adding a primitive.
883     * @param primitive primitive to add
884     * @return {@code true} is user confirms the operation, {@code false} otherwise
885     * @throws AddAbortException if user aborts operation
886     */
887    public static boolean confirmAddingPrimitive(OsmPrimitive primitive) throws AddAbortException {
888        String msg = tr("<html>This relation already has one or more members referring to<br>"
889                + "the object ''{0}''<br>"
890                + "<br>"
891                + "Do you really want to add another relation member?</html>",
892                Utils.escapeReservedCharactersHTML(primitive.getDisplayName(DefaultNameFormatter.getInstance()))
893            );
894        int ret = ConditionalOptionPaneUtil.showOptionDialog(
895                "add_primitive_to_relation",
896                Main.parent,
897                msg,
898                tr("Multiple members referring to same object."),
899                JOptionPane.YES_NO_CANCEL_OPTION,
900                JOptionPane.WARNING_MESSAGE,
901                null,
902                null
903        );
904        switch(ret) {
905        case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION:
906        case JOptionPane.YES_OPTION:
907            return true;
908        case JOptionPane.NO_OPTION:
909        case JOptionPane.CLOSED_OPTION:
910            return false;
911        case JOptionPane.CANCEL_OPTION:
912        default:
913            throw new AddAbortException();
914        }
915    }
916
917    /**
918     * Warn about circular references.
919     * @param primitive the concerned primitive
920     */
921    public static void warnOfCircularReferences(OsmPrimitive primitive) {
922        String msg = tr("<html>You are trying to add a relation to itself.<br>"
923                + "<br>"
924                + "This creates circular references and is therefore discouraged.<br>"
925                + "Skipping relation ''{0}''.</html>",
926                Utils.escapeReservedCharactersHTML(primitive.getDisplayName(DefaultNameFormatter.getInstance())));
927        JOptionPane.showMessageDialog(
928                Main.parent,
929                msg,
930                tr("Warning"),
931                JOptionPane.WARNING_MESSAGE);
932    }
933
934    /**
935     * Adds primitives to a given relation.
936     * @param orig The relation to modify
937     * @param primitivesToAdd The primitives to add as relation members
938     * @return The resulting command
939     * @throws IllegalArgumentException if orig is null
940     */
941    public static Command addPrimitivesToRelation(final Relation orig, Collection<? extends OsmPrimitive> primitivesToAdd) {
942        CheckParameterUtil.ensureParameterNotNull(orig, "orig");
943        try {
944            final Collection<TaggingPreset> presets = TaggingPresets.getMatchingPresets(
945                    EnumSet.of(TaggingPresetType.forPrimitive(orig)), orig.getKeys(), false);
946            Relation relation = new Relation(orig);
947            boolean modified = false;
948            for (OsmPrimitive p : primitivesToAdd) {
949                if (p instanceof Relation && orig.equals(p)) {
950                    if (!GraphicsEnvironment.isHeadless()) {
951                        warnOfCircularReferences(p);
952                    }
953                    continue;
954                } else if (MemberTableModel.hasMembersReferringTo(relation.getMembers(), Collections.singleton(p))
955                        && !confirmAddingPrimitive(p)) {
956                    continue;
957                }
958                final Set<String> roles = findSuggestedRoles(presets, p);
959                relation.addMember(new RelationMember(roles.size() == 1 ? roles.iterator().next() : "", p));
960                modified = true;
961            }
962            return modified ? new ChangeCommand(orig, relation) : null;
963        } catch (AddAbortException ign) {
964            Logging.trace(ign);
965            return null;
966        }
967    }
968
969    protected static Set<String> findSuggestedRoles(final Collection<TaggingPreset> presets, OsmPrimitive p) {
970        final Set<String> roles = new HashSet<>();
971        for (TaggingPreset preset : presets) {
972            String role = preset.suggestRoleForOsmPrimitive(p);
973            if (role != null && !role.isEmpty()) {
974                roles.add(role);
975            }
976        }
977        return roles;
978    }
979
980    class MemberTableDblClickAdapter extends MouseAdapter {
981        @Override
982        public void mouseClicked(MouseEvent e) {
983            if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
984                new EditAction(memberTable, memberTableModel, getLayer()).actionPerformed(null);
985            }
986        }
987    }
988}
Note: See TracBrowser for help on using the repository browser.