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

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

Improved cancellation of upload tasks
Refactored upload dialog
fixed #2597: Size of upload dialog - now restores dialog size from preferences
fixed #2913: Upload dialog enters scrolling-mode when JOSM isn't maximized - no scroling anymore in ExtendeDialog for UploadDialog
fixed #2518: focus & select comment editing field

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