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

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