source: josm/trunk/src/org/openstreetmap/josm/gui/preferences/display/ColorPreference.java@ 12987

Last change on this file since 12987 was 12987, checked in by bastiK, 7 years ago

see #15410 - change preferences scheme for named colors - makes runtime color name registry obsolete

  • Property svn:eol-style set to native
File size: 16.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.preferences.display;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Color;
7import java.awt.Component;
8import java.awt.Dimension;
9import java.awt.Font;
10import java.awt.GridBagLayout;
11import java.awt.event.MouseAdapter;
12import java.awt.event.MouseEvent;
13import java.text.Collator;
14import java.util.ArrayList;
15import java.util.HashMap;
16import java.util.List;
17import java.util.Map;
18import java.util.Objects;
19import java.util.Optional;
20import java.util.stream.Collectors;
21
22import javax.swing.BorderFactory;
23import javax.swing.Box;
24import javax.swing.JButton;
25import javax.swing.JColorChooser;
26import javax.swing.JLabel;
27import javax.swing.JOptionPane;
28import javax.swing.JPanel;
29import javax.swing.JScrollPane;
30import javax.swing.JTable;
31import javax.swing.ListSelectionModel;
32import javax.swing.event.ListSelectionEvent;
33import javax.swing.table.AbstractTableModel;
34import javax.swing.table.DefaultTableCellRenderer;
35
36import org.openstreetmap.josm.Main;
37import org.openstreetmap.josm.data.preferences.ColorInfo;
38import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
39import org.openstreetmap.josm.data.preferences.NamedColorProperty;
40import org.openstreetmap.josm.data.validation.Severity;
41import org.openstreetmap.josm.gui.MapScaler;
42import org.openstreetmap.josm.gui.MapStatus;
43import org.openstreetmap.josm.gui.conflict.ConflictColors;
44import org.openstreetmap.josm.gui.dialogs.ConflictDialog;
45import org.openstreetmap.josm.gui.layer.OsmDataLayer;
46import org.openstreetmap.josm.gui.layer.gpx.GpxDrawHelper;
47import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
48import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
49import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
50import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
51import org.openstreetmap.josm.gui.preferences.SubPreferenceSetting;
52import org.openstreetmap.josm.gui.preferences.TabPreferenceSetting;
53import org.openstreetmap.josm.gui.util.GuiHelper;
54import org.openstreetmap.josm.tools.CheckParameterUtil;
55import org.openstreetmap.josm.tools.ColorHelper;
56import org.openstreetmap.josm.tools.GBC;
57import org.openstreetmap.josm.tools.I18n;
58
59/**
60 * Color preferences.
61 *
62 * GUI preference to let the user customize named colors.
63 * @see NamedColorProperty
64 */
65public class ColorPreference implements SubPreferenceSetting {
66
67 /**
68 * Factory used to create a new {@code ColorPreference}.
69 */
70 public static class Factory implements PreferenceSettingFactory {
71 @Override
72 public PreferenceSetting createPreferenceSetting() {
73 return new ColorPreference();
74 }
75 }
76
77 private ColorTableModel tableModel;
78 private JTable colors;
79
80 private JButton colorEdit;
81 private JButton defaultSet;
82 private JButton remove;
83
84 private static class ColorEntry {
85 String key;
86 ColorInfo info;
87
88 ColorEntry(String key, ColorInfo info) {
89 CheckParameterUtil.ensureParameterNotNull(key, "key");
90 CheckParameterUtil.ensureParameterNotNull(info, "info");
91 this.key = key;
92 this.info = info;
93 }
94
95 /**
96 * Get a description of the color based on the given info.
97 * @return a description of the color
98 */
99 public String getDisplay() {
100 switch (info.getCategory()) {
101 case NamedColorProperty.COLOR_CATEGORY_LAYER:
102 String v = null;
103 if (info.getSource() != null) {
104 v = info.getSource();
105 }
106 if (!info.getName().isEmpty()) {
107 if (v == null) {
108 v = tr(I18n.escape(info.getName()));
109 } else {
110 v += " - " + tr(I18n.escape(info.getName()));
111 }
112 }
113 return tr("Layer: {0}", v);
114 case NamedColorProperty.COLOR_CATEGORY_MAPPAINT:
115 if (info.getSource() != null)
116 return tr("Paint style {0}: {1}", tr(I18n.escape(info.getSource())), tr(info.getName()));
117 // fall through
118 default:
119 if (info.getSource() != null)
120 return tr(I18n.escape(info.getSource())) + " - " + tr(I18n.escape(info.getName()));
121 else
122 return tr(I18n.escape(info.getName()));
123 }
124 }
125
126 /**
127 * Get the color value to display.
128 * Either value (if set) or default value.
129 * @return the color value to display
130 */
131 public Color getDisplayColor() {
132 return Optional.ofNullable(info.getValue()).orElse(info.getDefaultValue());
133 }
134
135 /**
136 * Check if color has been customized by the user or not.
137 * @return true if the color is at its default value, false if it is customized by the user.
138 */
139 public boolean isDefault() {
140 return info.getValue() == null || Objects.equals(info.getValue(), info.getDefaultValue());
141 }
142
143 /**
144 * Convert to a {@link NamedColorProperty}.
145 * @return a {@link NamedColorProperty}
146 */
147 public NamedColorProperty toProperty() {
148 return new NamedColorProperty(info.getCategory(), info.getSource(),
149 info.getName(), info.getDefaultValue());
150 }
151 }
152
153 private static class ColorTableModel extends AbstractTableModel {
154
155 private final List<ColorEntry> data;
156 private final List<ColorEntry> deleted;
157
158 ColorTableModel() {
159 this.data = new ArrayList<>();
160 this.deleted = new ArrayList<>();
161 }
162
163 public void addEntry(ColorEntry entry) {
164 data.add(entry);
165 }
166
167 public void removeEntry(int row) {
168 deleted.add(data.get(row));
169 data.remove(row);
170 fireTableDataChanged();
171 }
172
173 public ColorEntry getEntry(int row) {
174 return data.get(row);
175 }
176
177 public List<ColorEntry> getData() {
178 return data;
179 }
180
181 public List<ColorEntry> getDeleted() {
182 return deleted;
183 }
184
185 public void clear() {
186 data.clear();
187 deleted.clear();
188 }
189
190 @Override
191 public int getRowCount() {
192 return data.size();
193 }
194
195 @Override
196 public int getColumnCount() {
197 return 2;
198 }
199
200 @Override
201 public Object getValueAt(int rowIndex, int columnIndex) {
202 return columnIndex == 0 ? data.get(rowIndex) : data.get(rowIndex).getDisplayColor();
203 }
204
205 @Override
206 public String getColumnName(int column) {
207 return column == 0 ? tr("Name") : tr("Color");
208 }
209
210 @Override
211 public boolean isCellEditable(int rowIndex, int columnIndex) {
212 return false;
213 }
214
215 @Override
216 public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
217 if (columnIndex == 1 && aValue instanceof Color) {
218 data.get(rowIndex).info.setValue((Color) aValue);
219 fireTableRowsUpdated(rowIndex, rowIndex);
220 }
221 }
222 }
223
224 /**
225 * Set the colors to be shown in the preference table. This method creates a table model if
226 * none exists and overwrites all existing values.
227 * @param colorMap the map holding the colors
228 * (key = preference key, value = {@link ColorInfo} instance)
229 */
230 public void setColors(Map<String, ColorInfo> colorMap) {
231 if (tableModel == null) {
232 tableModel = new ColorTableModel();
233 }
234 tableModel.clear();
235
236 // fill model with colors:
237 colorMap.entrySet().stream()
238 .map(e -> new ColorEntry(e.getKey(), e.getValue()))
239 .sorted((e1, e2) -> {
240 int cat = Integer.compare(
241 getCategroyPriority(e1.info.getCategory()),
242 getCategroyPriority(e2.info.getCategory()));
243 if (cat != 0) return cat;
244 return Collator.getInstance().compare(e1.getDisplay(), e2.getDisplay());
245 })
246 .forEach(tableModel::addEntry);
247
248 if (this.colors != null) {
249 this.colors.repaint();
250 }
251
252 }
253
254 private static int getCategroyPriority(String category) {
255 switch (category) {
256 case NamedColorProperty.COLOR_CATEGORY_GENERAL: return 1;
257 case NamedColorProperty.COLOR_CATEGORY_MAPPAINT: return 2;
258 case NamedColorProperty.COLOR_CATEGORY_LAYER: return 3;
259 default: return 4;
260 }
261 }
262
263 /**
264 * Set the colors to be shown in the preference table. This method creates a table model if
265 * none exists and overwrites all existing values.
266 * @param colorMap the map holding the colors
267 * (key = color id (without prefixes, so only <code>background</code>; not <code>color.background</code>),
268 * value = html representation of the color.
269 * @deprecated (since 12987) replaced by {@link #setColors(java.util.Map)}
270 */
271 @Deprecated
272 public void setColorModel(Map<String, String> colorMap) {
273 setColors(colorMap.entrySet().stream().collect(Collectors.toMap(e->e.getKey(), e->
274 new ColorInfo(NamedColorProperty.COLOR_CATEGORY_GENERAL, null, e.getKey(), ColorHelper.html2color(e.getValue()), null))));
275 }
276
277 /**
278 * Returns a map with the colors in the table (key = preference key, value = color info).
279 * @return a map holding the colors.
280 */
281 public Map<String, ColorInfo> getColors() {
282 return tableModel.getData().stream().collect(Collectors.toMap(e -> e.key, e -> e.info));
283 }
284
285 /**
286 * Returns a map with the colors in the table (key = color name without prefix, value = html color code).
287 * @return a map holding the colors.
288 * @deprecated replaced by {@link #getColors()}
289 */
290 @Deprecated
291 public Map<String, String> getColorModel() {
292 Map<String, String> colorMap = new HashMap<>();
293 for (ColorEntry e : tableModel.getData()) {
294 colorMap.put(e.key, ColorHelper.color2html(e.getDisplayColor()));
295 }
296 return colorMap;
297 }
298
299 @Override
300 public void addGui(final PreferenceTabbedPane gui) {
301 fixColorPrefixes();
302 setColors(Main.pref.getAllNamedColors());
303
304 colorEdit = new JButton(tr("Choose"));
305 colorEdit.addActionListener(e -> {
306 int sel = colors.getSelectedRow();
307 ColorEntry ce = tableModel.getEntry(sel);
308 JColorChooser chooser = new JColorChooser(ce.getDisplayColor());
309 int answer = JOptionPane.showConfirmDialog(
310 gui, chooser,
311 tr("Choose a color for {0}", ce.getDisplay()),
312 JOptionPane.OK_CANCEL_OPTION,
313 JOptionPane.PLAIN_MESSAGE);
314 if (answer == JOptionPane.OK_OPTION) {
315 colors.setValueAt(chooser.getColor(), sel, 1);
316 }
317 });
318 defaultSet = new JButton(tr("Set to default"));
319 defaultSet.addActionListener(e -> {
320 int sel = colors.getSelectedRow();
321 ColorEntry ce = tableModel.getEntry(sel);
322 Color c = Main.pref.getDefaultColor(ce.key);
323 if (c != null) {
324 colors.setValueAt(c, sel, 1);
325 }
326 });
327 JButton defaultAll = new JButton(tr("Set all to default"));
328 defaultAll.addActionListener(e -> {
329 List<ColorEntry> data = tableModel.getData();
330 for (int i = 0; i < data.size(); ++i) {
331 ColorEntry ce = data.get(i);
332 Color c = Main.pref.getDefaultColor(ce.key);
333 if (c != null) {
334 colors.setValueAt(c, i, 1);
335 }
336 }
337 });
338 remove = new JButton(tr("Remove"));
339 remove.addActionListener(e -> {
340 int sel = colors.getSelectedRow();
341 tableModel.removeEntry(sel);
342 });
343 remove.setEnabled(false);
344 colorEdit.setEnabled(false);
345 defaultSet.setEnabled(false);
346
347 colors = new JTable(tableModel) {
348 @Override public void valueChanged(ListSelectionEvent e) {
349 super.valueChanged(e);
350 int sel = getSelectedRow();
351 remove.setEnabled(sel >= 0 && isRemoveColor(sel));
352 colorEdit.setEnabled(sel >= 0);
353 defaultSet.setEnabled(sel >= 0);
354 }
355 };
356 colors.addMouseListener(new MouseAdapter() {
357 @Override
358 public void mousePressed(MouseEvent me) {
359 if (me.getClickCount() == 2) {
360 colorEdit.doClick();
361 }
362 }
363 });
364 colors.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
365 colors.getColumnModel().getColumn(0).setCellRenderer(new DefaultTableCellRenderer() {
366 @Override
367 public Component getTableCellRendererComponent(
368 JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
369 Component comp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
370 if (value != null && comp instanceof JLabel) {
371 JLabel label = (JLabel) comp;
372 ColorEntry e = (ColorEntry) value;
373 label.setText(e.getDisplay());
374 if (!e.isDefault()) {
375 label.setFont(label.getFont().deriveFont(Font.BOLD));
376 } else {
377 label.setFont(label.getFont().deriveFont(Font.PLAIN));
378 }
379 return label;
380 }
381 return comp;
382 }
383 });
384 colors.getColumnModel().getColumn(1).setCellRenderer(new DefaultTableCellRenderer() {
385 @Override
386 public Component getTableCellRendererComponent(
387 JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
388 Component comp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
389 if (value != null && comp instanceof JLabel) {
390 JLabel label = (JLabel) comp;
391 Color c = (Color) value;
392 label.setText(ColorHelper.color2html(c));
393 GuiHelper.setBackgroundReadable(label, c);
394 label.setOpaque(true);
395 return label;
396 }
397 return comp;
398 }
399 });
400 colors.getColumnModel().getColumn(1).setWidth(100);
401 colors.setToolTipText(tr("Colors used by different objects in JOSM."));
402 colors.setPreferredScrollableViewportSize(new Dimension(100, 112));
403
404 JPanel panel = new JPanel(new GridBagLayout());
405 panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
406 JScrollPane scrollpane = new JScrollPane(colors);
407 scrollpane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
408 panel.add(scrollpane, GBC.eol().fill(GBC.BOTH));
409 JPanel buttonPanel = new JPanel(new GridBagLayout());
410 panel.add(buttonPanel, GBC.eol().insets(5, 0, 5, 5).fill(GBC.HORIZONTAL));
411 buttonPanel.add(Box.createHorizontalGlue(), GBC.std().fill(GBC.HORIZONTAL));
412 buttonPanel.add(colorEdit, GBC.std().insets(0, 5, 0, 0));
413 buttonPanel.add(defaultSet, GBC.std().insets(5, 5, 5, 0));
414 buttonPanel.add(defaultAll, GBC.std().insets(0, 5, 0, 0));
415 buttonPanel.add(remove, GBC.std().insets(0, 5, 0, 0));
416 gui.getDisplayPreference().addSubTab(this, tr("Colors"), panel);
417 }
418
419 Boolean isRemoveColor(int row) {
420 return tableModel.getEntry(row).info.getCategory().equals(NamedColorProperty.COLOR_CATEGORY_LAYER);
421 }
422
423 /**
424 * Add all missing color entries.
425 */
426 private static void fixColorPrefixes() {
427 PaintColors.values();
428 ConflictColors.getColors();
429 Severity.getColors();
430 MarkerLayer.getGenericColor();
431 GpxDrawHelper.getGenericColor();
432 OsmDataLayer.getOutsideColor();
433 MapScaler.getColor();
434 MapStatus.getColors();
435 ConflictDialog.getColor();
436 }
437
438 @Override
439 public boolean ok() {
440 boolean ret = false;
441 for (ColorEntry d : tableModel.getDeleted()) {
442 d.toProperty().remove();
443 }
444 for (ColorEntry e : tableModel.getData()) {
445 if (!e.isDefault()) {
446 if (e.toProperty().put(e.info.getValue())
447 && e.key.startsWith("mappaint.")) {
448 ret = true;
449 }
450 }
451 }
452 OsmDataLayer.createHatchTexture();
453 return ret;
454 }
455
456 @Override
457 public boolean isExpert() {
458 return false;
459 }
460
461 @Override
462 public TabPreferenceSetting getTabPreferenceSetting(final PreferenceTabbedPane gui) {
463 return gui.getDisplayPreference();
464 }
465}
Note: See TracBrowser for help on using the repository browser.