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

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

see #16288 - replace similar i18n strings (removes 35 strings from Launchpad)

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