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; 014import java.util.Optional; 015 016import javax.swing.AbstractAction; 017import javax.swing.BorderFactory; 018import javax.swing.JButton; 019import javax.swing.JCheckBox; 020import javax.swing.JPanel; 021import javax.swing.SwingUtilities; 022 023import org.openstreetmap.josm.data.osm.Changeset; 024import org.openstreetmap.josm.data.osm.ChangesetCache; 025import org.openstreetmap.josm.data.osm.ChangesetCacheEvent; 026import org.openstreetmap.josm.data.osm.ChangesetCacheListener; 027import org.openstreetmap.josm.gui.MainApplication; 028import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 029import org.openstreetmap.josm.gui.widgets.JosmComboBox; 030import org.openstreetmap.josm.gui.widgets.JosmComboBoxModel; 031import org.openstreetmap.josm.io.OsmTransferException; 032import org.openstreetmap.josm.spi.preferences.Config; 033import org.openstreetmap.josm.tools.ImageProvider; 034 035/** 036 * ChangesetManagementPanel allows to configure changeset to be used in the next 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 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 chose 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 ItemListener, ChangesetCacheListener { 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 JosmComboBox<Changeset> cbOpenChangesets; 054 private JosmComboBoxModel<Changeset> model; 055 private JCheckBox cbCloseAfterUpload; 056 private JButton btnClose; 057 058 /** 059 * Constructs a new {@code ChangesetManagementPanel}. 060 * 061 * @since 18283 (signature) 062 */ 063 public ChangesetManagementPanel() { 064 build(); 065 } 066 067 /** 068 * Initializes this life cycle of the panel. 069 * 070 * @since 18283 071 */ 072 public void initLifeCycle() { 073 refreshChangesets(); 074 } 075 076 /** 077 * Returns the model in use. 078 * @return the model 079 */ 080 public JosmComboBoxModel<Changeset> getModel() { 081 return model; 082 } 083 084 /** 085 * builds the GUI 086 */ 087 protected void build() { 088 setLayout(new GridBagLayout()); 089 setBorder(BorderFactory.createTitledBorder(tr("Please select a changeset:"))); 090 091 GridBagConstraints gc = new GridBagConstraints(); 092 gc.gridx = 0; 093 gc.gridy = 0; 094 gc.weightx = 1.0; 095 gc.weighty = 0.0; 096 gc.fill = GridBagConstraints.HORIZONTAL; 097 gc.anchor = GridBagConstraints.NORTHWEST; 098 gc.insets = new Insets(3, 3, 3, 3); 099 100 gc.gridwidth = 3; 101 add(new JMultilineLabel(tr( 102 "Please select which changeset the data shall be uploaded to and whether to close that changeset after the next upload." 103 )), gc); 104 105 gc.gridwidth = 1; 106 gc.gridy++; 107 model = new JosmComboBoxModel<>(); 108 cbOpenChangesets = new JosmComboBox<>(model); 109 cbOpenChangesets.setToolTipText(tr("Select a changeset")); 110 cbOpenChangesets.setRenderer(new ChangesetCellRenderer()); 111 Dimension d = cbOpenChangesets.getPreferredSize(); 112 d.width = 200; 113 cbOpenChangesets.setPreferredSize(d); 114 d.width = 100; 115 cbOpenChangesets.setMinimumSize(d); 116 add(cbOpenChangesets, gc); 117 int h = cbOpenChangesets.getPreferredSize().height; 118 Dimension prefSize = new Dimension(h, h); 119 120 gc.gridx++; 121 gc.weightx = 0.0; 122 JButton btnRefresh = new JButton(new RefreshAction()); 123 btnRefresh.setPreferredSize(prefSize); 124 btnRefresh.setMinimumSize(prefSize); 125 add(btnRefresh, gc); 126 127 gc.gridx++; 128 CloseChangesetAction closeChangesetAction = new CloseChangesetAction(); 129 btnClose = new JButton(closeChangesetAction); 130 btnClose.setPreferredSize(prefSize); 131 btnClose.setMinimumSize(prefSize); 132 add(btnClose, gc); 133 134 gc.gridy++; 135 gc.gridx = 0; 136 gc.gridwidth = 3; 137 gc.weightx = 1.0; 138 cbCloseAfterUpload = new JCheckBox(tr("Close changeset after upload")); 139 cbCloseAfterUpload.setToolTipText(tr("Select to close the changeset after the next upload")); 140 add(cbCloseAfterUpload, gc); 141 142 cbOpenChangesets.addItemListener(this); 143 cbOpenChangesets.addItemListener(closeChangesetAction); 144 145 cbCloseAfterUpload.setSelected(Config.getPref().getBoolean("upload.changeset.close", true)); 146 cbCloseAfterUpload.addItemListener(new CloseAfterUploadItemStateListener()); 147 148 ChangesetCache.getInstance().addChangesetCacheListener(this); 149 } 150 151 /** 152 * Sets the changeset to be used in the next upload 153 * <p> 154 * Note: The changeset may be a new changeset that was automatically opened because the old 155 * changeset overflowed. In that case it was already added to the changeset cache and the 156 * combobox. 157 * 158 * @param cs the changeset 159 * @see UploadPrimitivesTask#handleChangesetFullResponse 160 */ 161 public void setSelectedChangesetForNextUpload(Changeset cs) { 162 model.setSelectedItem(cs); 163 } 164 165 /** 166 * Returns the currently selected changeset or an empty new one. 167 * 168 * @return the currently selected changeset 169 */ 170 public Changeset getSelectedChangeset() { 171 return Optional.ofNullable((Changeset) model.getSelectedItem()).orElse(new Changeset()); 172 } 173 174 /** 175 * Determines if the user has chosen to close the changeset after the next upload. 176 * @return {@code true} if the user has chosen to close the changeset after the next upload 177 */ 178 public boolean isCloseChangesetAfterUpload() { 179 return cbCloseAfterUpload.isSelected(); 180 } 181 182 /** 183 * Listens to changes in the selected changeset and fires property change events. 184 */ 185 @Override 186 public void itemStateChanged(ItemEvent e) { 187 firePropertyChange(SELECTED_CHANGESET_PROP, null, model.getSelectedItem()); 188 } 189 190 /** 191 * Listens to changes in "close after upload" flag and fires property change events. 192 */ 193 class CloseAfterUploadItemStateListener implements ItemListener { 194 @Override 195 public void itemStateChanged(ItemEvent e) { 196 if (e.getItemSelectable() != cbCloseAfterUpload) 197 return; 198 switch(e.getStateChange()) { 199 case ItemEvent.SELECTED: 200 firePropertyChange(CLOSE_CHANGESET_AFTER_UPLOAD, false, true); 201 Config.getPref().putBoolean("upload.changeset.close", true); 202 break; 203 case ItemEvent.DESELECTED: 204 firePropertyChange(CLOSE_CHANGESET_AFTER_UPLOAD, true, false); 205 Config.getPref().putBoolean("upload.changeset.close", false); 206 break; 207 default: // Do nothing 208 } 209 } 210 } 211 212 /** 213 * Refreshes the list of open changesets 214 */ 215 class RefreshAction extends AbstractAction { 216 RefreshAction() { 217 putValue(SHORT_DESCRIPTION, tr("Load the list of your open changesets from the server")); 218 new ImageProvider("dialogs", "refresh").getResource().attachImageIcon(this, true); 219 } 220 221 @Override 222 public void actionPerformed(ActionEvent e) { 223 MainApplication.worker.submit(new DownloadOpenChangesetsTask(ChangesetManagementPanel.this)); 224 } 225 } 226 227 /** 228 * Closes the currently selected changeset 229 */ 230 class CloseChangesetAction extends AbstractAction implements ItemListener { 231 CloseChangesetAction() { 232 new ImageProvider("closechangeset").getResource().attachImageIcon(this, true); 233 putValue(SHORT_DESCRIPTION, tr("Close the currently selected open changeset")); 234 refreshEnabledState(); 235 } 236 237 @Override 238 public void actionPerformed(ActionEvent e) { 239 Changeset cs = (Changeset) cbOpenChangesets.getSelectedItem(); 240 if (cs == null) return; 241 MainApplication.worker.submit(new CloseChangesetTask(Collections.singletonList(cs))); 242 } 243 244 protected void refreshEnabledState() { 245 setEnabled(!getSelectedChangeset().isNew()); 246 } 247 248 @Override 249 public void itemStateChanged(ItemEvent e) { 250 refreshEnabledState(); 251 } 252 } 253 254 /** 255 * Refreshes the changesets combobox form the server. 256 * <p> 257 * Note: This calls into {@link #refreshCombo} through {@link #changesetCacheUpdated} 258 * 259 * @see ChangesetCache#refreshChangesetsFromServer 260 */ 261 protected void refreshChangesets() { 262 try { 263 ChangesetCache.getInstance().refreshChangesetsFromServer(); 264 } catch (OsmTransferException e) { 265 return; 266 } 267 } 268 269 private void refreshCombo() { 270 Changeset selected = (Changeset) cbOpenChangesets.getSelectedItem(); 271 model.removeAllElements(); 272 model.addElement(new Changeset()); 273 model.addAllElements(ChangesetCache.getInstance().getOpenChangesetsForCurrentUser()); 274 cbOpenChangesets.setSelectedItem(selected != null && model.getIndexOf(selected) != -1 ? selected : model.getElementAt(0)); 275 } 276 277 @Override 278 public void changesetCacheUpdated(ChangesetCacheEvent event) { 279 // This listener might have been called by a background task. 280 SwingUtilities.invokeLater(() -> refreshCombo()); 281 } 282}