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

Last change on this file since 13849 was 13130, checked in by Don-vip, 6 years ago

fix #15572 - use ImageProvider attach API for all JOSM actions to ensure proper icon size everywhere

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