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