source: josm/trunk/src/org/openstreetmap/josm/gui/preferences/advanced/AdvancedPreference.java@ 9622

Last change on this file since 9622 was 9622, checked in by Don-vip, 8 years ago

add more unit tests + fix some sonar issues

  • Property svn:eol-style set to native
File size: 17.8 KB
RevLine 
[6380]1// License: GPL. For details, see LICENSE file.
[4634]2package org.openstreetmap.josm.gui.preferences.advanced;
[626]3
[6248]4import static org.openstreetmap.josm.tools.I18n.marktr;
[626]5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.Dimension;
8import java.awt.event.ActionEvent;
9import java.awt.event.ActionListener;
[5114]10import java.io.File;
[6021]11import java.io.IOException;
[3536]12import java.util.ArrayList;
[4874]13import java.util.Collections;
[5114]14import java.util.Comparator;
[6022]15import java.util.LinkedHashMap;
[4634]16import java.util.List;
[8404]17import java.util.Locale;
[626]18import java.util.Map;
19import java.util.Map.Entry;
[7536]20import java.util.Objects;
[6248]21
[6021]22import javax.swing.AbstractAction;
[626]23import javax.swing.Box;
24import javax.swing.JButton;
[5114]25import javax.swing.JFileChooser;
[626]26import javax.swing.JLabel;
[6022]27import javax.swing.JMenu;
[626]28import javax.swing.JOptionPane;
29import javax.swing.JPanel;
[6021]30import javax.swing.JPopupMenu;
[626]31import javax.swing.JScrollPane;
[1576]32import javax.swing.event.DocumentEvent;
33import javax.swing.event.DocumentListener;
[6022]34import javax.swing.event.MenuEvent;
35import javax.swing.event.MenuListener;
[5114]36import javax.swing.filechooser.FileFilter;
[626]37
38import org.openstreetmap.josm.Main;
[5438]39import org.openstreetmap.josm.actions.DiskAccessAction;
[5114]40import org.openstreetmap.josm.data.CustomConfigurator;
[4634]41import org.openstreetmap.josm.data.Preferences;
42import org.openstreetmap.josm.data.Preferences.Setting;
[8469]43import org.openstreetmap.josm.gui.dialogs.LogShowDialog;
[4969]44import org.openstreetmap.josm.gui.preferences.DefaultTabPreferenceSetting;
[4874]45import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
46import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
47import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
[6021]48import org.openstreetmap.josm.gui.util.GuiHelper;
[7578]49import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
[5886]50import org.openstreetmap.josm.gui.widgets.JosmTextField;
[626]51import org.openstreetmap.josm.tools.GBC;
[8404]52import org.openstreetmap.josm.tools.Utils;
[626]53
[6767]54/**
55 * Advanced preferences, allowing to set preference entries directly.
56 */
[6362]57public final class AdvancedPreference extends DefaultTabPreferenceSetting {
[626]58
[6529]59 /**
60 * Factory used to create a new {@code AdvancedPreference}.
61 */
[1742]62 public static class Factory implements PreferenceSettingFactory {
[6021]63 @Override
[1742]64 public PreferenceSetting createPreferenceSetting() {
65 return new AdvancedPreference();
66 }
67 }
[5114]68
[8468]69 private List<PrefEntry> allData;
[9078]70 private final List<PrefEntry> displayData = new ArrayList<>();
[8468]71 private JosmTextField txtFilter;
72 private PreferencesTable table;
73
[9622]74 private final Map<String, String> profileTypes = new LinkedHashMap<>();
75
76 private final Comparator<PrefEntry> customComparator = new Comparator<PrefEntry>() {
77 @Override
78 public int compare(PrefEntry o1, PrefEntry o2) {
79 if (o1.isChanged() && !o2.isChanged())
80 return -1;
81 if (o2.isChanged() && !o1.isChanged())
82 return 1;
83 if (!(o1.isDefault()) && o2.isDefault())
84 return -1;
85 if (!(o2.isDefault()) && o1.isDefault())
86 return 1;
87 return o1.compareTo(o2);
88 }
89 };
90
[4969]91 private AdvancedPreference() {
[7668]92 super(/* ICON(preferences/) */ "advanced", tr("Advanced Preferences"), tr("Setting Preference entries directly. Use with caution!"));
[4969]93 }
[1742]94
[4969]95 @Override
96 public boolean isExpert() {
97 return true;
98 }
99
[6021]100 @Override
[2745]101 public void addGui(final PreferenceTabbedPane gui) {
[4969]102 JPanel p = gui.createPreferenceTab(this);
[1677]103
[5886]104 txtFilter = new JosmTextField();
[1576]105 JLabel lbFilter = new JLabel(tr("Search: "));
106 lbFilter.setLabelFor(txtFilter);
107 p.add(lbFilter);
108 p.add(txtFilter, GBC.eol().fill(GBC.HORIZONTAL));
[8510]109 txtFilter.getDocument().addDocumentListener(new DocumentListener() {
110 @Override
111 public void changedUpdate(DocumentEvent e) {
[1576]112 action();
113 }
[8510]114
115 @Override
116 public void insertUpdate(DocumentEvent e) {
[1576]117 action();
118 }
[8510]119
120 @Override
121 public void removeUpdate(DocumentEvent e) {
[1576]122 action();
123 }
[8510]124
[1576]125 private void action() {
[4634]126 applyFilter();
[1576]127 }
128 });
[5114]129 readPreferences(Main.pref);
[6070]130
[4634]131 applyFilter();
[6021]132 table = new PreferencesTable(displayData);
133 JScrollPane scroll = new JScrollPane(table);
[1169]134 p.add(scroll, GBC.eol().fill(GBC.BOTH));
[8510]135 scroll.setPreferredSize(new Dimension(400, 200));
[626]136
[1169]137 JButton add = new JButton(tr("Add"));
138 p.add(Box.createHorizontalGlue(), GBC.std().fill(GBC.HORIZONTAL));
[8510]139 p.add(add, GBC.std().insets(0, 5, 0, 0));
140 add.addActionListener(new ActionListener() {
[6021]141 @Override public void actionPerformed(ActionEvent e) {
142 PrefEntry pe = table.addPreference(gui);
[8510]143 if (pe != null) {
[6021]144 allData.add(pe);
145 Collections.sort(allData);
146 applyFilter();
147 }
[1169]148 }
149 });
[626]150
[1169]151 JButton edit = new JButton(tr("Edit"));
[8510]152 p.add(edit, GBC.std().insets(5, 5, 5, 0));
153 edit.addActionListener(new ActionListener() {
[6021]154 @Override public void actionPerformed(ActionEvent e) {
[9622]155 if (table.editPreference(gui))
156 applyFilter();
[1169]157 }
158 });
[626]159
[4634]160 JButton reset = new JButton(tr("Reset"));
[8510]161 p.add(reset, GBC.std().insets(0, 5, 0, 0));
162 reset.addActionListener(new ActionListener() {
[6021]163 @Override public void actionPerformed(ActionEvent e) {
164 table.resetPreferences(gui);
[1169]165 }
166 });
[626]167
[5114]168 JButton read = new JButton(tr("Read from file"));
[8510]169 p.add(read, GBC.std().insets(5, 5, 0, 0));
170 read.addActionListener(new ActionListener() {
[6021]171 @Override public void actionPerformed(ActionEvent e) {
172 readPreferencesFromXML();
[5114]173 }
174 });
[6070]175
[5114]176 JButton export = new JButton(tr("Export selected items"));
[8510]177 p.add(export, GBC.std().insets(5, 5, 0, 0));
178 export.addActionListener(new ActionListener() {
[6021]179 @Override public void actionPerformed(ActionEvent e) {
180 exportSelectedToXML();
[5114]181 }
182 });
[6070]183
[6021]184 final JButton more = new JButton(tr("More..."));
[8510]185 p.add(more, GBC.std().insets(5, 5, 0, 0));
[6021]186 more.addActionListener(new ActionListener() {
[8285]187 private JPopupMenu menu = buildPopupMenu();
[6021]188 @Override public void actionPerformed(ActionEvent ev) {
189 menu.show(more, 0, 0);
[1169]190 }
191 });
192 }
[626]193
[5114]194 private void readPreferences(Preferences tmpPrefs) {
[7027]195 Map<String, Setting<?>> loaded;
196 Map<String, Setting<?>> orig = Main.pref.getAllSettings();
197 Map<String, Setting<?>> defaults = tmpPrefs.getAllDefaults();
[5114]198 orig.remove("osm-server.password");
199 defaults.remove("osm-server.password");
200 if (tmpPrefs != Main.pref) {
201 loaded = tmpPrefs.getAllSettings();
[5115]202 // plugins preference keys may be changed directly later, after plugins are downloaded
203 // so we do not want to show it in the table as "changed" now
[7027]204 Setting<?> pluginSetting = orig.get("plugins");
[8510]205 if (pluginSetting != null) {
[5162]206 loaded.put("plugins", pluginSetting);
207 }
[5114]208 } else {
209 loaded = orig;
210 }
[6021]211 allData = prepareData(loaded, orig, defaults);
[5114]212 }
[6070]213
[8870]214 private static File[] askUserForCustomSettingsFiles(boolean saveFileFlag, String title) {
[5438]215 FileFilter filter = new FileFilter() {
[5114]216 @Override
217 public boolean accept(File f) {
[8404]218 return f.isDirectory() || Utils.hasExtension(f, "xml");
[5114]219 }
[8510]220
[5114]221 @Override
222 public String getDescription() {
223 return tr("JOSM custom settings files (*.xml)");
224 }
[5438]225 };
[7578]226 AbstractFileChooser fc = DiskAccessAction.createAndOpenFileChooser(!saveFileFlag, !saveFileFlag, title, filter,
227 JFileChooser.FILES_ONLY, "customsettings.lastDirectory");
[5438]228 if (fc != null) {
[6085]229 File[] sel = fc.isMultiSelectionEnabled() ? fc.getSelectedFiles() : (new File[]{fc.getSelectedFile()});
[9622]230 if (sel.length == 1 && !sel[0].getName().contains("."))
231 sel[0] = new File(sel[0].getAbsolutePath()+".xml");
[5438]232 return sel;
[6070]233 }
[5438]234 return new File[0];
[5114]235 }
[6070]236
[6021]237 private void exportSelectedToXML() {
[7005]238 List<String> keys = new ArrayList<>();
[6021]239 boolean hasLists = false;
[6070]240
[6021]241 for (PrefEntry p: table.getSelectedItems()) {
242 // preferences with default values are not saved
243 if (!(p.getValue() instanceof Preferences.StringSetting)) {
244 hasLists = true; // => append and replace differs
245 }
246 if (!p.isDefault()) {
247 keys.add(p.getKey());
248 }
249 }
[6070]250
[6021]251 if (keys.isEmpty()) {
252 JOptionPane.showMessageDialog(Main.parent,
253 tr("Please select some preference keys not marked as default"), tr("Warning"), JOptionPane.WARNING_MESSAGE);
254 return;
255 }
256
257 File[] files = askUserForCustomSettingsFiles(true, tr("Export preferences keys to JOSM customization file"));
258 if (files.length == 0) {
259 return;
260 }
261
262 int answer = 0;
263 if (hasLists) {
264 answer = JOptionPane.showOptionDialog(
265 Main.parent, tr("What to do with preference lists when this file is to be imported?"), tr("Question"),
266 JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null,
267 new String[]{tr("Append preferences from file to existing values"), tr("Replace existing values")}, 0);
268 }
269 CustomConfigurator.exportPreferencesKeysToFile(files[0].getAbsolutePath(), answer == 0, keys);
270 }
[6070]271
[6021]272 private void readPreferencesFromXML() {
273 File[] files = askUserForCustomSettingsFiles(false, tr("Open JOSM customization file"));
[9622]274 if (files.length == 0)
275 return;
[6021]276
277 Preferences tmpPrefs = CustomConfigurator.clonePreferences(Main.pref);
278
279 StringBuilder log = new StringBuilder();
280 log.append("<html>");
281 for (File f : files) {
282 CustomConfigurator.readXML(f, tmpPrefs);
283 log.append(CustomConfigurator.getLog());
284 }
285 log.append("</html>");
286 String msg = log.toString().replace("\n", "<br/>");
287
288 new LogShowDialog(tr("Import log"), tr("<html>Here is file import summary. <br/>"
289 + "You can reject preferences changes by pressing \"Cancel\" in preferences dialog <br/>"
290 + "To activate some changes JOSM restart may be needed.</html>"), msg).showDialog();
291
292 readPreferences(tmpPrefs);
293 // sorting after modification - first modified, then non-default, then default entries
[6022]294 Collections.sort(allData, customComparator);
[6021]295 applyFilter();
296 }
[6070]297
[7027]298 private List<PrefEntry> prepareData(Map<String, Setting<?>> loaded, Map<String, Setting<?>> orig, Map<String, Setting<?>> defaults) {
[7005]299 List<PrefEntry> data = new ArrayList<>();
[7027]300 for (Entry<String, Setting<?>> e : loaded.entrySet()) {
301 Setting<?> value = e.getValue();
302 Setting<?> old = orig.get(e.getKey());
303 Setting<?> def = defaults.get(e.getKey());
[4634]304 if (def == null) {
305 def = value.getNullInstance();
[1865]306 }
[4634]307 PrefEntry en = new PrefEntry(e.getKey(), value, def, false);
[5114]308 // after changes we have nondefault value. Value is changed if is not equal to old value
[7536]309 if (!Objects.equals(old, value)) {
[5114]310 en.markAsChanged();
311 }
[4634]312 data.add(en);
[1576]313 }
[7027]314 for (Entry<String, Setting<?>> e : defaults.entrySet()) {
[5114]315 if (!loaded.containsKey(e.getKey())) {
[4634]316 PrefEntry en = new PrefEntry(e.getKey(), e.getValue(), e.getValue(), true);
[5114]317 // after changes we have default value. So, value is changed if old value is not default
[7027]318 Setting<?> old = orig.get(e.getKey());
319 if (old != null) {
[5114]320 en.markAsChanged();
321 }
[4634]322 data.add(en);
[1865]323 }
[1576]324 }
[4634]325 Collections.sort(data);
[6021]326 displayData.clear();
327 displayData.addAll(data);
328 return data;
[1576]329 }
[6070]330
[6021]331 private JPopupMenu buildPopupMenu() {
332 JPopupMenu menu = new JPopupMenu();
[6022]333 profileTypes.put(marktr("shortcut"), "shortcut\\..*");
334 profileTypes.put(marktr("color"), "color\\..*");
335 profileTypes.put(marktr("toolbar"), "toolbar.*");
336 profileTypes.put(marktr("imagery"), "imagery.*");
[6070]337
[8510]338 for (Entry<String, String> e: profileTypes.entrySet()) {
[6022]339 menu.add(new ExportProfileAction(Main.pref, e.getKey(), e.getValue()));
340 }
[6070]341
[6022]342 menu.addSeparator();
343 menu.add(getProfileMenu());
344 menu.addSeparator();
[6021]345 menu.add(new AbstractAction(tr("Reset preferences")) {
[7027]346 @Override
347 public void actionPerformed(ActionEvent ae) {
[6021]348 if (!GuiHelper.warnUser(tr("Reset preferences"),
349 "<html>"+
350 tr("You are about to clear all preferences to their default values<br />"+
351 "All your settings will be deleted: plugins, imagery, filters, toolbar buttons, keyboard, etc. <br />"+
352 "Are you sure you want to continue?")
353 +"</html>", null, "")) {
354 Main.pref.resetToDefault();
[6246]355 try {
356 Main.pref.save();
357 } catch (IOException e) {
358 Main.warn("IOException while saving preferences: "+e.getMessage());
359 }
[6021]360 readPreferences(Main.pref);
361 applyFilter();
[4880]362 }
363 }
[6021]364 });
365 return menu;
[4634]366 }
[6070]367
[6022]368 private JMenu getProfileMenu() {
[8510]369 final JMenu p = new JMenu(tr("Load profile"));
[6022]370 p.addMenuListener(new MenuListener() {
371 @Override
372 public void menuSelected(MenuEvent me) {
373 p.removeAll();
[8308]374 File[] files = new File(".").listFiles();
375 if (files != null) {
376 for (File f: files) {
377 String s = f.getName();
378 int idx = s.indexOf('_');
[8510]379 if (idx >= 0) {
380 String t = s.substring(0, idx);
[8308]381 if (profileTypes.containsKey(t)) {
382 p.add(new ImportProfileAction(s, f, t));
383 }
384 }
385 }
[6022]386 }
[8308]387 files = Main.pref.getPreferencesDirectory().listFiles();
388 if (files != null) {
389 for (File f: files) {
390 String s = f.getName();
391 int idx = s.indexOf('_');
[8510]392 if (idx >= 0) {
393 String t = s.substring(0, idx);
[8308]394 if (profileTypes.containsKey(t)) {
395 p.add(new ImportProfileAction(s, f, t));
396 }
397 }
398 }
[6022]399 }
400 }
[8510]401
[8308]402 @Override
403 public void menuDeselected(MenuEvent me) {
404 // Not implemented
405 }
[8510]406
[8308]407 @Override
408 public void menuCanceled(MenuEvent me) {
409 // Not implemented
410 }
[6022]411 });
412 return p;
413 }
[6070]414
[6022]415 private class ImportProfileAction extends AbstractAction {
416 private final File file;
417 private final String type;
[6070]418
[8836]419 ImportProfileAction(String name, File file, String type) {
[6022]420 super(name);
421 this.file = file;
422 this.type = type;
423 }
[4634]424
[6022]425 @Override
426 public void actionPerformed(ActionEvent ae) {
427 Preferences tmpPrefs = CustomConfigurator.clonePreferences(Main.pref);
428 CustomConfigurator.readXML(file, tmpPrefs);
429 readPreferences(tmpPrefs);
430 String prefRegex = profileTypes.get(type);
431 // clean all the preferences from the chosen group
432 for (PrefEntry p : allData) {
433 if (p.getKey().matches(prefRegex) && !p.isDefault()) {
434 p.reset();
435 }
436 }
437 // allow user to review the changes in table
438 Collections.sort(allData, customComparator);
439 applyFilter();
440 }
441 }
442
[4634]443 private void applyFilter() {
444 displayData.clear();
[6021]445 for (PrefEntry e : allData) {
[4634]446 String prefKey = e.getKey();
[7027]447 Setting<?> valueSetting = e.getValue();
[4634]448 String prefValue = valueSetting.getValue() == null ? "" : valueSetting.getValue().toString();
449
[6085]450 String[] input = txtFilter.getText().split("\\s+");
[1673]451 boolean canHas = true;
452
453 // Make 'wmsplugin cache' search for e.g. 'cache.wmsplugin'
[8404]454 final String prefKeyLower = prefKey.toLowerCase(Locale.ENGLISH);
455 final String prefValueLower = prefValue.toLowerCase(Locale.ENGLISH);
[1673]456 for (String bit : input) {
[8404]457 bit = bit.toLowerCase(Locale.ENGLISH);
[4392]458 if (!prefKeyLower.contains(bit) && !prefValueLower.contains(bit)) {
[1673]459 canHas = false;
[4634]460 break;
[1865]461 }
[1673]462 }
463 if (canHas) {
[4634]464 displayData.add(e);
[1576]465 }
466 }
[9622]467 if (table != null)
468 table.fireDataChanged();
[1576]469 }
470
[4634]471 @Override
[1180]472 public boolean ok() {
[6021]473 for (PrefEntry e : allData) {
[4634]474 if (e.isChanged()) {
[6578]475 Main.pref.putSetting(e.getKey(), e.getValue().getValue() == null ? null : e.getValue());
[1169]476 }
477 }
[1180]478 return false;
[1169]479 }
[6986]480}
Note: See TracBrowser for help on using the repository browser.