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

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

see #15410 - update tests + minor fixes (2)

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