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 final transient 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( 078 tr("Please decide what changeset the data is uploaded to and whether to close the changeset after the next upload.")), gc); 079 080 gc.gridwidth = 4; 081 gc.gridy = 1; 082 gc.fill = GridBagConstraints.HORIZONTAL; 083 gc.weightx = 1.0; 084 gc.weighty = 0.0; 085 gc.insets = new Insets(0, 0, 0, 0); 086 gc.anchor = GridBagConstraints.FIRST_LINE_START; 087 rbUseNew = new JRadioButton(tr("Upload to a new changeset")); 088 rbUseNew.setToolTipText(tr("Open a new changeset and use it in the next upload")); 089 bgUseNewOrExisting.add(rbUseNew); 090 add(rbUseNew, gc); 091 092 gc.gridx = 0; 093 gc.gridy = 2; 094 gc.gridwidth = 1; 095 gc.weightx = 0.0; 096 gc.fill = GridBagConstraints.HORIZONTAL; 097 rbExisting = new JRadioButton(tr("Upload to an existing changeset")); 098 rbExisting.setToolTipText(tr("Upload data to an already existing and open changeset")); 099 bgUseNewOrExisting.add(rbExisting); 100 add(rbExisting, gc); 101 102 gc.gridx = 1; 103 gc.gridy = 2; 104 gc.gridwidth = 1; 105 gc.weightx = 1.0; 106 model = new OpenChangesetComboBoxModel(); 107 ChangesetCache.getInstance().addChangesetCacheListener(model); 108 cbOpenChangesets = new JosmComboBox<>(model); 109 cbOpenChangesets.setToolTipText(tr("Select an open changeset")); 110 cbOpenChangesets.setRenderer(new ChangesetCellRenderer()); 111 cbOpenChangesets.addItemListener(new ChangesetListItemStateListener()); 112 Dimension d = cbOpenChangesets.getPreferredSize(); 113 d.width = 200; 114 cbOpenChangesets.setPreferredSize(d); 115 d.width = 100; 116 cbOpenChangesets.setMinimumSize(d); 117 model.addListDataListener(this); 118 add(cbOpenChangesets, gc); 119 120 gc.gridx = 2; 121 gc.gridy = 2; 122 gc.weightx = 0.0; 123 gc.gridwidth = 1; 124 gc.weightx = 0.0; 125 JButton btnRefresh = new JButton(new RefreshAction()); 126 btnRefresh.setMargin(new Insets(0, 0, 0, 0)); 127 add(btnRefresh, gc); 128 129 gc.gridx = 3; 130 gc.gridy = 2; 131 gc.gridwidth = 1; 132 CloseChangesetAction closeChangesetAction = new CloseChangesetAction(); 133 JButton btnClose = new JButton(closeChangesetAction); 134 btnClose.setMargin(new Insets(0, 0, 0, 0)); 135 cbOpenChangesets.addItemListener(closeChangesetAction); 136 rbExisting.addItemListener(closeChangesetAction); 137 add(btnClose, gc); 138 139 gc.gridx = 0; 140 gc.gridy = 3; 141 gc.gridwidth = 4; 142 gc.weightx = 1.0; 143 cbCloseAfterUpload = new JCheckBox(tr("Close changeset after upload")); 144 cbCloseAfterUpload.setToolTipText(tr("Select to close the changeset after the next upload")); 145 add(cbCloseAfterUpload, gc); 146 cbCloseAfterUpload.setSelected(Main.pref.getBoolean("upload.changeset.close", true)); 147 cbCloseAfterUpload.addItemListener(new CloseAfterUploadItemStateListener()); 148 149 gc.gridx = 0; 150 gc.gridy = 5; 151 gc.gridwidth = 4; 152 gc.weightx = 1.0; 153 gc.weighty = 1.0; 154 gc.fill = GridBagConstraints.BOTH; 155 add(new JPanel(), gc); 156 157 rbUseNew.getModel().addItemListener(new RadioButtonHandler()); 158 rbExisting.getModel().addItemListener(new RadioButtonHandler()); 159 } 160 161 /** 162 * Creates a new panel 163 * 164 * @param changesetCommentModel the changeset comment model. Must not be null. 165 * @throws IllegalArgumentException if {@code changesetCommentModel} is null 166 */ 167 public ChangesetManagementPanel(ChangesetCommentModel changesetCommentModel) { 168 CheckParameterUtil.ensureParameterNotNull(changesetCommentModel, "changesetCommentModel"); 169 this.changesetCommentModel = changesetCommentModel; 170 build(); 171 refreshGUI(); 172 } 173 174 protected void refreshGUI() { 175 rbExisting.setEnabled(model.getSize() > 0); 176 if (model.getSize() == 0) { 177 if (!rbUseNew.isSelected()) { 178 rbUseNew.setSelected(true); 179 } 180 } 181 cbOpenChangesets.setEnabled(model.getSize() > 0 && rbExisting.isSelected()); 182 } 183 184 /** 185 * Sets the changeset to be used in the next upload 186 * 187 * @param cs the changeset 188 */ 189 public void setSelectedChangesetForNextUpload(Changeset cs) { 190 int idx = model.getIndexOf(cs); 191 if (idx >= 0) { 192 rbExisting.setSelected(true); 193 model.setSelectedItem(cs); 194 } 195 } 196 197 /** 198 * Replies the currently selected changeset. null, if no changeset is 199 * selected or if the user has chosen to use a new changeset. 200 * 201 * @return the currently selected changeset. null, if no changeset is 202 * selected. 203 */ 204 public Changeset getSelectedChangeset() { 205 if (rbUseNew.isSelected()) 206 return null; 207 return (Changeset) cbOpenChangesets.getSelectedItem(); 208 } 209 210 /** 211 * Determines if the user has chosen to close the changeset after the next upload. 212 * @return {@code true} if the user has chosen to close the changeset after the next upload 213 */ 214 public boolean isCloseChangesetAfterUpload() { 215 return cbCloseAfterUpload.isSelected(); 216 } 217 218 /* ---------------------------------------------------------------------------- */ 219 /* Interface ListDataListener */ 220 /* ---------------------------------------------------------------------------- */ 221 @Override 222 public void contentsChanged(ListDataEvent e) { 223 refreshGUI(); 224 } 225 226 @Override 227 public void intervalAdded(ListDataEvent e) { 228 refreshGUI(); 229 } 230 231 @Override 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 @Override 243 public void itemStateChanged(ItemEvent e) { 244 Changeset cs = (Changeset) cbOpenChangesets.getSelectedItem(); 245 if (cs == null) return; 246 if (rbExisting.isSelected()) { 247 firePropertyChange(SELECTED_CHANGESET_PROP, null, cs); 248 } 249 } 250 } 251 252 /** 253 * Listens to changes in "close after upload" flag and fires 254 * property change events. 255 * 256 */ 257 class CloseAfterUploadItemStateListener implements ItemListener { 258 @Override 259 public void itemStateChanged(ItemEvent e) { 260 if (e.getItemSelectable() != cbCloseAfterUpload) 261 return; 262 switch(e.getStateChange()) { 263 case ItemEvent.SELECTED: 264 firePropertyChange(CLOSE_CHANGESET_AFTER_UPLOAD, false, true); 265 Main.pref.put("upload.changeset.close", true); 266 break; 267 case ItemEvent.DESELECTED: 268 firePropertyChange(CLOSE_CHANGESET_AFTER_UPLOAD, true, false); 269 Main.pref.put("upload.changeset.close", false); 270 break; 271 } 272 } 273 } 274 275 /** 276 * Listens to changes in the two radio buttons rbUseNew and rbUseExisting. 277 * 278 */ 279 class RadioButtonHandler implements ItemListener { 280 @Override 281 public void itemStateChanged(ItemEvent e) { 282 if (rbUseNew.isSelected()) { 283 cbOpenChangesets.setEnabled(false); 284 firePropertyChange(SELECTED_CHANGESET_PROP, null, null); 285 } else if (rbExisting.isSelected()) { 286 cbOpenChangesets.setEnabled(true); 287 if (cbOpenChangesets.getSelectedItem() == null) { 288 model.selectFirstChangeset(); 289 } 290 Changeset cs = (Changeset) cbOpenChangesets.getSelectedItem(); 291 if (cs == null) return; 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 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 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}