001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.history;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Component;
007import java.awt.Dimension;
008import java.awt.Point;
009import java.awt.Rectangle;
010import java.awt.event.ActionEvent;
011import java.awt.event.ItemEvent;
012import java.awt.event.ItemListener;
013import java.awt.event.MouseEvent;
014import java.util.Observable;
015import java.util.Observer;
016
017import javax.swing.DefaultCellEditor;
018import javax.swing.JCheckBox;
019import javax.swing.JLabel;
020import javax.swing.JPopupMenu;
021import javax.swing.JRadioButton;
022import javax.swing.JTable;
023import javax.swing.SwingConstants;
024import javax.swing.UIManager;
025import javax.swing.event.TableModelEvent;
026import javax.swing.event.TableModelListener;
027import javax.swing.table.TableCellRenderer;
028
029import org.openstreetmap.josm.Main;
030import org.openstreetmap.josm.actions.AbstractInfoAction;
031import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
032import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
033import org.openstreetmap.josm.tools.ImageProvider;
034
035/**
036 * VersionTable shows a list of version in a {@link org.openstreetmap.josm.data.osm.history.History}
037 * of an {@link org.openstreetmap.josm.data.osm.OsmPrimitive}.
038 *
039 */
040public class VersionTable extends JTable implements Observer{
041    private VersionTablePopupMenu popupMenu;
042    private final HistoryBrowserModel model;
043
044    protected void build() {
045        getTableHeader().setFont(getTableHeader().getFont().deriveFont(9f));
046        setRowSelectionAllowed(false);
047        setShowGrid(false);
048        setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
049        setBackground(UIManager.getColor("Button.background"));
050        setIntercellSpacing(new Dimension(6, 0));
051        putClientProperty("terminateEditOnFocusLost", true);
052        popupMenu = new VersionTablePopupMenu();
053        addMouseListener(new MouseListener());
054        getModel().addTableModelListener(new TableModelListener() {
055            @Override
056            public void tableChanged(TableModelEvent e) {
057                adjustColumnWidth(VersionTable.this, 0, 0);
058                adjustColumnWidth(VersionTable.this, 1, -8);
059                adjustColumnWidth(VersionTable.this, 2, -8);
060                adjustColumnWidth(VersionTable.this, 3, 0);
061                adjustColumnWidth(VersionTable.this, 4, 0);
062            }
063        });
064    }
065
066    public VersionTable(HistoryBrowserModel model) {
067        super(model.getVersionTableModel(), new VersionTableColumnModel());
068        model.addObserver(this);
069        build();
070        this.model = model;
071    }
072
073    // some kind of hack to prevent the table from scrolling to the
074    // right when clicking on the cells
075    @Override
076    public void scrollRectToVisible(Rectangle aRect) {
077        super.scrollRectToVisible(new Rectangle(0, aRect.y, aRect.width, aRect.height));
078    }
079
080    protected HistoryBrowserModel.VersionTableModel getVersionTableModel() {
081        return (HistoryBrowserModel.VersionTableModel) getModel();
082    }
083
084    @Override
085    public void update(Observable o, Object arg) {
086        repaint();
087    }
088
089    class MouseListener extends PopupMenuLauncher {
090        public MouseListener() {
091            super(popupMenu);
092        }
093        @Override
094        public void mousePressed(MouseEvent e) {
095            super.mousePressed(e);
096            if (!e.isPopupTrigger() && e.getButton() == MouseEvent.BUTTON1) {
097                int row = rowAtPoint(e.getPoint());
098                int col = columnAtPoint(e.getPoint());
099                if (row > 0 && (col == VersionTableColumnModel.COL_DATE || col == VersionTableColumnModel.COL_USER)) {
100                    model.getVersionTableModel().setCurrentPointInTime(row);
101                    model.getVersionTableModel().setReferencePointInTime(row - 1);
102                }
103            }
104        }
105        @Override
106        protected int checkTableSelection(JTable table, Point p) {
107            HistoryBrowserModel.VersionTableModel model = getVersionTableModel();
108            int row = rowAtPoint(p);
109            if (row > -1 && !model.isLatest(row)) {
110                popupMenu.prepare(model.getPrimitive(row));
111            }
112            return row;
113        }
114    }
115
116    static class ChangesetInfoAction extends AbstractInfoAction {
117        private HistoryOsmPrimitive primitive;
118
119        public ChangesetInfoAction() {
120            super(true);
121            putValue(NAME, tr("Changeset info"));
122            putValue(SHORT_DESCRIPTION, tr("Launch browser with information about the changeset"));
123            putValue(SMALL_ICON, ImageProvider.get("about"));
124        }
125
126        @Override
127        protected String createInfoUrl(Object infoObject) {
128            HistoryOsmPrimitive primitive = (HistoryOsmPrimitive) infoObject;
129            return getBaseBrowseUrl() + "/changeset/" + primitive.getChangesetId();
130        }
131
132        @Override
133        public void actionPerformed(ActionEvent e) {
134            if (!isEnabled())
135                return;
136            String url = createInfoUrl(primitive);
137            launchBrowser(url);
138        }
139
140        public void prepare(HistoryOsmPrimitive primitive) {
141            putValue(NAME, tr("Show changeset {0}", primitive.getChangesetId()));
142            this.primitive = primitive;
143        }
144    }
145
146    static class VersionTablePopupMenu extends JPopupMenu {
147
148        private ChangesetInfoAction changesetInfoAction;
149
150        protected void build() {
151            changesetInfoAction = new ChangesetInfoAction();
152            add(changesetInfoAction);
153        }
154        public VersionTablePopupMenu() {
155            super();
156            build();
157        }
158
159        public void prepare(HistoryOsmPrimitive primitive) {
160            changesetInfoAction.prepare(primitive);
161            invalidate();
162        }
163    }
164
165    public static class RadioButtonRenderer extends JRadioButton implements TableCellRenderer {
166
167        @Override
168        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,int row,int column) {
169            setSelected(value != null && (Boolean)value);
170            setHorizontalAlignment(SwingConstants.CENTER);
171            return this;
172        }
173    }
174
175    public static class RadioButtonEditor extends DefaultCellEditor implements ItemListener {
176
177        private JRadioButton btn;
178
179        public RadioButtonEditor() {
180            super(new JCheckBox());
181            btn = new JRadioButton();
182            btn.setHorizontalAlignment(SwingConstants.CENTER);
183        }
184
185        @Override
186        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
187            if (value == null) return null;
188            boolean val = (Boolean) value;
189            btn.setSelected(val);
190            btn.addItemListener(this);
191            return btn;
192        }
193
194        @Override
195        public Object getCellEditorValue() {
196            btn.removeItemListener(this);
197            return btn.isSelected();
198        }
199
200        @Override
201        public void itemStateChanged(ItemEvent e) {
202            fireEditingStopped();
203        }
204    }
205
206    public static class AlignedRenderer extends JLabel implements TableCellRenderer {
207        public AlignedRenderer(int hAlignment) {
208            setHorizontalAlignment(hAlignment);
209        }
210        @Override
211        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,int row,int column) {
212            String v = value.toString();
213            setText(v);
214            return this;
215        }
216    }
217
218    private static void adjustColumnWidth(JTable tbl, int col, int cellInset) {
219        int maxwidth = 0;
220
221        for (int row=0; row<tbl.getRowCount(); row++) {
222            TableCellRenderer tcr = tbl.getCellRenderer(row, col);
223            Object val = tbl.getValueAt(row, col);
224            Component comp = tcr.getTableCellRendererComponent(tbl, val, false, false, row, col);
225            maxwidth = Math.max(comp.getPreferredSize().width + cellInset, maxwidth);
226        }
227        TableCellRenderer tcr = tbl.getTableHeader().getDefaultRenderer();
228        Object val = tbl.getColumnModel().getColumn(col).getHeaderValue();
229        Component comp = tcr.getTableCellRendererComponent(tbl, val, false, false, -1, col);
230        maxwidth = Math.max(comp.getPreferredSize().width + Main.pref.getInteger("table.header-inset", 0), maxwidth);
231
232        int spacing = tbl.getIntercellSpacing().width;
233        tbl.getColumnModel().getColumn(col).setPreferredWidth(maxwidth + spacing);
234    }
235
236}