Changeset 4332 in josm


Ignore:
Timestamp:
2011-08-21T21:03:06+02:00 (10 years ago)
Author:
xeen
Message:

add ability to select which tracks to show for each GPX Layer (fixes #4133 and #5661)

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java

    r4323 r4332  
    1717import java.awt.RenderingHints;
    1818import java.awt.event.ActionEvent;
     19import java.awt.event.MouseAdapter;
     20import java.awt.event.MouseEvent;
     21import java.awt.event.MouseListener;
    1922import java.awt.geom.Area;
    2023import java.awt.geom.Rectangle2D;
     
    3033import java.util.LinkedList;
    3134import java.util.List;
     35import java.util.Map;
    3236import java.util.concurrent.Future;
    3337
     
    3539import javax.swing.Action;
    3640import javax.swing.Icon;
     41import javax.swing.JComponent;
    3742import javax.swing.JFileChooser;
    3843import javax.swing.JLabel;
     
    4247import javax.swing.JPanel;
    4348import javax.swing.JScrollPane;
     49import javax.swing.JTable;
     50import javax.swing.ListSelectionModel;
    4451import javax.swing.SwingUtilities;
     52import javax.swing.event.ListSelectionEvent;
     53import javax.swing.event.ListSelectionListener;
    4554import javax.swing.filechooser.FileFilter;
     55import javax.swing.table.TableCellRenderer;
    4656
    4757import org.openstreetmap.josm.Main;
     
    6272import org.openstreetmap.josm.data.projection.Projection;
    6373import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
     74import org.openstreetmap.josm.gui.ExtendedDialog;
    6475import org.openstreetmap.josm.gui.HelpAwareOptionPane;
    6576import org.openstreetmap.josm.gui.MapView;
     
    8091import org.openstreetmap.josm.tools.GBC;
    8192import org.openstreetmap.josm.tools.ImageProvider;
     93import org.openstreetmap.josm.tools.OpenBrowser;
    8294import org.openstreetmap.josm.tools.UrlLabel;
    8395import org.openstreetmap.josm.tools.Utils;
     96import org.openstreetmap.josm.tools.WindowGeometry;
    8497
    8598public class GpxLayer extends Layer {
     
    98111    private int computeCacheColorTracksTune;
    99112    private boolean isLocalFile;
     113    // used by ChooseTrackVisibilityAction to determine which tracks to show/hide
     114    private boolean[] trackVisibility;
    100115
    101116    private final List<GpxTrack> lastTracks = new ArrayList<GpxTrack>(); // List of tracks at last paint
     
    111126        data = d;
    112127        computeCacheInSync = false;
     128        trackVisibility = new boolean[d.tracks.size()];
     129        for(int i=0; i < d.tracks.size(); i++) {
     130            trackVisibility[i] = true;
     131        }
    113132    }
    114133
     
    122141        this.setName(name);
    123142        this.isLocalFile = isLocal;
     143    }
     144
     145    /**
     146     * returns a human readable string that shows the timespan of the given track
     147     */
     148    private static String getTimespanForTrack(GpxTrack trk) {
     149        WayPoint earliest = null, latest = null;
     150
     151        for (GpxTrackSegment seg : trk.getSegments()) {
     152            for (WayPoint pnt : seg.getWayPoints()) {
     153                if (latest == null) {
     154                    latest = earliest = pnt;
     155                } else {
     156                    if (pnt.compareTo(earliest) < 0) {
     157                        earliest = pnt;
     158                    } else {
     159                        latest = pnt;
     160                    }
     161                }
     162            }
     163        }
     164
     165        String ts = "";
     166
     167        if (earliest != null && latest != null) {
     168            DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
     169            String earliestDate = df.format(earliest.getTime());
     170            String latestDate = df.format(latest.getTime());
     171
     172            if (earliestDate.equals(latestDate)) {
     173                DateFormat tf = DateFormat.getTimeInstance(DateFormat.SHORT);
     174                ts += earliestDate + " ";
     175                ts += tf.format(earliest.getTime()) + " - " + tf.format(latest.getTime());
     176            } else {
     177                DateFormat dtf = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
     178                ts += dtf.format(earliest.getTime()) + " - " + dtf.format(latest.getTime());
     179            }
     180
     181            int diff = (int) (latest.time - earliest.time);
     182            ts += String.format(" (%d:%02d)", diff / 3600, (diff % 3600) / 60);
     183        }
     184        return ts;
    124185    }
    125186
     
    150211
    151212            for (GpxTrack trk : data.tracks) {
    152                 WayPoint earliest = null, latest = null;
    153 
    154213                info.append("<tr><td>");
    155214                if (trk.getAttributes().containsKey("name")) {
     
    161220                }
    162221                info.append("</td><td>");
    163 
    164                 for (GpxTrackSegment seg : trk.getSegments()) {
    165                     for (WayPoint pnt : seg.getWayPoints()) {
    166                         if (latest == null) {
    167                             latest = earliest = pnt;
    168                         } else {
    169                             if (pnt.compareTo(earliest) < 0) {
    170                                 earliest = pnt;
    171                             } else {
    172                                 latest = pnt;
    173                             }
    174                         }
    175                     }
    176                 }
    177 
    178                 if (earliest != null && latest != null) {
    179                     DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
    180                     String earliestDate = df.format(earliest.getTime());
    181                     String latestDate = df.format(latest.getTime());
    182 
    183                     if (earliestDate.equals(latestDate)) {
    184                         DateFormat tf = DateFormat.getTimeInstance(DateFormat.SHORT);
    185                         info.append(earliestDate).append(" ");
    186                         info.append(tf.format(earliest.getTime())).append(" - ").append(tf.format(latest.getTime()));
    187                     } else {
    188                         DateFormat dtf = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
    189                         info.append(dtf.format(earliest.getTime())).append(" - ").append(dtf.format(latest.getTime()));
    190                     }
    191 
    192                     int diff = (int) (latest.time - earliest.time);
    193                     info.append(String.format(" (%d:%02d)", diff / 3600, (diff % 3600) / 60));
    194                 }
    195 
     222                info.append(getTimespanForTrack(trk));
    196223                info.append("</td><td>");
    197224                info.append(NavigatableComponent.getSystemOfMeasurement().getDistText(trk.length()));
     
    255282                new ConvertToDataLayerAction(),
    256283                SeparatorLayerAction.INSTANCE,
     284                new ChooseTrackVisibilityAction(),
    257285                new RenameLayerAction(getAssociatedFile(), this),
    258286                SeparatorLayerAction.INSTANCE,
     
    272300                new DownloadAlongTrackAction(),
    273301                SeparatorLayerAction.INSTANCE,
     302                new ChooseTrackVisibilityAction(),
    274303                new RenameLayerAction(getAssociatedFile(), this),
    275304                SeparatorLayerAction.INSTANCE,
     
    592621        LinkedList<WayPoint> visibleSegments = new LinkedList<WayPoint>();
    593622        WayPoint last = null;
     623        int i = 0;
    594624        for (GpxTrack trk: data.tracks) {
     625            // hide tracks that were de-selected in ChooseTrackVisibilityAction
     626            if(!trackVisibility[i++]) {
     627                continue;
     628            }
     629
    595630            for (GpxTrackSegment trkSeg: trk.getSegments()) {
    596631                for(WayPoint pt : trkSeg.getWayPoints())
     
    822857    public void setAssociatedFile(File file) {
    823858        data.storageFile = file;
     859    }
     860
     861    /**
     862     * allows the user to choose which of the downloaded tracks should be displayed.
     863     * they can be chosen from the gpx layer context menu.
     864     */
     865    public class ChooseTrackVisibilityAction extends AbstractAction {
     866        public ChooseTrackVisibilityAction() {
     867            super(tr("Choose visible tracks"), ImageProvider.get("dialogs/filter"));
     868        }
     869
     870        /**
     871         * gathers all available data for the tracks and returns them as array of arrays
     872         * in the expected column order  */
     873        private Object[][] buildTableContents() {
     874            Object[][] tracks = new Object[data.tracks.size()][5];
     875            int i = 0;
     876            for (GpxTrack trk : data.tracks) {
     877                Map<String, Object> attr = trk.getAttributes();
     878                String name = (String) (attr.containsKey("name") ? attr.get("name") : "");
     879                String desc = (String) (attr.containsKey("desc") ? attr.get("desc") : "");
     880                String time = getTimespanForTrack(trk);
     881                String length = NavigatableComponent.getSystemOfMeasurement().getDistText(trk.length());
     882                String url = (String) (attr.containsKey("url") ? attr.get("url") : "");
     883                tracks[i] = new String[] {name, desc, time, length, url};
     884                i++;
     885            }
     886            return tracks;
     887        }
     888
     889        /**
     890         * Builds an non-editable table whose 5th column will open a browser when double clicked.
     891         * The table will fill its parent. */
     892        private JTable buildTable(String[] headers, Object[][] content) {
     893            final JTable t = new JTable(content, headers) {
     894                @Override
     895                public Component prepareRenderer(TableCellRenderer renderer, int row, int col) {
     896                    Component c = super.prepareRenderer(renderer, row, col);
     897                    if (c instanceof JComponent) {
     898                        JComponent jc = (JComponent)c;
     899                        jc.setToolTipText((String)getValueAt(row, col));
     900                    }
     901                    return c;
     902                }
     903
     904                @Override
     905                public boolean isCellEditable(int rowIndex, int colIndex) {
     906                    return false;
     907                }
     908            };
     909            // default column widths
     910            t.getColumnModel().getColumn(0).setPreferredWidth(220);
     911            t.getColumnModel().getColumn(1).setPreferredWidth(300);
     912            t.getColumnModel().getColumn(2).setPreferredWidth(200);
     913            t.getColumnModel().getColumn(3).setPreferredWidth(50);
     914            t.getColumnModel().getColumn(4).setPreferredWidth(100);
     915            // make the link clickable
     916            final MouseListener urlOpener = new MouseAdapter() {
     917                @Override
     918                public void mouseClicked(MouseEvent e) {
     919                    if (e.getClickCount() != 2)
     920                        return;
     921                    JTable t = (JTable)e.getSource();
     922                    int col = t.convertColumnIndexToModel(t.columnAtPoint(e.getPoint()));
     923                    if(col != 4) // only accept clicks on the URL column
     924                        return;
     925                    int row = t.rowAtPoint(e.getPoint());
     926                    String url = (String) t.getValueAt(row, col);
     927                    if(url == "")
     928                        return;
     929                    OpenBrowser.displayUrl(url);
     930                }
     931            };
     932            t.addMouseListener(urlOpener);
     933            t.setFillsViewportHeight(true);
     934            return t;
     935        }
     936
     937        /** selects all rows (=tracks) in the table that are currently visible */
     938        private void selectVisibleTracksInTable(JTable table) {
     939            // don't select any tracks if the layer is not visible
     940            if(!isVisible())
     941                return;
     942            ListSelectionModel s = table.getSelectionModel();
     943            s.clearSelection();
     944            for(int i=0; i < trackVisibility.length; i++)
     945                if(trackVisibility[i]) {
     946                    s.addSelectionInterval(i, i);
     947                }
     948        }
     949
     950        /** listens to selection changes in the table and redraws the map */
     951        private void listenToSelectionChanges(JTable table) {
     952            table.getSelectionModel().addListSelectionListener(new ListSelectionListener(){
     953                public void valueChanged(ListSelectionEvent e) {
     954                    if(!(e.getSource() instanceof ListSelectionModel))
     955                        return;
     956
     957                    ListSelectionModel s =  (ListSelectionModel) e.getSource();
     958                    for(int i = 0; i < data.tracks.size(); i++) {
     959                        trackVisibility[i] = s.isSelectedIndex(i);
     960                    }
     961                    Main.map.mapView.preferenceChanged(null);
     962                    Main.map.repaint(100);
     963                }
     964            });
     965        }
     966
     967        @Override
     968        public void actionPerformed(ActionEvent arg0) {
     969            final JPanel msg = new JPanel(new GridBagLayout());
     970            msg.add(new JLabel(tr("<html>Select all tracks that you want to be displayed. You can drag select a "
     971                    + "range of tracks or use CTRL+Click to select specific ones. The map is updated live in the "
     972                    + "background. Open the URLs by double clicking them.</html>")),
     973                    GBC.eol().fill(GBC.HORIZONTAL));
     974
     975            // build table
     976            final boolean[] trackVisibilityBackup = trackVisibility.clone();
     977            final String[] headers = {tr("Name"), tr("Description"), tr("Timespan"), tr("Length"), tr("URL")};
     978            final JTable table = buildTable(headers, buildTableContents());
     979            selectVisibleTracksInTable(table);
     980            listenToSelectionChanges(table);
     981
     982            // make the table scrollable
     983            JScrollPane scrollPane = new JScrollPane(table);
     984            msg.add(scrollPane, GBC.eol().fill(GBC.BOTH));
     985
     986            // build dialog
     987            ExtendedDialog ed = new ExtendedDialog(
     988                    Main.parent, tr("Set track visibility for {0}", getName()),
     989                    new String[] {tr("Show all"), tr("Show selected only"), tr("Cancel")});
     990            ed.setButtonIcons(new String[] {"dialogs/layerlist/eye", "dialogs/filter", "cancel"});
     991            ed.setContent(msg, false);
     992            ed.setDefaultButton(2);
     993            ed.setCancelButton(3);
     994            ed.configureContextsensitiveHelp("/Action/ChooseTrackVisibility", true);
     995            ed.setRememberWindowGeometry(
     996                    getClass().getName() + ".geometry",
     997                    WindowGeometry.centerInWindow(Main.parent, new Dimension(1000, 500))
     998            );
     999            ed.showDialog();
     1000            int v = ed.getValue();
     1001            // cancel for unknown buttons and copy back original settings
     1002            if(v != 1 && v != 2) {
     1003                for(int i = 0; i < data.tracks.size(); i++) {
     1004                    trackVisibility[i] = trackVisibilityBackup[i];
     1005                }
     1006                Main.map.repaint();
     1007                return;
     1008            }
     1009
     1010            // set visibility (1 = show all, 2 = filter). If no tracks are selected
     1011            // set all of them visible and...
     1012            ListSelectionModel s = table.getSelectionModel();
     1013            final boolean all = v == 1 || s.isSelectionEmpty();
     1014            for(int i = 0; i < data.tracks.size(); i++) {
     1015                trackVisibility[i] = all || s.isSelectedIndex(i);
     1016            }
     1017            // ...sync with layer visibility instead to avoid having two ways to hide everything
     1018            setVisible(v == 1 || !s.isSelectionEmpty());
     1019            Main.map.repaint();
     1020        }
    8241021    }
    8251022
Note: See TracChangeset for help on using the changeset viewer.