001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.layer;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Component;
007import java.awt.GridBagLayout;
008import java.awt.event.ActionEvent;
009import java.awt.event.ActionListener;
010import java.util.Collections;
011import java.util.List;
012
013import javax.swing.JButton;
014import javax.swing.JCheckBox;
015import javax.swing.JLabel;
016import javax.swing.JOptionPane;
017import javax.swing.JPanel;
018import javax.swing.JScrollPane;
019import javax.swing.JTable;
020import javax.swing.ListSelectionModel;
021import javax.swing.event.ListSelectionEvent;
022import javax.swing.event.ListSelectionListener;
023import javax.swing.table.AbstractTableModel;
024import javax.swing.table.TableColumnModel;
025
026import org.openstreetmap.josm.data.SystemOfMeasurement;
027import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
028import org.openstreetmap.josm.gui.ExtendedDialog;
029import org.openstreetmap.josm.gui.layer.GpxLayer;
030import org.openstreetmap.josm.spi.preferences.Config;
031import org.openstreetmap.josm.tools.GBC;
032import org.openstreetmap.josm.tools.ImageProvider;
033import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
034
035/**
036 * The Dialog asking the user to prioritize GPX layers when cutting overlapping tracks.
037 * Shows a checkbox asking whether to combine the tracks on cuts.
038 * @since 14338
039 */
040public class MergeGpxLayerDialog extends ExtendedDialog {
041
042    private final GpxLayersTableModel model;
043    private final JTable t;
044    private final JCheckBox c;
045    private final JButton btnUp;
046    private final JButton btnDown;
047
048    /**
049     * Constructs a new {@code MergeGpxLayerDialog}
050     * @param parent the parent
051     * @param layers the GpxLayers to choose from
052     */
053    public MergeGpxLayerDialog(Component parent, List<GpxLayer> layers) {
054        super(parent, tr("Merge GPX layers"), tr("Merge"), tr("Cancel"));
055        setButtonIcons("dialogs/mergedown", "cancel");
056
057        JPanel p = new JPanel(new GridBagLayout());
058        p.add(new JLabel("<html>" +
059                tr("Please select the order of the selected layers:<br>Tracks will be cut, when timestamps of higher layers are overlapping.") +
060                "</html>"), GBC.std(0, 0).fill(GBC.HORIZONTAL).span(2));
061
062        c = new JCheckBox(tr("Connect overlapping tracks on cuts"));
063        c.setSelected(Config.getPref().getBoolean("mergelayer.gpx.connect", true));
064        p.add(c, GBC.std(0, 1).fill(GBC.HORIZONTAL).span(2));
065
066        model = new GpxLayersTableModel(layers);
067        t = new JTable(model);
068        t.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
069        t.setRowSelectionInterval(0, 0);
070
071        JScrollPane sp = new JScrollPane(t);
072        p.add(sp, GBC.std(0, 2).fill().span(2));
073
074        t.getSelectionModel().addListSelectionListener(new RowSelectionChangedListener());
075        TableColumnModel cmodel = t.getColumnModel();
076        cmodel.getColumn(0).setPreferredWidth((int) (sp.getPreferredSize().getWidth() - 150));
077        cmodel.getColumn(1).setPreferredWidth(75);
078        cmodel.getColumn(2).setPreferredWidth(75);
079
080        btnUp = new JButton(tr("Move layer up"));
081        btnUp.setIcon(ImageProvider.get("dialogs", "up", ImageSizes.SMALLICON));
082        btnUp.setEnabled(false);
083
084        btnDown = new JButton(tr("Move layer down"));
085        btnDown.setIcon(ImageProvider.get("dialogs", "down", ImageSizes.SMALLICON));
086
087        p.add(btnUp, GBC.std(0, 3).fill(GBC.HORIZONTAL));
088        p.add(btnDown, GBC.std(1, 3).fill(GBC.HORIZONTAL));
089
090        btnUp.addActionListener(new MoveLayersActionListener(true));
091        btnDown.addActionListener(new MoveLayersActionListener(false));
092
093        setContent(p);
094    }
095
096    @Override
097    public MergeGpxLayerDialog showDialog() {
098        super.showDialog();
099        if (getValue() == 1) {
100            Config.getPref().putBoolean("mergelayer.gpx.connect", c.isSelected());
101        }
102        return this;
103    }
104
105    /**
106     * Whether the user chose to connect the tracks on cuts
107     * @return the checkbox state
108     */
109    public boolean connectCuts() {
110        return c.isSelected();
111    }
112
113    /**
114     * The {@code List<GpxLayer>} as sorted by the user
115     * @return the list
116     */
117    public List<GpxLayer> getSortedLayers() {
118        return model.getSortedLayers();
119    }
120
121    private class MoveLayersActionListener implements ActionListener {
122
123        private final boolean moveUp;
124
125        MoveLayersActionListener(boolean up) {
126            moveUp = up;
127        }
128
129        @Override
130        public void actionPerformed(ActionEvent e) {
131            int row = t.getSelectedRow();
132            int newRow = row + (moveUp ? -1 : 1);
133
134            if ((row == 0 || newRow == 0)
135                    && !ConditionalOptionPaneUtil.showConfirmationDialog(
136                            "gpx_target_change",
137                            getParent(),
138                            new JLabel("<html>" +
139                                    tr("This will change the target layer to \"{0}\".<br>Would you like to continue?",
140                                    model.getValueAt(1, 0).toString()) + "</html>"),
141                            tr("Information"),
142                            JOptionPane.OK_CANCEL_OPTION,
143                            JOptionPane.INFORMATION_MESSAGE,
144                            JOptionPane.OK_OPTION)) {
145                return;
146            }
147
148            model.moveRow(row, newRow);
149            t.getSelectionModel().setSelectionInterval(newRow, newRow);
150            t.repaint();
151        }
152    }
153
154    private class RowSelectionChangedListener implements ListSelectionListener {
155
156        @Override
157        public void valueChanged(ListSelectionEvent e) {
158            btnUp.setEnabled(t.getSelectedRow() > 0);
159            btnDown.setEnabled(t.getSelectedRow() < model.getRowCount() - 1);
160        }
161    }
162
163    private static class GpxLayersTableModel extends AbstractTableModel {
164
165        private final String[] cols = {tr("GPX layer"), tr("Length"), tr("Segments")};
166        private final List<GpxLayer> layers;
167
168        GpxLayersTableModel(List<GpxLayer> l) {
169            layers = l;
170        }
171
172        @Override
173        public String getColumnName(int column) {
174            return cols[column];
175        }
176
177        @Override
178        public int getColumnCount() {
179            return cols.length;
180        }
181
182        @Override
183        public int getRowCount() {
184            return layers.size();
185        }
186
187        void moveRow(int row, int newRow) {
188            Collections.swap(layers, row, newRow);
189        }
190
191        List<GpxLayer> getSortedLayers() {
192            return layers;
193        }
194
195        @Override
196        public Object getValueAt(int row, int col) {
197            switch (col) {
198            case 0:
199                String n = layers.get(row).getName();
200                if (row == 0) {
201                    return tr("{0} (target layer)", n);
202                } else {
203                    return n;
204                }
205            case 1:
206                return SystemOfMeasurement.getSystemOfMeasurement().getDistText(layers.get(row).data.length());
207            case 2:
208                return layers.get(row).data.getTrackSegsCount();
209            default:
210                throw new IndexOutOfBoundsException(Integer.toString(col));
211            }
212        }
213    }
214}