source: josm/trunk/src/org/openstreetmap/josm/actions/UploadAction.java@ 2040

Last change on this file since 2040 was 2040, checked in by Gubaer, 15 years ago

improved upload dialog
new: tags for changesets
new: multiple uploads to the same changeset
fixed #3381: simple imput of a changeset source
fixed #2491: Allow arbitrary key-value pairs in changesets
fixed #2436: Allow multiple uploads to one changeset

  • Property svn:eol-style set to native
File size: 33.2 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Dimension;
8import java.awt.GridBagConstraints;
9import java.awt.GridBagLayout;
10import java.awt.event.ActionEvent;
11import java.awt.event.KeyEvent;
12import java.io.IOException;
13import java.net.HttpURLConnection;
14import java.util.Collection;
15import java.util.LinkedList;
16import java.util.List;
17import java.util.logging.Logger;
18import java.util.regex.Matcher;
19import java.util.regex.Pattern;
20
21import javax.swing.BoxLayout;
22import javax.swing.ButtonGroup;
23import javax.swing.JCheckBox;
24import javax.swing.JLabel;
25import javax.swing.JList;
26import javax.swing.JOptionPane;
27import javax.swing.JPanel;
28import javax.swing.JRadioButton;
29import javax.swing.JScrollPane;
30import javax.swing.JTabbedPane;
31
32import org.openstreetmap.josm.Main;
33import org.openstreetmap.josm.data.APIDataSet;
34import org.openstreetmap.josm.data.conflict.ConflictCollection;
35import org.openstreetmap.josm.data.osm.Changeset;
36import org.openstreetmap.josm.data.osm.DataSet;
37import org.openstreetmap.josm.data.osm.OsmPrimitive;
38import org.openstreetmap.josm.gui.ExceptionDialogUtil;
39import org.openstreetmap.josm.gui.ExtendedDialog;
40import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
41import org.openstreetmap.josm.gui.PleaseWaitRunnable;
42import org.openstreetmap.josm.gui.historycombobox.SuggestingJHistoryComboBox;
43import org.openstreetmap.josm.gui.layer.OsmDataLayer;
44import org.openstreetmap.josm.gui.progress.ProgressMonitor;
45import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
46import org.openstreetmap.josm.io.ChangesetProcessingType;
47import org.openstreetmap.josm.io.OsmApi;
48import org.openstreetmap.josm.io.OsmApiException;
49import org.openstreetmap.josm.io.OsmApiInitializationException;
50import org.openstreetmap.josm.io.OsmChangesetCloseException;
51import org.openstreetmap.josm.io.OsmServerWriter;
52import org.openstreetmap.josm.tools.GBC;
53import org.openstreetmap.josm.tools.Shortcut;
54import org.openstreetmap.josm.tools.WindowGeometry;
55import org.xml.sax.SAXException;
56
57
58/**
59 * Action that opens a connection to the osm server and uploads all changes.
60 *
61 * An dialog is displayed asking the user to specify a rectangle to grab.
62 * The url and account settings from the preferences are used.
63 *
64 * If the upload fails this action offers various options to resolve conflicts.
65 *
66 * @author imi
67 */
68public class UploadAction extends JosmAction{
69 static private Logger logger = Logger.getLogger(UploadAction.class.getName());
70
71 public static final String HISTORY_KEY = "upload.comment.history";
72
73 /** Upload Hook */
74 public interface UploadHook {
75 /**
76 * Checks the upload.
77 * @param add The added primitives
78 * @param update The updated primitives
79 * @param delete The deleted primitives
80 * @return true, if the upload can continue
81 */
82 public boolean checkUpload(Collection<OsmPrimitive> add, Collection<OsmPrimitive> update, Collection<OsmPrimitive> delete);
83 }
84
85 /**
86 * The list of upload hooks. These hooks will be called one after the other
87 * when the user wants to upload data. Plugins can insert their own hooks here
88 * if they want to be able to veto an upload.
89 *
90 * Be default, the standard upload dialog is the only element in the list.
91 * Plugins should normally insert their code before that, so that the upload
92 * dialog is the last thing shown before upload really starts; on occasion
93 * however, a plugin might also want to insert something after that.
94 */
95 public final LinkedList<UploadHook> uploadHooks = new LinkedList<UploadHook>();
96
97 public UploadAction() {
98 super(tr("Upload to OSM..."), "upload", tr("Upload all changes to the OSM server."),
99 Shortcut.registerShortcut("file:upload", tr("File: {0}", tr("Upload to OSM...")), KeyEvent.VK_U, Shortcut.GROUPS_ALT1+Shortcut.GROUP_HOTKEY), true);
100
101 /**
102 * Checks server capabilities before upload.
103 */
104 uploadHooks.add(new ApiPreconditionChecker());
105
106 /**
107 * Displays a screen where the actions that would be taken are displayed and
108 * give the user the possibility to cancel the upload.
109 */
110 uploadHooks.add(new UploadConfirmationHook());
111 }
112
113 /**
114 * Refreshes the enabled state
115 *
116 */
117 @Override
118 protected void updateEnabledState() {
119 setEnabled(getEditLayer() != null);
120 }
121
122 public boolean checkPreUploadConditions(OsmDataLayer layer) {
123 return checkPreUploadConditions(layer, new APIDataSet(layer.data));
124 }
125
126 public boolean checkPreUploadConditions(OsmDataLayer layer, APIDataSet apiData) {
127 ConflictCollection conflicts = layer.getConflicts();
128 if (conflicts !=null && !conflicts.isEmpty()) {
129 JOptionPane.showMessageDialog(
130 Main.parent,
131 tr("<html>There are unresolved conflicts in layer ''{0}''.<br>"
132 + "You have to resolve them first.<html>", layer.getName()),
133 tr("Warning"),
134 JOptionPane.WARNING_MESSAGE
135 );
136 return false;
137 }
138 // Call all upload hooks in sequence. The upload confirmation dialog
139 // is one of these.
140 for(UploadHook hook : uploadHooks)
141 if(!hook.checkUpload(apiData.getPrimitivesToAdd(), apiData.getPrimitivesToUpdate(), apiData.getPrimitivesToDelete()))
142 return false;
143
144 return true;
145 }
146
147 public void actionPerformed(ActionEvent e) {
148 if (!isEnabled())
149 return;
150 if (Main.map == null) {
151 JOptionPane.showMessageDialog(
152 Main.parent,
153 tr("Nothing to upload. Get some data first."),
154 tr("Warning"),
155 JOptionPane.WARNING_MESSAGE
156 );
157 return;
158 }
159
160 APIDataSet apiData = new APIDataSet(Main.main.getCurrentDataSet());
161 if (apiData.isEmpty()) {
162 JOptionPane.showMessageDialog(
163 Main.parent,
164 tr("No changes to upload."),
165 tr("Warning"),
166 JOptionPane.INFORMATION_MESSAGE
167 );
168 return;
169 }
170 if (!checkPreUploadConditions(Main.map.mapView.getEditLayer(), apiData))
171 return;
172 Main.worker.execute(
173 createUploadTask(
174 Main.map.mapView.getEditLayer(),
175 apiData.getPrimitives(),
176 UploadConfirmationHook.getUploadDialogPanel().getChangeset(),
177 UploadConfirmationHook.getUploadDialogPanel().getChangesetProcessingType()
178 )
179 );
180 }
181
182 /**
183 * Synchronizes the local state of an {@see OsmPrimitive} with its state on the
184 * server. The method uses an individual GET for the primitive.
185 *
186 * @param id the primitive ID
187 */
188 protected void synchronizePrimitive(final String id) {
189 Main.worker.execute(new UpdatePrimitiveTask(Long.parseLong(id)));
190 }
191
192 /**
193 * Synchronizes the local state of the dataset with the state on the server.
194 *
195 * Reuses the functionality of {@see UpdateDataAction}.
196 *
197 * @see UpdateDataAction#actionPerformed(ActionEvent)
198 */
199 protected void synchronizeDataSet() {
200 UpdateDataAction act = new UpdateDataAction();
201 act.actionPerformed(new ActionEvent(this,0,""));
202 }
203
204 /**
205 * Handles the case that a conflict in a specific {@see OsmPrimitive} was detected while
206 * uploading
207 *
208 * @param primitiveType the type of the primitive, either <code>node</code>, <code>way</code> or
209 * <code>relation</code>
210 * @param id the id of the primitive
211 * @param serverVersion the version of the primitive on the server
212 * @param myVersion the version of the primitive in the local dataset
213 */
214 protected void handleUploadConflictForKnownConflict(String primitiveType, String id, String serverVersion, String myVersion) {
215 Object[] options = new Object[] {
216 tr("Synchronize {0} {1} only", tr(primitiveType), id),
217 tr("Synchronize entire dataset"),
218 tr("Cancel")
219 };
220 Object defaultOption = options[0];
221 String msg = tr("<html>Uploading <strong>failed</strong> because the server has a newer version of one<br>"
222 + "of your nodes, ways, or relations.<br>"
223 + "The conflict is caused by the <strong>{0}</strong> with id <strong>{1}</strong>,<br>"
224 + "the server has version {2}, your version is {3}.<br>"
225 + "<br>"
226 + "Click <strong>{4}</strong> to synchronize the conflicting primitive only.<br>"
227 + "Click <strong>{5}</strong> to synchronize the entire local dataset with the server.<br>"
228 + "Click <strong>{6}</strong> to abort and continue editing.<br></html>",
229 tr(primitiveType), id, serverVersion, myVersion,
230 options[0], options[1], options[2]
231 );
232 int optionsType = JOptionPane.YES_NO_CANCEL_OPTION;
233 int ret = JOptionPane.showOptionDialog(
234 null,
235 msg,
236 tr("Conflict detected"),
237 optionsType,
238 JOptionPane.ERROR_MESSAGE,
239 null,
240 options,
241 defaultOption
242 );
243 switch(ret) {
244 case JOptionPane.CLOSED_OPTION: return;
245 case JOptionPane.CANCEL_OPTION: return;
246 case 0: synchronizePrimitive(id); break;
247 case 1: synchronizeDataSet(); break;
248 default:
249 // should not happen
250 throw new IllegalStateException(tr("unexpected return value. Got {0}", ret));
251 }
252 }
253
254 /**
255 * Handles the case that a conflict was detected while uploading where we don't
256 * know what {@see OsmPrimitive} actually caused the conflict (for whatever reason)
257 *
258 */
259 protected void handleUploadConflictForUnknownConflict() {
260 Object[] options = new Object[] {
261 tr("Synchronize entire dataset"),
262 tr("Cancel")
263 };
264 Object defaultOption = options[0];
265 String msg = tr("<html>Uploading <strong>failed</strong> because the server has a newer version of one<br>"
266 + "of your nodes, ways, or relations.<br>"
267 + "<br>"
268 + "Click <strong>{0}</strong> to synchronize the entire local dataset with the server.<br>"
269 + "Click <strong>{1}</strong> to abort and continue editing.<br></html>",
270 options[0], options[1]
271 );
272 int optionsType = JOptionPane.YES_NO_OPTION;
273 int ret = JOptionPane.showOptionDialog(
274 null,
275 msg,
276 tr("Conflict detected"),
277 optionsType,
278 JOptionPane.ERROR_MESSAGE,
279 null,
280 options,
281 defaultOption
282 );
283 switch(ret) {
284 case JOptionPane.CLOSED_OPTION: return;
285 case 1: return;
286 case 0: synchronizeDataSet(); break;
287 default:
288 // should not happen
289 throw new IllegalStateException(tr("unexpected return value. Got {0}", ret));
290 }
291 }
292
293 /**
294 * handles an upload conflict, i.e. an error indicated by a HTTP return code 409.
295 *
296 * @param e the exception
297 */
298 protected void handleUploadConflict(OsmApiException e) {
299 String pattern = "Version mismatch: Provided (\\d+), server had: (\\d+) of (\\S+) (\\d+)";
300 Pattern p = Pattern.compile(pattern);
301 Matcher m = p.matcher(e.getErrorHeader());
302 if (m.matches()) {
303 handleUploadConflictForKnownConflict(m.group(3), m.group(4), m.group(2),m.group(1));
304 } else {
305 logger.warning(tr("Warning: error header \"{0}\" did not match expected pattern \"{1}\"", e.getErrorHeader(),pattern));
306 handleUploadConflictForUnknownConflict();
307 }
308 }
309
310 /**
311 * Handles an error due to a delete request on an already deleted
312 * {@see OsmPrimitive}, i.e. a HTTP response code 410, where we know what
313 * {@see OsmPrimitive} is responsible for the error.
314 *
315 * Reuses functionality of the {@see UpdateSelectionAction} to resolve
316 * conflicts due to mismatches in the deleted state.
317 *
318 * @param primitiveType the type of the primitive
319 * @param id the id of the primitive
320 *
321 * @see UpdateSelectionAction#handlePrimitiveGoneException(long)
322 */
323 protected void handleGoneForKnownPrimitive(String primitiveType, String id) {
324 UpdateSelectionAction act = new UpdateSelectionAction();
325 act.handlePrimitiveGoneException(Long.parseLong(id));
326 }
327
328 /**
329 * Handles an error which is caused by a delete request for an already deleted
330 * {@see OsmPrimitive} on the server, i.e. a HTTP response code of 410.
331 * Note that an <strong>update</strong> on an already deleted object results
332 * in a 409, not a 410.
333 *
334 * @param e the exception
335 */
336 protected void handleGone(OsmApiException e) {
337 String pattern = "The (\\S+) with the id (\\d+) has already been deleted";
338 Pattern p = Pattern.compile(pattern);
339 Matcher m = p.matcher(e.getErrorHeader());
340 if (m.matches()) {
341 handleGoneForKnownPrimitive(m.group(1), m.group(2));
342 } else {
343 logger.warning(tr("Error header \"{0}\" does not match expected pattern \"{1}\"",e.getErrorHeader(), pattern));
344 ExceptionDialogUtil.explainGoneForUnknownPrimitive(e);
345 }
346 }
347
348
349 /**
350 * error handler for any exception thrown during upload
351 *
352 * @param e the exception
353 */
354 protected void handleFailedUpload(Exception e) {
355 // API initialization failed. Notify the user and return.
356 //
357 if (e instanceof OsmApiInitializationException) {
358 ExceptionDialogUtil.explainOsmApiInitializationException((OsmApiInitializationException)e);
359 return;
360 }
361
362 if (e instanceof OsmChangesetCloseException) {
363 ExceptionDialogUtil.explainOsmChangesetCloseException((OsmChangesetCloseException)e);
364 return;
365 }
366 if (e instanceof OsmApiException) {
367 OsmApiException ex = (OsmApiException)e;
368 // There was an upload conflict. Let the user decide whether
369 // and how to resolve it
370 //
371 if(ex.getResponseCode() == HttpURLConnection.HTTP_CONFLICT) {
372 handleUploadConflict(ex);
373 return;
374 }
375 // There was a precondition failed. Notify the user.
376 //
377 else if (ex.getResponseCode() == HttpURLConnection.HTTP_PRECON_FAILED) {
378 ExceptionDialogUtil.explainPreconditionFailed(ex);
379 return;
380 }
381 // Tried to delete an already deleted primitive? Let the user
382 // decide whether and how to resolve this conflict.
383 //
384 else if (ex.getResponseCode() == HttpURLConnection.HTTP_GONE) {
385 handleGone(ex);
386 return;
387 }
388 // any other API exception
389 //
390 else {
391 ex.printStackTrace();
392 String msg = tr("<html>Uploading <strong>failed</strong>."
393 + "<br>"
394 + "{0}"
395 + "</html>",
396 ex.getDisplayMessage()
397 );
398 JOptionPane.showMessageDialog(
399 Main.map,
400 msg,
401 tr("Upload to OSM API failed"),
402 JOptionPane.ERROR_MESSAGE
403 );
404 return;
405 }
406 }
407
408 ExceptionDialogUtil.explainException(e);
409 }
410
411 /**
412 * The asynchronous task to update a specific id
413 *
414 */
415 class UpdatePrimitiveTask extends PleaseWaitRunnable {
416
417 private boolean uploadCancelled = false;
418 private boolean uploadFailed = false;
419 private Exception lastException = null;
420 private long id;
421
422 public UpdatePrimitiveTask(long id) {
423 super(tr("Updating primitive"),false /* don't ignore exceptions */);
424 this.id = id;
425 }
426
427 @Override protected void realRun() throws SAXException, IOException {
428 try {
429 UpdateSelectionAction act = new UpdateSelectionAction();
430 act.updatePrimitive(id);
431 } catch (Exception sxe) {
432 if (uploadCancelled) {
433 System.out.println("Ignoring exception caught because upload is cancelled. Exception is: " + sxe.toString());
434 return;
435 }
436 uploadFailed = true;
437 lastException = sxe;
438 }
439 }
440
441 @Override protected void finish() {
442 if (uploadFailed) {
443 handleFailedUpload(lastException);
444 }
445 }
446
447 @Override protected void cancel() {
448 OsmApi.getOsmApi().cancel();
449 uploadCancelled = true;
450 }
451 }
452
453
454 static public class UploadConfirmationHook implements UploadHook {
455 static private UploadDialogPanel uploadDialogPanel;
456
457 static public UploadDialogPanel getUploadDialogPanel() {
458 if (uploadDialogPanel == null) {
459 uploadDialogPanel = new UploadDialogPanel();
460 }
461 return uploadDialogPanel;
462 }
463
464 public boolean checkUpload(Collection<OsmPrimitive> add, Collection<OsmPrimitive> update, Collection<OsmPrimitive> delete) {
465 final UploadDialogPanel panel = getUploadDialogPanel();
466 panel.setUploadedPrimitives(add, update, delete);
467
468 ExtendedDialog dialog = new ExtendedDialog(
469 Main.parent,
470 tr("Upload these changes?"),
471 new String[] {tr("Upload Changes"), tr("Cancel")}
472 ) {
473 @Override
474 public void setVisible(boolean visible) {
475 if (visible) {
476 new WindowGeometry(
477 panel.getClass().getName(),
478 WindowGeometry.centerInWindow(JOptionPane.getFrameForComponent(Main.parent), new Dimension(400,400))
479 ).apply(this);
480 panel.startUserInput();
481 } else {
482 new WindowGeometry(this).remember(panel.getClass().getName());
483 }
484 super.setVisible(visible);
485 }
486 };
487
488 dialog.setButtonIcons(new String[] {"upload.png", "cancel.png"});
489 dialog.setContent(panel, false /* no scroll pane */);
490 while(true) {
491 dialog.showDialog();
492 int result = dialog.getValue();
493 // cancel pressed
494 if (result != 1) return false;
495 // don't allow empty commit message
496 if (! panel.hasChangesetComment()) {
497 continue;
498 }
499 panel.rememberUserInput();
500 break;
501 }
502 return true;
503 }
504 }
505
506 public UploadDiffTask createUploadTask(OsmDataLayer layer, Collection<OsmPrimitive> toUpload, Changeset changeset, ChangesetProcessingType changesetProcessingType) {
507 return new UploadDiffTask(layer, toUpload, changeset, changesetProcessingType);
508 }
509
510 public class UploadDiffTask extends PleaseWaitRunnable {
511 private boolean uploadCancelled = false;
512 private Exception lastException = null;
513 private Collection <OsmPrimitive> toUpload;
514 private OsmServerWriter writer;
515 private OsmDataLayer layer;
516 private Changeset changeset;
517 private ChangesetProcessingType changesetProcessingType;
518
519 private UploadDiffTask(OsmDataLayer layer, Collection <OsmPrimitive> toUpload, Changeset changeset, ChangesetProcessingType changesetProcessingType) {
520 super(tr("Uploading data for layer ''{0}''", layer.getName()),false /* don't ignore exceptions */);
521 this.toUpload = toUpload;
522 this.layer = layer;
523 this.changeset = changeset;
524 this.changesetProcessingType = changesetProcessingType == null ? ChangesetProcessingType.USE_NEW_AND_CLOSE : changesetProcessingType;
525 }
526
527 @Override protected void realRun() throws SAXException, IOException {
528 writer = new OsmServerWriter();
529 try {
530 ProgressMonitor monitor = progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false);
531 writer.uploadOsm(layer.data.version, toUpload, changeset,changesetProcessingType, monitor);
532 } catch (Exception sxe) {
533 if (uploadCancelled) {
534 System.out.println("Ignoring exception caught because upload is cancelled. Exception is: " + sxe.toString());
535 return;
536 }
537 lastException = sxe;
538 }
539 }
540
541 @Override protected void finish() {
542 if (uploadCancelled)
543 return;
544
545 // we always clean the data, even in case of errors. It's possible the data was
546 // partially uploaded
547 //
548 layer.cleanupAfterUpload(writer.getProcessedPrimitives());
549 DataSet.fireSelectionChanged(layer.data.getSelected());
550 layer.fireDataChange();
551 if (lastException != null) {
552 handleFailedUpload(lastException);
553 } else {
554 layer.onPostUploadToServer();
555 }
556 }
557
558 @Override protected void cancel() {
559 uploadCancelled = true;
560 if (writer != null) {
561 writer.cancel();
562 }
563 }
564
565 public boolean isSuccessful() {
566 return !isCancelled() && !isFailed();
567 }
568
569 public boolean isCancelled() {
570 return uploadCancelled;
571 }
572
573 public boolean isFailed() {
574 return lastException != null;
575 }
576 }
577
578 /**
579 * The panel displaying information about primitives to upload and providing
580 * UI widgets for entering the changeset comment and other configuration
581 * settings.
582 *
583 */
584 static public class UploadDialogPanel extends JPanel {
585
586 private JList lstAdd;
587 private JList lstUpdate;
588 private JList lstDelete;
589 private JLabel lblAdd;
590 private JLabel lblUpdate;
591 private JLabel lblDelete;
592 private JCheckBox cbUseAtomicUpload;
593 private SuggestingJHistoryComboBox cmt;
594 private TagEditorPanel tagEditorPanel;
595 private JTabbedPane southTabbedPane;
596 private ButtonGroup bgChangesetHandlingOptions;
597 private JRadioButton rbUseNewAndClose;
598 private JRadioButton rbUseNewAndLeaveOpen;
599 private JRadioButton rbUseExistingAndClose;
600 private JRadioButton rbUseExistingAndLeaveOpen;
601 private ChangesetProcessingType changesetProcessingType;
602
603 protected JPanel buildListsPanel() {
604 JPanel pnl = new JPanel();
605 pnl.setLayout(new GridBagLayout());
606
607 GridBagConstraints gcLabel = new GridBagConstraints();
608 gcLabel.fill = GridBagConstraints.HORIZONTAL;
609 gcLabel.weightx = 1.0;
610 gcLabel.weighty = 0.0;
611 gcLabel.anchor = GridBagConstraints.FIRST_LINE_START;
612
613 GridBagConstraints gcList = new GridBagConstraints();
614 gcList.fill = GridBagConstraints.BOTH;
615 gcList.weightx = 1.0;
616 gcList.weighty = 1.0;
617 gcList.anchor = GridBagConstraints.CENTER;
618
619 gcLabel.gridy = 0;
620 pnl.add(lblAdd = new JLabel(tr("Objects to add:")), gcLabel);
621
622 gcList.gridy = 1;
623 pnl.add(new JScrollPane(lstAdd), gcList);
624
625 gcLabel.gridy = 2;
626 pnl.add(lblUpdate = new JLabel(tr("Objects to modify:")), gcLabel);
627
628 gcList.gridy = 3;
629 pnl.add(new JScrollPane(lstUpdate), gcList);
630
631 gcLabel.gridy = 4;
632 pnl.add(lblDelete = new JLabel(tr("Objects to delete:")), gcLabel);
633
634 gcList.gridy = 5;
635 pnl.add(new JScrollPane(lstDelete), gcList);
636 return pnl;
637 }
638
639 protected JPanel buildChangesetHandlingControlPanel() {
640 JPanel pnl = new JPanel();
641 pnl.setLayout(new BoxLayout(pnl, BoxLayout.Y_AXIS));
642 bgChangesetHandlingOptions = new ButtonGroup();
643
644 rbUseNewAndClose = new JRadioButton(tr("Use a new changeset and close it"));
645 rbUseNewAndClose.setToolTipText(tr("Select to upload the data using a new changeset and to close the changeset after the upload"));
646
647 rbUseNewAndLeaveOpen = new JRadioButton(tr("Use a new changeset and leave it open"));
648 rbUseNewAndLeaveOpen.setToolTipText(tr("Select to upload the data using a new changeset and to leave the changeset open after the upload"));
649
650 rbUseExistingAndClose = new JRadioButton();
651 rbUseExistingAndLeaveOpen = new JRadioButton();
652
653 pnl.add(new JLabel(tr("Upload to a new or to an existing changeset?")));
654 pnl.add(rbUseNewAndClose);
655 pnl.add(rbUseNewAndLeaveOpen);
656 pnl.add(rbUseExistingAndClose);
657 pnl.add(rbUseExistingAndLeaveOpen);
658
659 rbUseNewAndClose.setVisible(false);
660 rbUseNewAndLeaveOpen.setVisible(false);
661 rbUseExistingAndClose.setVisible(false);
662 rbUseExistingAndLeaveOpen.setVisible(false);
663
664 bgChangesetHandlingOptions.add(rbUseNewAndClose);
665 bgChangesetHandlingOptions.add(rbUseNewAndLeaveOpen);
666 bgChangesetHandlingOptions.add(rbUseExistingAndClose);
667 bgChangesetHandlingOptions.add(rbUseExistingAndLeaveOpen);
668 return pnl;
669 }
670
671 protected JPanel buildChangesetControlPanel() {
672 JPanel pnl = new JPanel();
673 pnl.setLayout(new BoxLayout(pnl, BoxLayout.Y_AXIS));
674 pnl.add(cbUseAtomicUpload = new JCheckBox(tr("upload all changes in one request")));
675 cbUseAtomicUpload.setToolTipText(tr("Enable to upload all changes in one request, disable to use one request per changed primitive"));
676 boolean useAtomicUpload = Main.pref.getBoolean("osm-server.atomic-upload", true);
677 cbUseAtomicUpload.setSelected(useAtomicUpload);
678 cbUseAtomicUpload.setEnabled(OsmApi.getOsmApi().hasSupportForDiffUploads());
679
680 pnl.add(buildChangesetHandlingControlPanel());
681 return pnl;
682 }
683
684 protected JPanel buildUploadControlPanel() {
685 JPanel pnl = new JPanel();
686 pnl.setLayout(new GridBagLayout());
687 pnl.add(new JLabel(tr("Provide a brief comment for the changes you are uploading:")), GBC.eol().insets(0, 5, 10, 3));
688 cmt = new SuggestingJHistoryComboBox();
689 List<String> cmtHistory = new LinkedList<String>(Main.pref.getCollection(HISTORY_KEY, new LinkedList<String>()));
690 cmt.setHistory(cmtHistory);
691 pnl.add(cmt, GBC.eol().fill(GBC.HORIZONTAL));
692
693 // configuration options for atomic upload
694 //
695 pnl.add(buildChangesetControlPanel(), GBC.eol().fill(GridBagConstraints.HORIZONTAL));
696 return pnl;
697 }
698
699 protected void build() {
700 setLayout(new GridBagLayout());
701 GridBagConstraints gc = new GridBagConstraints();
702 gc.fill = GridBagConstraints.BOTH;
703 gc.weightx = 1.0;
704 gc.weighty = 1.0;
705 add(buildListsPanel(), gc);
706
707 southTabbedPane = new JTabbedPane();
708 southTabbedPane.add(buildUploadControlPanel());
709 tagEditorPanel = new TagEditorPanel();
710 southTabbedPane.add(tagEditorPanel);
711 southTabbedPane.setTitleAt(0, tr("Settings"));
712 southTabbedPane.setTitleAt(1, tr("Changeset Tags"));
713 JPanel pnl = new JPanel();
714 pnl.setLayout(new BorderLayout());
715 pnl.add(southTabbedPane,BorderLayout.CENTER);
716 gc.fill = GridBagConstraints.HORIZONTAL;
717 gc.gridy = 1;
718 gc.weightx = 1.0;
719 gc.weighty = 0.0;
720 add(pnl, gc);
721 }
722
723
724 protected UploadDialogPanel() {
725 OsmPrimitivRenderer renderer = new OsmPrimitivRenderer();
726
727 lstAdd = new JList();
728 lstAdd.setCellRenderer(renderer);
729 lstAdd.setVisibleRowCount(Math.min(lstAdd.getModel().getSize(), 10));
730
731 lstUpdate = new JList();
732 lstUpdate.setCellRenderer(renderer);
733 lstUpdate.setVisibleRowCount(Math.min(lstUpdate.getModel().getSize(), 10));
734
735 lstDelete = new JList();
736 lstDelete.setCellRenderer(renderer);
737 lstDelete.setVisibleRowCount(Math.min(lstDelete.getModel().getSize(), 10));
738 build();
739 }
740
741 public void setUploadedPrimitives(Collection<OsmPrimitive> add, Collection<OsmPrimitive> update, Collection<OsmPrimitive> delete) {
742 lstAdd.setListData(add.toArray());
743 lstAdd.setVisible(!add.isEmpty());
744 lblAdd.setVisible(!add.isEmpty());
745 lstUpdate.setListData(update.toArray());
746 lstUpdate.setVisible(!update.isEmpty());
747 lblUpdate.setVisible(!update.isEmpty());
748 lstDelete.setListData(delete.toArray());
749 lstDelete.setVisible(!delete.isEmpty());
750 lblDelete.setVisible(!delete.isEmpty());
751 }
752
753 public boolean hasChangesetComment() {
754 return cmt.getText().trim().length() >= 3;
755 }
756
757 public void rememberUserInput() {
758 // store the history of comments
759 cmt.addCurrentItemToHistory();
760 Main.pref.putCollection(HISTORY_KEY, cmt.getHistory());
761 Main.pref.put("osm-server.atomic-upload", cbUseAtomicUpload.isSelected());
762
763 if (rbUseNewAndClose.isSelected()) {
764 changesetProcessingType = ChangesetProcessingType.USE_NEW_AND_CLOSE;
765 } else if (rbUseNewAndLeaveOpen.isSelected()) {
766 changesetProcessingType = ChangesetProcessingType.USE_NEW_AND_LEAVE_OPEN;
767 } else if (rbUseExistingAndClose.isSelected()) {
768 changesetProcessingType = ChangesetProcessingType.USE_EXISTING_AND_CLOSE;
769 } else if (rbUseExistingAndLeaveOpen.isSelected()) {
770 changesetProcessingType = ChangesetProcessingType.USE_EXISTING_AND_LEAVE_OPEN;
771 }
772 }
773
774 public void startUserInput() {
775 rbUseNewAndClose.setVisible(true);
776 rbUseNewAndLeaveOpen.setVisible(true);
777 if (OsmApi.getOsmApi().getCurrentChangeset() != null) {
778 Changeset cs = OsmApi.getOsmApi().getCurrentChangeset();
779 rbUseExistingAndClose.setVisible(true);
780 rbUseExistingAndLeaveOpen.setVisible(true);
781
782 rbUseExistingAndClose.setText(tr("Use the existing changeset {0} and close it after upload",cs.getId()));
783 rbUseExistingAndClose.setToolTipText(tr("Select to upload to the existing changeset {0} and to close the changeset after this upload",cs.getId()));
784
785 rbUseExistingAndLeaveOpen.setText(tr("Use the existing changeset {0} and leave it open",cs.getId()));
786 rbUseExistingAndLeaveOpen.setToolTipText(tr("Select to upload to the existing changeset {0} and to leave the changeset open for further uploads",cs.getId()));
787
788 if (changesetProcessingType == null) {
789 rbUseNewAndClose.setSelected(true);
790 } else {
791 switch(changesetProcessingType) {
792 case USE_NEW_AND_CLOSE: rbUseNewAndClose.setSelected(true); break;
793 case USE_NEW_AND_LEAVE_OPEN: rbUseNewAndLeaveOpen.setSelected(true); break;
794 case USE_EXISTING_AND_CLOSE: rbUseExistingAndClose.setSelected(true); break;
795 case USE_EXISTING_AND_LEAVE_OPEN: rbUseExistingAndLeaveOpen.setSelected(true); break;
796 }
797 }
798 } else {
799 if (changesetProcessingType == null) {
800 changesetProcessingType = ChangesetProcessingType.USE_NEW_AND_CLOSE;
801 }
802 rbUseExistingAndClose.setVisible(false);
803 rbUseExistingAndLeaveOpen.setVisible(false);
804 switch(changesetProcessingType) {
805 case USE_NEW_AND_CLOSE: rbUseNewAndClose.setSelected(true); break;
806 case USE_NEW_AND_LEAVE_OPEN: rbUseNewAndLeaveOpen.setSelected(true); break;
807 default: rbUseNewAndClose.setSelected(true); break;
808 }
809 }
810 cmt.getEditor().selectAll();
811 cmt.requestFocus();
812 }
813
814 public ChangesetProcessingType getChangesetProcessingType() {
815 if (changesetProcessingType == null) return ChangesetProcessingType.USE_NEW_AND_CLOSE;
816 return changesetProcessingType;
817 }
818
819 public Changeset getChangeset() {
820 Changeset changeset = new Changeset();
821 tagEditorPanel.getModel().applyToPrimitive(changeset);
822 changeset.put("comment", cmt.getText());
823 return changeset;
824 }
825 }
826}
Note: See TracBrowser for help on using the repository browser.