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

Last change on this file since 17921 was 17889, checked in by simon04, 3 years ago

see #20880 - Extract class VersionTableCellRenderer

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