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

Last change on this file since 2447 was 2447, checked in by Gubaer, 14 years ago

fixed #3897: Previous changeset comment gets overwritten

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