001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Dimension;
007import java.awt.GridBagLayout;
008import java.awt.Rectangle;
009import java.awt.event.ActionEvent;
010import java.awt.event.KeyEvent;
011import java.awt.event.KeyListener;
012import java.awt.event.MouseAdapter;
013import java.awt.event.MouseEvent;
014import java.util.List;
015import java.util.Locale;
016import java.util.Map;
017
018import javax.swing.AbstractAction;
019import javax.swing.ImageIcon;
020import javax.swing.JMenuItem;
021import javax.swing.JOptionPane;
022import javax.swing.JPanel;
023import javax.swing.JPopupMenu;
024import javax.swing.JScrollPane;
025import javax.swing.JTree;
026import javax.swing.tree.DefaultMutableTreeNode;
027import javax.swing.tree.TreePath;
028
029import org.openstreetmap.josm.actions.ValidateAction;
030import org.openstreetmap.josm.data.validation.OsmValidator;
031import org.openstreetmap.josm.data.validation.TestError;
032import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
033import org.openstreetmap.josm.gui.ExtendedDialog;
034import org.openstreetmap.josm.gui.MainApplication;
035import org.openstreetmap.josm.gui.MapFrame;
036import org.openstreetmap.josm.gui.util.GuiHelper;
037import org.openstreetmap.josm.tools.GBC;
038import org.openstreetmap.josm.tools.ImageProvider;
039import org.openstreetmap.josm.tools.Logging;
040
041
042/**
043 * A management window for the validator's ignorelist
044 * @author Taylor Smock
045 * @since 14828
046 */
047public class ValidatorListManagementDialog extends ExtendedDialog {
048    enum BUTTONS {
049        OK(0, tr("OK"), new ImageProvider("ok")),
050        CANCEL(1, tr("Cancel"), new ImageProvider("cancel"));
051
052        private int index;
053        private String name;
054        private ImageIcon icon;
055
056        BUTTONS(int index, String name, ImageProvider image) {
057            this.index = index;
058            this.name = name;
059            Dimension dim = new Dimension();
060            ImageIcon sizeto = new ImageProvider("ok").getResource().getImageIcon();
061            dim.setSize(-1, sizeto.getIconHeight());
062            this.icon = image.getResource().getImageIcon(dim);
063        }
064
065        public ImageIcon getImageIcon() {
066            return icon;
067        }
068
069        public int getIndex() {
070            return index;
071        }
072
073        public String getName() {
074            return name;
075        }
076    }
077
078    private static final String[] BUTTON_TEXTS = {BUTTONS.OK.getName(), BUTTONS.CANCEL.getName()};
079
080    private static final ImageIcon[] BUTTON_IMAGES = {BUTTONS.OK.getImageIcon(), BUTTONS.CANCEL.getImageIcon()};
081
082    private final JPanel panel = new JPanel(new GridBagLayout());
083
084    private final JTree ignoreErrors;
085
086    private final String type;
087
088    /**
089     * Create a new {@link ValidatorListManagementDialog}
090     * @param type The type of list to create (first letter may or may not be
091     * capitalized, it is put into all lowercase after building the title)
092     */
093    public ValidatorListManagementDialog(String type) {
094        super(MainApplication.getMainFrame(), tr("Validator {0} List Management", type), BUTTON_TEXTS, false);
095        this.type = type.toLowerCase(Locale.ENGLISH);
096        setButtonIcons(BUTTON_IMAGES);
097
098        ignoreErrors = buildList();
099        JScrollPane scroll = GuiHelper.embedInVerticalScrollPane(ignoreErrors);
100
101        panel.add(scroll, GBC.eol().fill(GBC.BOTH).anchor(GBC.CENTER));
102        setContent(panel);
103        setDefaultButton(1);
104        setupDialog();
105        setModal(true);
106        showDialog();
107    }
108
109    @Override
110    public void buttonAction(int buttonIndex, ActionEvent evt) {
111        // Currently OK/Cancel buttons do nothing
112        final int answer;
113        if (buttonIndex == BUTTONS.OK.getIndex()) {
114            Map<String, String> errors = OsmValidator.getIgnoredErrors();
115            Map<String, String> tree = OsmValidator.buildIgnore(ignoreErrors);
116            if (!errors.equals(tree)) {
117                answer = rerunValidatorPrompt();
118                if (answer == JOptionPane.YES_OPTION || answer == JOptionPane.NO_OPTION) {
119                    OsmValidator.resetErrorList();
120                    tree.forEach(OsmValidator::addIgnoredError);
121                    OsmValidator.saveIgnoredErrors();
122                    OsmValidator.initialize();
123                }
124            }
125            dispose();
126        } else {
127            super.buttonAction(buttonIndex, evt);
128        }
129    }
130
131    /**
132     * Build a JTree with a list
133     * @return &lt;type&gt;list as a {@code JTree}
134     */
135    public JTree buildList() {
136        JTree tree;
137
138        if ("ignore".equals(type)) {
139            tree = OsmValidator.buildJTreeList();
140        } else {
141            Logging.error(tr("Cannot understand the following type: {0}", type));
142            return null;
143        }
144        tree.setRootVisible(false);
145        tree.setShowsRootHandles(true);
146        tree.addMouseListener(new MouseAdapter() {
147            @Override
148            public void mousePressed(MouseEvent e) {
149                process(e);
150            }
151
152            @Override
153            public void mouseReleased(MouseEvent e) {
154                process(e);
155            }
156
157            private void process(MouseEvent e) {
158                if (e.isPopupTrigger()) {
159                    TreePath[] paths = tree.getSelectionPaths();
160                    if (paths == null) return;
161                    Rectangle bounds = tree.getUI().getPathBounds(tree, paths[0]);
162                    if (bounds != null) {
163                        JPopupMenu menu = new JPopupMenu();
164                        JMenuItem delete = new JMenuItem(new AbstractAction(tr("Don''t ignore")) {
165                            @Override
166                            public void actionPerformed(ActionEvent e1) {
167                                deleteAction(tree, paths);
168                            }
169                        });
170                        menu.add(delete);
171                        menu.show(e.getComponent(), e.getX(), e.getY());
172                    }
173                }
174            }
175        });
176
177        tree.addKeyListener(new KeyListener() {
178
179            @Override
180            public void keyTyped(KeyEvent e) {
181                // Do nothing
182            }
183
184            @Override
185            public void keyPressed(KeyEvent e) {
186                // Do nothing
187            }
188
189            @Override
190            public void keyReleased(KeyEvent e) {
191                TreePath[] paths = tree.getSelectionPaths();
192                if (e.getKeyCode() == KeyEvent.VK_DELETE && paths != null) {
193                    deleteAction(tree, paths);
194                }
195            }
196        });
197        return tree;
198    }
199
200    private static void deleteAction(JTree tree, TreePath[] paths) {
201        for (TreePath path : paths) {
202            tree.clearSelection();
203            tree.addSelectionPath(path);
204            DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
205            DefaultMutableTreeNode parent = (DefaultMutableTreeNode) node.getParent();
206            node.removeAllChildren();
207            while (node.getChildCount() == 0) {
208                node.removeFromParent();
209                node = parent;
210                if (parent == null || parent.isRoot()) break;
211                parent = (DefaultMutableTreeNode) node.getParent();
212            }
213        }
214        tree.updateUI();
215    }
216
217
218    /**
219     * Prompt to rerun the validator when the ignore list changes
220     * @return {@code JOptionPane.YES_OPTION}, {@code JOptionPane.NO_OPTION},
221     *  or {@code JOptionPane.CANCEL_OPTION}
222     */
223    public int rerunValidatorPrompt() {
224        MapFrame map = MainApplication.getMap();
225        List<TestError> errors = map.validatorDialog.tree.getErrors();
226        ValidateAction validateAction = ValidatorDialog.validateAction;
227        if (!validateAction.isEnabled() || errors == null || errors.isEmpty()) return JOptionPane.NO_OPTION;
228        final int answer = ConditionalOptionPaneUtil.showOptionDialog(
229                "rerun_validation_when_ignorelist_changed",
230                MainApplication.getMainFrame(),
231                tr("{0}Should the validation be rerun?{1}", "<hmtl><h3>", "</h3></html>"),
232                tr("Ignored error filter changed"),
233                JOptionPane.YES_NO_CANCEL_OPTION,
234                JOptionPane.QUESTION_MESSAGE,
235                null,
236                null);
237        if (answer == JOptionPane.YES_OPTION) {
238            validateAction.doValidate(true);
239        }
240        return answer;
241    }
242}