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

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

fix #20026 - fix tooltip for Date/time in gpx filter dialog

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