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