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

Last change on this file since 11366 was 11322, checked in by Don-vip, 7 years ago

fix #14032 - include editor (retrieved from created_by changeset tag) in history dialog

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