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