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;
040import org.openstreetmap.josm.tools.Utils;
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 final int index;
053        private final String name;
054        @SuppressWarnings("ImmutableEnumChecker")
055        private final ImageIcon icon;
056
057        BUTTONS(int index, String name, ImageProvider image) {
058            this.index = index;
059            this.name = name;
060            Dimension dim = new Dimension();
061            ImageIcon sizeto = new ImageProvider("ok").getResource().getImageIcon();
062            dim.setSize(-1, sizeto.getIconHeight());
063            this.icon = image.getResource().getImageIcon(dim);
064        }
065
066        public ImageIcon getImageIcon() {
067            return icon;
068        }
069
070        public int getIndex() {
071            return index;
072        }
073
074        public String getName() {
075            return name;
076        }
077    }
078
079    private static final String[] BUTTON_TEXTS = {BUTTONS.OK.getName(), BUTTONS.CANCEL.getName()};
080
081    private static final ImageIcon[] BUTTON_IMAGES = {BUTTONS.OK.getImageIcon(), BUTTONS.CANCEL.getImageIcon()};
082
083    private final JPanel panel = new JPanel(new GridBagLayout());
084
085    private final JTree ignoreErrors;
086
087    private final String type;
088
089    /**
090     * Create a new {@link ValidatorListManagementDialog}
091     * @param type The type of list to create (first letter may or may not be
092     * capitalized, it is put into all lowercase after building the title)
093     */
094    public ValidatorListManagementDialog(String type) {
095        super(MainApplication.getMainFrame(), tr("Validator {0} List Management", type), BUTTON_TEXTS, false);
096        this.type = type.toLowerCase(Locale.ENGLISH);
097        setButtonIcons(BUTTON_IMAGES);
098
099        ignoreErrors = buildList();
100        JScrollPane scroll = GuiHelper.embedInVerticalScrollPane(ignoreErrors);
101
102        panel.add(scroll, GBC.eol().fill(GBC.BOTH).anchor(GBC.CENTER));
103        setContent(panel);
104        setDefaultButton(1);
105        setupDialog();
106        setModal(true);
107        showDialog();
108    }
109
110    @Override
111    public void buttonAction(int buttonIndex, ActionEvent evt) {
112        // Currently OK/Cancel buttons do nothing
113        final int answer;
114        if (buttonIndex == BUTTONS.OK.getIndex()) {
115            Map<String, String> errors = OsmValidator.getIgnoredErrors();
116            Map<String, String> tree = OsmValidator.buildIgnore(ignoreErrors);
117            if (!errors.equals(tree)) {
118                answer = rerunValidatorPrompt();
119                if (answer == JOptionPane.YES_OPTION || answer == JOptionPane.NO_OPTION) {
120                    OsmValidator.resetErrorList();
121                    tree.forEach(OsmValidator::addIgnoredError);
122                    OsmValidator.saveIgnoredErrors();
123                    OsmValidator.initialize();
124                }
125            }
126            dispose();
127        } else {
128            super.buttonAction(buttonIndex, evt);
129        }
130    }
131
132    /**
133     * Build a JTree with a list
134     * @return &lt;type&gt;list as a {@code JTree}
135     */
136    public JTree buildList() {
137        JTree tree;
138
139        if ("ignore".equals(type)) {
140            tree = OsmValidator.buildJTreeList();
141        } else {
142            Logging.error(tr("Cannot understand the following type: {0}", type));
143            return null;
144        }
145        tree.setRootVisible(false);
146        tree.setShowsRootHandles(true);
147        tree.addMouseListener(new MouseAdapter() {
148            @Override
149            public void mousePressed(MouseEvent e) {
150                process(e);
151            }
152
153            @Override
154            public void mouseReleased(MouseEvent e) {
155                process(e);
156            }
157
158            private void process(MouseEvent e) {
159                if (e.isPopupTrigger()) {
160                    TreePath[] paths = tree.getSelectionPaths();
161                    if (paths == null) return;
162                    Rectangle bounds = tree.getUI().getPathBounds(tree, paths[0]);
163                    if (bounds != null) {
164                        JPopupMenu menu = new JPopupMenu();
165                        JMenuItem delete = new JMenuItem(new AbstractAction(tr("Don''t ignore")) {
166                            @Override
167                            public void actionPerformed(ActionEvent e1) {
168                                deleteAction(tree, paths);
169                            }
170                        });
171                        menu.add(delete);
172                        menu.show(e.getComponent(), e.getX(), e.getY());
173                    }
174                }
175            }
176        });
177
178        tree.addKeyListener(new KeyListener() {
179
180            @Override
181            public void keyTyped(KeyEvent e) {
182                // Do nothing
183            }
184
185            @Override
186            public void keyPressed(KeyEvent e) {
187                // Do nothing
188            }
189
190            @Override
191            public void keyReleased(KeyEvent e) {
192                TreePath[] paths = tree.getSelectionPaths();
193                if (e.getKeyCode() == KeyEvent.VK_DELETE && paths != null) {
194                    deleteAction(tree, paths);
195                }
196            }
197        });
198        return tree;
199    }
200
201    private static void deleteAction(JTree tree, TreePath[] paths) {
202        for (TreePath path : paths) {
203            tree.clearSelection();
204            tree.addSelectionPath(path);
205            DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
206            DefaultMutableTreeNode parent = (DefaultMutableTreeNode) node.getParent();
207            node.removeAllChildren();
208            while (node.getChildCount() == 0) {
209                node.removeFromParent();
210                node = parent;
211                if (parent == null || parent.isRoot()) break;
212                parent = (DefaultMutableTreeNode) node.getParent();
213            }
214        }
215        tree.updateUI();
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() || Utils.isEmpty(errors)) 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}