Changeset 4332 in josm


Ignore:
Timestamp:
Aug 21, 2011 9:03:06 PM (21 months 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.