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 }