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

Last change on this file since 16562 was 16562, checked in by simon04, 4 years ago

fix #19196 - Don't require a restart when a MapPaint color is changed (patch by taylor.smock)

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