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