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

Last change on this file since 16845 was 16845, checked in by simon04, 4 years ago

AdvancedPreference: remove code duplication

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