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