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

Last change on this file since 12156 was 12156, checked in by michael2402, 7 years ago

See #14120: Don't make gpx tracks depend on the isChanged method, use a listener based approach instead.

  • Property svn:eol-style set to native
File size: 11.6 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.Component;
8import java.awt.Dimension;
9import java.awt.GraphicsEnvironment;
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.Arrays;
17import java.util.Comparator;
18import java.util.Map;
19import java.util.Optional;
20
21import javax.swing.AbstractAction;
22import javax.swing.JComponent;
23import javax.swing.JLabel;
24import javax.swing.JPanel;
25import javax.swing.JScrollPane;
26import javax.swing.JTable;
27import javax.swing.JToggleButton;
28import javax.swing.ListSelectionModel;
29import javax.swing.table.DefaultTableModel;
30import javax.swing.table.TableCellRenderer;
31import javax.swing.table.TableRowSorter;
32
33import org.openstreetmap.josm.Main;
34import org.openstreetmap.josm.data.SystemOfMeasurement;
35import org.openstreetmap.josm.data.gpx.GpxConstants;
36import org.openstreetmap.josm.data.gpx.GpxTrack;
37import org.openstreetmap.josm.gui.ExtendedDialog;
38import org.openstreetmap.josm.gui.layer.GpxLayer;
39import org.openstreetmap.josm.tools.GBC;
40import org.openstreetmap.josm.tools.ImageProvider;
41import org.openstreetmap.josm.tools.OpenBrowser;
42import org.openstreetmap.josm.tools.WindowGeometry;
43
44/**
45 * allows the user to choose which of the downloaded tracks should be displayed.
46 * they can be chosen from the gpx layer context menu.
47 */
48public class ChooseTrackVisibilityAction extends AbstractAction {
49 private final transient GpxLayer layer;
50
51 private DateFilterPanel dateFilter;
52 private JTable table;
53
54 /**
55 * Constructs a new {@code ChooseTrackVisibilityAction}.
56 * @param layer The associated GPX layer
57 */
58 public ChooseTrackVisibilityAction(final GpxLayer layer) {
59 super(tr("Choose visible tracks"), ImageProvider.get("dialogs/filter"));
60 this.layer = layer;
61 putValue("help", ht("/Action/ChooseTrackVisibility"));
62 }
63
64 /**
65 * Class to format a length according to SystemOfMesurement.
66 */
67 private static final class TrackLength {
68 private final double value;
69
70 /**
71 * Constructs a new {@code TrackLength} object with a given length.
72 * @param value length of the track
73 */
74 TrackLength(double value) {
75 this.value = value;
76 }
77
78 /**
79 * Provides string representation.
80 * @return String representation depending of SystemOfMeasurement
81 */
82 @Override
83 public String toString() {
84 return SystemOfMeasurement.getSystemOfMeasurement().getDistText(value);
85 }
86 }
87
88 /**
89 * Comparator for TrackLength objects
90 */
91 private static final class LengthContentComparator implements Comparator<TrackLength>, Serializable {
92
93 private static final long serialVersionUID = 1L;
94
95 /**
96 * Compare 2 TrackLength objects relative to the real length
97 */
98 @Override
99 public int compare(TrackLength l0, TrackLength l1) {
100 return Double.compare(l0.value, l1.value);
101 }
102 }
103
104 /**
105 * Gathers all available data for the tracks and returns them as array of arrays
106 * in the expected column order.
107 * @return table data
108 */
109 private Object[][] buildTableContents() {
110 Object[][] tracks = new Object[layer.data.tracks.size()][5];
111 int i = 0;
112 for (GpxTrack trk : layer.data.tracks) {
113 Map<String, Object> attr = trk.getAttributes();
114 String name = (String) Optional.ofNullable(attr.get(GpxConstants.GPX_NAME)).orElse("");
115 String desc = (String) Optional.ofNullable(attr.get(GpxConstants.GPX_DESC)).orElse("");
116 String time = GpxLayer.getTimespanForTrack(trk);
117 TrackLength length = new TrackLength(trk.length());
118 String url = (String) Optional.ofNullable(attr.get("url")).orElse("");
119 tracks[i] = new Object[]{name, desc, time, length, url};
120 i++;
121 }
122 return tracks;
123 }
124
125 /**
126 * Builds an non-editable table whose 5th column will open a browser when double clicked.
127 * The table will fill its parent.
128 * @param content table data
129 * @return non-editable table
130 */
131 private static JTable buildTable(Object[]... content) {
132 final String[] headers = {tr("Name"), tr("Description"), tr("Timespan"), tr("Length"), tr("URL")};
133 DefaultTableModel model = new DefaultTableModel(content, headers);
134 final JTable t = new JTable(model) {
135 @Override
136 public Component prepareRenderer(TableCellRenderer renderer, int row, int col) {
137 Component c = super.prepareRenderer(renderer, row, col);
138 if (c instanceof JComponent) {
139 JComponent jc = (JComponent) c;
140 jc.setToolTipText(getValueAt(row, col).toString());
141 }
142 return c;
143 }
144
145 @Override
146 public boolean isCellEditable(int rowIndex, int colIndex) {
147 return false;
148 }
149 };
150 // define how to sort row
151 TableRowSorter<DefaultTableModel> rowSorter = new TableRowSorter<>();
152 t.setRowSorter(rowSorter);
153 rowSorter.setModel(model);
154 rowSorter.setComparator(3, new LengthContentComparator());
155 // default column widths
156 t.getColumnModel().getColumn(0).setPreferredWidth(220);
157 t.getColumnModel().getColumn(1).setPreferredWidth(300);
158 t.getColumnModel().getColumn(2).setPreferredWidth(200);
159 t.getColumnModel().getColumn(3).setPreferredWidth(50);
160 t.getColumnModel().getColumn(4).setPreferredWidth(100);
161 // make the link clickable
162 final MouseListener urlOpener = new MouseAdapter() {
163 @Override
164 public void mouseClicked(MouseEvent e) {
165 if (e.getClickCount() != 2) {
166 return;
167 }
168 JTable t = (JTable) e.getSource();
169 int col = t.convertColumnIndexToModel(t.columnAtPoint(e.getPoint()));
170 if (col != 4) {
171 return;
172 }
173 int row = t.rowAtPoint(e.getPoint());
174 String url = (String) t.getValueAt(row, col);
175 if (url == null || url.isEmpty()) {
176 return;
177 }
178 OpenBrowser.displayUrl(url);
179 }
180 };
181 t.addMouseListener(urlOpener);
182 t.setFillsViewportHeight(true);
183 return t;
184 }
185
186 private boolean noUpdates;
187
188 /** selects all rows (=tracks) in the table that are currently visible on the layer*/
189 private void selectVisibleTracksInTable() {
190 // don't select any tracks if the layer is not visible
191 if (!layer.isVisible()) {
192 return;
193 }
194 ListSelectionModel s = table.getSelectionModel();
195 s.setValueIsAdjusting(true);
196 s.clearSelection();
197 for (int i = 0; i < layer.trackVisibility.length; i++) {
198 if (layer.trackVisibility[i]) {
199 s.addSelectionInterval(i, i);
200 }
201 }
202 s.setValueIsAdjusting(false);
203 }
204
205 /** listens to selection changes in the table and redraws the map */
206 private void listenToSelectionChanges() {
207 table.getSelectionModel().addListSelectionListener(e -> {
208 if (noUpdates || !(e.getSource() instanceof ListSelectionModel)) {
209 return;
210 }
211 updateVisibilityFromTable();
212 });
213 }
214
215 private void updateVisibilityFromTable() {
216 ListSelectionModel s = table.getSelectionModel();
217 for (int i = 0; i < layer.trackVisibility.length; i++) {
218 layer.trackVisibility[table.convertRowIndexToModel(i)] = s.isSelectedIndex(i);
219 }
220 layer.invalidate();
221 }
222
223 @Override
224 public void actionPerformed(ActionEvent ae) {
225 final JPanel msg = new JPanel(new GridBagLayout());
226
227 dateFilter = new DateFilterPanel(layer, "gpx.traces", false);
228 dateFilter.setFilterAppliedListener(e -> {
229 noUpdates = true;
230 selectVisibleTracksInTable();
231 noUpdates = false;
232 Main.map.mapView.preferenceChanged(null);
233 Main.map.repaint(100);
234 });
235 dateFilter.loadFromPrefs();
236
237 final JToggleButton b = new JToggleButton(new AbstractAction(tr("Select by date")) {
238 @Override public void actionPerformed(ActionEvent e) {
239 if (((JToggleButton) e.getSource()).isSelected()) {
240 dateFilter.setEnabled(true);
241 dateFilter.applyFilter();
242 } else {
243 dateFilter.setEnabled(false);
244 }
245 }
246 });
247 dateFilter.setEnabled(false);
248 msg.add(b, GBC.std().insets(0, 0, 5, 0));
249 msg.add(dateFilter, GBC.eol().insets(0, 0, 10, 0).fill(GBC.HORIZONTAL));
250
251 msg.add(new JLabel(tr("<html>Select all tracks that you want to be displayed. " +
252 "You can drag select a range of tracks or use CTRL+Click to select specific ones. " +
253 "The map is updated live in the background. Open the URLs by double clicking them.</html>")),
254 GBC.eop().fill(GBC.HORIZONTAL));
255 // build table
256 final boolean[] trackVisibilityBackup = layer.trackVisibility.clone();
257 table = buildTable(buildTableContents());
258 selectVisibleTracksInTable();
259 listenToSelectionChanges();
260 // make the table scrollable
261 JScrollPane scrollPane = new JScrollPane(table);
262 msg.add(scrollPane, GBC.eol().fill(GBC.BOTH));
263
264 int v = 1;
265 if (!GraphicsEnvironment.isHeadless()) {
266 // build dialog
267 ExtendedDialog ed = new ExtendedDialog(Main.parent, tr("Set track visibility for {0}", layer.getName()),
268 new String[]{tr("Show all"), tr("Show selected only"), tr("Cancel")});
269 ed.setButtonIcons(new String[]{"eye", "dialogs/filter", "cancel"});
270 ed.setContent(msg, false);
271 ed.setDefaultButton(2);
272 ed.setCancelButton(3);
273 ed.configureContextsensitiveHelp("/Action/ChooseTrackVisibility", true);
274 ed.setRememberWindowGeometry(getClass().getName() + ".geometry",
275 WindowGeometry.centerInWindow(Main.parent, new Dimension(1000, 500)));
276 ed.showDialog();
277 dateFilter.saveInPrefs();
278 v = ed.getValue();
279 // cancel for unknown buttons and copy back original settings
280 if (v != 1 && v != 2) {
281 layer.trackVisibility = Arrays.copyOf(trackVisibilityBackup, layer.trackVisibility.length);
282 Main.map.repaint();
283 return;
284 }
285 }
286 // set visibility (1 = show all, 2 = filter). If no tracks are selected
287 // set all of them visible and...
288 ListSelectionModel s = table.getSelectionModel();
289 final boolean all = v == 1 || s.isSelectionEmpty();
290 for (int i = 0; i < layer.trackVisibility.length; i++) {
291 layer.trackVisibility[table.convertRowIndexToModel(i)] = all || s.isSelectedIndex(i);
292 }
293 // layer has been changed
294 layer.invalidate();
295 // ...sync with layer visibility instead to avoid having two ways to hide everything
296 layer.setVisible(v == 1 || !s.isSelectionEmpty());
297
298 if (Main.isDisplayingMapView()) {
299 Main.map.mapView.preferenceChanged(null);
300 }
301 if (Main.map != null) {
302 Main.map.repaint();
303 }
304 }
305}
Note: See TracBrowser for help on using the repository browser.