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

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

fix #18302 - support national taginfo instances

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