001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.io;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.awt.Dimension;
007    import java.awt.GridBagConstraints;
008    import java.awt.GridBagLayout;
009    import java.awt.Insets;
010    import java.awt.event.ActionEvent;
011    import java.awt.event.ItemEvent;
012    import java.awt.event.ItemListener;
013    import java.util.Collections;
014    
015    import javax.swing.AbstractAction;
016    import javax.swing.BorderFactory;
017    import javax.swing.ButtonGroup;
018    import javax.swing.JButton;
019    import javax.swing.JCheckBox;
020    import javax.swing.JPanel;
021    import javax.swing.JRadioButton;
022    import javax.swing.event.ListDataEvent;
023    import javax.swing.event.ListDataListener;
024    
025    import org.openstreetmap.josm.Main;
026    import org.openstreetmap.josm.data.osm.Changeset;
027    import org.openstreetmap.josm.data.osm.ChangesetCache;
028    import org.openstreetmap.josm.gui.JMultilineLabel;
029    import org.openstreetmap.josm.gui.widgets.JosmComboBox;
030    import org.openstreetmap.josm.tools.CheckParameterUtil;
031    import org.openstreetmap.josm.tools.ImageProvider;
032    
033    /**
034     * ChangesetManagementPanel allows to configure changeset to be used in the next
035     * upload.
036     *
037     * It is displayed as one of the configuration panels in the {@link UploadDialog}.
038     *
039     * ChangesetManagementPanel is a source for {@link PropertyChangeEvent}s. Clients can listen
040     * to
041     * <ul>
042     *   <li>{@link #SELECTED_CHANGESET_PROP}  - the new value in the property change event is
043     *   the changeset selected by the user. The value is null if the user didn't select a
044     *   a changeset or if he chosed to use a new changeset.</li>
045     *   <li> {@link #CLOSE_CHANGESET_AFTER_UPLOAD} - the new value is a boolean value indicating
046     *   whether the changeset should be closed after the next upload</li>
047     * </ul>
048     */
049    public class ChangesetManagementPanel extends JPanel implements ListDataListener{
050        public final static String SELECTED_CHANGESET_PROP = ChangesetManagementPanel.class.getName() + ".selectedChangeset";
051        public final static String CLOSE_CHANGESET_AFTER_UPLOAD = ChangesetManagementPanel.class.getName() + ".closeChangesetAfterUpload";
052    
053        private ButtonGroup bgUseNewOrExisting;
054        private JRadioButton rbUseNew;
055        private JRadioButton rbExisting;
056        private JosmComboBox cbOpenChangesets;
057        private JButton btnRefresh;
058        private JButton btnClose;
059        private JCheckBox cbCloseAfterUpload;
060        private OpenChangesetComboBoxModel model;
061        private ChangesetCommentModel changesetCommentModel;
062    
063        /**
064         * builds the GUI
065         */
066        protected void build() {
067            setLayout(new GridBagLayout());
068            GridBagConstraints gc = new GridBagConstraints();
069            setBorder(BorderFactory.createEmptyBorder(3,3,3,3));
070    
071            bgUseNewOrExisting = new ButtonGroup();
072    
073            gc.gridwidth = 4;
074            gc.gridx = 0;
075            gc.gridy = 0;
076            gc.fill = GridBagConstraints.HORIZONTAL;
077            gc.weightx = 1.0;
078            gc.weighty = 0.0;
079            gc.insets = new Insets(0, 0, 5, 0);
080            add(new JMultilineLabel(tr("Please decide what changeset the data is uploaded to and whether to close the changeset after the next upload.")), gc);
081    
082            gc.gridwidth = 4;
083            gc.gridy = 1;
084            gc.fill = GridBagConstraints.HORIZONTAL;
085            gc.weightx = 1.0;
086            gc.weighty = 0.0;
087            gc.insets = new Insets(0,0,0,0);
088            gc.anchor = GridBagConstraints.FIRST_LINE_START;
089            rbUseNew = new JRadioButton(tr("Upload to a new changeset"));
090            rbUseNew.setToolTipText(tr("Open a new changeset and use it in the next upload"));
091            bgUseNewOrExisting.add(rbUseNew);
092            add(rbUseNew, gc);
093    
094            gc.gridx = 0;
095            gc.gridy = 2;
096            gc.gridwidth = 1;
097            gc.weightx = 0.0;
098            gc.fill = GridBagConstraints.HORIZONTAL;
099            rbExisting = new JRadioButton(tr("Upload to an existing changeset"));
100            rbExisting.setToolTipText(tr("Upload data to an already existing and open changeset"));
101            bgUseNewOrExisting.add(rbExisting);
102            add(rbExisting, gc);
103    
104            gc.gridx = 1;
105            gc.gridy = 2;
106            gc.gridwidth = 1;
107            gc.weightx = 1.0;
108            model = new OpenChangesetComboBoxModel();
109            ChangesetCache.getInstance().addChangesetCacheListener(model);
110            cbOpenChangesets = new JosmComboBox(model);
111            cbOpenChangesets.setToolTipText(tr("Select an open changeset"));
112            cbOpenChangesets.setRenderer(new ChangesetCellRenderer());
113            cbOpenChangesets.addItemListener(new ChangesetListItemStateListener());
114            Dimension d = cbOpenChangesets.getPreferredSize();
115            d.width = 200;
116            cbOpenChangesets.setPreferredSize(d);
117            d.width = 100;
118            cbOpenChangesets.setMinimumSize(d);
119            model.addListDataListener(this);
120            add(cbOpenChangesets, gc);
121    
122            gc.gridx = 2;
123            gc.gridy = 2;
124            gc.weightx = 0.0;
125            gc.gridwidth = 1;
126            gc.weightx = 0.0;
127            btnRefresh = new JButton(new RefreshAction());
128            btnRefresh.setMargin(new Insets(0,0,0,0));
129            add(btnRefresh, gc);
130    
131            gc.gridx = 3;
132            gc.gridy = 2;
133            gc.gridwidth = 1;
134            CloseChangesetAction closeChangesetAction = new CloseChangesetAction();
135            btnClose = new JButton(closeChangesetAction);
136            btnClose.setMargin(new Insets(0,0,0,0));
137            cbOpenChangesets.addItemListener(closeChangesetAction);
138            rbExisting.addItemListener(closeChangesetAction);
139            add(btnClose, gc);
140    
141            gc.gridx = 0;
142            gc.gridy = 3;
143            gc.gridwidth = 4;
144            gc.weightx = 1.0;
145            cbCloseAfterUpload = new JCheckBox(tr("Close changeset after upload"));
146            cbCloseAfterUpload.setToolTipText(tr("Select to close the changeset after the next upload"));
147            add(cbCloseAfterUpload, gc);
148            cbCloseAfterUpload.setSelected(Main.pref.getBoolean("upload.changeset.close", true));
149            cbCloseAfterUpload.addItemListener(new CloseAfterUploadItemStateListener());
150    
151            gc.gridx = 0;
152            gc.gridy = 5;
153            gc.gridwidth = 4;
154            gc.weightx = 1.0;
155            gc.weighty = 1.0;
156            gc.fill = GridBagConstraints.BOTH;
157            add(new JPanel(), gc);
158    
159            rbUseNew.getModel().addItemListener(new RadioButtonHandler());
160            rbExisting.getModel().addItemListener(new RadioButtonHandler());
161        }
162    
163        /**
164         * Creates a new panel
165         *
166         * @param changesetCommentModel the changeset comment model. Must not be null.
167         * @throws IllegalArgumentException thrown if {@code changesetCommentModel} is null
168         */
169        public ChangesetManagementPanel(ChangesetCommentModel changesetCommentModel) {
170            CheckParameterUtil.ensureParameterNotNull(changesetCommentModel, "changesetCommentModel");
171            this.changesetCommentModel = changesetCommentModel;
172            build();
173            refreshGUI();
174        }
175    
176        protected void refreshGUI() {
177            rbExisting.setEnabled(model.getSize() > 0);
178            if (model.getSize() == 0) {
179                if (!rbUseNew.isSelected()) {
180                    rbUseNew.setSelected(true);
181                }
182            }
183            cbOpenChangesets.setEnabled(model.getSize() > 0 && rbExisting.isSelected());
184        }
185    
186        /**
187         * Sets the changeset to be used in the next upload
188         *
189         * @param cs the changeset
190         */
191        public void setSelectedChangesetForNextUpload(Changeset cs) {
192            int idx  = model.getIndexOf(cs);
193            if (idx >=0) {
194                rbExisting.setSelected(true);
195                model.setSelectedItem(cs);
196            }
197        }
198    
199        /**
200         * Replies the currently selected changeset. null, if no changeset is
201         * selected or if the user has chosen to use a new changeset.
202         *
203         * @return the currently selected changeset. null, if no changeset is
204         * selected.
205         */
206        public Changeset getSelectedChangeset() {
207            if (rbUseNew.isSelected())
208                return null;
209            return (Changeset)cbOpenChangesets.getSelectedItem();
210        }
211    
212        /**
213         * Replies true if the user has chosen to close the changeset after the
214         * next upload
215         *
216         */
217        public boolean isCloseChangesetAfterUpload() {
218            return cbCloseAfterUpload.isSelected();
219        }
220    
221        /* ---------------------------------------------------------------------------- */
222        /* Interface ListDataListener                                                   */
223        /* ---------------------------------------------------------------------------- */
224        public void contentsChanged(ListDataEvent e) {
225            refreshGUI();
226        }
227    
228        public void intervalAdded(ListDataEvent e) {
229            refreshGUI();
230        }
231    
232        public void intervalRemoved(ListDataEvent e) {
233            refreshGUI();
234        }
235    
236        /**
237         * Listens to changes in the selected changeset and fires property
238         * change events.
239         *
240         */
241        class ChangesetListItemStateListener implements ItemListener {
242            public void itemStateChanged(ItemEvent e) {
243                Changeset cs = (Changeset)cbOpenChangesets.getSelectedItem();
244                if (cs == null) return;
245                if (rbExisting.isSelected()) {
246                    firePropertyChange(SELECTED_CHANGESET_PROP, null, cs);
247                }
248            }
249        }
250    
251        /**
252         * Listens to changes in "close after upload" flag and fires
253         * property change events.
254         *
255         */
256        class CloseAfterUploadItemStateListener implements ItemListener {
257            public void itemStateChanged(ItemEvent e) {
258                if (e.getItemSelectable() != cbCloseAfterUpload)
259                    return;
260                switch(e.getStateChange()) {
261                case ItemEvent.SELECTED:
262                    firePropertyChange(CLOSE_CHANGESET_AFTER_UPLOAD, false, true);
263                    Main.pref.put("upload.changeset.close", true);
264                    break;
265                case ItemEvent.DESELECTED:
266                    firePropertyChange(CLOSE_CHANGESET_AFTER_UPLOAD, true, false);
267                    Main.pref.put("upload.changeset.close", false);
268                    break;
269                }
270            }
271        }
272    
273        /**
274         * Listens to changes in the two radio buttons rbUseNew and rbUseExisting.
275         *
276         */
277        class RadioButtonHandler implements ItemListener {
278            public void itemStateChanged(ItemEvent e) {
279                if (rbUseNew.isSelected()) {
280                    cbOpenChangesets.setEnabled(false);
281                    firePropertyChange(SELECTED_CHANGESET_PROP, null, null);
282                } else if (rbExisting.isSelected()) {
283                    cbOpenChangesets.setEnabled(true);
284                    if (cbOpenChangesets.getSelectedItem() == null) {
285                        model.selectFirstChangeset();
286                    }
287                    Changeset cs = (Changeset)cbOpenChangesets.getSelectedItem();
288                    if (cs == null) return;
289                    changesetCommentModel.setComment(cs.get("comment"));
290                    firePropertyChange(SELECTED_CHANGESET_PROP, null, cs);
291                }
292            }
293        }
294    
295        /**
296         * Refreshes the list of open changesets
297         *
298         */
299        class RefreshAction extends AbstractAction {
300            public RefreshAction() {
301                //putValue(NAME, tr("Reload"));
302                putValue(SHORT_DESCRIPTION, tr("Load the list of your open changesets from the server"));
303                putValue(SMALL_ICON, ImageProvider.get("dialogs", "refresh"));
304            }
305    
306            public void actionPerformed(ActionEvent e) {
307                DownloadOpenChangesetsTask task = new DownloadOpenChangesetsTask(ChangesetManagementPanel.this);
308                Main.worker.submit(task);
309            }
310        }
311    
312        /**
313         * Closes the currently selected changeset
314         *
315         */
316        class CloseChangesetAction extends AbstractAction implements ItemListener{
317            public CloseChangesetAction() {
318                //putValue(NAME, tr("Close"));
319                putValue(SMALL_ICON, ImageProvider.get("closechangeset"));
320                putValue(SHORT_DESCRIPTION, tr("Close the currently selected open changeset"));
321                refreshEnabledState();
322            }
323    
324            public void actionPerformed(ActionEvent e) {
325                Changeset cs = (Changeset)cbOpenChangesets.getSelectedItem();
326                if (cs == null) return;
327                CloseChangesetTask task = new CloseChangesetTask(Collections.singletonList(cs));
328                Main.worker.submit(task);
329            }
330    
331            protected void refreshEnabledState() {
332                setEnabled(
333                        cbOpenChangesets.getModel().getSize() > 0
334                        && cbOpenChangesets.getSelectedItem() != null
335                        && rbExisting.isSelected()
336                );
337            }
338    
339            public void itemStateChanged(ItemEvent e) {
340                refreshEnabledState();
341            }
342        }
343    }