001 // License: GPL. For details, see LICENSE file.
002 package org.openstreetmap.josm.gui.dialogs.changeset;
003
004 import static org.openstreetmap.josm.tools.I18n.tr;
005 import static org.openstreetmap.josm.tools.I18n.trc;
006
007 import java.awt.BorderLayout;
008 import java.awt.FlowLayout;
009 import java.awt.GridBagConstraints;
010 import java.awt.GridBagLayout;
011 import java.awt.Insets;
012 import java.awt.event.ActionEvent;
013 import java.awt.event.ComponentAdapter;
014 import java.awt.event.ComponentEvent;
015 import java.beans.PropertyChangeEvent;
016 import java.beans.PropertyChangeListener;
017 import java.text.DateFormat;
018 import java.util.Collection;
019 import java.util.Collections;
020 import java.util.HashSet;
021 import java.util.Set;
022
023 import javax.swing.AbstractAction;
024 import javax.swing.BorderFactory;
025 import javax.swing.JLabel;
026 import javax.swing.JOptionPane;
027 import javax.swing.JPanel;
028 import javax.swing.JTextArea;
029 import javax.swing.JTextField;
030 import javax.swing.JToolBar;
031
032 import org.openstreetmap.josm.Main;
033 import org.openstreetmap.josm.actions.AutoScaleAction;
034 import org.openstreetmap.josm.data.osm.Changeset;
035 import org.openstreetmap.josm.data.osm.ChangesetCache;
036 import org.openstreetmap.josm.data.osm.OsmPrimitive;
037 import org.openstreetmap.josm.gui.HelpAwareOptionPane;
038 import org.openstreetmap.josm.gui.MapView;
039 import org.openstreetmap.josm.gui.MapView.EditLayerChangeListener;
040 import org.openstreetmap.josm.gui.help.HelpUtil;
041 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
042 import org.openstreetmap.josm.tools.ImageProvider;
043
044 /**
045 * This panel displays the properties of the currently selected changeset in the
046 * {@link ChangesetCacheManager}.
047 *
048 */
049 public class ChangesetDetailPanel extends JPanel implements PropertyChangeListener{
050
051 private JTextField tfID;
052 private JTextArea taComment;
053 private JTextField tfOpen;
054 private JTextField tfUser;
055 private JTextField tfCreatedOn;
056 private JTextField tfClosedOn;
057 private DonwloadChangesetContentAction actDownloadChangesetContent;
058 private UpdateChangesetAction actUpdateChangesets;
059 private RemoveFromCacheAction actRemoveFromCache;
060 private SelectInCurrentLayerAction actSelectInCurrentLayer;
061 private ZoomInCurrentLayerAction actZoomInCurrentLayerAction;
062
063 private Changeset current = null;
064
065 protected JPanel buildActionButtonPanel() {
066 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
067
068 JToolBar tb = new JToolBar(JToolBar.VERTICAL);
069 tb.setFloatable(false);
070
071 // -- remove from cache action
072 tb.add(actRemoveFromCache = new RemoveFromCacheAction());
073 actRemoveFromCache.initProperties(current);
074
075 // -- changeset update
076 tb.add(actUpdateChangesets = new UpdateChangesetAction());
077 actUpdateChangesets.initProperties(current);
078
079 // -- changeset content download
080 tb.add(actDownloadChangesetContent =new DonwloadChangesetContentAction());
081 actDownloadChangesetContent.initProperties(current);
082
083 tb.add(actSelectInCurrentLayer = new SelectInCurrentLayerAction());
084 MapView.addEditLayerChangeListener(actSelectInCurrentLayer);
085
086 tb.add(actZoomInCurrentLayerAction = new ZoomInCurrentLayerAction());
087 MapView.addEditLayerChangeListener(actZoomInCurrentLayerAction);
088
089 addComponentListener(
090 new ComponentAdapter() {
091 @Override
092 public void componentHidden(ComponentEvent e) {
093 // make sure the listener is unregistered when the panel becomes
094 // invisible
095 MapView.removeEditLayerChangeListener(actSelectInCurrentLayer);
096 MapView.removeEditLayerChangeListener(actZoomInCurrentLayerAction);
097 }
098 }
099 );
100
101 pnl.add(tb);
102 return pnl;
103 }
104
105 protected JPanel buildDetailViewPanel() {
106 JPanel pnl = new JPanel(new GridBagLayout());
107
108 GridBagConstraints gc = new GridBagConstraints();
109 gc.anchor = GridBagConstraints.FIRST_LINE_START;
110 gc.insets = new Insets(0,0,2,3);
111
112 //-- id
113 gc.fill = GridBagConstraints.HORIZONTAL;
114 gc.weightx = 0.0;
115 pnl.add(new JLabel(tr("ID:")), gc);
116
117 gc.fill = GridBagConstraints.HORIZONTAL;
118 gc.weightx = 0.0;
119 gc.gridx = 1;
120 pnl.add(tfID = new JTextField(10), gc);
121 tfID.setEditable(false);
122
123 //-- comment
124 gc.gridx = 0;
125 gc.gridy = 1;
126 gc.fill = GridBagConstraints.HORIZONTAL;
127 gc.weightx = 0.0;
128 pnl.add(new JLabel(tr("Comment:")), gc);
129
130 gc.fill = GridBagConstraints.BOTH;
131 gc.weightx = 1.0;
132 gc.weighty = 1.0;
133 gc.gridx = 1;
134 pnl.add(taComment= new JTextArea(5,40), gc);
135 taComment.setEditable(false);
136
137 //-- Open/Closed
138 gc.gridx = 0;
139 gc.gridy = 2;
140 gc.fill = GridBagConstraints.HORIZONTAL;
141 gc.weightx = 0.0;
142 gc.weighty = 0.0;
143 pnl.add(new JLabel(tr("Open/Closed:")), gc);
144
145 gc.fill = GridBagConstraints.HORIZONTAL;
146 gc.gridx = 1;
147 pnl.add(tfOpen= new JTextField(10), gc);
148 tfOpen.setEditable(false);
149
150 //-- Created by:
151 gc.gridx = 0;
152 gc.gridy = 3;
153 gc.fill = GridBagConstraints.HORIZONTAL;
154 gc.weightx = 0.0;
155 pnl.add(new JLabel(tr("Created by:")), gc);
156
157 gc.fill = GridBagConstraints.HORIZONTAL;
158 gc.weightx = 1.0;
159 gc.gridx = 1;
160 pnl.add(tfUser= new JTextField(""), gc);
161 tfUser.setEditable(false);
162
163 //-- Created On:
164 gc.gridx = 0;
165 gc.gridy = 4;
166 gc.fill = GridBagConstraints.HORIZONTAL;
167 gc.weightx = 0.0;
168 pnl.add(new JLabel(tr("Created on:")), gc);
169
170 gc.fill = GridBagConstraints.HORIZONTAL;
171 gc.gridx = 1;
172 pnl.add(tfCreatedOn= new JTextField(20), gc);
173 tfCreatedOn.setEditable(false);
174
175 //-- Closed On:
176 gc.gridx = 0;
177 gc.gridy = 5;
178 gc.fill = GridBagConstraints.HORIZONTAL;
179 gc.weightx = 0.0;
180 pnl.add(new JLabel(tr("Closed on:")), gc);
181
182 gc.fill = GridBagConstraints.HORIZONTAL;
183 gc.gridx = 1;
184 pnl.add(tfClosedOn= new JTextField(20), gc);
185 tfClosedOn.setEditable(false);
186
187 return pnl;
188 }
189
190 protected void build() {
191 setLayout(new BorderLayout());
192 setBorder(BorderFactory.createEmptyBorder(3,3,3,3));
193 add(buildDetailViewPanel(), BorderLayout.CENTER);
194 add(buildActionButtonPanel(), BorderLayout.WEST);
195 }
196
197 protected void clearView() {
198 tfID.setText("");
199 taComment.setText("");
200 tfOpen.setText("");
201 tfUser.setText("");
202 tfCreatedOn.setText("");
203 tfClosedOn.setText("");
204 }
205
206 protected void updateView(Changeset cs) {
207 String msg;
208 if (cs == null) return;
209 tfID.setText(Integer.toString(cs.getId()));
210 String comment = cs.get("comment");
211 taComment.setText(comment == null ? "" : comment);
212
213 if (cs.isOpen()) {
214 msg = trc("changeset.state", "Open");
215 } else {
216 msg = trc("changeset.state", "Closed");
217 }
218 tfOpen.setText(msg);
219
220 if (cs.getUser() == null) {
221 msg = tr("anonymous");
222 } else {
223 msg = cs.getUser().getName();
224 }
225 tfUser.setText(msg);
226 DateFormat sdf = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
227
228 tfCreatedOn.setText(cs.getCreatedAt() == null ? "" : sdf.format(cs.getCreatedAt()));
229 tfClosedOn.setText(cs.getClosedAt() == null ? "" : sdf.format(cs.getClosedAt()));
230 }
231
232 public ChangesetDetailPanel() {
233 build();
234 }
235
236 protected void setCurrentChangeset(Changeset cs) {
237 current = cs;
238 if (cs == null) {
239 clearView();
240 } else {
241 updateView(cs);
242 }
243 actDownloadChangesetContent.initProperties(current);
244 actUpdateChangesets.initProperties(current);
245 actRemoveFromCache.initProperties(current);
246 actSelectInCurrentLayer.updateEnabledState();
247 actZoomInCurrentLayerAction.updateEnabledState();
248 }
249
250 /* ---------------------------------------------------------------------------- */
251 /* interface PropertyChangeListener */
252 /* ---------------------------------------------------------------------------- */
253 public void propertyChange(PropertyChangeEvent evt) {
254 if (! evt.getPropertyName().equals(ChangesetCacheManagerModel.CHANGESET_IN_DETAIL_VIEW_PROP))
255 return;
256 Changeset cs = (Changeset)evt.getNewValue();
257 setCurrentChangeset(cs);
258 }
259
260 /**
261 * The action for removing the currently selected changeset from the changeset cache
262 */
263 class RemoveFromCacheAction extends AbstractAction {
264 public RemoveFromCacheAction() {
265 putValue(NAME, tr("Remove from cache"));
266 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
267 putValue(SHORT_DESCRIPTION, tr("Remove the changeset in the detail view panel from the local cache"));
268 }
269
270 public void actionPerformed(ActionEvent evt) {
271 if (current == null)
272 return;
273 ChangesetCache.getInstance().remove(current);
274 }
275
276 public void initProperties(Changeset cs) {
277 setEnabled(cs != null);
278 }
279 }
280
281 /**
282 * Removes the selected changesets from the local changeset cache
283 *
284 */
285 class DonwloadChangesetContentAction extends AbstractAction{
286 public DonwloadChangesetContentAction() {
287 putValue(NAME, tr("Download content"));
288 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","downloadchangesetcontent"));
289 putValue(SHORT_DESCRIPTION, tr("Download the changeset content from the OSM server"));
290 }
291
292 public void actionPerformed(ActionEvent evt) {
293 if (current == null) return;
294 ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(ChangesetDetailPanel.this,current.getId());
295 ChangesetCacheManager.getInstance().runDownloadTask(task);
296 }
297
298 public void initProperties(Changeset cs) {
299 if (cs == null) {
300 setEnabled(false);
301 return;
302 } else {
303 setEnabled(true);
304 }
305 if (cs.getContent() == null) {
306 putValue(NAME, tr("Download content"));
307 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","downloadchangesetcontent"));
308 putValue(SHORT_DESCRIPTION, tr("Download the changeset content from the OSM server"));
309 } else {
310 putValue(NAME, tr("Update content"));
311 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","updatechangesetcontent"));
312 putValue(SHORT_DESCRIPTION, tr("Update the changeset content from the OSM server"));
313 }
314 }
315 }
316
317 /**
318 * Updates the current changeset from the OSM server
319 *
320 */
321 class UpdateChangesetAction extends AbstractAction{
322 public UpdateChangesetAction() {
323 putValue(NAME, tr("Update changeset"));
324 putValue(SMALL_ICON,ImageProvider.get("dialogs/changeset","updatechangeset"));
325 putValue(SHORT_DESCRIPTION, tr("Update the changeset from the OSM server"));
326 }
327
328 public void actionPerformed(ActionEvent evt) {
329 if (current == null) return;
330 Main.worker.submit(
331 new ChangesetHeaderDownloadTask(
332 ChangesetDetailPanel.this,
333 Collections.singleton(current.getId())
334 )
335 );
336 }
337
338 public void initProperties(Changeset cs) {
339 if (cs == null) {
340 setEnabled(false);
341 return;
342 } else {
343 setEnabled(true);
344 }
345 }
346 }
347
348 /**
349 * Selects the primitives in the content of this changeset in the current
350 * data layer.
351 *
352 */
353 class SelectInCurrentLayerAction extends AbstractAction implements EditLayerChangeListener{
354
355 public SelectInCurrentLayerAction() {
356 putValue(NAME, tr("Select in layer"));
357 putValue(SMALL_ICON, ImageProvider.get("dialogs", "select"));
358 putValue(SHORT_DESCRIPTION, tr("Select the primitives in the content of this changeset in the current data layer"));
359 updateEnabledState();
360 }
361
362 protected void alertNoPrimitivesToSelect(Collection<OsmPrimitive> primitives) {
363 HelpAwareOptionPane.showOptionDialog(
364 ChangesetDetailPanel.this,
365 tr("<html>None of the objects in the content of changeset {0} is available in the current<br>"
366 + "edit layer ''{1}''.</html>",
367 current.getId(),
368 Main.main.getEditLayer().getName()
369 ),
370 tr("Nothing to select"),
371 JOptionPane.WARNING_MESSAGE,
372 HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToSelectInLayer")
373 );
374 }
375
376 public void actionPerformed(ActionEvent arg0) {
377 if (!isEnabled())
378 return;
379 if (Main.main == null || Main.main.getEditLayer() == null) return;
380 OsmDataLayer layer = Main.main.getEditLayer();
381 Set<OsmPrimitive> target = new HashSet<OsmPrimitive>();
382 for (OsmPrimitive p: layer.data.allPrimitives()) {
383 if (p.isUsable() && p.getChangesetId() == current.getId()) {
384 target.add(p);
385 }
386 }
387 if (target.isEmpty()) {
388 alertNoPrimitivesToSelect(target);
389 return;
390 }
391 layer.data.setSelected(target);
392 }
393
394 public void updateEnabledState() {
395 if (Main.main == null || Main.main.getEditLayer() == null){
396 setEnabled(false);
397 return;
398 }
399 setEnabled(current != null);
400 }
401
402 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
403 updateEnabledState();
404 }
405 }
406
407 /**
408 * Zooms to the primitives in the content of this changeset in the current
409 * data layer.
410 *
411 */
412 class ZoomInCurrentLayerAction extends AbstractAction implements EditLayerChangeListener{
413
414 public ZoomInCurrentLayerAction() {
415 putValue(NAME, tr("Zoom to in layer"));
416 putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale", "selection"));
417 putValue(SHORT_DESCRIPTION, tr("Zoom to the objects in the content of this changeset in the current data layer"));
418 updateEnabledState();
419 }
420
421 protected void alertNoPrimitivesToZoomTo() {
422 HelpAwareOptionPane.showOptionDialog(
423 ChangesetDetailPanel.this,
424 tr("<html>None of the objects in the content of changeset {0} is available in the current<br>"
425 + "edit layer ''{1}''.</html>",
426 current.getId(),
427 Main.main.getEditLayer().getName()
428 ),
429 tr("Nothing to zoom to"),
430 JOptionPane.WARNING_MESSAGE,
431 HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToZoomTo")
432 );
433 }
434
435 public void actionPerformed(ActionEvent arg0) {
436 if (!isEnabled())
437 return;
438 if (Main.main == null || Main.main.getEditLayer() == null) return;
439 OsmDataLayer layer = Main.main.getEditLayer();
440 Set<OsmPrimitive> target = new HashSet<OsmPrimitive>();
441 for (OsmPrimitive p: layer.data.allPrimitives()) {
442 if (p.isUsable() && p.getChangesetId() == current.getId()) {
443 target.add(p);
444 }
445 }
446 if (target.isEmpty()) {
447 alertNoPrimitivesToZoomTo();
448 return;
449 }
450 layer.data.setSelected(target);
451 AutoScaleAction.zoomToSelection();
452 }
453
454 public void updateEnabledState() {
455 if (Main.main == null || Main.main.getEditLayer() == null){
456 setEnabled(false);
457 return;
458 }
459 setEnabled(current != null);
460 }
461
462 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
463 updateEnabledState();
464 }
465 }
466 }