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