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 static final String SELECTED_CHANGESET_PROP = ChangesetManagementPanel.class.getName() + ".selectedChangeset"; 051 public static final String CLOSE_CHANGESET_AFTER_UPLOAD = ChangesetManagementPanel.class.getName() + ".closeChangesetAfterUpload"; 052 053 private JRadioButton rbUseNew; 054 private JRadioButton rbExisting; 055 private JosmComboBox<Changeset> 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 * Determines if the user has chosen to close the changeset after the next upload. 211 * @return {@code true} if the user has chosen to close the changeset after the next upload 212 */ 213 public boolean isCloseChangesetAfterUpload() { 214 return cbCloseAfterUpload.isSelected(); 215 } 216 217 /* ---------------------------------------------------------------------------- */ 218 /* Interface ListDataListener */ 219 /* ---------------------------------------------------------------------------- */ 220 @Override 221 public void contentsChanged(ListDataEvent e) { 222 refreshGUI(); 223 } 224 225 @Override 226 public void intervalAdded(ListDataEvent e) { 227 refreshGUI(); 228 } 229 230 @Override 231 public void intervalRemoved(ListDataEvent e) { 232 refreshGUI(); 233 } 234 235 /** 236 * Listens to changes in the selected changeset and fires property 237 * change events. 238 * 239 */ 240 class ChangesetListItemStateListener implements ItemListener { 241 @Override 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 @Override 258 public void itemStateChanged(ItemEvent e) { 259 if (e.getItemSelectable() != cbCloseAfterUpload) 260 return; 261 switch(e.getStateChange()) { 262 case ItemEvent.SELECTED: 263 firePropertyChange(CLOSE_CHANGESET_AFTER_UPLOAD, false, true); 264 Main.pref.put("upload.changeset.close", true); 265 break; 266 case ItemEvent.DESELECTED: 267 firePropertyChange(CLOSE_CHANGESET_AFTER_UPLOAD, true, false); 268 Main.pref.put("upload.changeset.close", false); 269 break; 270 } 271 } 272 } 273 274 /** 275 * Listens to changes in the two radio buttons rbUseNew and rbUseExisting. 276 * 277 */ 278 class RadioButtonHandler implements ItemListener { 279 @Override 280 public void itemStateChanged(ItemEvent e) { 281 if (rbUseNew.isSelected()) { 282 cbOpenChangesets.setEnabled(false); 283 firePropertyChange(SELECTED_CHANGESET_PROP, null, null); 284 } else if (rbExisting.isSelected()) { 285 cbOpenChangesets.setEnabled(true); 286 if (cbOpenChangesets.getSelectedItem() == null) { 287 model.selectFirstChangeset(); 288 } 289 Changeset cs = (Changeset)cbOpenChangesets.getSelectedItem(); 290 if (cs == null) return; 291 changesetCommentModel.setComment(cs.get("comment")); 292 firePropertyChange(SELECTED_CHANGESET_PROP, null, cs); 293 } 294 } 295 } 296 297 /** 298 * Refreshes the list of open changesets 299 * 300 */ 301 class RefreshAction extends AbstractAction { 302 public RefreshAction() { 303 putValue(SHORT_DESCRIPTION, tr("Load the list of your open changesets from the server")); 304 putValue(SMALL_ICON, ImageProvider.get("dialogs", "refresh")); 305 } 306 307 @Override 308 public void actionPerformed(ActionEvent e) { 309 DownloadOpenChangesetsTask task = new DownloadOpenChangesetsTask(ChangesetManagementPanel.this); 310 Main.worker.submit(task); 311 } 312 } 313 314 /** 315 * Closes the currently selected changeset 316 * 317 */ 318 class CloseChangesetAction extends AbstractAction implements ItemListener{ 319 public CloseChangesetAction() { 320 putValue(SMALL_ICON, ImageProvider.get("closechangeset")); 321 putValue(SHORT_DESCRIPTION, tr("Close the currently selected open changeset")); 322 refreshEnabledState(); 323 } 324 325 @Override 326 public void actionPerformed(ActionEvent e) { 327 Changeset cs = (Changeset)cbOpenChangesets.getSelectedItem(); 328 if (cs == null) return; 329 CloseChangesetTask task = new CloseChangesetTask(Collections.singletonList(cs)); 330 Main.worker.submit(task); 331 } 332 333 protected void refreshEnabledState() { 334 setEnabled( 335 cbOpenChangesets.getModel().getSize() > 0 336 && cbOpenChangesets.getSelectedItem() != null 337 && rbExisting.isSelected() 338 ); 339 } 340 341 @Override 342 public void itemStateChanged(ItemEvent e) { 343 refreshEnabledState(); 344 } 345 } 346}