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