source: josm/trunk/src/org/openstreetmap/josm/gui/layer/gpx/ChooseTrackVisibilityAction.java@ 15586

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

code cleanup

  • Property svn:eol-style set to native
File size: 16.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer.gpx;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.Color;
8import java.awt.Component;
9import java.awt.Dimension;
10import java.awt.GridBagLayout;
11import java.awt.event.ActionEvent;
12import java.awt.event.MouseAdapter;
13import java.awt.event.MouseEvent;
14import java.awt.event.MouseListener;
15import java.io.Serializable;
16import java.util.ArrayList;
17import java.util.Arrays;
18import java.util.Comparator;
19import java.util.List;
20import java.util.Map;
21import java.util.Objects;
22import java.util.Optional;
23
24import javax.swing.AbstractAction;
25import javax.swing.JColorChooser;
26import javax.swing.JComponent;
27import javax.swing.JLabel;
28import javax.swing.JOptionPane;
29import javax.swing.JPanel;
30import javax.swing.JScrollPane;
31import javax.swing.JTable;
32import javax.swing.JToggleButton;
33import javax.swing.ListSelectionModel;
34import javax.swing.event.TableModelEvent;
35import javax.swing.table.DefaultTableModel;
36import javax.swing.table.TableCellRenderer;
37import javax.swing.table.TableModel;
38import javax.swing.table.TableRowSorter;
39
40import org.apache.commons.jcs.access.exception.InvalidArgumentException;
41import org.openstreetmap.josm.data.SystemOfMeasurement;
42import org.openstreetmap.josm.data.gpx.GpxConstants;
43import org.openstreetmap.josm.data.gpx.IGpxTrack;
44import org.openstreetmap.josm.gui.ExtendedDialog;
45import org.openstreetmap.josm.gui.MainApplication;
46import org.openstreetmap.josm.gui.layer.GpxLayer;
47import org.openstreetmap.josm.gui.preferences.display.GPXSettingsPanel;
48import org.openstreetmap.josm.gui.util.WindowGeometry;
49import org.openstreetmap.josm.tools.GBC;
50import org.openstreetmap.josm.tools.ImageProvider;
51import org.openstreetmap.josm.tools.OpenBrowser;
52
53/**
54 * allows the user to choose which of the downloaded tracks should be displayed.
55 * they can be chosen from the gpx layer context menu.
56 */
57public class ChooseTrackVisibilityAction extends AbstractAction {
58 private final transient GpxLayer layer;
59
60 private DateFilterPanel dateFilter;
61 private JTable table;
62
63 /**
64 * Constructs a new {@code ChooseTrackVisibilityAction}.
65 * @param layer The associated GPX layer
66 */
67 public ChooseTrackVisibilityAction(final GpxLayer layer) {
68 super(tr("Choose track visibility and colors"));
69 new ImageProvider("dialogs/filter").getResource().attachImageIcon(this, true);
70 this.layer = layer;
71 putValue("help", ht("/Action/ChooseTrackVisibility"));
72 }
73
74 /**
75 * Class to format a length according to SystemOfMesurement.
76 */
77 private static final class TrackLength {
78 private final double value;
79
80 /**
81 * Constructs a new {@code TrackLength} object with a given length.
82 * @param value length of the track
83 */
84 TrackLength(double value) {
85 this.value = value;
86 }
87
88 /**
89 * Provides string representation.
90 * @return String representation depending of SystemOfMeasurement
91 */
92 @Override
93 public String toString() {
94 return SystemOfMeasurement.getSystemOfMeasurement().getDistText(value);
95 }
96 }
97
98 /**
99 * Comparator for TrackLength objects
100 */
101 private static final class LengthContentComparator implements Comparator<TrackLength>, Serializable {
102
103 private static final long serialVersionUID = 1L;
104
105 /**
106 * Compare 2 TrackLength objects relative to the real length
107 */
108 @Override
109 public int compare(TrackLength l0, TrackLength l1) {
110 return Double.compare(l0.value, l1.value);
111 }
112 }
113
114 /**
115 * Gathers all available data for the tracks and returns them as array of arrays
116 * in the expected column order.
117 * @return table data
118 */
119 private Object[][] buildTableContents() {
120 Object[][] tracks = new Object[layer.data.tracks.size()][5];
121 int i = 0;
122 for (IGpxTrack trk : layer.data.tracks) {
123 Map<String, Object> attr = trk.getAttributes();
124 String name = (String) Optional.ofNullable(attr.get(GpxConstants.GPX_NAME)).orElse("");
125 String desc = (String) Optional.ofNullable(attr.get(GpxConstants.GPX_DESC)).orElse("");
126 String time = GpxLayer.getTimespanForTrack(trk);
127 TrackLength length = new TrackLength(trk.length());
128 String url = (String) Optional.ofNullable(attr.get("url")).orElse("");
129 tracks[i] = new Object[]{name, desc, time, length, url, trk};
130 i++;
131 }
132 return tracks;
133 }
134
135 private void showColorDialog(List<IGpxTrack> tracks) {
136 Color cl = tracks.stream().filter(Objects::nonNull)
137 .map(IGpxTrack::getColor).filter(Objects::nonNull)
138 .findAny().orElse(GpxDrawHelper.DEFAULT_COLOR_PROPERTY.get());
139 JColorChooser c = new JColorChooser(cl);
140 Object[] options = {tr("OK"), tr("Cancel"), tr("Default")};
141 int answer = JOptionPane.showOptionDialog(
142 MainApplication.getMainFrame(),
143 c,
144 tr("Choose a color"),
145 JOptionPane.OK_CANCEL_OPTION,
146 JOptionPane.PLAIN_MESSAGE,
147 null,
148 options,
149 options[0]
150 );
151 switch (answer) {
152 case 0:
153 tracks.stream().forEach(t -> t.setColor(c.getColor()));
154 GPXSettingsPanel.putLayerPrefLocal(layer, "colormode", "0"); //set Colormode to none
155 break;
156 case 1:
157 return;
158 case 2:
159 tracks.stream().forEach(t -> t.setColor(null));
160 break;
161 }
162 table.repaint();
163 }
164
165 /**
166 * Builds an editable table whose 5th column will open a browser when double clicked.
167 * The table will fill its parent.
168 * @param content table data
169 * @return non-editable table
170 */
171 private static JTable buildTable(Object[]... content) {
172 final String[] headers = {tr("Name"), tr("Description"), tr("Timespan"), tr("Length"), tr("URL")};
173 DefaultTableModel model = new DefaultTableModel(content, headers);
174 final GpxTrackTable t = new GpxTrackTable(content, model);
175 // define how to sort row
176 TableRowSorter<DefaultTableModel> rowSorter = new TableRowSorter<>();
177 t.setRowSorter(rowSorter);
178 rowSorter.setModel(model);
179 rowSorter.setComparator(3, new LengthContentComparator());
180 // default column widths
181 t.getColumnModel().getColumn(0).setPreferredWidth(220);
182 t.getColumnModel().getColumn(1).setPreferredWidth(300);
183 t.getColumnModel().getColumn(2).setPreferredWidth(200);
184 t.getColumnModel().getColumn(3).setPreferredWidth(50);
185 t.getColumnModel().getColumn(4).setPreferredWidth(100);
186 // make the link clickable
187 final MouseListener urlOpener = new MouseAdapter() {
188 @Override
189 public void mouseClicked(MouseEvent e) {
190 if (e.getClickCount() != 2) {
191 return;
192 }
193 JTable t = (JTable) e.getSource();
194 int col = t.convertColumnIndexToModel(t.columnAtPoint(e.getPoint()));
195 if (col != 4) {
196 return;
197 }
198 int row = t.rowAtPoint(e.getPoint());
199 String url = (String) t.getValueAt(row, col);
200 if (url == null || url.isEmpty()) {
201 return;
202 }
203 OpenBrowser.displayUrl(url);
204 }
205 };
206 t.addMouseListener(urlOpener);
207 t.setFillsViewportHeight(true);
208 t.putClientProperty("terminateEditOnFocusLost", true);
209 return t;
210 }
211
212 private boolean noUpdates;
213
214 /** selects all rows (=tracks) in the table that are currently visible on the layer*/
215 private void selectVisibleTracksInTable() {
216 // don't select any tracks if the layer is not visible
217 if (!layer.isVisible()) {
218 return;
219 }
220 ListSelectionModel s = table.getSelectionModel();
221 s.setValueIsAdjusting(true);
222 s.clearSelection();
223 for (int i = 0; i < layer.trackVisibility.length; i++) {
224 if (layer.trackVisibility[i]) {
225 s.addSelectionInterval(i, i);
226 }
227 }
228 s.setValueIsAdjusting(false);
229 }
230
231 /** listens to selection changes in the table and redraws the map */
232 private void listenToSelectionChanges() {
233 table.getSelectionModel().addListSelectionListener(e -> {
234 if (noUpdates || !(e.getSource() instanceof ListSelectionModel)) {
235 return;
236 }
237 updateVisibilityFromTable();
238 });
239 }
240
241 private void updateVisibilityFromTable() {
242 ListSelectionModel s = table.getSelectionModel();
243 for (int i = 0; i < layer.trackVisibility.length; i++) {
244 layer.trackVisibility[table.convertRowIndexToModel(i)] = s.isSelectedIndex(i);
245 }
246 layer.invalidate();
247 }
248
249 @Override
250 public void actionPerformed(ActionEvent ae) {
251 final JPanel msg = new JPanel(new GridBagLayout());
252
253 dateFilter = new DateFilterPanel(layer, "gpx.traces", false);
254 dateFilter.setFilterAppliedListener(e -> {
255 noUpdates = true;
256 selectVisibleTracksInTable();
257 noUpdates = false;
258 layer.invalidate();
259 });
260 dateFilter.loadFromPrefs();
261
262 final JToggleButton b = new JToggleButton(new AbstractAction(tr("Select by date")) {
263 @Override public void actionPerformed(ActionEvent e) {
264 if (((JToggleButton) e.getSource()).isSelected()) {
265 dateFilter.setEnabled(true);
266 dateFilter.applyFilter();
267 } else {
268 dateFilter.setEnabled(false);
269 }
270 }
271 });
272 dateFilter.setEnabled(false);
273 msg.add(b, GBC.std().insets(0, 0, 5, 0));
274 msg.add(dateFilter, GBC.eol().insets(0, 0, 10, 0).fill(GBC.HORIZONTAL));
275
276 msg.add(new JLabel(tr("<html>Select all tracks that you want to be displayed. " +
277 "You can drag select a range of tracks or use CTRL+Click to select specific ones. " +
278 "The map is updated live in the background. Open the URLs by double clicking them, " +
279 "edit name and description by double clicking the cell.</html>")),
280 GBC.eop().fill(GBC.HORIZONTAL));
281 // build table
282 final boolean[] trackVisibilityBackup = layer.trackVisibility.clone();
283 Object[][] content = buildTableContents();
284 table = buildTable(content);
285 selectVisibleTracksInTable();
286 listenToSelectionChanges();
287 // make the table scrollable
288 JScrollPane scrollPane = new JScrollPane(table);
289 msg.add(scrollPane, GBC.eol().fill(GBC.BOTH));
290
291 int v = 1;
292 // build dialog
293 ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(),
294 tr("Set track visibility for {0}", layer.getName()),
295 tr("Set color for selected tracks..."), tr("Show all"), tr("Show selected only"), tr("Close")) {
296 @Override
297 protected void buttonAction(int buttonIndex, ActionEvent evt) {
298 if (buttonIndex == 0) {
299 List<IGpxTrack> trks = new ArrayList<>();
300 for (int i : table.getSelectedRows()) {
301 Object trk = content[i][5];
302 if (trk != null && trk instanceof IGpxTrack) {
303 trks.add((IGpxTrack) trk);
304 }
305 }
306 showColorDialog(trks);
307 } else {
308 super.buttonAction(buttonIndex, evt);
309 }
310 }
311 };
312 ed.setButtonIcons("colorchooser", "eye", "dialogs/filter", "cancel");
313 ed.setContent(msg, false);
314 ed.setDefaultButton(2);
315 ed.setCancelButton(3);
316 ed.configureContextsensitiveHelp("/Action/ChooseTrackVisibility", true);
317 ed.setRememberWindowGeometry(getClass().getName() + ".geometry",
318 WindowGeometry.centerInWindow(MainApplication.getMainFrame(), new Dimension(1000, 500)));
319 ed.showDialog();
320 dateFilter.saveInPrefs();
321 v = ed.getValue();
322 // cancel for unknown buttons and copy back original settings
323 if (v != 2 && v != 3) {
324 layer.trackVisibility = Arrays.copyOf(trackVisibilityBackup, layer.trackVisibility.length);
325 MainApplication.getMap().repaint();
326 return;
327 }
328 // set visibility (2 = show all, 3 = filter). If no tracks are selected
329 // set all of them visible and...
330 ListSelectionModel s = table.getSelectionModel();
331 final boolean all = v == 2 || s.isSelectionEmpty();
332 for (int i = 0; i < layer.trackVisibility.length; i++) {
333 layer.trackVisibility[table.convertRowIndexToModel(i)] = all || s.isSelectedIndex(i);
334 }
335 // layer has been changed
336 layer.invalidate();
337 // ...sync with layer visibility instead to avoid having two ways to hide everything
338 layer.setVisible(v == 2 || !s.isSelectionEmpty());
339 }
340
341 private static class GpxTrackTable extends JTable {
342 final Object[][] content;
343
344 GpxTrackTable(Object[][] content, TableModel model) {
345 super(model);
346 this.content = content;
347 }
348
349 @Override
350 public Component prepareRenderer(TableCellRenderer renderer, int row, int col) {
351 Component c = super.prepareRenderer(renderer, row, col);
352 if (c instanceof JComponent) {
353 JComponent jc = (JComponent) c;
354 jc.setToolTipText(getValueAt(row, col).toString());
355 if (content.length > row
356 && content[row].length > 5
357 && content[row][5] instanceof IGpxTrack) {
358 Color color = ((IGpxTrack) content[row][5]).getColor();
359 if (color != null) {
360 double brightness = Math.sqrt(Math.pow(color.getRed(), 2) * .241
361 + Math.pow(color.getGreen(), 2) * .691
362 + Math.pow(color.getBlue(), 2) * .068);
363 if (brightness > 250) {
364 color = color.darker();
365 }
366 if (isRowSelected(row)) {
367 jc.setBackground(color);
368 if (brightness <= 130) {
369 jc.setForeground(Color.WHITE);
370 } else {
371 jc.setForeground(Color.BLACK);
372 }
373 } else {
374 if (brightness > 200) {
375 color = color.darker(); //brightness >250 is darkened twice on purpose
376 }
377 jc.setForeground(color);
378 jc.setBackground(Color.WHITE);
379 }
380 } else {
381 jc.setForeground(Color.BLACK);
382 if (isRowSelected(row)) {
383 jc.setBackground(new Color(175, 210, 210));
384 } else {
385 jc.setBackground(Color.WHITE);
386 }
387 }
388 }
389 }
390 return c;
391 }
392
393 @Override
394 public boolean isCellEditable(int rowIndex, int colIndex) {
395 return colIndex <= 1;
396 }
397
398 @Override
399 public void tableChanged(TableModelEvent e) {
400 super.tableChanged(e);
401 int col = e.getColumn();
402 int row = e.getFirstRow();
403 if (row >= 0 && row < content.length && col >= 0 && col <= 1) {
404 Object t = content[row][5];
405 String val = (String) getValueAt(row, col);
406 if (t != null && t instanceof IGpxTrack) {
407 IGpxTrack trk = (IGpxTrack) t;
408 if (col == 0) {
409 trk.put("name", val);
410 } else {
411 trk.put("desc", val);
412 }
413 } else {
414 throw new InvalidArgumentException("Invalid object in table, must be IGpxTrack.");
415 }
416 }
417 }
418 }
419}
Note: See TracBrowser for help on using the repository browser.