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