001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Component;
007import java.awt.event.ActionEvent;
008import java.awt.event.KeyEvent;
009import java.awt.event.MouseAdapter;
010import java.awt.event.MouseEvent;
011import java.util.ArrayList;
012import java.util.Arrays;
013import java.util.Collection;
014import java.util.Collections;
015import java.util.List;
016
017import javax.swing.AbstractAction;
018import javax.swing.Action;
019import javax.swing.DefaultListSelectionModel;
020import javax.swing.JComponent;
021import javax.swing.JLabel;
022import javax.swing.JTable;
023import javax.swing.ListSelectionModel;
024import javax.swing.event.ListSelectionEvent;
025import javax.swing.event.ListSelectionListener;
026import javax.swing.table.DefaultTableCellRenderer;
027import javax.swing.table.DefaultTableColumnModel;
028import javax.swing.table.DefaultTableModel;
029import javax.swing.table.TableCellRenderer;
030import javax.swing.table.TableColumn;
031
032import org.openstreetmap.josm.Main;
033import org.openstreetmap.josm.data.SelectionChangedListener;
034import org.openstreetmap.josm.data.osm.DataSet;
035import org.openstreetmap.josm.data.osm.OsmPrimitive;
036import org.openstreetmap.josm.data.osm.PrimitiveId;
037import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
038import org.openstreetmap.josm.data.osm.history.HistoryDataSetListener;
039import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
040import org.openstreetmap.josm.gui.SideButton;
041import org.openstreetmap.josm.gui.help.HelpUtil;
042import org.openstreetmap.josm.gui.history.HistoryBrowserDialogManager;
043import org.openstreetmap.josm.gui.history.HistoryLoadTask;
044import org.openstreetmap.josm.io.OnlineResource;
045import org.openstreetmap.josm.tools.ImageProvider;
046import org.openstreetmap.josm.tools.InputMapUtils;
047import org.openstreetmap.josm.tools.Shortcut;
048
049/**
050 * HistoryDialog displays a list of the currently selected primitives and provides
051 * two actions for (1) (re)loading the history of the selected primitives and (2)
052 * for launching a history browser for each selected primitive.
053 *
054 */
055public class HistoryDialog extends ToggleDialog implements HistoryDataSetListener {
056
057    /** the table model */
058    protected HistoryItemTableModel model;
059    /** the table with the history items */
060    protected JTable historyTable;
061
062    protected ShowHistoryAction showHistoryAction;
063    protected ReloadAction reloadAction;
064
065    /**
066     * Constructs a new {@code HistoryDialog}.
067     */
068    public HistoryDialog() {
069        super(tr("History"), "history", tr("Display the history of all selected items."),
070                Shortcut.registerShortcut("subwindow:history", tr("Toggle: {0}", tr("History")), KeyEvent.VK_H,
071                        Shortcut.ALT_SHIFT), 150);
072        build();
073        HelpUtil.setHelpContext(this, HelpUtil.ht("/Dialog/History"));
074    }
075
076    /**
077     * builds the GUI
078     */
079    protected void build() {
080        DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
081        historyTable = new JTable(
082                model = new HistoryItemTableModel(selectionModel),
083                new HistoryTableColumnModel(),
084                selectionModel
085        );
086        historyTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
087        final TableCellRenderer oldRenderer = historyTable.getTableHeader().getDefaultRenderer();
088        historyTable.getTableHeader().setDefaultRenderer(new DefaultTableCellRenderer(){
089            @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
090                JComponent c = (JComponent)oldRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
091                if (!"".equals(value))
092                    return c;
093                JLabel l = new JLabel(ImageProvider.get("misc","showhide"));
094                l.setForeground(c.getForeground());
095                l.setBackground(c.getBackground());
096                l.setFont(c.getFont());
097                l.setBorder(c.getBorder());
098                l.setOpaque(true);
099                return l;
100            }
101        });
102        historyTable.addMouseListener(new ShowHistoryMouseAdapter());
103        historyTable.setTableHeader(null);
104
105        createLayout(historyTable, true, Arrays.asList(new SideButton[] {
106            new SideButton(reloadAction = new ReloadAction()),
107            new SideButton(showHistoryAction = new ShowHistoryAction())
108        }));
109
110        // wire actions
111        //
112        historyTable.getSelectionModel().addListSelectionListener(showHistoryAction);
113        historyTable.getSelectionModel().addListSelectionListener(reloadAction);
114
115        // Show history dialog on Enter/Spacebar
116        InputMapUtils.addEnterAction(historyTable, showHistoryAction);
117        InputMapUtils.addSpacebarAction(historyTable, showHistoryAction);
118    }
119
120    @Override
121    public void showNotify() {
122        HistoryDataSet.getInstance().addHistoryDataSetListener(this);
123        DataSet.addSelectionListener(model);
124        if (Main.main.getCurrentDataSet() == null) {
125            model.selectionChanged(null);
126        } else {
127            model.selectionChanged(Main.main.getCurrentDataSet().getAllSelected());
128        }
129    }
130
131    @Override
132    public void hideNotify() {
133        HistoryDataSet.getInstance().removeHistoryDataSetListener(this);
134        DataSet.removeSelectionListener(model);
135    }
136
137    /* ----------------------------------------------------------------------------- */
138    /* interface HistoryDataSetListener                                              */
139    /* ----------------------------------------------------------------------------- */
140    @Override
141    public void historyUpdated(HistoryDataSet source, PrimitiveId primitiveId) {
142        model.refresh();
143    }
144
145    @Override
146    public void historyDataSetCleared(HistoryDataSet source) {
147        model.refresh();
148    }
149
150    /**
151     * The table model with the history items
152     *
153     */
154    static class HistoryItemTableModel extends DefaultTableModel implements SelectionChangedListener{
155        private List<OsmPrimitive> data;
156        private DefaultListSelectionModel selectionModel;
157
158        public HistoryItemTableModel(DefaultListSelectionModel selectionModel) {
159            data = new ArrayList<>();
160            this.selectionModel = selectionModel;
161        }
162
163        @Override
164        public int getRowCount() {
165            if (data == null)
166                return 0;
167            return data.size();
168        }
169
170        @Override
171        public Object getValueAt(int row, int column) {
172            return data.get(row);
173        }
174
175        @Override
176        public boolean isCellEditable(int row, int column) {
177            return false;
178        }
179
180        protected List<OsmPrimitive> getSelectedPrimitives() {
181            List<OsmPrimitive> ret = new ArrayList<>();
182            for (int i=0; i< data.size(); i++) {
183                if (selectionModel.isSelectedIndex(i)) {
184                    ret.add(data.get(i));
185                }
186            }
187            return ret;
188        }
189
190        protected void selectPrimitives(Collection<OsmPrimitive> primitives) {
191            for (OsmPrimitive p: primitives) {
192                int idx = data.indexOf(p);
193                if (idx < 0) {
194                    continue;
195                }
196                selectionModel.addSelectionInterval(idx, idx);
197            }
198        }
199
200        public void refresh() {
201            List<OsmPrimitive> selectedPrimitives = getSelectedPrimitives();
202            data.clear();
203            if (Main.main.getCurrentDataSet() == null)
204                return;
205            for (OsmPrimitive primitive: Main.main.getCurrentDataSet().getAllSelected()) {
206                if (primitive.isNew()) {
207                    continue;
208                }
209                data.add(primitive);
210            }
211            fireTableDataChanged();
212            selectPrimitives(selectedPrimitives);
213        }
214
215        @Override
216        public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
217            data.clear();
218            selectionModel.clearSelection();
219            if (newSelection != null && !newSelection.isEmpty()) {
220                for (OsmPrimitive primitive: newSelection) {
221                    if (primitive.isNew()) {
222                        continue;
223                    }
224                    data.add(primitive);
225                }
226            }
227            fireTableDataChanged();
228            selectionModel.addSelectionInterval(0, data.size()-1);
229        }
230
231        public List<OsmPrimitive> getPrimitives(int [] rows) {
232            if (rows == null || rows.length == 0) return Collections.emptyList();
233            List<OsmPrimitive> ret = new ArrayList<>(rows.length);
234            for (int row: rows) {
235                ret.add(data.get(row));
236            }
237            return ret;
238        }
239
240        public OsmPrimitive getPrimitive(int row) {
241            return data.get(row);
242        }
243    }
244
245    /**
246     * The column model
247     */
248    static class HistoryTableColumnModel extends DefaultTableColumnModel {
249        protected void createColumns() {
250            TableColumn col = null;
251            OsmPrimitivRenderer renderer = new OsmPrimitivRenderer();
252            // column 0 - History item
253            col = new TableColumn(0);
254            col.setHeaderValue(tr("Object with history"));
255            col.setCellRenderer(renderer);
256            addColumn(col);
257        }
258
259        public HistoryTableColumnModel() {
260            createColumns();
261        }
262    }
263
264    /**
265     * The action for reloading history information of the currently selected primitives.
266     *
267     */
268    class ReloadAction extends AbstractAction implements ListSelectionListener {
269
270        public ReloadAction() {
271            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs","refresh"));
272            putValue(Action.NAME, tr("Reload"));
273            putValue(Action.SHORT_DESCRIPTION, tr("Reload all currently selected objects and refresh the list."));
274            updateEnabledState();
275        }
276
277        @Override
278        public void actionPerformed(ActionEvent e) {
279            int [] rows = historyTable.getSelectedRows();
280            if (rows == null || rows.length == 0) return;
281
282            List<OsmPrimitive> selectedItems = model.getPrimitives(rows);
283            HistoryLoadTask task = new HistoryLoadTask();
284            task.add(selectedItems);
285            Main.worker.execute(task);
286        }
287
288        protected void updateEnabledState() {
289            setEnabled(historyTable.getSelectedRowCount() > 0 && !Main.isOffline(OnlineResource.OSM_API));
290        }
291
292        @Override
293        public void valueChanged(ListSelectionEvent e) {
294            updateEnabledState();
295        }
296    }
297
298    class ShowHistoryMouseAdapter extends MouseAdapter {
299        @Override
300        public void mouseClicked(MouseEvent e) {
301            if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
302                int row = historyTable.rowAtPoint(e.getPoint());
303                HistoryBrowserDialogManager.getInstance().showHistory(Collections.singletonList(model.getPrimitive(row)));
304            }
305        }
306    }
307
308    /**
309     * The action for showing history information of the current history item.
310     */
311    class ShowHistoryAction extends AbstractAction implements ListSelectionListener {
312        public ShowHistoryAction() {
313            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs","history"));
314            putValue(Action.NAME, tr("Show"));
315            putValue(Action.SHORT_DESCRIPTION, tr("Display the history of the selected objects."));
316            updateEnabledState();
317        }
318
319        @Override
320        public void actionPerformed(ActionEvent e) {
321            int [] rows = historyTable.getSelectedRows();
322            if (rows == null || rows.length == 0) return;
323            HistoryBrowserDialogManager.getInstance().showHistory(model.getPrimitives(rows));
324        }
325
326        protected void updateEnabledState() {
327            setEnabled(historyTable.getSelectedRowCount() > 0);
328        }
329
330        @Override
331        public void valueChanged(ListSelectionEvent e) {
332            updateEnabledState();
333        }
334    }
335}