source: josm/trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java@ 2301

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

fixed #3765: Unable to resolve conflict
Improved warning message for conflicting relation versions and added help page

File size: 40.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.io;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.BorderLayout;
8import java.awt.Dimension;
9import java.awt.FlowLayout;
10import java.awt.GridBagConstraints;
11import java.awt.GridBagLayout;
12import java.awt.event.ActionEvent;
13import java.awt.event.ActionListener;
14import java.awt.event.ItemEvent;
15import java.awt.event.ItemListener;
16import java.awt.event.KeyEvent;
17import java.awt.event.WindowAdapter;
18import java.awt.event.WindowEvent;
19import java.util.ArrayList;
20import java.util.Collection;
21import java.util.Collections;
22import java.util.LinkedList;
23import java.util.List;
24
25import javax.swing.AbstractAction;
26import javax.swing.AbstractListModel;
27import javax.swing.BorderFactory;
28import javax.swing.ButtonGroup;
29import javax.swing.DefaultComboBoxModel;
30import javax.swing.InputMap;
31import javax.swing.JButton;
32import javax.swing.JCheckBox;
33import javax.swing.JComboBox;
34import javax.swing.JComponent;
35import javax.swing.JDialog;
36import javax.swing.JLabel;
37import javax.swing.JList;
38import javax.swing.JOptionPane;
39import javax.swing.JPanel;
40import javax.swing.JRadioButton;
41import javax.swing.JScrollPane;
42import javax.swing.JTabbedPane;
43import javax.swing.KeyStroke;
44import javax.swing.event.ChangeEvent;
45import javax.swing.event.ChangeListener;
46import javax.swing.event.ListDataEvent;
47import javax.swing.event.ListDataListener;
48
49import org.openstreetmap.josm.Main;
50import org.openstreetmap.josm.data.osm.Changeset;
51import org.openstreetmap.josm.data.osm.OsmPrimitive;
52import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
53import org.openstreetmap.josm.gui.SideButton;
54import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
55import org.openstreetmap.josm.gui.help.HelpUtil;
56import org.openstreetmap.josm.gui.tagging.TagEditorModel;
57import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
58import org.openstreetmap.josm.gui.tagging.TagModel;
59import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
60import org.openstreetmap.josm.io.OsmApi;
61import org.openstreetmap.josm.tools.GBC;
62import org.openstreetmap.josm.tools.ImageProvider;
63import org.openstreetmap.josm.tools.WindowGeometry;
64
65/**
66 * This is a dialog for entering upload options like the parameters for
67 * the upload changeset and the strategy for opening/closing a changeset.
68 *
69 */
70public class UploadDialog extends JDialog {
71
72 public static final String HISTORY_KEY = "upload.comment.history";
73
74 /** the unique instance of the upload dialog */
75 static private UploadDialog uploadDialog;
76
77 /**
78 * Replies the unique instance of the upload dialog
79 *
80 * @return the unique instance of the upload dialog
81 */
82 static public UploadDialog getUploadDialog() {
83 if (uploadDialog == null) {
84 uploadDialog = new UploadDialog();
85 }
86 return uploadDialog;
87 }
88
89 /** the list with the added primitives */
90 private PrimitiveList lstAdd;
91 private JLabel lblAdd;
92 private JScrollPane spAdd;
93 /** the list with the updated primitives */
94 private PrimitiveList lstUpdate;
95 private JLabel lblUpdate;
96 private JScrollPane spUpdate;
97 /** the list with the deleted primitives */
98 private PrimitiveList lstDelete;
99 private JLabel lblDelete;
100 private JScrollPane spDelete;
101 /** the panel containing the widgets for the lists of primitives */
102 private JPanel pnlLists;
103 /** checkbox for selecting whether an atomic upload is to be used */
104 private TagEditorPanel tagEditorPanel;
105 /** the tabbed pane used below of the list of primitives */
106 private JTabbedPane southTabbedPane;
107 /** the upload button */
108 private JButton btnUpload;
109
110 private ChangesetSelectionPanel pnlChangesetSelection;
111 private boolean canceled = false;
112
113 /**
114 * builds the panel with the lists of primitives
115 *
116 * @return the panel with the lists of primitives
117 */
118 protected JPanel buildListsPanel() {
119 pnlLists = new JPanel();
120 pnlLists.setLayout(new GridBagLayout());
121 // we don't add the lists yet, see setUploadPrimivies()
122 //
123 return pnlLists;
124 }
125
126 /**
127 * builds the content panel for the upload dialog
128 *
129 * @return the content panel
130 */
131 protected JPanel buildContentPanel() {
132 JPanel pnl = new JPanel();
133 pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
134 pnl.setLayout(new GridBagLayout());
135 GridBagConstraints gc = new GridBagConstraints();
136
137 // first the panel with the list in the upper half
138 //
139 gc.fill = GridBagConstraints.BOTH;
140 gc.weightx = 1.0;
141 gc.weighty = 1.0;
142 pnl.add(buildListsPanel(), gc);
143
144 // a tabbed pane with two configuration panels in the
145 // lower half
146 //
147 southTabbedPane = new JTabbedPane();
148 southTabbedPane.add(new JPanel());
149 tagEditorPanel = new TagEditorPanel();
150 southTabbedPane.add(tagEditorPanel);
151 southTabbedPane.setComponentAt(0, pnlChangesetSelection = new ChangesetSelectionPanel());
152 southTabbedPane.setTitleAt(0, tr("Settings"));
153 southTabbedPane.setToolTipTextAt(0, tr("Decide how to upload the data and which changeset to use"));
154 southTabbedPane.setTitleAt(1, tr("Tags of new changeset"));
155 southTabbedPane.setToolTipTextAt(1, tr("Apply tags to the changeset data is uploaded to"));
156 southTabbedPane.addChangeListener(new TabbedPaneChangeLister());
157 JPanel pnl1 = new JPanel();
158 pnl1.setLayout(new BorderLayout());
159 pnl1.add(southTabbedPane,BorderLayout.CENTER);
160 gc.fill = GridBagConstraints.HORIZONTAL;
161 gc.gridy = 1;
162 gc.weightx = 1.0;
163 gc.weighty = 0.0;
164 pnl.add(pnl1, gc);
165 return pnl;
166 }
167
168 /**
169 * builds the panel with the OK and CANCEL buttons
170 *
171 * @return
172 */
173 protected JPanel buildActionPanel() {
174 JPanel pnl = new JPanel();
175 pnl.setLayout(new FlowLayout(FlowLayout.CENTER));
176 pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
177
178 // -- upload button
179 UploadAction uploadAction = new UploadAction();
180 pnl.add(btnUpload = new SideButton(uploadAction));
181 btnUpload.setFocusable(true);
182 InputMap inputMap = btnUpload.getInputMap();
183 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "doUpload");
184 btnUpload.getActionMap().put("doUpload", uploadAction);
185
186 // -- cancel button
187 CancelAction cancelAction = new CancelAction();
188 pnl.add(new SideButton(cancelAction));
189 getRootPane().registerKeyboardAction(
190 cancelAction,
191 KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0),
192 JComponent.WHEN_IN_FOCUSED_WINDOW
193 );
194
195 pnl.add(new SideButton(new ContextSensitiveHelpAction("/Help/Dialogs/UploadDialog")));
196 HelpUtil.setHelpContext(getRootPane(),"/Help/Dialogs/UploadDialog");
197 return pnl;
198 }
199
200 /**
201 * builds the gui
202 */
203 protected void build() {
204 setTitle(tr("Upload"));
205 getContentPane().setLayout(new BorderLayout());
206 getContentPane().add(buildContentPanel(), BorderLayout.CENTER);
207 getContentPane().add(buildActionPanel(), BorderLayout.SOUTH);
208
209 addWindowListener(new WindowEventHandler());
210 }
211
212 /**
213 * constructor
214 */
215 public UploadDialog() {
216 super(JOptionPane.getFrameForComponent(Main.parent), true /* modal */);
217 OsmPrimitivRenderer renderer = new OsmPrimitivRenderer();
218
219 // initialize the three lists for primitives
220 //
221 lstAdd = new PrimitiveList();
222 lstAdd.setCellRenderer(renderer);
223 lstAdd.setVisibleRowCount(Math.min(lstAdd.getModel().getSize(), 10));
224 spAdd = new JScrollPane(lstAdd);
225 lblAdd = new JLabel(tr("Objects to add:"));
226
227 lstUpdate = new PrimitiveList();
228 lstUpdate.setCellRenderer(renderer);
229 lstUpdate.setVisibleRowCount(Math.min(lstUpdate.getModel().getSize(), 10));
230 spUpdate = new JScrollPane(lstUpdate);
231 lblUpdate = new JLabel(tr("Objects to modify:"));
232
233 lstDelete = new PrimitiveList();
234 lstDelete.setCellRenderer(renderer);
235 lstDelete.setVisibleRowCount(Math.min(lstDelete.getModel().getSize(), 10));
236 spDelete = new JScrollPane(lstDelete);
237 lblDelete = new JLabel(tr("Objects to delete:"));
238
239 // build the GUI
240 //
241 build();
242 }
243
244 /**
245 * sets the collection of primitives which will be uploaded
246 *
247 * @param add the collection of primitives to add
248 * @param update the collection of primitives to update
249 * @param delete the collection of primitives to delete
250 */
251 public void setUploadedPrimitives(List<OsmPrimitive> add, List<OsmPrimitive> update, List<OsmPrimitive> delete) {
252 lstAdd.getPrimitiveListModel().setPrimitives(add);
253 lstUpdate.getPrimitiveListModel().setPrimitives(update);
254 lstDelete.getPrimitiveListModel().setPrimitives(delete);
255
256 GridBagConstraints gcLabel = new GridBagConstraints();
257 gcLabel.fill = GridBagConstraints.HORIZONTAL;
258 gcLabel.weightx = 1.0;
259 gcLabel.weighty = 0.0;
260 gcLabel.anchor = GridBagConstraints.FIRST_LINE_START;
261
262 GridBagConstraints gcList = new GridBagConstraints();
263 gcList.fill = GridBagConstraints.BOTH;
264 gcList.weightx = 1.0;
265 gcList.weighty = 1.0;
266 gcList.anchor = GridBagConstraints.CENTER;
267 pnlLists.removeAll();
268 int y = -1;
269 if (!add.isEmpty()) {
270 y++;
271 gcLabel.gridy = y;
272 lblAdd.setText(trn("{0} object to add:", "{0} objects to add:", add.size(),add.size()));
273 pnlLists.add(lblAdd, gcLabel);
274 y++;
275 gcList.gridy = y;
276 pnlLists.add(spAdd, gcList);
277 }
278 if (!update.isEmpty()) {
279 y++;
280 gcLabel.gridy = y;
281 lblUpdate.setText(trn("{0} object to modifiy:", "{0} objects to modify:", update.size(),update.size()));
282 pnlLists.add(lblUpdate, gcLabel);
283 y++;
284 gcList.gridy = y;
285 pnlLists.add(spUpdate, gcList);
286 }
287 if (!delete.isEmpty()) {
288 y++;
289 gcLabel.gridy = y;
290 lblDelete.setText(trn("{0} object to delete:", "{0} objects to delete:", delete.size(),delete.size()));
291 pnlLists.add(lblDelete, gcLabel);
292 y++;
293 gcList.gridy = y;
294 pnlLists.add(spDelete, gcList);
295 }
296 }
297
298 /**
299 * Remembers the user input in the preference settings
300 */
301 public void rememberUserInput() {
302 pnlChangesetSelection.rememberUserInput();
303 }
304
305 /**
306 * Initializes the panel for user input
307 */
308 public void startUserInput() {
309 tagEditorPanel.initAutoCompletion(Main.main.getEditLayer());
310 pnlChangesetSelection.startUserInput();
311 }
312
313 /**
314 * Replies the current changeset
315 *
316 * @return the current changeset
317 */
318 public Changeset getChangeset() {
319 Changeset cs = pnlChangesetSelection.getChangeset();
320 tagEditorPanel.getModel().applyToPrimitive(cs);
321 cs.put("comment", getUploadComment());
322 return cs;
323 }
324
325 /**
326 * Sets or updates the changeset cs.
327 * If cs is null, does nothing.
328 * If cs.getId() == 0 does nothing.
329 * If cs.getId() > 0 and cs is open, adds it to the list of open
330 * changesets. If it is closed, removes it from the list of open
331 * changesets.
332 *
333 * @param cs the changeset
334 */
335 public void setOrUpdateChangeset(Changeset cs) {
336 pnlChangesetSelection.setOrUpdateChangeset(cs);
337 }
338
339 /**
340 * Replies true if the changeset is to be closed after the
341 * next upload
342 *
343 * @return true if the changeset is to be closed after the
344 * next upload; false, otherwise
345 */
346 public boolean isDoCloseAfterUpload() {
347 return pnlChangesetSelection.isCloseAfterUpload();
348 }
349
350 /**
351 * Replies the default value for "created_by"
352 *
353 * @return the default value for "created_by"
354 */
355 protected String getDefaultCreatedBy() {
356 Object ua = System.getProperties().get("http.agent");
357 return(ua == null) ? "JOSM" : ua.toString();
358 }
359
360 /**
361 * Replies the current value for the upload comment
362 *
363 * @return the current value for the upload comment
364 */
365 protected String getUploadComment() {
366 switch(southTabbedPane.getSelectedIndex()) {
367 case 0:
368 return pnlChangesetSelection.getUploadComment();
369 case 1:
370 TagModel tm = tagEditorPanel.getModel().get("comment");
371 return tm == null? "" : tm.getValue();
372 }
373 return "";
374 }
375
376 /**
377 * Replies true, if the dialog was canceled
378 *
379 * @return true, if the dialog was canceled
380 */
381 public boolean isCanceled() {
382 return canceled;
383 }
384
385 /**
386 * Sets whether the dialog was canceld
387 *
388 * @param canceled true, if the dialog is canceled
389 */
390 protected void setCanceled(boolean canceled) {
391 this.canceled = canceled;
392 }
393
394 @Override
395 public void setVisible(boolean visible) {
396 if (visible) {
397 new WindowGeometry(
398 getClass().getName() + ".geometry",
399 WindowGeometry.centerInWindow(
400 Main.parent,
401 new Dimension(400,600)
402 )
403 ).apply(this);
404 } else if (!visible && isShowing()){
405 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
406 }
407 super.setVisible(visible);
408 }
409
410 /**
411 * This change listener is triggered when current tab in the tabbed pane in
412 * the lower half of the dialog is changed.
413 *
414 * It's main purpose is to keep the content in the text field for the changeset
415 * comment in sync with the changeset tag "comment".
416 *
417 */
418 class TabbedPaneChangeLister implements ChangeListener {
419
420 protected boolean hasCommentTag() {
421 TagEditorModel model = tagEditorPanel.getModel();
422 return model.get("comment") != null;
423 }
424
425 protected TagModel getEmptyTag() {
426 TagEditorModel model = tagEditorPanel.getModel();
427 TagModel tm = model.get("");
428 if (tm != null) return tm;
429 tm = new TagModel("", "");
430 model.add(tm);
431 return tm;
432 }
433 protected TagModel getOrCreateCommentTag() {
434 TagEditorModel model = tagEditorPanel.getModel();
435 if (hasCommentTag())
436 return model.get("comment");
437 TagModel tm = getEmptyTag();
438 tm.setName("comment");
439 return tm;
440 }
441
442 protected void removeCommentTag() {
443 TagEditorModel model = tagEditorPanel.getModel();
444 model.delete("comment");
445 }
446
447 protected void refreshCommentTag() {
448 TagModel tm = getOrCreateCommentTag();
449 tm.setName("comment");
450 tm.setValue(pnlChangesetSelection.getUploadComment().trim());
451 if (pnlChangesetSelection.getUploadComment().trim().equals("")) {
452 removeCommentTag();
453 }
454 tagEditorPanel.getModel().fireTableDataChanged();
455 }
456
457 public void stateChanged(ChangeEvent e) {
458 if (southTabbedPane.getSelectedIndex() ==0) {
459 TagModel tm = tagEditorPanel.getModel().get("comment");
460 pnlChangesetSelection.initEditingOfUploadComment(tm == null ? "" : tm.getValue());
461 } else if (southTabbedPane.getSelectedIndex() == 1) {
462 refreshCommentTag();
463 }
464 }
465 }
466
467 /**
468 * Handles an upload
469 *
470 */
471 class UploadAction extends AbstractAction {
472 public UploadAction() {
473 putValue(NAME, tr("Upload Changes"));
474 putValue(SMALL_ICON, ImageProvider.get("upload"));
475 putValue(SHORT_DESCRIPTION, tr("Upload the changed primitives"));
476 }
477
478 protected void warnIllegalUploadComment() {
479 JOptionPane.showMessageDialog(
480 UploadDialog.this,
481 tr("Please enter a comment for this upload changeset (min. 3 characters)"),
482 tr("Illegal upload comment"),
483 JOptionPane.ERROR_MESSAGE
484
485 );
486 }
487 public void actionPerformed(ActionEvent e) {
488 if (getUploadComment().trim().length() < 3) {
489 warnIllegalUploadComment();
490 southTabbedPane.setSelectedIndex(0);
491 pnlChangesetSelection.initEditingOfUploadComment(getUploadComment());
492 return;
493 }
494 setCanceled(false);
495 setVisible(false);
496
497 }
498 }
499
500 /**
501 * Action for canceling the dialog
502 *
503 */
504 class CancelAction extends AbstractAction {
505 public CancelAction() {
506 putValue(NAME, tr("Cancel"));
507 putValue(SMALL_ICON, ImageProvider.get("cancel"));
508 putValue(SHORT_DESCRIPTION, tr("Cancel the upload and resume editing"));
509 }
510
511
512 public void actionPerformed(ActionEvent e) {
513 setCanceled(true);
514 setVisible(false);
515 }
516 }
517
518 /**
519 * A simple list of OSM primitives.
520 *
521 */
522 class PrimitiveList extends JList {
523 public PrimitiveList() {
524 super(new PrimitiveListModel());
525 }
526
527 public PrimitiveListModel getPrimitiveListModel() {
528 return (PrimitiveListModel)getModel();
529 }
530 }
531
532 /**
533 * A list model for a list of OSM primitives.
534 *
535 */
536 class PrimitiveListModel extends AbstractListModel{
537 private List<OsmPrimitive> primitives;
538
539 public PrimitiveListModel() {
540 primitives = new ArrayList<OsmPrimitive>();
541 }
542
543 public PrimitiveListModel(List<OsmPrimitive> primitives) {
544 setPrimitives(primitives);
545 }
546
547 public void setPrimitives(List<OsmPrimitive> primitives) {
548 if (primitives == null) {
549 this.primitives = new ArrayList<OsmPrimitive>();
550 } else {
551 this.primitives = primitives;
552 }
553 fireContentsChanged(this,0,getSize());
554 }
555
556 public Object getElementAt(int index) {
557 if (primitives == null) return null;
558 return primitives.get(index);
559 }
560
561 public int getSize() {
562 if (primitives == null) return 0;
563 return primitives.size();
564 }
565 }
566
567 /**
568 * Listens to window closing events and processes them as cancel events.
569 * Listens to window open events and initializes user input
570 *
571 */
572 class WindowEventHandler extends WindowAdapter {
573 @Override
574 public void windowClosing(WindowEvent e) {
575 setCanceled(true);
576 }
577
578 @Override
579 public void windowOpened(WindowEvent e) {
580 startUserInput();
581 }
582 }
583
584 /**
585 * The panel which provides various UI widgets for controlling how to use
586 * changesets during upload.
587 *
588 */
589 class ChangesetSelectionPanel extends JPanel implements ListDataListener{
590
591 private ButtonGroup bgUseNewOrExisting;
592 private JRadioButton rbUseNew;
593 private JRadioButton rbExisting;
594 private JComboBox cbOpenChangesets;
595 private JButton btnRefresh;
596 private JButton btnClose;
597 private JCheckBox cbCloseAfterUpload;
598 private OpenChangesetModel model;
599 private HistoryComboBox cmt;
600 private JCheckBox cbUseAtomicUpload;
601
602 /**
603 * build the panel with the widgets for controlling whether an atomic upload
604 * should be used or not
605 *
606 * @return the panel
607 */
608 protected JPanel buildAtomicUploadControlPanel() {
609 JPanel pnl = new JPanel();
610 pnl.setLayout(new GridBagLayout());
611 GridBagConstraints gc = new GridBagConstraints();
612 gc.fill = GridBagConstraints.HORIZONTAL;
613 gc.weightx = 1.0;
614 gc.anchor = GridBagConstraints.FIRST_LINE_START;
615 pnl.add(cbUseAtomicUpload = new JCheckBox(tr("Upload all changes in one request")), gc);
616 cbUseAtomicUpload.setToolTipText(tr("Enable to upload all changes in one request, disable to use one request per changed primitive"));
617 boolean useAtomicUpload = Main.pref.getBoolean("osm-server.atomic-upload", true);
618 cbUseAtomicUpload.setSelected(useAtomicUpload);
619 cbUseAtomicUpload.setEnabled(OsmApi.getOsmApi().hasSupportForDiffUploads());
620 return pnl;
621 }
622
623 protected JPanel buildUploadCommentPanel() {
624 JPanel pnl = new JPanel();
625 pnl.setLayout(new GridBagLayout());
626 pnl.add(new JLabel(tr("Provide a brief comment for the changes you are uploading:")), GBC.eol().insets(0, 5, 10, 3));
627 cmt = new HistoryComboBox();
628 cmt.setToolTipText(tr("Enter an upload comment (min. 3 characters)"));
629 List<String> cmtHistory = new LinkedList<String>(Main.pref.getCollection(HISTORY_KEY, new LinkedList<String>()));
630 // we have to reverse the history, because ComboBoxHistory will reverse it again
631 // in addElement()
632 //
633 Collections.reverse(cmtHistory);
634 cmt.setPossibleItems(cmtHistory);
635 cmt.getEditor().addActionListener(
636 new ActionListener() {
637 public void actionPerformed(ActionEvent e) {
638 TagModel tm = tagEditorPanel.getModel().get("comment");
639 if (tm == null) {
640 tagEditorPanel.getModel().add(new TagModel("comment", cmt.getText()));
641 } else {
642 tm.setValue(cmt.getText());
643 }
644 tagEditorPanel.getModel().fireTableDataChanged();
645 }
646 }
647 );
648 cmt.getEditor().addActionListener(
649 new ActionListener() {
650 public void actionPerformed(ActionEvent e) {
651 btnUpload.requestFocusInWindow();
652 }
653 }
654 );
655 pnl.add(cmt, GBC.eol().fill(GBC.HORIZONTAL));
656 return pnl;
657 }
658
659 protected void build() {
660 setLayout(new GridBagLayout());
661 GridBagConstraints gc = new GridBagConstraints();
662
663 bgUseNewOrExisting = new ButtonGroup();
664
665 // -- atomic upload
666 gc.gridwidth = 4;
667 gc.gridy = 0;
668 gc.fill = GridBagConstraints.HORIZONTAL;
669 gc.weightx = 1.0;
670 gc.anchor = GridBagConstraints.FIRST_LINE_START;
671 add(buildAtomicUploadControlPanel(), gc);
672
673 // -- changeset command
674 gc.gridwidth = 4;
675 gc.gridy = 1;
676 gc.fill = GridBagConstraints.HORIZONTAL;
677 gc.weightx = 1.0;
678 gc.anchor = GridBagConstraints.FIRST_LINE_START;
679 add(buildUploadCommentPanel(), gc);
680
681 gc.gridwidth = 4;
682 gc.gridy = 2;
683 gc.fill = GridBagConstraints.HORIZONTAL;
684 gc.weightx = 0.0;
685 gc.anchor = GridBagConstraints.FIRST_LINE_START;
686 rbUseNew = new JRadioButton(tr("Open a new changeset"));
687 rbUseNew.setToolTipText(tr("Open a new changeset and use it in the next upload"));
688 bgUseNewOrExisting.add(rbUseNew);
689 add(rbUseNew, gc);
690
691 gc.gridx = 0;
692 gc.gridy = 3;
693 gc.gridwidth = 1;
694 rbExisting = new JRadioButton(tr("Use an open changeset"));
695 rbExisting.setToolTipText(tr("Upload data to an already opened changeset"));
696 bgUseNewOrExisting.add(rbExisting);
697 add(rbExisting, gc);
698
699 gc.gridx = 1;
700 gc.gridy = 3;
701 gc.gridwidth = 1;
702 gc.weightx = 1.0;
703 model = new OpenChangesetModel();
704 cbOpenChangesets = new JComboBox(model);
705 cbOpenChangesets.setToolTipText("Select an open changeset");
706 cbOpenChangesets.setRenderer(new ChangesetCellRenderer());
707 cbOpenChangesets.addItemListener(new ChangesetListItemStateListener());
708 Dimension d = cbOpenChangesets.getPreferredSize();
709 d.width = 200;
710 cbOpenChangesets.setPreferredSize(d);
711 d.width = 100;
712 cbOpenChangesets.setMinimumSize(d);
713 model.addListDataListener(this);
714 add(cbOpenChangesets, gc);
715
716 gc.gridx = 2;
717 gc.gridy = 3;
718 gc.gridwidth = 1;
719 gc.weightx = 0.0;
720 btnRefresh = new JButton(new RefreshAction());
721 add(btnRefresh, gc);
722
723 gc.gridx = 3;
724 gc.gridy = 3;
725 gc.gridwidth = 1;
726 gc.weightx = 0.0;
727 CloseChangesetAction closeChangesetAction = new CloseChangesetAction();
728 btnClose = new JButton(closeChangesetAction);
729 cbOpenChangesets.addItemListener(closeChangesetAction);
730 add(btnClose, gc);
731
732 gc.gridx = 0;
733 gc.gridy = 4;
734 gc.gridwidth = 4;
735 cbCloseAfterUpload = new JCheckBox(tr("Close changeset after upload"));
736 cbCloseAfterUpload.setToolTipText(tr("Select to close the changeset after the next upload"));
737 add(cbCloseAfterUpload, gc);
738 cbCloseAfterUpload.setSelected(true);
739
740 rbUseNew.getModel().addItemListener(new RadioButtonHandler());
741 rbExisting.getModel().addItemListener(new RadioButtonHandler());
742
743 refreshGUI();
744 }
745
746 public ChangesetSelectionPanel() {
747 build();
748 }
749
750 /**
751 * Remembers the user input in the preference settings
752 */
753 public void rememberUserInput() {
754 // store the history of comments
755 cmt.addCurrentItemToHistory();
756 Main.pref.putCollection(HISTORY_KEY, cmt.getHistory());
757 Main.pref.put("osm-server.atomic-upload", cbUseAtomicUpload.isSelected());
758 }
759
760 /**
761 * Initializes the panel for user input
762 */
763 public void startUserInput() {
764 List<String> history = cmt.getHistory();
765 if (history != null && !history.isEmpty()) {
766 cmt.setText(history.get(0));
767 }
768 cmt.requestFocusInWindow();
769 cmt.getEditor().getEditorComponent().requestFocusInWindow();
770 }
771
772 public void prepareDialogForNextUpload(Changeset cs) {
773 if (cs == null || cs.getId() == 0) {
774 rbUseNew.setSelected(true);
775 cbCloseAfterUpload.setSelected(true);
776 } if (cs.getId() == 0) {
777 rbUseNew.setSelected(true);
778 cbCloseAfterUpload.setSelected(true);
779 } else if (cs.isOpen()) {
780 rbExisting.setSelected(true);
781 cbCloseAfterUpload.setSelected(false);
782 } else {
783 rbUseNew.setSelected(true);
784 cbCloseAfterUpload.setSelected(true);
785 }
786 }
787
788 /**
789 * Replies the current upload comment
790 *
791 * @return
792 */
793 public String getUploadComment() {
794 return cmt.getText();
795 }
796
797 /**
798 * Replies the current upload comment
799 *
800 * @return
801 */
802 public void setUploadComment(String uploadComment) {
803 cmt.setText(uploadComment);
804 }
805
806 public void initEditingOfUploadComment(String comment) {
807 setUploadComment(comment);
808 cmt.getEditor().selectAll();
809 cmt.requestFocusInWindow();
810 }
811
812 protected void refreshGUI() {
813 rbExisting.setEnabled(model.getSize() > 0);
814 if (model.getSize() == 0) {
815 if (!rbUseNew.isSelected()) {
816 rbUseNew.setSelected(true);
817 }
818 }
819 cbOpenChangesets.setEnabled(model.getSize() > 0 && rbExisting.isSelected());
820 }
821
822 public void contentsChanged(ListDataEvent e) {
823 refreshGUI();
824 }
825
826 public void intervalAdded(ListDataEvent e) {
827 refreshGUI();
828 }
829
830 public void intervalRemoved(ListDataEvent e) {
831 refreshGUI();
832 }
833
834 public Changeset getChangeset() {
835 if (rbUseNew.isSelected() || cbOpenChangesets.getSelectedItem() == null)
836 return new Changeset();
837 Changeset cs = (Changeset)cbOpenChangesets.getSelectedItem();
838 if (cs == null)
839 return new Changeset();
840 return cs;
841 }
842
843 public void setOrUpdateChangeset(Changeset cs) {
844 if (cs == null) {
845 tagEditorPanel.getModel().clear();
846 tagEditorPanel.getModel().add("created_by", getDefaultCreatedBy());
847 tagEditorPanel.getModel().appendNewTag();
848 } else if (cs.getId() == 0) {
849 if (cs.get("created_by") == null) {
850 cs.put("created_by", getDefaultCreatedBy());
851 }
852 tagEditorPanel.getModel().initFromPrimitive(cs);
853 tagEditorPanel.getModel().appendNewTag();
854 } else if (cs.getId() > 0 && cs.isOpen()){
855 if (cs.get("created_by") == null) {
856 cs.put("created_by", getDefaultCreatedBy());
857 }
858 tagEditorPanel.getModel().initFromPrimitive(cs);
859 model.addOrUpdate(cs);
860 cs = model.getChangesetById(cs.getId());
861 cbOpenChangesets.setSelectedItem(cs);
862 } else if (cs.getId() > 0 && !cs.isOpen()){
863 if (cs.get("created_by") == null) {
864 cs.put("created_by", getDefaultCreatedBy());
865 }
866 tagEditorPanel.getModel().initFromPrimitive(cs);
867 model.removeChangeset(cs);
868 if (model.getSize() == 0) {
869 rbUseNew.setSelected(true);
870 model.setSelectedItem(null);
871 southTabbedPane.setTitleAt(1, tr("Tags of new changeset"));
872 }
873 }
874 prepareDialogForNextUpload(cs);
875 }
876
877 /**
878 * Sets whether a new changeset is to be used
879 *
880 */
881 public void setUseNewChangeset() {
882 rbUseNew.setSelected(true);
883 }
884
885 /**
886 * Sets whether an existing changeset is to be used
887 */
888 public void setUseExistingChangeset() {
889 rbExisting.setSelected(true);
890 if (cbOpenChangesets.getSelectedItem() == null && model.getSize() > 0) {
891 cbOpenChangesets.setSelectedItem(model.getElementAt(0));
892 }
893 }
894
895 /**
896 * Replies true if the selected changeset should be closed after the
897 * next upload
898 *
899 * @return true if the selected changeset should be closed after the
900 * next upload
901 */
902 public boolean isCloseAfterUpload() {
903 return cbCloseAfterUpload.isSelected();
904 }
905
906 class RadioButtonHandler implements ItemListener {
907 public void itemStateChanged(ItemEvent e) {
908 if (rbUseNew.isSelected()) {
909 southTabbedPane.setTitleAt(1, tr("Tags of new changeset"));
910 // init a new changeset from the currently edited tags
911 // and the comment field
912 //
913 Changeset cs = new Changeset();
914 tagEditorPanel.getModel().applyToPrimitive(cs);
915 if (cs.get("created_by") == null) {
916 cs.put("created_by", getDefaultCreatedBy());
917 }
918 cs.put("comment", cmt.getText());
919 tagEditorPanel.getModel().initFromPrimitive(cs);
920 } else {
921 if (cbOpenChangesets.getSelectedItem() == null) {
922 model.selectFirstChangeset();
923 }
924 Changeset cs = (Changeset)cbOpenChangesets.getSelectedItem();
925 if (cs != null) {
926 cs.put("comment", cmt.getText());
927 southTabbedPane.setTitleAt(1, tr("Tags of changeset {0}", cs.getId()));
928 tagEditorPanel.getModel().initFromPrimitive(cs);
929 }
930 }
931 refreshGUI();
932 }
933 }
934
935 class ChangesetListItemStateListener implements ItemListener {
936 public void itemStateChanged(ItemEvent e) {
937 Changeset cs = (Changeset)cbOpenChangesets.getSelectedItem();
938 if (cs == null) {
939 southTabbedPane.setTitleAt(1, tr("Tags of new changeset"));
940 // init a new changeset from the currently edited tags
941 // and the comment field
942 //
943 cs = new Changeset();
944 tagEditorPanel.getModel().applyToPrimitive(cs);
945 if (cs.get("created_by") == null) {
946 cs.put("created_by", getDefaultCreatedBy());
947 }
948 cs.put("comment", cmt.getText());
949 tagEditorPanel.getModel().initFromPrimitive(cs);
950 } else {
951 southTabbedPane.setTitleAt(1, tr("Tags of changeset {0}", cs.getId()));
952 if (cs.get("created_by") == null) {
953 cs.put("created_by", getDefaultCreatedBy());
954 }
955 tagEditorPanel.getModel().initFromPrimitive(cs);
956 }
957 }
958 }
959
960 /**
961 * Refreshes the list of open changesets
962 *
963 */
964 class RefreshAction extends AbstractAction {
965 public RefreshAction() {
966 //putValue(NAME, tr("Reload"));
967 putValue(SHORT_DESCRIPTION, tr("Load the list of your open changesets from the server"));
968 putValue(SMALL_ICON, ImageProvider.get("dialogs", "refresh"));
969 }
970
971 public void actionPerformed(ActionEvent e) {
972 DownloadOpenChangesetsTask task = new DownloadOpenChangesetsTask(model);
973 Main.worker.submit(task);
974 }
975 }
976
977 class CloseChangesetAction extends AbstractAction implements ItemListener{
978 public CloseChangesetAction() {
979 putValue(NAME, tr("Close"));
980 putValue(SMALL_ICON, ImageProvider.get("closechangeset"));
981 putValue(SHORT_DESCRIPTION, tr("Close the currently selected open changeset"));
982 refreshEnabledState();
983 }
984
985 public void actionPerformed(ActionEvent e) {
986 Changeset cs = (Changeset)cbOpenChangesets.getSelectedItem();
987 if (cs == null) return;
988 CloseChangesetTask task = new CloseChangesetTask(Collections.singletonList(cs));
989 Main.worker.submit(task);
990 }
991
992 protected void refreshEnabledState() {
993 setEnabled(cbOpenChangesets.getModel().getSize() > 0 && cbOpenChangesets.getSelectedItem() != null);
994 }
995
996 public void itemStateChanged(ItemEvent e) {
997 refreshEnabledState();
998 }
999 }
1000 }
1001
1002 /**
1003 * A combobox model for the list of open changesets
1004 *
1005 */
1006 public class OpenChangesetModel extends DefaultComboBoxModel {
1007 private List<Changeset> changesets;
1008 private long uid;
1009 private Changeset selectedChangeset = null;
1010
1011 protected Changeset getChangesetById(long id) {
1012 for (Changeset cs : changesets) {
1013 if (cs.getId() == id) return cs;
1014 }
1015 return null;
1016 }
1017
1018 public OpenChangesetModel() {
1019 this.changesets = new ArrayList<Changeset>();
1020 }
1021
1022 protected void internalAddOrUpdate(Changeset cs) {
1023 Changeset other = getChangesetById(cs.getId());
1024 if (other != null) {
1025 cs.cloneFrom(other);
1026 } else {
1027 changesets.add(cs);
1028 }
1029 }
1030
1031 public void addOrUpdate(Changeset cs) {
1032 if (cs.getId() <= 0 )
1033 throw new IllegalArgumentException(tr("Changeset ID > 0 expected. Got {0}.", cs.getId()));
1034 internalAddOrUpdate(cs);
1035 fireContentsChanged(this, 0, getSize());
1036 }
1037
1038 public void remove(long id) {
1039 Changeset cs = getChangesetById(id);
1040 if (cs != null) {
1041 changesets.remove(cs);
1042 }
1043 fireContentsChanged(this, 0, getSize());
1044 }
1045
1046 public void setChangesets(Collection<Changeset> changesets) {
1047 this.changesets.clear();
1048 if (changesets != null) {
1049 for (Changeset cs: changesets) {
1050 internalAddOrUpdate(cs);
1051 }
1052 }
1053 fireContentsChanged(this, 0, getSize());
1054 if (getSelectedItem() == null && !this.changesets.isEmpty()) {
1055 setSelectedItem(this.changesets.get(0));
1056 } else if (getSelectedItem() != null) {
1057 if (changesets.contains(getSelectedItem())) {
1058 setSelectedItem(getSelectedItem());
1059 } else if (!this.changesets.isEmpty()){
1060 setSelectedItem(this.changesets.get(0));
1061 } else {
1062 setSelectedItem(null);
1063 }
1064 } else {
1065 setSelectedItem(null);
1066 }
1067 }
1068
1069 public void setUserId(long uid) {
1070 this.uid = uid;
1071 }
1072
1073 public long getUserId() {
1074 return uid;
1075 }
1076
1077 public void selectFirstChangeset() {
1078 if (changesets == null || changesets.isEmpty()) return;
1079 setSelectedItem(changesets.get(0));
1080 }
1081
1082 public void removeChangeset(Changeset cs) {
1083 if (cs == null) return;
1084 changesets.remove(cs);
1085 if (selectedChangeset == cs) {
1086 selectFirstChangeset();
1087 }
1088 fireContentsChanged(this, 0, getSize());
1089 }
1090 /* ------------------------------------------------------------------------------------ */
1091 /* ComboBoxModel */
1092 /* ------------------------------------------------------------------------------------ */
1093 @Override
1094 public Object getElementAt(int index) {
1095 return changesets.get(index);
1096 }
1097
1098 @Override
1099 public int getIndexOf(Object anObject) {
1100 return changesets.indexOf(anObject);
1101 }
1102
1103 @Override
1104 public int getSize() {
1105 return changesets.size();
1106 }
1107
1108 @Override
1109 public Object getSelectedItem() {
1110 return selectedChangeset;
1111 }
1112
1113 @Override
1114 public void setSelectedItem(Object anObject) {
1115 if (anObject == null) {
1116 this.selectedChangeset = null;
1117 super.setSelectedItem(null);
1118 return;
1119 }
1120 if (! (anObject instanceof Changeset)) return;
1121 Changeset cs = (Changeset)anObject;
1122 if (cs.getId() == 0 || ! cs.isOpen()) return;
1123 Changeset candidate = getChangesetById(cs.getId());
1124 if (candidate == null) return;
1125 this.selectedChangeset = candidate;
1126 super.setSelectedItem(selectedChangeset);
1127 }
1128 }
1129}
Note: See TracBrowser for help on using the repository browser.