source: josm/trunk/src/org/openstreetmap/josm/gui/history/VersionTable.java @ 11646

Last change on this file since 11646 was 11646, checked in by Don-vip, 8 months ago

see #14175 - refactor HistoryBrowserModel

  • Property svn:eol-style set to native
File size: 12.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.history;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Component;
7import java.awt.Dimension;
8import java.awt.Point;
9import java.awt.Rectangle;
10import java.awt.event.ActionEvent;
11import java.awt.event.ItemEvent;
12import java.awt.event.ItemListener;
13import java.awt.event.KeyAdapter;
14import java.awt.event.KeyEvent;
15import java.awt.event.MouseEvent;
16
17import javax.swing.DefaultCellEditor;
18import javax.swing.JCheckBox;
19import javax.swing.JLabel;
20import javax.swing.JPopupMenu;
21import javax.swing.JRadioButton;
22import javax.swing.JTable;
23import javax.swing.SwingConstants;
24import javax.swing.UIManager;
25import javax.swing.event.ChangeEvent;
26import javax.swing.event.ChangeListener;
27import javax.swing.table.TableCellRenderer;
28
29import org.openstreetmap.josm.Main;
30import org.openstreetmap.josm.actions.AbstractInfoAction;
31import org.openstreetmap.josm.data.osm.User;
32import org.openstreetmap.josm.data.osm.history.History;
33import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
34import org.openstreetmap.josm.gui.util.GuiHelper;
35import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
36import org.openstreetmap.josm.io.XmlWriter;
37import org.openstreetmap.josm.tools.ImageProvider;
38import org.openstreetmap.josm.tools.OpenBrowser;
39
40/**
41 * VersionTable shows a list of version in a {@link org.openstreetmap.josm.data.osm.history.History}
42 * of an {@link org.openstreetmap.josm.data.osm.OsmPrimitive}.
43 * @since 1709
44 */
45public class VersionTable extends JTable implements ChangeListener {
46    private VersionTablePopupMenu popupMenu;
47    private final transient HistoryBrowserModel model;
48
49    /**
50     * Constructs a new {@code VersionTable}.
51     * @param model model used by the history browser
52     */
53    public VersionTable(HistoryBrowserModel model) {
54        super(model.getVersionTableModel(), new VersionTableColumnModel());
55        model.addChangeListener(this);
56        build();
57        this.model = model;
58    }
59
60    /**
61     * Builds the table.
62     */
63    protected void build() {
64        getTableHeader().setFont(getTableHeader().getFont().deriveFont(9f));
65        setRowSelectionAllowed(false);
66        setShowGrid(false);
67        setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
68        GuiHelper.setBackgroundReadable(this, UIManager.getColor("Button.background"));
69        setIntercellSpacing(new Dimension(6, 0));
70        putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
71        popupMenu = new VersionTablePopupMenu();
72        addMouseListener(new MouseListener());
73        addKeyListener(new KeyAdapter() {
74            @Override
75            public void keyReleased(KeyEvent e) {
76                // navigate history down/up using the corresponding arrow keys.
77                long ref = model.getReferencePointInTime().getVersion();
78                long cur = model.getCurrentPointInTime().getVersion();
79                if (e.getKeyCode() == KeyEvent.VK_DOWN) {
80                    History refNext = model.getHistory().from(ref);
81                    History curNext = model.getHistory().from(cur);
82                    if (refNext.getNumVersions() > 1 && curNext.getNumVersions() > 1) {
83                        model.setReferencePointInTime(refNext.sortAscending().get(1));
84                        model.setCurrentPointInTime(curNext.sortAscending().get(1));
85                    }
86                } else if (e.getKeyCode() == KeyEvent.VK_UP) {
87                    History refNext = model.getHistory().until(ref);
88                    History curNext = model.getHistory().until(cur);
89                    if (refNext.getNumVersions() > 1 && curNext.getNumVersions() > 1) {
90                        model.setReferencePointInTime(refNext.sortDescending().get(1));
91                        model.setCurrentPointInTime(curNext.sortDescending().get(1));
92                    }
93                }
94            }
95        });
96        getModel().addTableModelListener(e -> {
97            adjustColumnWidth(this, 0, 0);
98            adjustColumnWidth(this, 1, -8);
99            adjustColumnWidth(this, 2, -8);
100            adjustColumnWidth(this, 3, 0);
101            adjustColumnWidth(this, 4, 0);
102            adjustColumnWidth(this, 5, 0);
103        });
104    }
105
106    // some kind of hack to prevent the table from scrolling to the
107    // right when clicking on the cells
108    @Override
109    public void scrollRectToVisible(Rectangle aRect) {
110        super.scrollRectToVisible(new Rectangle(0, aRect.y, aRect.width, aRect.height));
111    }
112
113    @Override
114    public void stateChanged(ChangeEvent e) {
115        repaint();
116    }
117
118    final class MouseListener extends PopupMenuLauncher {
119        private MouseListener() {
120            super(popupMenu);
121        }
122
123        @Override
124        public void mousePressed(MouseEvent e) {
125            super.mousePressed(e);
126            if (!e.isPopupTrigger() && e.getButton() == MouseEvent.BUTTON1) {
127                int row = rowAtPoint(e.getPoint());
128                int col = columnAtPoint(e.getPoint());
129                if (row >= 0 && (col == VersionTableColumnModel.COL_DATE || col == VersionTableColumnModel.COL_USER)) {
130                    model.setCurrentPointInTime(row);
131                    model.setReferencePointInTime(Math.max(0, row - 1));
132                }
133            }
134        }
135
136        @Override
137        protected int checkTableSelection(JTable table, Point p) {
138            int row = rowAtPoint(p);
139            if (row > -1 && !model.isLatest(row)) {
140                popupMenu.prepare(model.getPrimitive(row));
141            }
142            return row;
143        }
144    }
145
146    static class ChangesetInfoAction extends AbstractInfoAction {
147        private transient HistoryOsmPrimitive primitive;
148
149        /**
150         * Constructs a new {@code ChangesetInfoAction}.
151         */
152        ChangesetInfoAction() {
153            super(true);
154            putValue(NAME, tr("Changeset info"));
155            putValue(SHORT_DESCRIPTION, tr("Launch browser with information about the changeset"));
156            putValue(SMALL_ICON, ImageProvider.get("data/changeset"));
157        }
158
159        @Override
160        protected String createInfoUrl(Object infoObject) {
161            if (infoObject instanceof HistoryOsmPrimitive) {
162                HistoryOsmPrimitive prim = (HistoryOsmPrimitive) infoObject;
163                return Main.getBaseBrowseUrl() + "/changeset/" + prim.getChangesetId();
164            } else {
165                return null;
166            }
167        }
168
169        @Override
170        public void actionPerformed(ActionEvent e) {
171            if (!isEnabled())
172                return;
173            String url = createInfoUrl(primitive);
174            OpenBrowser.displayUrl(url);
175        }
176
177        public void prepare(HistoryOsmPrimitive primitive) {
178            putValue(NAME, tr("Show changeset {0}", primitive.getChangesetId()));
179            this.primitive = primitive;
180        }
181    }
182
183    static class UserInfoAction extends AbstractInfoAction {
184        private transient HistoryOsmPrimitive primitive;
185
186        /**
187         * Constructs a new {@code UserInfoAction}.
188         */
189        UserInfoAction() {
190            super(true);
191            putValue(NAME, tr("User info"));
192            putValue(SHORT_DESCRIPTION, tr("Launch browser with information about the user"));
193            putValue(SMALL_ICON, ImageProvider.get("data/user"));
194        }
195
196        @Override
197        protected String createInfoUrl(Object infoObject) {
198            if (infoObject instanceof HistoryOsmPrimitive) {
199                HistoryOsmPrimitive hp = (HistoryOsmPrimitive) infoObject;
200                return hp.getUser() == null ? null : Main.getBaseUserUrl() + '/' + hp.getUser().getName();
201            } else {
202                return null;
203            }
204        }
205
206        @Override
207        public void actionPerformed(ActionEvent e) {
208            if (!isEnabled())
209                return;
210            String url = createInfoUrl(primitive);
211            OpenBrowser.displayUrl(url);
212        }
213
214        public void prepare(HistoryOsmPrimitive primitive) {
215            final User user = primitive.getUser();
216            putValue(NAME, "<html>" + tr("Show user {0}", user == null ? "?" :
217                    XmlWriter.encode(user.getName(), true) + " <font color=gray>(" + user.getId() + ")</font>") + "</html>");
218            this.primitive = primitive;
219        }
220    }
221
222    static class VersionTablePopupMenu extends JPopupMenu {
223
224        private ChangesetInfoAction changesetInfoAction;
225        private UserInfoAction userInfoAction;
226
227        /**
228         * Constructs a new {@code VersionTablePopupMenu}.
229         */
230        VersionTablePopupMenu() {
231            super();
232            build();
233        }
234
235        protected void build() {
236            changesetInfoAction = new ChangesetInfoAction();
237            add(changesetInfoAction);
238            userInfoAction = new UserInfoAction();
239            add(userInfoAction);
240        }
241
242        public void prepare(HistoryOsmPrimitive primitive) {
243            changesetInfoAction.prepare(primitive);
244            userInfoAction.prepare(primitive);
245            invalidate();
246        }
247    }
248
249    /**
250     * Renderer for history radio buttons in columns A and B.
251     */
252    public static class RadioButtonRenderer extends JRadioButton implements TableCellRenderer {
253
254        @Override
255        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
256                int row, int column) {
257            setSelected(value != null && (Boolean) value);
258            setHorizontalAlignment(SwingConstants.CENTER);
259            return this;
260        }
261    }
262
263    /**
264     * Editor for history radio buttons in columns A and B.
265     */
266    public static class RadioButtonEditor extends DefaultCellEditor implements ItemListener {
267
268        private final JRadioButton btn;
269
270        /**
271         * Constructs a new {@code RadioButtonEditor}.
272         */
273        public RadioButtonEditor() {
274            super(new JCheckBox());
275            btn = new JRadioButton();
276            btn.setHorizontalAlignment(SwingConstants.CENTER);
277        }
278
279        @Override
280        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
281            if (value == null)
282                return null;
283            boolean val = (Boolean) value;
284            btn.setSelected(val);
285            btn.addItemListener(this);
286            return btn;
287        }
288
289        @Override
290        public Object getCellEditorValue() {
291            btn.removeItemListener(this);
292            return btn.isSelected();
293        }
294
295        @Override
296        public void itemStateChanged(ItemEvent e) {
297            fireEditingStopped();
298        }
299    }
300
301    /**
302     * Renderer for history version labels, allowing to define horizontal alignment.
303     */
304    public static class AlignedRenderer extends JLabel implements TableCellRenderer {
305
306        /**
307         * Constructs a new {@code AlignedRenderer}.
308         * @param hAlignment Horizontal alignement. One of the following constants defined in SwingConstants:
309         *        LEFT, CENTER (the default for image-only labels), RIGHT, LEADING (the default for text-only labels) or TRAILING
310         */
311        public AlignedRenderer(int hAlignment) {
312            setHorizontalAlignment(hAlignment);
313        }
314
315        AlignedRenderer() {
316            this(SwingConstants.LEFT);
317        }
318
319        @Override
320        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
321                int row, int column) {
322            String v = "";
323            if (value != null) {
324                v = value.toString();
325            }
326            setText(v);
327            return this;
328        }
329    }
330
331    private static void adjustColumnWidth(JTable tbl, int col, int cellInset) {
332        int maxwidth = 0;
333
334        for (int row = 0; row < tbl.getRowCount(); row++) {
335            TableCellRenderer tcr = tbl.getCellRenderer(row, col);
336            Object val = tbl.getValueAt(row, col);
337            Component comp = tcr.getTableCellRendererComponent(tbl, val, false, false, row, col);
338            maxwidth = Math.max(comp.getPreferredSize().width + cellInset, maxwidth);
339        }
340        TableCellRenderer tcr = tbl.getTableHeader().getDefaultRenderer();
341        Object val = tbl.getColumnModel().getColumn(col).getHeaderValue();
342        Component comp = tcr.getTableCellRendererComponent(tbl, val, false, false, -1, col);
343        maxwidth = Math.max(comp.getPreferredSize().width + Main.pref.getInteger("table.header-inset", 0), maxwidth);
344
345        int spacing = tbl.getIntercellSpacing().width;
346        tbl.getColumnModel().getColumn(col).setPreferredWidth(maxwidth + spacing);
347    }
348}
Note: See TracBrowser for help on using the repository browser.