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