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

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

fix #19366 - Add filter box for Color Preferences (patch by taylor.smock, modified)

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