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.BorderLayout; 007import java.awt.Dimension; 008import java.awt.FlowLayout; 009import java.awt.event.ActionEvent; 010import java.awt.event.WindowAdapter; 011import java.awt.event.WindowEvent; 012import java.io.Serializable; 013import java.util.ArrayList; 014import java.util.Collection; 015import java.util.Collections; 016import java.util.Comparator; 017import java.util.List; 018 019import javax.swing.AbstractAction; 020import javax.swing.AbstractListModel; 021import javax.swing.Action; 022import javax.swing.BorderFactory; 023import javax.swing.JButton; 024import javax.swing.JDialog; 025import javax.swing.JLabel; 026import javax.swing.JList; 027import javax.swing.JPanel; 028import javax.swing.JScrollPane; 029import javax.swing.JSplitPane; 030import javax.swing.ListSelectionModel; 031import javax.swing.event.ListSelectionEvent; 032import javax.swing.event.ListSelectionListener; 033 034import org.openstreetmap.josm.data.osm.DefaultNameFormatter; 035import org.openstreetmap.josm.data.osm.OsmPrimitive; 036import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 037import org.openstreetmap.josm.gui.MainApplication; 038import org.openstreetmap.josm.gui.PrimitiveRenderer; 039import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; 040import org.openstreetmap.josm.gui.help.HelpUtil; 041import org.openstreetmap.josm.gui.util.GuiHelper; 042import org.openstreetmap.josm.gui.util.WindowGeometry; 043import org.openstreetmap.josm.tools.ImageProvider; 044import org.openstreetmap.josm.tools.InputMapUtils; 045 046/** 047 * This dialog can be used to select individual object for uploading. 048 * 049 * @since 2250 050 */ 051public class UploadSelectionDialog extends JDialog { 052 053 private final OsmPrimitiveList lstSelectedPrimitives = new OsmPrimitiveList(); 054 private final OsmPrimitiveList lstDeletedPrimitives = new OsmPrimitiveList(); 055 private JSplitPane spLists; 056 private boolean canceled; 057 private JButton btnContinue; 058 059 /** 060 * Constructs a new {@code UploadSelectionDialog}. 061 */ 062 public UploadSelectionDialog() { 063 super(GuiHelper.getFrameForComponent(MainApplication.getMainFrame()), ModalityType.DOCUMENT_MODAL); 064 build(); 065 } 066 067 protected JPanel buildSelectedPrimitivesPanel() { 068 JPanel pnl = new JPanel(new BorderLayout()); 069 JLabel lbl = new JLabel( 070 tr("<html>Mark modified objects <strong>from the current selection</strong> to be uploaded to the server.</html>")); 071 lbl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 072 pnl.add(lbl, BorderLayout.NORTH); 073 pnl.add(new JScrollPane(lstSelectedPrimitives), BorderLayout.CENTER); 074 lbl.setLabelFor(lstSelectedPrimitives); 075 return pnl; 076 } 077 078 protected JPanel buildDeletedPrimitivesPanel() { 079 JPanel pnl = new JPanel(new BorderLayout()); 080 JLabel lbl = new JLabel(tr("<html>Mark <strong>locally deleted objects</strong> to be deleted on the server.</html>")); 081 lbl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 082 pnl.add(lbl, BorderLayout.NORTH); 083 pnl.add(new JScrollPane(lstDeletedPrimitives), BorderLayout.CENTER); 084 lbl.setLabelFor(lstDeletedPrimitives); 085 return pnl; 086 } 087 088 protected JPanel buildButtonPanel() { 089 JPanel pnl = new JPanel(new FlowLayout()); 090 ContinueAction continueAction = new ContinueAction(); 091 btnContinue = new JButton(continueAction); 092 pnl.add(btnContinue); 093 btnContinue.setFocusable(true); 094 lstDeletedPrimitives.getSelectionModel().addListSelectionListener(continueAction); 095 lstSelectedPrimitives.getSelectionModel().addListSelectionListener(continueAction); 096 097 pnl.add(new JButton(new CancelAction())); 098 pnl.add(new JButton(new ContextSensitiveHelpAction(HelpUtil.ht("/Action/UploadSelection")))); 099 return pnl; 100 } 101 102 protected void build() { 103 setLayout(new BorderLayout()); 104 spLists = new JSplitPane(JSplitPane.VERTICAL_SPLIT); 105 spLists.setTopComponent(buildSelectedPrimitivesPanel()); 106 spLists.setBottomComponent(buildDeletedPrimitivesPanel()); 107 add(spLists, BorderLayout.CENTER); 108 add(buildButtonPanel(), BorderLayout.SOUTH); 109 addWindowListener( 110 new WindowAdapter() { 111 @Override 112 public void windowOpened(WindowEvent e) { 113 spLists.setDividerLocation(0.5); 114 btnContinue.requestFocusInWindow(); 115 } 116 117 @Override 118 public void windowClosing(WindowEvent e) { 119 setCanceled(true); 120 } 121 } 122 ); 123 setTitle(tr("Select objects to upload")); 124 HelpUtil.setHelpContext(getRootPane(), HelpUtil.ht("/Action/UploadSelection")); 125 } 126 127 public void populate(Collection<OsmPrimitive> selected, Collection<OsmPrimitive> deleted) { 128 if (selected != null) { 129 lstSelectedPrimitives.getOsmPrimitiveListModel().setPrimitives(new ArrayList<>(selected)); 130 if (!selected.isEmpty()) { 131 lstSelectedPrimitives.getSelectionModel().setSelectionInterval(0, selected.size()-1); 132 } else { 133 lstSelectedPrimitives.getSelectionModel().clearSelection(); 134 } 135 } else { 136 lstSelectedPrimitives.getOsmPrimitiveListModel().setPrimitives(null); 137 lstSelectedPrimitives.getSelectionModel().clearSelection(); 138 } 139 140 if (deleted != null) { 141 lstDeletedPrimitives.getOsmPrimitiveListModel().setPrimitives(new ArrayList<>(deleted)); 142 } else { 143 lstDeletedPrimitives.getOsmPrimitiveListModel().setPrimitives(null); 144 } 145 } 146 147 /** 148 * See if the user pressed the cancel button 149 * @return <code>true</code> if the user canceled the upload 150 */ 151 public boolean isCanceled() { 152 return canceled; 153 } 154 155 protected void setCanceled(boolean canceled) { 156 this.canceled = canceled; 157 } 158 159 /** 160 * Gets the list of primitives the user selected 161 * @return The primitives 162 */ 163 public List<OsmPrimitive> getSelectedPrimitives() { 164 List<OsmPrimitive> ret = new ArrayList<>(); 165 ret.addAll(lstSelectedPrimitives.getOsmPrimitiveListModel().getPrimitives(lstSelectedPrimitives.getSelectedIndices())); 166 ret.addAll(lstDeletedPrimitives.getOsmPrimitiveListModel().getPrimitives(lstDeletedPrimitives.getSelectedIndices())); 167 return ret; 168 } 169 170 @Override 171 public void setVisible(boolean visible) { 172 if (visible) { 173 new WindowGeometry( 174 getClass().getName() + ".geometry", 175 WindowGeometry.centerInWindow( 176 MainApplication.getMainFrame(), 177 new Dimension(400, 400) 178 ) 179 ).applySafe(this); 180 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775 181 new WindowGeometry(this).remember(getClass().getName() + ".geometry"); 182 } 183 super.setVisible(visible); 184 } 185 186 static class OsmPrimitiveList extends JList<OsmPrimitive> { 187 OsmPrimitiveList() { 188 this(new OsmPrimitiveListModel()); 189 } 190 191 OsmPrimitiveList(OsmPrimitiveListModel model) { 192 super(model); 193 init(); 194 } 195 196 protected void init() { 197 setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 198 setCellRenderer(new PrimitiveRenderer()); 199 } 200 201 public OsmPrimitiveListModel getOsmPrimitiveListModel() { 202 return (OsmPrimitiveListModel) getModel(); 203 } 204 } 205 206 static class OsmPrimitiveListModel extends AbstractListModel<OsmPrimitive> { 207 static final class OsmPrimitiveComparator implements Comparator<OsmPrimitive>, Serializable { 208 private static final long serialVersionUID = 1L; 209 @Override 210 public int compare(OsmPrimitive o1, OsmPrimitive o2) { 211 int ret = OsmPrimitiveType.from(o1).compareTo(OsmPrimitiveType.from(o2)); 212 if (ret != 0) 213 return ret; 214 DefaultNameFormatter formatter = DefaultNameFormatter.getInstance(); 215 return o1.getDisplayName(formatter).compareTo(o1.getDisplayName(formatter)); 216 } 217 } 218 219 private transient List<OsmPrimitive> data; 220 221 protected void sort() { 222 if (data != null) 223 data.sort(new OsmPrimitiveComparator()); 224 } 225 226 public void setPrimitives(List<OsmPrimitive> data) { 227 this.data = data; 228 sort(); 229 if (data != null) { 230 fireContentsChanged(this, 0, data.size()); 231 } else { 232 fireContentsChanged(this, 0, 0); 233 } 234 } 235 236 @Override 237 public OsmPrimitive getElementAt(int index) { 238 if (data == null) 239 return null; 240 return data.get(index); 241 } 242 243 @Override 244 public int getSize() { 245 if (data == null) 246 return 0; 247 return data.size(); 248 } 249 250 public List<OsmPrimitive> getPrimitives(int... indices) { 251 if (indices == null || indices.length == 0) 252 return Collections.emptyList(); 253 List<OsmPrimitive> ret = new ArrayList<>(indices.length); 254 for (int i: indices) { 255 if (i < 0) { 256 continue; 257 } 258 ret.add(data.get(i)); 259 } 260 return ret; 261 } 262 } 263 264 class CancelAction extends AbstractAction { 265 CancelAction() { 266 putValue(Action.NAME, tr("Cancel")); 267 new ImageProvider("cancel").getResource().attachImageIcon(this); 268 InputMapUtils.addEscapeAction(getRootPane(), this); 269 setEnabled(true); 270 } 271 272 @Override 273 public void actionPerformed(ActionEvent e) { 274 setCanceled(true); 275 setVisible(false); 276 } 277 } 278 279 class ContinueAction extends AbstractAction implements ListSelectionListener { 280 ContinueAction() { 281 putValue(Action.NAME, tr("Continue")); 282 new ImageProvider("ok").getResource().attachImageIcon(this); 283 updateEnabledState(); 284 } 285 286 @Override 287 public void actionPerformed(ActionEvent e) { 288 setCanceled(false); 289 setVisible(false); 290 } 291 292 protected void updateEnabledState() { 293 setEnabled(lstSelectedPrimitives.getSelectedIndex() >= 0 294 || lstDeletedPrimitives.getSelectedIndex() >= 0); 295 } 296 297 @Override 298 public void valueChanged(ListSelectionEvent e) { 299 updateEnabledState(); 300 } 301 } 302}