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

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

fix #16442 - IndexOutOfBoundsException when deleting the last color entry in preferences

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