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

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

fix #10024 - Add an option in Preferences/Look-and-Feel to use native file-choosing dialogs.
They look nicer but they do not support file filters, so we cannot use them (yet) as default.
Based on patch by Lesath and code review by simon04.
The native dialogs are not used if selection mode is not supported ("files and directories" on all platforms, "directories" on systems other than OS X)

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