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