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

Last change on this file since 17331 was 17331, checked in by GerdP, 3 years ago

fix #19825: Advanced preferences table is empty w/o search text on Java 8

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