Index: trunk/src/org/openstreetmap/josm/gui/DefaultNameFormatter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/DefaultNameFormatter.java (revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/DefaultNameFormatter.java (revision 2689)
@@ -20,4 +20,9 @@
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.history.HistoryNameFormatter;
+import org.openstreetmap.josm.data.osm.history.HistoryNode;
+import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
+import org.openstreetmap.josm.data.osm.history.HistoryRelation;
+import org.openstreetmap.josm.data.osm.history.HistoryWay;
/**
@@ -25,5 +30,5 @@
*
*/
-public class DefaultNameFormatter implements NameFormatter {
+public class DefaultNameFormatter implements NameFormatter, HistoryNameFormatter {
static private DefaultNameFormatter instance;
@@ -238,3 +243,168 @@
return sb.toString();
}
+
+
+ /**
+ * Decorates the name of primitive with its id, if the preference
+ * osm-primitives.showid is set.
+ *
+ * The id is append to the {@see StringBuilder} passed in in name
.
+ *
+ * @param name the name without the id
+ * @param primitive the primitive
+ */
+ protected void decorateNameWithId(StringBuilder name, HistoryOsmPrimitive primitive) {
+ if (Main.pref.getBoolean("osm-primitives.showid")) {
+ name.append(tr(" [id: {0}]", primitive.getId()));
+ }
+ }
+
+
+ /**
+ * Formats a name for a history node
+ *
+ * @param node the node
+ * @return the name
+ */
+ public String format(HistoryNode node) {
+ StringBuilder sb = new StringBuilder();
+ String name;
+ if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
+ name = node.getLocalName();
+ } else {
+ name = node.getName();
+ }
+ if (name == null) {
+ sb.append(node.getId());
+ } else {
+ sb.append(name);
+ }
+ sb.append(" (")
+ .append(node.getCoords().latToString(CoordinateFormat.getDefaultFormat()))
+ .append(", ")
+ .append(node.getCoords().lonToString(CoordinateFormat.getDefaultFormat()))
+ .append(")");
+ decorateNameWithId(sb, node);
+ return sb.toString();
+ }
+
+ /**
+ * Formats a name for a way
+ *
+ * @param way the way
+ * @return the name
+ */
+ public String format(HistoryWay way) {
+ StringBuilder sb = new StringBuilder();
+ String name;
+ if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
+ name = way.getLocalName();
+ } else {
+ name = way.getName();
+ }
+ if (name != null) {
+ sb.append(name);
+ }
+ if (sb.length() == 0 && way.get("ref") != null) {
+ sb.append(way.get("ref"));
+ }
+ if (sb.length() == 0) {
+ sb.append(
+ (way.get("highway") != null) ? tr("highway") :
+ (way.get("railway") != null) ? tr("railway") :
+ (way.get("waterway") != null) ? tr("waterway") :
+ (way.get("landuse") != null) ? tr("landuse") : ""
+ );
+ }
+
+ int nodesNo = way.isClosed() ? way.getNumNodes() -1 : way.getNumNodes();
+ String nodes = trn("{0} node", "{0} nodes", nodesNo, nodesNo);
+ sb.append((sb.length() > 0) ? " ("+nodes+")" : nodes);
+ decorateNameWithId(sb, way);
+ return sb.toString();
+ }
+
+
+ /**
+ * Formats a name for a {@see HistoryRelation})
+ *
+ * @param relation the relation
+ * @return the name
+ */
+ public String format(HistoryRelation relation) {
+ StringBuilder sb = new StringBuilder();
+ if (relation.get("type") != null) {
+ sb.append(relation.get("type"));
+ } else {
+ sb.append(tr("relation"));
+ }
+ sb.append(" (");
+ String nameTag = null;
+ Set namingTags = new HashSet(getNamingtagsForRelations());
+ for (String n : relation.getTags().keySet()) {
+ // #3328: "note " and " note" are name tags too
+ if (namingTags.contains(n.trim())) {
+ if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
+ nameTag = relation.getLocalName();
+ } else {
+ nameTag = relation.getName();
+ }
+ if (nameTag == null) {
+ nameTag = relation.get(n);
+ }
+ }
+ if (nameTag != null) {
+ break;
+ }
+ }
+ if (nameTag == null) {
+ sb.append(Long.toString(relation.getId())).append(", ");
+ } else {
+ sb.append("\"").append(nameTag).append("\", ");
+ }
+
+ int mbno = relation.getNumMembers();
+ sb.append(trn("{0} member", "{0} members", mbno, mbno)).append(")");
+
+ decorateNameWithId(sb, relation);
+ return sb.toString();
+ }
+
+ /**
+ * Builds a default tooltip text for an HistoryOsmPrimitive primitive
.
+ *
+ * @param primitive the primitmive
+ * @return the tooltip text
+ */
+ public String buildDefaultToolTip(HistoryOsmPrimitive primitive) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("");
+ sb.append("id=")
+ .append(primitive.getId())
+ .append("
");
+ ArrayList keyList = new ArrayList(primitive.getTags().keySet());
+ Collections.sort(keyList);
+ for (int i = 0; i < keyList.size(); i++) {
+ if (i > 0) {
+ sb.append("
");
+ }
+ String key = keyList.get(i);
+ sb.append("")
+ .append(key)
+ .append("")
+ .append("=");
+ String value = primitive.get(key);
+ while(value.length() != 0) {
+ sb.append(value.substring(0,Math.min(50, value.length())));
+ if (value.length() > 50) {
+ sb.append("
");
+ value = value.substring(50);
+ } else {
+ value = "";
+ }
+ }
+ }
+ sb.append("");
+ return sb.toString();
+ }
}
Index: trunk/src/org/openstreetmap/josm/gui/JosmUserIdentityManager.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/JosmUserIdentityManager.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/JosmUserIdentityManager.java (revision 2689)
@@ -0,0 +1,222 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
+import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
+import org.openstreetmap.josm.data.osm.UserInfo;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+
+/**
+ * JosmUserStateManager is a global object which keeps track of what JOSM knows about
+ * the identity of the current user.
+ *
+ * JOSM can be operated anonymously provided the current user never invokes an operation
+ * on the OSM server which required authentication. In this case JOSM neither knows
+ * the user name of the OSM account of the current user nor its unique id. Perhaps the
+ * user doesn't have one.
+ *
+ * If the current user supplies a user name and a password in the JOSM preferences JOSM
+ * can partially identify the user.
+ *
+ * The current user is fully identified if JOSM knows both the user name and the unique
+ * id of the users OSM account. The later is retrieved from the OSM server with a
+ * GET /api/0.6/user/details request, submitted with the user name and password
+ * of the current user.
+ *
+ * The global JosmUserStateManager listens to {@see PreferenceChangeEvent}s and keeps track
+ * of what the current JOSM instance knows about the current user. Other subsystems can
+ * let the global JosmUserStateManager know in case they fully identify the current user, see
+ * {@see #setFullyIdentified(String, long)}.
+ *
+ * The information kept by the JosmUserStateManager can be used to
+ *
+ * - safely query changesets owned by the current user based on its user id, not on its user name
+ * - safely search for objects last touched by the current user based on its user id, not on its user name
+ *
+ *
+ */
+public class JosmUserIdentityManager implements PreferenceChangedListener{
+
+ static private JosmUserIdentityManager instance;
+
+ /**
+ * Replies the unique instance of teh JOSM user identity manager
+ *
+ * @return the unique instance of teh JOSM user identity manager
+ */
+ static public JosmUserIdentityManager getInstance() {
+ if (instance == null) {
+ instance = new JosmUserIdentityManager();
+ instance.initFromPreferences();
+ Main.pref.addPreferenceChangeListener(instance);
+ }
+ return instance;
+ }
+
+ private String userName;
+ private UserInfo userInfo;
+
+ private JosmUserIdentityManager() {
+ }
+
+ /**
+ * Remembers the fact that the current JOSM user is anonymous.
+ */
+ public void setAnonymous() {
+ userName = null;
+ userInfo = null;
+ }
+
+ /**
+ * Remebers the fact that the current JOSM user is partially identified
+ * by the user name of its OSM account.
+ *
+ * @param userName the user name. Must not be null. Must not be empty (whitespace only).
+ * @throws IllegalArgumentException thrown if userName is null
+ * @throws IllegalArgumentException thrown if userName is empty
+ */
+ public void setPartiallyIdentified(String userName) throws IllegalArgumentException {
+ CheckParameterUtil.ensureParameterNotNull(userName, "userName");
+ if (userName.trim().equals(""))
+ throw new IllegalArgumentException(tr("Expected non-empty value for parameter ''{0}'', got ''{1}''", "userName", userName));
+ this.userName = userName;
+ userInfo = null;
+ }
+
+ /**
+ * Remembers the fact that the current JOSM user is fully identified with a
+ * verified pair of user name and user id.
+ *
+ * @param userName the user name. Must not be null. Must not be empty.
+ * @param userinfo additional information about the user, retrieved from the OSM server and including the user id
+ * @throws IllegalArgumentException thrown if userName is null
+ * @throws IllegalArgumentException thrown if userName is empty
+ * @throws IllegalArgumentException thrown if userinfo is null
+ */
+ public void setFullyIdentified(String username, UserInfo userinfo) throws IllegalArgumentException {
+ CheckParameterUtil.ensureParameterNotNull(username, "username");
+ if (username.trim().equals(""))
+ throw new IllegalArgumentException(tr("Expected non-empty value for parameter ''{0}'', got ''{1}''", "userName", userName));
+ CheckParameterUtil.ensureParameterNotNull(userinfo, "userinfo");
+ this.userName = username;
+ this.userInfo = userinfo;
+ }
+
+ /**
+ * Replies true if the current JOSM user is anonymous.
+ *
+ * @return true if the current user is anonymous.
+ */
+ public boolean isAnonymous() {
+ return userName == null && userInfo == null;
+ }
+
+ /**
+ * Replies true if the current JOSM user is partially identified.
+ *
+ * @return true if the current JOSM user is partially identified.
+ */
+ public boolean isPartiallyIdentified() {
+ return userName != null && userInfo == null;
+ }
+
+
+ /**
+ * Replies true if the current JOSM user is fully identified.
+ *
+ * @return true if the current JOSM user is fully identified.
+ */
+ public boolean isFullyIdentified() {
+ return userName != null && userInfo != null;
+ }
+
+ /**
+ * Replies the user name of the current JOSM user. null, if {@see #isAnonymous()} is true.
+ *
+ * @return the user name of the current JOSM user
+ */
+ public String getUserName() {
+ return userName;
+ }
+
+ /**
+ * Replies the user id of the current JOSM user. 0, if {@see #isAnonymous()} or
+ * {@see #isPartiallyIdentified()} is true.
+ *
+ * @return the user id of the current JOSM user
+ */
+ public int getUserId() {
+ if (userInfo == null) return 0;
+ return userInfo.getId();
+ }
+
+ /**
+ * Replies verified additional information about the current user if the user is
+ * {@see #isFullyIdentified()}.
+ *
+ * @return verified additional information about the current user
+ */
+ public UserInfo getUserInfo() {
+ return userInfo;
+ }
+ /**
+ * Initializes the user identity manager from values in the {@see org.openstreetmap.josm.data.Preferences}
+ */
+ public void initFromPreferences() {
+ String userName = Main.pref.get("osm-server.username");
+ if (isAnonymous()) {
+ if (userName != null && ! userName.trim().equals("")) {
+ setPartiallyIdentified(userName);
+ }
+ } else {
+ if (!userName.equals(this.userName)) {
+ setPartiallyIdentified(userName);
+ } else {
+ // same name in the preferences as JOSM already knows about;
+ // keep the state, be it partially or fully identified
+ }
+ }
+ }
+
+ /**
+ * Replies true if the user with name username
is the current
+ * user
+ *
+ * @param username the user name
+ * @return true if the user with name username
is the current
+ * user
+ */
+ public boolean isCurrentUser(String username) {
+ if (username == null || this.userName == null) return false;
+ return this.userName.equals(username);
+ }
+
+ /* ------------------------------------------------------------------- */
+ /* interface PreferenceChangeListener */
+ /* ------------------------------------------------------------------- */
+ public void preferenceChanged(PreferenceChangeEvent evt) {
+ if (evt.getKey().equals("osm-server.username")) {
+ String newValue = evt.getNewValue();
+ if (newValue == null || newValue.trim().length() == 0) {
+ setAnonymous();
+ } else {
+ if (! newValue.equals(userName)) {
+ setPartiallyIdentified(newValue);
+ }
+ }
+ return;
+ }
+
+ if (evt.getKey().equals("osm-server.url")) {
+ String newValue = evt.getNewValue();
+ if (newValue == null || newValue.trim().equals("")) {
+ setAnonymous();
+ } else if (isFullyIdentified()) {
+ setPartiallyIdentified(getUserName());
+ }
+ }
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/MainMenu.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MainMenu.java (revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/MainMenu.java (revision 2689)
@@ -21,4 +21,5 @@
import org.openstreetmap.josm.actions.AlignInLineAction;
import org.openstreetmap.josm.actions.AutoScaleAction;
+import org.openstreetmap.josm.actions.ChangesetManagerToggleAction;
import org.openstreetmap.josm.actions.CloseChangesetAction;
import org.openstreetmap.josm.actions.CombineWayAction;
@@ -251,8 +252,16 @@
add(viewMenu, new ZoomOutAction());
viewMenu.addSeparator();
- for (String mode : AutoScaleAction.modes) {
+ for (String mode : AutoScaleAction.MODES) {
JosmAction autoScaleAction = new AutoScaleAction(mode);
add(viewMenu, autoScaleAction);
}
+
+ // -- changeset manager toggle action
+ ChangesetManagerToggleAction changesetManagerToggleAction = new ChangesetManagerToggleAction();
+ final JCheckBoxMenuItem mi = new JCheckBoxMenuItem(changesetManagerToggleAction);
+ viewMenu.addSeparator();
+ viewMenu.add(mi);
+ mi.setAccelerator(changesetManagerToggleAction.getShortcut().getKeyStroke());
+ changesetManagerToggleAction.addButtonModel(mi.getModel());
// -- fullscreen toggle action
Index: trunk/src/org/openstreetmap/josm/gui/OsmPrimitivRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/OsmPrimitivRenderer.java (revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/OsmPrimitivRenderer.java (revision 2689)
@@ -3,6 +3,4 @@
import java.awt.Component;
-import java.util.ArrayList;
-import java.util.Collections;
import javax.swing.DefaultListCellRenderer;
@@ -15,5 +13,5 @@
import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
import org.openstreetmap.josm.tools.ImageProvider;
@@ -27,4 +25,6 @@
*/
public class OsmPrimitivRenderer implements ListCellRenderer, TableCellRenderer {
+ private DefaultNameFormatter formatter = new DefaultNameFormatter();
+
/**
* Default list cell renderer - delegate for ListCellRenderer operation
@@ -50,5 +50,10 @@
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component def = defaultTableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
- return renderer(def, (OsmPrimitive) value);
+ if (value instanceof OsmPrimitive)
+ return renderer(def, (OsmPrimitive) value);
+ else if (value instanceof HistoryOsmPrimitive)
+ return renderer(def, (HistoryOsmPrimitive) value);
+ else
+ return def;
}
@@ -63,6 +68,6 @@
if (def != null && value != null && def instanceof JLabel) {
((JLabel)def).setText(value.getDisplayName(DefaultNameFormatter.getInstance()));
- ((JLabel)def).setIcon(ImageProvider.get(OsmPrimitiveType.from(value)));
- ((JLabel)def).setToolTipText(buildToolTipText(value));
+ ((JLabel)def).setIcon(ImageProvider.get(value.getType()));
+ ((JLabel)def).setToolTipText(formatter.buildDefaultToolTip(value));
}
return def;
@@ -70,49 +75,17 @@
/**
- * build the tool tip text for an {@see OsmPrimitive}. It consist of the formatted
- * key/value pairs for this primitive.
- *
- * @param primitive
- * @return the tool tip text
+ * Internal method that stuffs information into the rendering component
+ * provided that it's a kind of JLabel.
+ * @param def the rendering component
+ * @param value the HistoryOsmPrimtive to render
+ * @return the modified rendering component
*/
- public String buildToolTipText(OsmPrimitive primitive) {
- StringBuilder sb = new StringBuilder();
-
- sb.append("");
- // show the id
- //
- sb.append("id=")
- .append(primitive.getId())
- .append("
");
-
- // show the key/value-pairs, sorted by key
- //
- ArrayList keyList = new ArrayList(primitive.keySet());
- Collections.sort(keyList);
- for (int i = 0; i < keyList.size(); i++) {
- if (i > 0) {
- sb.append("
");
- }
- String key = keyList.get(i);
- sb.append("")
- .append(key)
- .append("")
- .append("=");
- // make sure long values are split into several rows. Otherwise
- // the tool tip window can become to wide
- //
- String value = primitive.get(key);
- while(value.length() != 0) {
- sb.append(value.substring(0,Math.min(50, value.length())));
- if (value.length() > 50) {
- sb.append("
");
- value = value.substring(50);
- } else {
- value = "";
- }
- }
+ private Component renderer(Component def, HistoryOsmPrimitive value) {
+ if (def != null && value != null && def instanceof JLabel) {
+ ((JLabel)def).setText(value.getDisplayName(DefaultNameFormatter.getInstance()));
+ ((JLabel)def).setIcon(ImageProvider.get(value.getType()));
+ ((JLabel)def).setToolTipText(formatter.buildDefaultToolTip(value));
}
- sb.append("");
- return sb.toString();
+ return def;
}
}
Index: trunk/src/org/openstreetmap/josm/gui/PleaseWaitRunnable.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/PleaseWaitRunnable.java (revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/PleaseWaitRunnable.java (revision 2689)
@@ -2,4 +2,5 @@
package org.openstreetmap.josm.gui;
+import java.awt.Component;
import java.awt.EventQueue;
import java.io.IOException;
@@ -12,4 +13,5 @@
import org.openstreetmap.josm.gui.progress.ProgressMonitor.CancelListener;
import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
import org.xml.sax.SAXException;
@@ -46,4 +48,21 @@
public PleaseWaitRunnable(String title, boolean ignoreException) {
this(title, new PleaseWaitProgressMonitor(title), ignoreException);
+ }
+
+ /**
+ * Create the runnable object with a given message for the user
+ *
+ * @param parent the parent component for the please wait dialog. Must not be null.
+ * @param title message for the user
+ * @param ignoreException If true, exception will be propagated to calling code. If false then
+ * exception will be thrown directly in EDT. When this runnable is executed using executor framework
+ * then use false unless you read result of task (because exception will get lost if you don't)
+ * @throws IllegalArgumentException thrown if parent is null
+ */
+ public PleaseWaitRunnable(Component parent, String title, boolean ignoreException) throws IllegalArgumentException{
+ CheckParameterUtil.ensureParameterNotNull(parent, "parent");
+ this.title = title;
+ this.progressMonitor = new PleaseWaitProgressMonitor(parent, title);
+ this.ignoreException = ignoreException;
}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/ChangesetDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/ChangesetDialog.java (revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/ChangesetDialog.java (revision 2689)
@@ -6,4 +6,5 @@
import java.awt.BorderLayout;
import java.awt.FlowLayout;
+import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
@@ -11,7 +12,10 @@
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
+import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
import javax.swing.AbstractAction;
@@ -37,11 +41,14 @@
import org.openstreetmap.josm.gui.MapFrame;
import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetCacheManager;
+import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetHeaderDownloadTask;
import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetInSelectionListModel;
import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetListCellRenderer;
import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetListModel;
import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetsInActiveDataLayerListModel;
-import org.openstreetmap.josm.gui.dialogs.changeset.DownloadChangesetsTask;
import org.openstreetmap.josm.gui.help.HelpUtil;
import org.openstreetmap.josm.gui.io.CloseChangesetTask;
+import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
+import org.openstreetmap.josm.tools.BugReportExceptionHandler;
import org.openstreetmap.josm.tools.ImageProvider;
import org.openstreetmap.josm.tools.OpenBrowser;
@@ -75,4 +82,5 @@
private ShowChangesetInfoAction showChangsetInfoAction;
private CloseOpenChangesetsAction closeChangesetAction;
+ private LaunchChangesetManagerAction launchChangesetManagerAction;
@@ -93,28 +101,56 @@
lstInActiveDataLayer.setCellRenderer(new ChangesetListCellRenderer());
- ChangesetCache.getInstance().addChangesetCacheListener(inSelectionModel);
- MapView.addLayerChangeListener(inSelectionModel);
- DataSet.selListeners.add(inSelectionModel);
-
- ChangesetCache.getInstance().addChangesetCacheListener(inActiveDataLayerModel);
-
DblClickHandler dblClickHandler = new DblClickHandler();
lstInSelection.addMouseListener(dblClickHandler);
lstInActiveDataLayer.addMouseListener(dblClickHandler);
- PopupMenuLauncher popupMenuLauncher = new PopupMenuLauncher();
+ ChangesetPopupMenuLauncher popupMenuLauncher = new ChangesetPopupMenuLauncher();
lstInSelection.addMouseListener(popupMenuLauncher);
lstInActiveDataLayer.addMouseListener(popupMenuLauncher);
}
+ protected void registerAsListener() {
+ // let the model for changesets in the current selection listen to various
+ // events
+ ChangesetCache.getInstance().addChangesetCacheListener(inSelectionModel);
+ MapView.addEditLayerChangeListener(inSelectionModel);
+ DataSet.selListeners.add(inSelectionModel);
+
+
+ // let the model for changesets in the current layer listen to various
+ // events and bootstrap it's content
+ ChangesetCache.getInstance().addChangesetCacheListener(inActiveDataLayerModel);
+ MapView.addEditLayerChangeListener(inActiveDataLayerModel);
+ if (Main.main.getEditLayer() != null) {
+ Main.main.getEditLayer().data.addDataSetListener(inActiveDataLayerModel);
+ inActiveDataLayerModel.initFromDataSet(Main.main.getEditLayer().data);
+ inSelectionModel.initFromPrimitives(Main.main.getEditLayer().data.getSelected());
+ }
+ }
+
+ protected void unregisterAsListener() {
+ // remove the list model for the current edit layer as listener
+ //
+ ChangesetCache.getInstance().removeChangesetCacheListener(inActiveDataLayerModel);
+ MapView.removeEditLayerChangeListener(inActiveDataLayerModel);
+ if (Main.main.getEditLayer() != null) {
+ Main.main.getEditLayer().data.removeDataSetListener(inActiveDataLayerModel);
+ }
+
+ // remove the list model for the changesets in the current selection as
+ // listener
+ //
+ MapView.removeEditLayerChangeListener(inSelectionModel);
+ DataSet.selListeners.remove(inSelectionModel);
+ }
+
@Override
public void tearDown() {
- ChangesetCache.getInstance().removeChangesetCacheListener(inActiveDataLayerModel);
- MapView.removeLayerChangeListener(inSelectionModel);
- DataSet.selListeners.remove(inSelectionModel);
+ unregisterAsListener();
}
@Override
public void showNotify() {
+ registerAsListener();
DatasetEventManager.getInstance().addDatasetListener(inActiveDataLayerModel, true);
}
@@ -122,4 +158,5 @@
@Override
public void hideNotify() {
+ unregisterAsListener();
DatasetEventManager.getInstance().removeDatasetListener(inActiveDataLayerModel);
}
@@ -179,4 +216,11 @@
lstInActiveDataLayer.getSelectionModel().addListSelectionListener(showChangsetInfoAction);
lstInSelection.getSelectionModel().addListSelectionListener(showChangsetInfoAction);
+
+ // -- launch changeset manager action
+ launchChangesetManagerAction = new LaunchChangesetManagerAction();
+ tp.add(launchChangesetManagerAction);
+ cbInSelectionOnly.addItemListener(launchChangesetManagerAction);
+ lstInActiveDataLayer.getSelectionModel().addListSelectionListener(launchChangesetManagerAction);
+ lstInSelection.getSelectionModel().addListSelectionListener(launchChangesetManagerAction);
pnl.add(tp);
@@ -334,5 +378,5 @@
if (sel.isEmpty())
return;
- DownloadChangesetsTask task = new DownloadChangesetsTask(sel);
+ ChangesetHeaderDownloadTask task = new ChangesetHeaderDownloadTask(sel);
Main.worker.submit(task);
}
@@ -424,8 +468,101 @@
}
- class PopupMenuLauncher extends MouseAdapter {
- protected void showPopup(MouseEvent evt) {
- if (!evt.isPopupTrigger())
- return;
+ /**
+ * Show information about the currently selected changesets
+ *
+ */
+ class LaunchChangesetManagerAction extends AbstractAction implements ListSelectionListener, ItemListener {
+ public LaunchChangesetManagerAction() {
+ putValue(NAME, tr("Details"));
+ putValue(SHORT_DESCRIPTION, tr("Opens the Changeset Manager window for the selected changesets"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "changesetmanager"));
+ updateEnabledState();
+ }
+
+ protected void launchChangesetManager(Collection toSelect) {
+ ChangesetCacheManager cm = ChangesetCacheManager.getInstance();
+ if (cm.isVisible()) {
+ cm.setExtendedState(Frame.NORMAL);
+ cm.toFront();
+ cm.requestFocus();
+ } else {
+ cm.setVisible(true);
+ cm.toFront();
+ cm.requestFocus();
+ }
+ cm.setSelectedChangesetsById(toSelect);
+ }
+
+ public void actionPerformed(ActionEvent arg0) {
+ ChangesetListModel model = getCurrentChangesetListModel();
+ Set sel = model.getSelectedChangesetIds();
+ if (sel.isEmpty())
+ return;
+ final Set toDownload = new HashSet();
+ ChangesetCache cc = ChangesetCache.getInstance();
+ for (int id: sel) {
+ if (!cc.contains(id)) {
+ toDownload.add(id);
+ }
+ }
+
+ final ChangesetHeaderDownloadTask task;
+ final Future> future;
+ if (toDownload.isEmpty()) {
+ task = null;
+ future = null;
+ } else {
+ task = new ChangesetHeaderDownloadTask(toDownload);
+ future = Main.worker.submit(task);
+ }
+
+ Runnable r = new Runnable() {
+ public void run() {
+ // first, wait for the download task to finish, if a download
+ // task was launched
+ if (future != null) {
+ try {
+ future.get();
+ } catch(InterruptedException e) {
+ e.printStackTrace();
+ } catch(ExecutionException e){
+ e.printStackTrace();
+ BugReportExceptionHandler.handleException(e.getCause());
+ return;
+ }
+ }
+ if (task != null) {
+ if (task.isCanceled())
+ // don't launch the changeset manager if the download task
+ // was canceled
+ return;
+ if (task.isFailed()) {
+ toDownload.clear();
+ }
+ }
+ // launch the task
+ launchChangesetManager(toDownload);
+ }
+ };
+ Main.worker.submit(r);
+ }
+
+ protected void updateEnabledState() {
+ setEnabled(getCurrentChangesetList().getSelectedIndices().length > 0);
+ }
+
+ public void itemStateChanged(ItemEvent arg0) {
+ updateEnabledState();
+ }
+
+ public void valueChanged(ListSelectionEvent e) {
+ updateEnabledState();
+ }
+ }
+
+
+ class ChangesetPopupMenuLauncher extends PopupMenuLauncher {
+ @Override
+ public void launch(MouseEvent evt) {
JList lst = getCurrentChangesetList();
if (lst.getSelectedIndices().length == 0) {
@@ -438,16 +575,4 @@
popup.show(lst, evt.getX(), evt.getY());
- }
- @Override
- public void mouseClicked(MouseEvent evt) {
- showPopup(evt);
- }
- @Override
- public void mousePressed(MouseEvent evt) {
- showPopup(evt);
- }
- @Override
- public void mouseReleased(MouseEvent evt) {
- showPopup(evt);
}
}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheManager.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheManager.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheManager.java (revision 2689)
@@ -0,0 +1,638 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTable;
+import javax.swing.JToolBar;
+import javax.swing.KeyStroke;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.ChangesetCache;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.JosmUserIdentityManager;
+import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.gui.dialogs.changeset.query.ChangesetQueryDialog;
+import org.openstreetmap.josm.gui.dialogs.changeset.query.ChangesetQueryTask;
+import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
+import org.openstreetmap.josm.gui.help.HelpUtil;
+import org.openstreetmap.josm.gui.io.CloseChangesetTask;
+import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
+import org.openstreetmap.josm.io.ChangesetQuery;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.WindowGeometry;
+
+/**
+ * ChangesetCacheManager manages the local cache of changesets
+ * retrieved from the OSM API. It displays both a table of the locally cached changesets
+ * and detail information about an individual changeset. It also provides actions for
+ * downloading, querying, closing changesets, in addition to removing changesets from
+ * the local cache.
+ *
+ */
+public class ChangesetCacheManager extends JFrame {
+
+ /** the unique instance of the cache manager */
+ private static ChangesetCacheManager instance;
+
+ /**
+ * Replies the unique instance of the changeset cache manager
+ *
+ * @return the unique instance of the changeset cache manager
+ */
+ public static ChangesetCacheManager getInstance() {
+ if (instance == null) {
+ instance = new ChangesetCacheManager();
+ }
+ return instance;
+ }
+
+ /**
+ * Hides and destroys the unique instance of the changeset cache
+ * manager.
+ *
+ */
+ public static void destroyInstance() {
+ if (instance != null) {
+ instance.setVisible(true);
+ instance.dispose();
+ instance = null;
+ }
+ }
+
+
+ private ChangesetCacheManagerModel model;
+ private JSplitPane spContent;
+ private boolean needsSplitPaneAdjustment;
+
+ private RemoveFromCacheAction actRemoveFromCacheAction;
+ private CloseSelectedChangesetsAction actCloseSelectedChangesetsAction;
+ private DownloadSelectedChangesetsAction actDownloadSelectedChangesets;
+ private DownloadSelectedChangesetContentAction actDownloadSelectedContent;
+ private JTable tblChangesets;
+
+
+ /**
+ * Creates the various models required
+ */
+ protected void buildModel() {
+ DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
+ selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+ model = new ChangesetCacheManagerModel(selectionModel);
+
+ actRemoveFromCacheAction = new RemoveFromCacheAction();
+ actCloseSelectedChangesetsAction = new CloseSelectedChangesetsAction();
+ actDownloadSelectedChangesets = new DownloadSelectedChangesetsAction();
+ actDownloadSelectedContent = new DownloadSelectedChangesetContentAction();
+ }
+
+ /**
+ * builds the toolbar panel in the heading of the dialog
+ *
+ * @return the toolbar panel
+ */
+ protected JPanel buildToolbarPanel() {
+ JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
+
+ SideButton btn = new SideButton(new QueryAction());
+ pnl.add(btn);
+ pnl.add(new SingleChangesetDownloadPanel());
+ pnl.add(new SideButton(new DownloadMyChangesets()));
+
+ return pnl;
+ }
+
+ /**
+ * builds the button panel in the footer of the dialog
+ *
+ * @return the button row pane
+ */
+ protected JPanel buildButtonPanel() {
+ JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
+
+ //-- cancel and close action
+ pnl.add(new SideButton(new CancelAction()));
+
+ //-- help action
+ pnl.add(new SideButton(
+ new ContextSensitiveHelpAction(
+ HelpUtil.ht("/Dialog/ChangesetCacheManager"))
+ )
+ );
+
+ return pnl;
+ }
+
+ /**
+ * Builds the panel with the changeset details
+ *
+ * @return the panel with the changeset details
+ */
+ protected JPanel buildChangesetDetailPanel() {
+ JPanel pnl = new JPanel(new BorderLayout());
+ JTabbedPane tp = new JTabbedPane();
+
+ // -- add the details panel
+ ChangesetDetailPanel pnlChangesetDetail;
+ tp.add(pnlChangesetDetail = new ChangesetDetailPanel());
+ model.addPropertyChangeListener(pnlChangesetDetail);
+
+ // -- add the tags panel
+ ChangesetTagsPanel pnlChangesetTags = new ChangesetTagsPanel();
+ tp.add(pnlChangesetTags);
+ model.addPropertyChangeListener(pnlChangesetTags);
+
+ // -- add the panel for the changeset content
+ ChangesetContentPanel pnlChangesetContent = new ChangesetContentPanel();
+ tp.add(pnlChangesetContent);
+ model.addPropertyChangeListener(pnlChangesetContent);
+
+ tp.setTitleAt(0, tr("Properties"));
+ tp.setToolTipTextAt(0, tr("Display the basic properties of the changeset"));
+ tp.setTitleAt(1, tr("Tags"));
+ tp.setToolTipTextAt(1, tr("Display the the tags of the changeset"));
+ tp.setTitleAt(2, tr("Content"));
+ tp.setToolTipTextAt(2, tr("Display the objects created, updated, and deleted by the changeset"));
+
+ pnl.add(tp, BorderLayout.CENTER);
+ return pnl;
+ }
+
+ /**
+ * builds the content panel of the dialog
+ *
+ * @return the content panel
+ */
+ protected JPanel buildContentPanel() {
+ JPanel pnl = new JPanel(new BorderLayout());
+
+ spContent = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+ spContent.setLeftComponent(buildChangesetTablePanel());
+ spContent.setRightComponent(buildChangesetDetailPanel());
+ spContent.setOneTouchExpandable(true);
+ spContent.setDividerLocation(0.5);
+
+ pnl.add(spContent, BorderLayout.CENTER);
+ return pnl;
+ }
+
+ /**
+ * Builds the table with actions which can be applied to the currently visible changesets
+ * in the changeset table.
+ *
+ * @return
+ */
+ protected JPanel buildChangesetTableActionPanel() {
+ JPanel pnl = new JPanel(new BorderLayout());
+
+ JToolBar tb = new JToolBar(JToolBar.VERTICAL);
+ tb.setFloatable(false);
+
+ // -- remove from cache action
+ model.getSelectionModel().addListSelectionListener(actRemoveFromCacheAction);
+ tb.add(actRemoveFromCacheAction);
+
+ // -- close selected changesets action
+ model.getSelectionModel().addListSelectionListener(actCloseSelectedChangesetsAction);
+ tb.add(actCloseSelectedChangesetsAction);
+
+ // -- download selected changesets
+ model.getSelectionModel().addListSelectionListener(actDownloadSelectedChangesets);
+ tb.add(actDownloadSelectedChangesets);
+
+ // -- download the content of the selected changesets
+ model.getSelectionModel().addListSelectionListener(actDownloadSelectedContent);
+ tb.add(actDownloadSelectedContent);
+
+ pnl.add(tb, BorderLayout.CENTER);
+ return pnl;
+ }
+
+ /**
+ * Builds the panel with the table of changesets
+ *
+ * @return the panel with the table of changesets
+ */
+ protected JPanel buildChangesetTablePanel() {
+ JPanel pnl = new JPanel(new BorderLayout());
+ tblChangesets = new JTable(
+ model,
+ new ChangesetCacheTableColumnModel(),
+ model.getSelectionModel()
+ );
+ tblChangesets.addMouseListener(new ChangesetTablePopupMenuLauncher());
+ tblChangesets.addMouseListener(new DblClickHandler());
+ tblChangesets.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "showDetails");
+ tblChangesets.getActionMap().put("showDetails", new ShowDetailAction());
+ model.getSelectionModel().addListSelectionListener(new ChangesetDetailViewSynchronizer());
+
+ // activate DEL on the table
+ tblChangesets.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,0), "removeFromCache");
+ tblChangesets.getActionMap().put("removeFromCache", actRemoveFromCacheAction);
+
+ pnl.add(new JScrollPane(tblChangesets), BorderLayout.CENTER);
+ pnl.add(buildChangesetTableActionPanel(), BorderLayout.WEST);
+ return pnl;
+ }
+
+
+ protected void build() {
+ setTitle(tr(("Changeset Management Dialog")));
+ setIconImage(ImageProvider.get("dialogs/changeset", "changesetmanager").getImage());
+ Container cp = getContentPane();
+
+ cp.setLayout(new BorderLayout());
+
+ buildModel();
+ cp.add(buildToolbarPanel(), BorderLayout.NORTH);
+ cp.add(buildContentPanel(), BorderLayout.CENTER);
+ cp.add(buildButtonPanel(), BorderLayout.SOUTH);
+
+ // the help context
+ HelpUtil.setHelpContext(getRootPane(), HelpUtil.ht("/Dialog/ChangesetCacheManager"));
+
+ // make the dialog respond to ESC
+ getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0), "cancelAndClose");
+ getRootPane().getActionMap().put("cancelAndClose", new CancelAction());
+
+ // install a window event handler
+ addWindowListener(new WindowEventHandler());
+ }
+
+
+ public ChangesetCacheManager() {
+ build();
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ if (visible) {
+ new WindowGeometry(
+ getClass().getName() + ".geometry",
+ WindowGeometry.centerInWindow(
+ getParent(),
+ new Dimension(1000,600)
+ )
+ ).apply(this);
+ needsSplitPaneAdjustment = true;
+ model.init();
+
+ } else if (!visible && isShowing()){
+ model.tearDown();
+ new WindowGeometry(this).remember(getClass().getName() + ".geometry");
+ }
+ super.setVisible(visible);
+ }
+
+ /**
+ * Handler for window events
+ *
+ */
+ class WindowEventHandler extends WindowAdapter {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ new CancelAction().cancelAndClose();
+ }
+
+ @Override
+ public void windowActivated(WindowEvent arg0) {
+ if (needsSplitPaneAdjustment) {
+ spContent.setDividerLocation(0.5);
+ needsSplitPaneAdjustment = false;
+ }
+ }
+ }
+
+ /**
+ * the cancel / close action
+ */
+ class CancelAction extends AbstractAction {
+ public CancelAction() {
+ putValue(NAME, tr("Close"));
+ putValue(SMALL_ICON, ImageProvider.get("cancel"));
+ putValue(SHORT_DESCRIPTION, tr("Close the dialog"));
+ }
+
+ public void cancelAndClose() {
+ destroyInstance();
+ }
+
+ public void actionPerformed(ActionEvent arg0) {
+ cancelAndClose();
+ }
+ }
+
+ /**
+ * The action to query and download changesets
+ */
+ class QueryAction extends AbstractAction {
+ public QueryAction() {
+ putValue(NAME, tr("Query"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs","search"));
+ putValue(SHORT_DESCRIPTION, tr("Launch the dialog for querying changesets"));
+ }
+
+ public void actionPerformed(ActionEvent evt) {
+ ChangesetQueryDialog dialog = new ChangesetQueryDialog(ChangesetCacheManager.this);
+ dialog.initForUserInput();
+ dialog.setVisible(true);
+ if (dialog.isCanceled())
+ return;
+
+ ChangesetQuery query = dialog.getChangesetQuery();
+ if (query == null) return;
+ ChangesetQueryTask task = new ChangesetQueryTask(ChangesetCacheManager.this, query);
+ ChangesetCacheManager.getInstance().runDownloadTask(task);
+ }
+ }
+
+ /**
+ * Removes the selected changesets from the local changeset cache
+ *
+ */
+ class RemoveFromCacheAction extends AbstractAction implements ListSelectionListener{
+ public RemoveFromCacheAction() {
+ putValue(NAME, tr("Remove from cache"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
+ putValue(SHORT_DESCRIPTION, tr("Remove the selected changesets from the local cache"));
+ updateEnabledState();
+ }
+
+ public void actionPerformed(ActionEvent arg0) {
+ List selected = model.getSelectedChangesets();
+ ChangesetCache.getInstance().remove(selected);
+ }
+
+ protected void updateEnabledState() {
+ setEnabled(model.hasSelectedChangesets());
+ }
+
+ public void valueChanged(ListSelectionEvent e) {
+ updateEnabledState();
+
+ }
+ }
+
+ /**
+ * Closes the selected changesets
+ *
+ */
+ class CloseSelectedChangesetsAction extends AbstractAction implements ListSelectionListener{
+ public CloseSelectedChangesetsAction() {
+ putValue(NAME, tr("Close"));
+ putValue(SMALL_ICON, ImageProvider.get("closechangeset"));
+ putValue(SHORT_DESCRIPTION, tr("Close the selected changesets"));
+ updateEnabledState();
+ }
+
+ public void actionPerformed(ActionEvent arg0) {
+ List selected = model.getSelectedChangesets();
+ Main.worker.submit(new CloseChangesetTask(selected));
+ }
+
+ protected void updateEnabledState() {
+ List selected = model.getSelectedChangesets();
+ JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
+ for (Changeset cs: selected) {
+ if (cs.isOpen()) {
+ if (im.isPartiallyIdentified() && cs.getUser() != null && cs.getUser().getName().equals(im.getUserName())) {
+ setEnabled(true);
+ return;
+ }
+ if (im.isFullyIdentified() && cs.getUser() != null && cs.getUser().getId() == im.getUserId()) {
+ setEnabled(true);
+ return;
+ }
+ }
+ }
+ setEnabled(false);
+ }
+
+ public void valueChanged(ListSelectionEvent e) {
+ updateEnabledState();
+ }
+ }
+
+ /**
+ * Downloads the selected changesets
+ *
+ */
+ class DownloadSelectedChangesetsAction extends AbstractAction implements ListSelectionListener{
+ public DownloadSelectedChangesetsAction() {
+ putValue(NAME, tr("Update changeset"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "updatechangeset"));
+ putValue(SHORT_DESCRIPTION, tr("Updates the selected changesets with current data from the OSM server"));
+ updateEnabledState();
+ }
+
+ public void actionPerformed(ActionEvent arg0) {
+ List selected = model.getSelectedChangesets();
+ ChangesetHeaderDownloadTask task =ChangesetHeaderDownloadTask.buildTaskForChangesets(ChangesetCacheManager.this,selected);
+ ChangesetCacheManager.getInstance().runDownloadTask(task);
+ }
+
+ protected void updateEnabledState() {
+ setEnabled(model.hasSelectedChangesets());
+ }
+
+ public void valueChanged(ListSelectionEvent e) {
+ updateEnabledState();
+ }
+ }
+
+ /**
+ * Downloads the content of selected changesets from the OSM server
+ *
+ */
+ class DownloadSelectedChangesetContentAction extends AbstractAction implements ListSelectionListener{
+ public DownloadSelectedChangesetContentAction() {
+ putValue(NAME, tr("Download changeset content"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "downloadchangesetcontent"));
+ putValue(SHORT_DESCRIPTION, tr("Download the content of the selected changesets from the server"));
+ updateEnabledState();
+ }
+
+ public void actionPerformed(ActionEvent arg0) {
+ ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(ChangesetCacheManager.this,model.getSelectedChangesetIds());
+ ChangesetCacheManager.getInstance().runDownloadTask(task);
+ }
+
+ protected void updateEnabledState() {
+ setEnabled(model.hasSelectedChangesets());
+ }
+
+ public void valueChanged(ListSelectionEvent e) {
+ updateEnabledState();
+ }
+ }
+
+ class ShowDetailAction extends AbstractAction {
+
+ public void showDetails() {
+ List selected = model.getSelectedChangesets();
+ if (selected.size() != 1) return;
+ model.setChangesetInDetailView(selected.get(0));
+ }
+
+ public void actionPerformed(ActionEvent arg0) {
+ showDetails();
+ }
+ }
+
+ class DownloadMyChangesets extends AbstractAction {
+ public DownloadMyChangesets() {
+ putValue(NAME, tr("My changesets"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "downloadchangeset"));
+ putValue(SHORT_DESCRIPTION, tr("Download my changesets from the OSM server (max. 100 changesets)"));
+ }
+
+ protected void alertAnonymousUser() {
+ HelpAwareOptionPane.showOptionDialog(
+ ChangesetCacheManager.this,
+ tr("JOSM is currently running with an anonymous user. It can't download
"
+ + "your changesets from the OSM server unless you enter your OSM user name
"
+ + "in the JOSM preferences."
+ ),
+ tr("Warning"),
+ JOptionPane.WARNING_MESSAGE,
+ HelpUtil.ht("/Dialog/ChangesetCacheManager#CanDownloadMyChangesets")
+ );
+ }
+
+ public void actionPerformed(ActionEvent arg0) {
+ JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
+ im.initFromPreferences();
+ if (im.isAnonymous()) {
+ alertAnonymousUser();
+ return;
+ }
+ ChangesetQuery query = new ChangesetQuery();
+ if (im.isFullyIdentified()) {
+ query = query.forUser(im.getUserId());
+ } else {
+ query = query.forUser(im.getUserName());
+ }
+ ChangesetQueryTask task = new ChangesetQueryTask(ChangesetCacheManager.this, query);
+ ChangesetCacheManager.getInstance().runDownloadTask(task);
+ }
+ }
+
+ class DblClickHandler extends MouseAdapter {
+ @Override
+ public void mouseClicked(MouseEvent evt) {
+ if (! SwingUtilities.isLeftMouseButton(evt) || evt.getClickCount()<2)
+ return;
+ new ShowDetailAction().showDetails();
+ }
+ }
+
+ class ChangesetTablePopupMenuLauncher extends PopupMenuLauncher {
+ ChangesetTablePopupMenu menu = new ChangesetTablePopupMenu();
+ @Override
+ public void launch(MouseEvent evt) {
+ if (! model.hasSelectedChangesets()) {
+ int row = tblChangesets.rowAtPoint(evt.getPoint());
+ if (row >= 0) {
+ model.setSelectedByIdx(row);
+ }
+ }
+ menu.show(tblChangesets, evt.getPoint().x, evt.getPoint().y);
+ }
+ }
+
+ class ChangesetTablePopupMenu extends JPopupMenu {
+ public ChangesetTablePopupMenu() {
+ add(actRemoveFromCacheAction);
+ add(actCloseSelectedChangesetsAction);
+ add(actDownloadSelectedChangesets);
+ add(actDownloadSelectedContent);
+ }
+ }
+
+ class ChangesetDetailViewSynchronizer implements ListSelectionListener {
+ public void valueChanged(ListSelectionEvent e) {
+ List selected = model.getSelectedChangesets();
+ if (selected.size() == 1) {
+ model.setChangesetInDetailView(selected.get(0));
+ } else {
+ model.setChangesetInDetailView(null);
+ }
+ }
+ }
+
+ /**
+ * Selects the changesets in changests
, provided the
+ * respective changesets are already present in the local changeset cache.
+ *
+ * @param ids the collection of changesets. If null, the selection is cleared.
+ */
+ public void setSelectedChangesets(Collection changesets) {
+ model.setSelectedChangesets(changesets);
+ int idx = model.getSelectionModel().getMinSelectionIndex();
+ if (idx < 0) return;
+ tblChangesets.scrollRectToVisible(tblChangesets.getCellRect(idx, 0, true));
+ repaint();
+ }
+
+ /**
+ * Selects the changesets with the ids in ids
, provided the
+ * respective changesets are already present in the local changeset cache.
+ *
+ * @param ids the collection of ids. If null, the selection is cleared.
+ */
+ public void setSelectedChangesetsById(Collection ids) {
+ if (ids == null) {
+ setSelectedChangesets(null);
+ return;
+ }
+ Set toSelect = new HashSet();
+ ChangesetCache cc = ChangesetCache.getInstance();
+ for (int id: ids) {
+ if (cc.contains(id)) {
+ toSelect.add(cc.get(id));
+ }
+ }
+ setSelectedChangesets(toSelect);
+ }
+
+ public void runDownloadTask(final ChangesetDownloadTask task) {
+ Main.worker.submit(task);
+ Runnable r = new Runnable() {
+ public void run() {
+ if (task.isCanceled() || task.isFailed()) return;
+ setSelectedChangesets(task.getDownloadedChangesets());
+ }
+ };
+ Main.worker.submit(r);
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheManagerModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheManagerModel.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheManagerModel.java (revision 2689)
@@ -0,0 +1,196 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.table.AbstractTableModel;
+
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.ChangesetCache;
+import org.openstreetmap.josm.data.osm.ChangesetCacheEvent;
+import org.openstreetmap.josm.data.osm.ChangesetCacheListener;
+
+/**
+ * This is the model for the changeset cache manager dialog.
+ *
+ */
+public class ChangesetCacheManagerModel extends AbstractTableModel implements ChangesetCacheListener{
+
+ /** the name of the property for the currently selected changeset in the detail view */
+ public final static String CHANGESET_IN_DETAIL_VIEW_PROP = ChangesetCacheManagerModel.class.getName() + ".changesetInDetailView";
+
+ private final ArrayList data = new ArrayList();
+ private DefaultListSelectionModel selectionModel;
+ private Changeset changesetInDetailView;
+ private final PropertyChangeSupport support = new PropertyChangeSupport(this);
+
+ public ChangesetCacheManagerModel(DefaultListSelectionModel selectionModel) {
+ this.selectionModel = selectionModel;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ support.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ support.removePropertyChangeListener(listener);
+ }
+
+ /**
+ * Sets the changeset currently displayed in the detail view. Fires a property change event
+ * for the property {@see #CHANGESET_IN_DETAIL_VIEW_PROP} if necessary.
+ *
+ * @param cs the changeset currently displayed in the detail view.
+ */
+ public void setChangesetInDetailView(Changeset cs) {
+ Changeset oldValue = changesetInDetailView;
+ changesetInDetailView = cs;
+ if (oldValue != cs) {
+ support.firePropertyChange(CHANGESET_IN_DETAIL_VIEW_PROP, oldValue, changesetInDetailView);
+ }
+ }
+
+ /**
+ * Replies true if there is at least one selected changeset
+ *
+ * @return true if there is at least one selected changeset
+ */
+ public boolean hasSelectedChangesets() {
+ return selectionModel.getMinSelectionIndex() >= 0;
+ }
+
+ /**
+ * Replies the list of selected changesets
+ *
+ * @return the list of selected changesets
+ */
+ public List getSelectedChangesets() {
+ ArrayList ret = new ArrayList();
+ for (int i =0; i< data.size();i++) {
+ Changeset cs = data.get(i);
+ if (selectionModel.isSelectedIndex(i)) {
+ ret.add(cs);
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Replies a set of ids of the selected changesets
+ *
+ * @return a set of ids of the selected changesets
+ */
+ public Set getSelectedChangesetIds() {
+ Set ret = new HashSet();
+ for (Changeset cs: getSelectedChangesets()) {
+ ret.add(cs.getId());
+ }
+ return ret;
+ }
+
+ /**
+ * Selects the changesets in selected
.
+ *
+ * @param selected the collection of changesets to select. Ignored if empty.
+ */
+ public void setSelectedChangesets(Collection selected) {
+ selectionModel.clearSelection();
+ if (selected == null || selected.isEmpty())
+ return;
+ for (Changeset cs: selected) {
+ int idx = data.indexOf(cs);
+ if (idx >= 0) {
+ selectionModel.addSelectionInterval(idx,idx);
+ }
+ }
+ }
+
+ /**
+ * Selects the changeset displayed at row row
+ *
+ * @param row the row. Ignored if < 0 or >= {@see #getRowCount()}
+ */
+ public void setSelectedByIdx(int row) {
+ if (row < 0 || row >= getRowCount()) return;
+ selectionModel.setSelectionInterval(row, row);
+ }
+
+ public int getColumnCount() {
+ return 5;
+ }
+
+ public int getRowCount() {
+ return data.size();
+ }
+
+ public Object getValueAt(int row, int column) {
+ return data.get(row);
+ }
+
+ public void init() {
+ ChangesetCache cc = ChangesetCache.getInstance();
+ List selected = getSelectedChangesets();
+ data.clear();
+ data.addAll(cc.getChangesets());
+ sort();
+ fireTableDataChanged();
+ setSelectedChangesets(selected);
+
+ cc.addChangesetCacheListener(this);
+ }
+
+ public void tearDown() {
+ ChangesetCache.getInstance().removeChangesetCacheListener(this);
+ }
+
+ public DefaultListSelectionModel getSelectionModel() {
+ return selectionModel;
+ }
+
+ protected void sort() {
+ Collections.sort(
+ this.data,
+ new Comparator() {
+ public int compare(Changeset o1, Changeset o2) {
+ if (o1.getId() < o2.getId()) return 1;
+ if (o1.getId() == o2.getId()) return 0;
+ return -1;
+ }
+ }
+ );
+ }
+
+ /* ------------------------------------------------------------------------------ */
+ /* interface ChangesetCacheListener */
+ /* ------------------------------------------------------------------------------ */
+ public void changesetCacheUpdated(ChangesetCacheEvent event) {
+ List selected = getSelectedChangesets();
+ for (Changeset cs: event.getAddedChangesets()) {
+ data.add(cs);
+ }
+ for (Changeset cs: event.getRemovedChangesets()) {
+ data.remove(cs);
+ }
+ for (Changeset cs: event.getUpdatedChangesets()) {
+ int idx = data.indexOf(cs);
+ if (idx >= 0) {
+ Changeset mine = data.get(idx);
+ if (mine != cs) {
+ mine.mergeFrom(cs);
+ }
+ }
+ }
+ sort();
+ fireTableDataChanged();
+ setSelectedChangesets(selected);
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheSortSpecification.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheSortSpecification.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheSortSpecification.java (revision 2689)
@@ -0,0 +1,20 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+public class ChangesetCacheSortSpecification {
+ static public enum SortCriterium {
+ ID,
+ COMMENT,
+ OPEN,
+ USER,
+ CREATED_AT,
+ CLOSED_AT
+ }
+
+ static public enum SortDirection {
+ ASCENDING,
+ DESCENDING
+ }
+
+
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheTableCellRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheTableCellRenderer.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheTableCellRenderer.java (revision 2689)
@@ -0,0 +1,105 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trc;
+
+import java.awt.Component;
+import java.awt.Font;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.UIManager;
+import javax.swing.table.TableCellRenderer;
+
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.User;
+
+public class ChangesetCacheTableCellRenderer extends JLabel implements TableCellRenderer{
+
+ public ChangesetCacheTableCellRenderer() {
+ setOpaque(true);
+ }
+
+ protected void reset() {
+ setBackground(UIManager.getColor("Table.background"));
+ setForeground(UIManager.getColor("Table.foreground"));
+ setFont(UIManager.getFont("Table.font"));
+ setToolTipText("");
+ }
+
+ protected void renderColors(boolean isSelected) {
+ if (isSelected) {
+ setBackground(UIManager.getColor("Table.selectionBackground"));
+ setForeground(UIManager.getColor("Table.selectionForeground"));
+ } else {
+ setBackground(UIManager.getColor("Table.background"));
+ setForeground(UIManager.getColor("Table.foreground"));
+ }
+ }
+
+ protected void renderId(Changeset cs) {
+ setText(Integer.toString(cs.getId()));
+ setToolTipText("");
+ }
+
+ protected void renderUploadComment(Changeset cs) {
+ String comment = cs.get("comment");
+ if (comment == null || comment.trim().equals("")) {
+ setText(trc("changeset.upload-comment", "empty"));
+ setFont(UIManager.getFont("Table.font").deriveFont(Font.ITALIC));
+ } else {
+ setText(comment);
+ setToolTipText(comment);
+ setFont(UIManager.getFont("Table.font"));
+ }
+ }
+
+ protected void renderOpen(Changeset cs) {
+ if (cs.isOpen()) {
+ setText(trc("changeset.open", "Open"));
+ } else {
+ setText(trc("changeset.open", "Closed"));
+ }
+ setToolTipText("");
+ }
+
+ protected void renderUser(Changeset cs) {
+ User user = cs.getUser();
+ if (user == null || user.getName().trim().equals("")) {
+ setFont(UIManager.getFont("Table.font").deriveFont(Font.ITALIC));
+ setText(tr("anonymous"));
+ } else {
+ setFont(UIManager.getFont("Table.font"));
+ setText(user.getName());
+ setToolTipText(user.getName());
+ }
+ }
+
+ protected void renderDate(Date d) {
+ if (d == null) {
+ setText("");
+ } else {
+ setText(new SimpleDateFormat().format(d));
+ }
+ setToolTipText("");
+ }
+
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
+ int row, int column) {
+ reset();
+ renderColors(isSelected);
+ Changeset cs = (Changeset)value;
+ switch(column) {
+ case 0: /* id */ renderId(cs); break;
+ case 1: /* upload comment */ renderUploadComment(cs); break;
+ case 2: /* open/closed */ renderOpen(cs); break;
+ case 3: /* user */ renderUser(cs); break;
+ case 4: /* created at */ renderDate(cs.getCreatedAt()); break;
+ case 5: /* closed at */ renderDate(cs.getClosedAt()); break;
+ }
+ return this;
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheTableColumnModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheTableColumnModel.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheTableColumnModel.java (revision 2689)
@@ -0,0 +1,72 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableColumn;
+
+/**
+ * The column model for the changeset table
+ *
+ */
+public class ChangesetCacheTableColumnModel extends DefaultTableColumnModel {
+
+ protected void createColumns() {
+ TableColumn col = null;
+ ChangesetCacheTableCellRenderer renderer = new ChangesetCacheTableCellRenderer();
+
+ // column 0 - Id
+ col = new TableColumn(0);
+ col.setHeaderValue("ID");
+ col.setResizable(true);
+ col.setWidth(20);
+ col.setPreferredWidth(20);
+ col.setCellRenderer(renderer);
+ addColumn(col);
+
+ // column 1 - Upload comment
+ col = new TableColumn(1);
+ col.setHeaderValue(tr("Comment"));
+ col.setResizable(true);
+ col.setPreferredWidth(200);
+ col.setCellRenderer(renderer);
+ addColumn(col);
+
+ // column 2 - Open
+ col = new TableColumn(2);
+ col.setHeaderValue(tr("Open"));
+ col.setResizable(true);
+ col.setPreferredWidth(50);
+ col.setCellRenderer(renderer);
+ addColumn(col);
+
+ // column 3 - User
+ col = new TableColumn(3);
+ col.setHeaderValue(tr("User"));
+ col.setResizable(true);
+ col.setPreferredWidth(50);
+ col.setCellRenderer(renderer);
+ addColumn(col);
+
+ // column 4 - Created at
+ col = new TableColumn(4);
+ col.setHeaderValue(tr("Created at"));
+ col.setResizable(true);
+ col.setPreferredWidth(100);
+ col.setCellRenderer(renderer);
+ addColumn(col);
+
+ // column 5 - Closed at
+ col = new TableColumn(4);
+ col.setHeaderValue(tr("Closed at"));
+ col.setResizable(true);
+ col.setPreferredWidth(100);
+ col.setCellRenderer(renderer);
+ addColumn(col);
+ }
+
+ public ChangesetCacheTableColumnModel() {
+ createColumns();
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentDownloadTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentDownloadTask.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentDownloadTask.java (revision 2689)
@@ -0,0 +1,216 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.ChangesetCache;
+import org.openstreetmap.josm.data.osm.ChangesetDataSet;
+import org.openstreetmap.josm.gui.ExceptionDialogUtil;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.io.OsmServerChangesetReader;
+import org.openstreetmap.josm.io.OsmTransferCancelledException;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.xml.sax.SAXException;
+
+/**
+ * This is an asynchronous task for downloading the changeset content of a collection of
+ * changesets.
+ *
+ */
+public class ChangesetContentDownloadTask extends PleaseWaitRunnable implements ChangesetDownloadTask{
+
+ /** the list of changeset ids to download */
+ private final List toDownload = new ArrayList();
+ /** true if the task was canceled */
+ private boolean canceled;
+ /** keeps the last exception thrown in the task, if any */
+ private Exception lastException;
+ /** the reader object used to read changesets from the API */
+ private OsmServerChangesetReader reader;
+ /** the set of downloaded changesets */
+ private Set downloadedChangesets;
+
+ /**
+ * Initialize the task with a collection of changeset ids to download
+ *
+ * @param ids the collection of ids. May be null.
+ */
+ protected void init(Collection ids) {
+ if (ids == null) {
+ ids = Collections.emptyList();
+ }
+ for (Integer id: ids) {
+ if (id == null || id <= 0) {
+ continue;
+ }
+ toDownload.add(id);
+ }
+ downloadedChangesets = new HashSet();
+ }
+
+ /**
+ * Creates a download task for a single changeset
+ *
+ * @param changesetId the changeset id. >0 required.
+ * @throws IllegalArgumentException thrown if changesetId <= 0
+ */
+ public ChangesetContentDownloadTask(int changesetId) throws IllegalArgumentException{
+ super(tr("Downloading changeset content"), false /* don't ignore exceptions */);
+ if (changesetId <= 0)
+ throw new IllegalArgumentException(tr("Expected integer value > 0 for parameter ''{0}'', got ''{1}''", "changesetId", changesetId));
+ init(Collections.singleton(changesetId));
+ }
+
+ /**
+ * Creates a download task for a collection of changesets. null values and id <=0 in
+ * the collection are sillently discarded.
+ *
+ * @param changesetIds the changeset ids. Empty collection assumed, if null.
+ */
+ public ChangesetContentDownloadTask(Collection changesetIds) {
+ super(tr("Downloading changeset content"), false /* don't ignore exceptions */);
+ init(changesetIds);
+ }
+
+ /**
+ * Creates a download task for a single changeset
+ *
+ * @param parent the parent component for the {@see PleaseWaitDialog}. Must not be null.
+ * @param changesetId the changeset id. >0 required.
+ * @throws IllegalArgumentException thrown if changesetId <= 0
+ * @throws IllegalArgumentException thrown if parent is null
+ */
+ public ChangesetContentDownloadTask(Component parent, int changesetId) throws IllegalArgumentException{
+ super(parent, tr("Downloading changeset content"), false /* don't ignore exceptions */);
+ if (changesetId <= 0)
+ throw new IllegalArgumentException(tr("Expected integer value > 0 for parameter ''{0}'', got ''{1}''", "changesetId", changesetId));
+ init(Collections.singleton(changesetId));
+ }
+
+ /**
+ * Creates a download task for a collection of changesets. null values and id <=0 in
+ * the collection are sillently discarded.
+ *
+ * @param parent the parent component for the {@see PleaseWaitDialog}. Must not be null.
+ * @param changesetIds the changeset ids. Empty collection assumed, if null.
+ * @throws IllegalArgumentException thrown if parent is null
+ */
+ public ChangesetContentDownloadTask(Component parent, Collection changesetIds) throws IllegalArgumentException {
+ super(parent, tr("Downloading changeset content"), false /* don't ignore exceptions */);
+ init(changesetIds);
+ }
+
+ /**
+ * Replies true if the local {@see ChangesetCache} already includes the changeset with
+ * id changesetId
.
+ *
+ * @param changsetId the changeset id
+ * @return true if the local {@see ChangesetCache} already includes the changeset with
+ * id changesetId
+ */
+ protected boolean isAvailableLocally(int changsetId) {
+ return ChangesetCache.getInstance().get(changsetId) != null;
+ }
+
+ /**
+ * Downloads the changeset with id changesetId
(only "header"
+ * information, no content)
+ *
+ * @param changesetId the changeset id
+ * @throws OsmTransferException thrown if something went wrong
+ */
+ protected void downloadChangeset(int changesetId) throws OsmTransferException {
+ synchronized(this) {
+ reader = new OsmServerChangesetReader();
+ }
+ Changeset cs = reader.readChangeset(changesetId, getProgressMonitor().createSubTaskMonitor(0, false));
+ synchronized(this) {
+ reader = null;
+ }
+ ChangesetCache.getInstance().update(cs);
+ }
+
+ @Override
+ protected void cancel() {
+ canceled = true;
+ synchronized (this) {
+ if (reader != null) {
+ reader.cancel();
+ }
+ }
+ }
+
+ @Override
+ protected void finish() {
+ if (canceled) return;
+ if (lastException != null) {
+ ExceptionDialogUtil.explainException(lastException);
+ }
+ }
+
+ @Override
+ protected void realRun() throws SAXException, IOException, OsmTransferException {
+ try {
+ getProgressMonitor().setTicksCount(toDownload.size());
+ int i=0;
+ for (int id: toDownload) {
+ i++;
+ if (!isAvailableLocally(id)) {
+ getProgressMonitor().setCustomText(tr("({0}/{1}) Downloading changeset {2}...", i, toDownload.size(), id));
+ downloadChangeset(id);
+ }
+ if (canceled) return;
+ synchronized(this) {
+ reader = new OsmServerChangesetReader();
+ }
+ getProgressMonitor().setCustomText(tr("({0}/{1}) Downloading content for changeset {2}...", i, toDownload.size(), id));
+ ChangesetDataSet ds = reader.downloadChangeset(id, getProgressMonitor().createSubTaskMonitor(0, false));
+ synchronized(this) {
+ reader = null;
+ }
+ Changeset cs = ChangesetCache.getInstance().get(id);
+ cs.setContent(ds);
+ ChangesetCache.getInstance().update(cs);
+ downloadedChangesets.add(cs);
+ getProgressMonitor().worked(1);
+ }
+ } catch(OsmTransferCancelledException e) {
+ // the download was cancelled by the user. This exception is caught if the
+ // user canceled the authentication dialog.
+ //
+ canceled = true;
+ return;
+ } catch(OsmTransferException e) {
+ if (canceled)
+ return;
+ lastException = e;
+ } catch(RuntimeException e) {
+ throw e;
+ }
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /* interface ChangesetDownloadTask */
+ /* ------------------------------------------------------------------------------- */
+ public Set getDownloadedChangesets() {
+ return downloadedChangesets;
+ }
+
+ public boolean isCanceled() {
+ return canceled;
+ }
+
+ public boolean isFailed() {
+ return lastException != null;
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentPanel.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentPanel.java (revision 2689)
@@ -0,0 +1,477 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.JButton;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.JTable;
+import javax.swing.JToolBar;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.AutoScaleAction;
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.history.History;
+import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
+import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.JMultilineLabel;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.MapView.EditLayerChangeListener;
+import org.openstreetmap.josm.gui.help.HelpUtil;
+import org.openstreetmap.josm.gui.history.HistoryBrowserDialogManager;
+import org.openstreetmap.josm.gui.history.HistoryLoadTask;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.tools.BugReportExceptionHandler;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * The panel which displays the content of a changeset in a scollable table.
+ *
+ * It listens to property change events for {@see ChangesetCacheManagerModel#CHANGESET_IN_DETAIL_VIEW_PROP}
+ * and updates its view accordingly.
+ *
+ */
+public class ChangesetContentPanel extends JPanel implements PropertyChangeListener{
+
+ private ChangesetContentTableModel model;
+ private Changeset currentChangeset;
+ private JTable tblContent;
+
+ private DonwloadChangesetContentAction actDownloadContentAction;
+ private ShowHistoryAction actShowHistory;
+ private SelectInCurrentLayerAction actSelectInCurrentLayerAction;
+ private ZoomInCurrentLayerAction actZoomInCurrentLayerAction;
+
+ private HeaderPanel pnlHeader;
+
+ protected void buildModels() {
+ DefaultListSelectionModel selectionModel =new DefaultListSelectionModel();
+ model = new ChangesetContentTableModel(selectionModel);
+ actDownloadContentAction = new DonwloadChangesetContentAction();
+ actDownloadContentAction.initProperties(currentChangeset);
+ actShowHistory = new ShowHistoryAction();
+ model.getSelectionModel().addListSelectionListener(actShowHistory);
+
+ actSelectInCurrentLayerAction = new SelectInCurrentLayerAction();
+ model.getSelectionModel().addListSelectionListener(actSelectInCurrentLayerAction);
+ MapView.addEditLayerChangeListener(actSelectInCurrentLayerAction);
+
+ actZoomInCurrentLayerAction = new ZoomInCurrentLayerAction();
+ model.getSelectionModel().addListSelectionListener(actZoomInCurrentLayerAction);
+ MapView.addEditLayerChangeListener(actZoomInCurrentLayerAction);
+
+ addComponentListener(
+ new ComponentAdapter() {
+ @Override
+ public void componentHidden(ComponentEvent e) {
+ // make sure the listener is unregistered when the panel becomes
+ // invisible
+ MapView.removeEditLayerChangeListener(actSelectInCurrentLayerAction);
+ MapView.removeEditLayerChangeListener(actZoomInCurrentLayerAction);
+ }
+ }
+ );
+ }
+
+ protected JPanel buildContentPanel() {
+ JPanel pnl = new JPanel(new BorderLayout());
+ tblContent = new JTable(
+ model,
+ new ChangesetContentTableColumnModel(),
+ model.getSelectionModel()
+ );
+ tblContent.addMouseListener(new ChangesetContentTablePopupMenuLauncher());
+ pnl.add(new JScrollPane(tblContent), BorderLayout.CENTER);
+ return pnl;
+ }
+
+ protected JPanel buildActionButtonPanel() {
+ JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ JToolBar tb = new JToolBar(JToolBar.VERTICAL);
+ tb.setFloatable(false);
+
+ tb.add(actDownloadContentAction);
+ tb.add(actShowHistory);
+ tb.add(actSelectInCurrentLayerAction);
+ tb.add(actZoomInCurrentLayerAction);
+
+ pnl.add(tb);
+ return pnl;
+ }
+
+ protected void build() {
+ setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+ setLayout(new BorderLayout());
+ buildModels();
+
+ add(pnlHeader = new HeaderPanel(), BorderLayout.NORTH);
+ add(buildActionButtonPanel(), BorderLayout.WEST);
+ add(buildContentPanel(), BorderLayout.CENTER);
+
+ }
+
+ public ChangesetContentPanel() {
+ build();
+ }
+
+ public ChangesetContentTableModel getModel() {
+ return model;
+ }
+
+ protected void setCurrentChangeset(Changeset cs) {
+ currentChangeset = cs;
+ if (cs == null) {
+ model.populate(null);
+ } else {
+ model.populate(cs.getContent());
+ }
+ actDownloadContentAction.initProperties(cs);
+ pnlHeader.setChangeset(cs);
+ }
+
+ /* ---------------------------------------------------------------------------- */
+ /* interface PropertyChangeListener */
+ /* ---------------------------------------------------------------------------- */
+ public void propertyChange(PropertyChangeEvent evt) {
+ if(!evt.getPropertyName().equals(ChangesetCacheManagerModel.CHANGESET_IN_DETAIL_VIEW_PROP))
+ return;
+ Changeset cs = (Changeset)evt.getNewValue();
+ setCurrentChangeset(cs);
+ }
+
+
+ /**
+ * Downloads/Updates the content of the changeset
+ *
+ */
+ class DonwloadChangesetContentAction extends AbstractAction{
+ public DonwloadChangesetContentAction() {
+ putValue(NAME, tr("Download content"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","downloadchangesetcontent"));
+ putValue(SHORT_DESCRIPTION, tr("Download the changeset content from the OSM server"));
+ }
+
+ public void actionPerformed(ActionEvent evt) {
+ if (currentChangeset == null) return;
+ ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(ChangesetContentPanel.this,currentChangeset.getId());
+ ChangesetCacheManager.getInstance().runDownloadTask(task);
+ }
+
+ public void initProperties(Changeset cs) {
+ if (cs == null) {
+ setEnabled(false);
+ return;
+ } else {
+ setEnabled(true);
+ }
+ if (cs.getContent() == null) {
+ putValue(NAME, tr("Download content"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","downloadchangesetcontent"));
+ putValue(SHORT_DESCRIPTION, tr("Download the changeset content from the OSM server"));
+ } else {
+ putValue(NAME, tr("Update content"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","updatechangesetcontent"));
+ putValue(SHORT_DESCRIPTION, tr("Update the changeset content from the OSM server"));
+ }
+ }
+ }
+
+ class ChangesetContentTablePopupMenuLauncher extends MouseAdapter {
+ ChangesetContentTablePopupMenu menu = new ChangesetContentTablePopupMenu();
+
+ protected void launch(MouseEvent evt) {
+ if (! evt.isPopupTrigger()) return;
+ if (! model.hasSelectedPrimitives()) {
+ int row = tblContent.rowAtPoint(evt.getPoint());
+ if (row >= 0) {
+ model.setSelectedByIdx(row);
+ }
+ }
+ menu.show(tblContent, evt.getPoint().x, evt.getPoint().y);
+ }
+
+ @Override
+ public void mouseClicked(MouseEvent evt) {
+ launch(evt);
+ }
+
+ @Override
+ public void mousePressed(MouseEvent evt) {
+ launch(evt);
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent evt) {
+ launch(evt);
+ }
+ }
+
+ class ChangesetContentTablePopupMenu extends JPopupMenu {
+ public ChangesetContentTablePopupMenu() {
+ add(actDownloadContentAction);
+ add(actShowHistory);
+ add(new JSeparator());
+ add(actSelectInCurrentLayerAction);
+ add(actZoomInCurrentLayerAction);
+ }
+ }
+
+ class ShowHistoryAction extends AbstractAction implements ListSelectionListener{
+ public ShowHistoryAction() {
+ putValue(NAME, tr("Show history"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs", "history"));
+ putValue(SHORT_DESCRIPTION, tr("Download and show the history of the selected primitives"));
+ updateEnabledState();
+ }
+
+ protected List filterPrimitivesWithUnloadedHistory(Collection primitives) {
+ ArrayList ret = new ArrayList(primitives.size());
+ for (HistoryOsmPrimitive p: primitives) {
+ if (HistoryDataSet.getInstance().getHistory(p.getPrimitiveId()) == null) {
+ ret.add(p);
+ }
+ }
+ return ret;
+ }
+
+ public void showHistory(final Collection primitives) {
+ List toLoad = filterPrimitivesWithUnloadedHistory(primitives);
+ if (!toLoad.isEmpty()) {
+ HistoryLoadTask task = new HistoryLoadTask(ChangesetContentPanel.this);
+ for (HistoryOsmPrimitive p: toLoad) {
+ task.add(p);
+ }
+ Main.worker.submit(task);
+ }
+
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ for (HistoryOsmPrimitive p : primitives) {
+ History h = HistoryDataSet.getInstance().getHistory(p.getPrimitiveId());
+ if (h == null) {
+ continue;
+ }
+ HistoryBrowserDialogManager.getInstance().show(h);
+ }
+ } catch (final Exception e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ BugReportExceptionHandler.handleException(e);
+ }
+ });
+ }
+
+ }
+ };
+ Main.worker.submit(r);
+ }
+
+ protected void updateEnabledState() {
+ setEnabled(model.hasSelectedPrimitives());
+ }
+
+ public void actionPerformed(ActionEvent arg0) {
+ Set selected = model.getSelectedPrimitives();
+ if (selected.isEmpty()) return;
+ showHistory(selected);
+ }
+
+ public void valueChanged(ListSelectionEvent e) {
+ updateEnabledState();
+ }
+ }
+
+ class SelectInCurrentLayerAction extends AbstractAction implements ListSelectionListener, EditLayerChangeListener{
+
+ public SelectInCurrentLayerAction() {
+ putValue(NAME, tr("Select in layer"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs", "select"));
+ putValue(SHORT_DESCRIPTION, tr("Select the corresponding primitives in the current data layer"));
+ updateEnabledState();
+ }
+
+ protected void alertNoPrimitivesToSelect(Collection primitives) {
+ HelpAwareOptionPane.showOptionDialog(
+ ChangesetContentPanel.this,
+ trn("The selected object isn''t available in the current
"
+ + "edit layer ''{0}''.",
+ "None of the selected objects is available in the current
"
+ + "edit layer ''{0}''.",
+ primitives.size(),
+ Main.main.getEditLayer().getName()
+ ),
+ tr("Nothing to select"),
+ JOptionPane.WARNING_MESSAGE,
+ HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToSelectInLayer")
+ );
+ }
+
+ public void actionPerformed(ActionEvent arg0) {
+ if (!isEnabled())
+ return;
+ if (Main.main == null || Main.main.getEditLayer() == null) return;
+ OsmDataLayer layer = Main.main.getEditLayer();
+ Set selected = model.getSelectedPrimitives();
+ Set target = new HashSet();
+ for (HistoryOsmPrimitive p : model.getSelectedPrimitives()) {
+ OsmPrimitive op = layer.data.getPrimitiveById(p.getPrimitiveId());
+ if (op != null) {
+ target.add(op);
+ }
+ }
+ if (target.isEmpty()) {
+ alertNoPrimitivesToSelect(selected);
+ return;
+ }
+ layer.data.setSelected(target);
+ }
+
+ public void updateEnabledState() {
+ if (Main.main == null || Main.main.getEditLayer() == null){
+ setEnabled(false);
+ return;
+ }
+ setEnabled(model.hasSelectedPrimitives());
+ }
+
+ public void valueChanged(ListSelectionEvent e) {
+ updateEnabledState();
+ }
+
+ public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
+ updateEnabledState();
+ }
+ }
+
+ class ZoomInCurrentLayerAction extends AbstractAction implements ListSelectionListener, EditLayerChangeListener{
+
+ public ZoomInCurrentLayerAction() {
+ putValue(NAME, tr("Zoom to in layer"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale", "selection"));
+ putValue(SHORT_DESCRIPTION, tr("Zoom to the corresponding primitives in the current data layer"));
+ updateEnabledState();
+ }
+
+ protected void alertNoPrimitivesToZoomTo(Collection primitives) {
+ HelpAwareOptionPane.showOptionDialog(
+ ChangesetContentPanel.this,
+ trn("The selected object isn''t available in the current
"
+ + "edit layer ''{0}''.",
+ "None of the selected objects is available in the current
"
+ + "edit layer ''{0}''.",
+ primitives.size(),
+ Main.main.getEditLayer().getName()
+ ),
+ tr("Nothing to zoom to"),
+ JOptionPane.WARNING_MESSAGE,
+ HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToZoomTo")
+ );
+ }
+
+ public void actionPerformed(ActionEvent arg0) {
+ if (!isEnabled())
+ return;
+ if (Main.main == null || Main.main.getEditLayer() == null) return;
+ OsmDataLayer layer = Main.main.getEditLayer();
+ Set selected = model.getSelectedPrimitives();
+ Set target = new HashSet();
+ for (HistoryOsmPrimitive p : model.getSelectedPrimitives()) {
+ OsmPrimitive op = layer.data.getPrimitiveById(p.getPrimitiveId());
+ if (op != null) {
+ target.add(op);
+ }
+ }
+ if (target.isEmpty()) {
+ alertNoPrimitivesToZoomTo(selected);
+ return;
+ }
+ layer.data.setSelected(target);
+ AutoScaleAction.zoomToSelection();
+ }
+
+ public void updateEnabledState() {
+ if (Main.main == null || Main.main.getEditLayer() == null){
+ setEnabled(false);
+ return;
+ }
+ setEnabled(model.hasSelectedPrimitives());
+ }
+
+ public void valueChanged(ListSelectionEvent e) {
+ updateEnabledState();
+ }
+
+ public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
+ updateEnabledState();
+ }
+ }
+
+ static private class HeaderPanel extends JPanel {
+
+ private JMultilineLabel lblMessage;
+ private Changeset current;
+ private DownloadAction actDownload;
+
+ protected void build() {
+ setLayout(new FlowLayout(FlowLayout.LEFT));
+ lblMessage = new JMultilineLabel(
+ tr("The content of this changeset is not downloaded yet.")
+ );
+ add(lblMessage);
+ add(new JButton(actDownload = new DownloadAction()));
+
+ }
+
+ public HeaderPanel() {
+ build();
+ }
+
+ public void setChangeset(Changeset cs) {
+ setVisible(cs != null && cs.getContent() == null);
+ this.current = cs;
+ }
+
+ private class DownloadAction extends AbstractAction {
+ public DownloadAction() {
+ putValue(NAME, tr("Download now"));
+ putValue(SHORT_DESCRIPTION, tr("Download the changeset content"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "downloadchangesetcontent"));
+ }
+
+ public void actionPerformed(ActionEvent evt) {
+ if (current == null) return;
+ ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(HeaderPanel.this, current.getId());
+ ChangesetCacheManager.getInstance().runDownloadTask(task);
+ }
+ }
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableCellRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableCellRenderer.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableCellRenderer.java (revision 2689)
@@ -0,0 +1,76 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.UIManager;
+import javax.swing.table.TableCellRenderer;
+
+import org.openstreetmap.josm.data.osm.ChangesetDataSet.ChangesetModificationType;
+import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
+
+/**
+ * The table cell renderer used in the changeset content table, except for the "name"
+ * column in which we use a {@see OsmPrimitivRenderer}.
+ *
+ */
+public class ChangesetContentTableCellRenderer extends JLabel implements TableCellRenderer{
+
+ public ChangesetContentTableCellRenderer() {
+ setOpaque(true);
+ }
+
+ protected void reset() {
+ setBackground(UIManager.getColor("Table.background"));
+ setForeground(UIManager.getColor("Table.foreground"));
+ setFont(UIManager.getFont("Table.font"));
+ }
+
+ protected void renderColors(boolean isSelected) {
+ if (isSelected) {
+ setBackground(UIManager.getColor("Table.selectionBackground"));
+ setForeground(UIManager.getColor("Table.selectionForeground"));
+ } else {
+ setBackground(UIManager.getColor("Table.background"));
+ setForeground(UIManager.getColor("Table.foreground"));
+ }
+ }
+
+ protected void renderId(HistoryOsmPrimitive primitive) {
+ setText(Long.toString(primitive.getId()));
+ setToolTipText("");
+ }
+
+ protected void renderModificationType(ChangesetModificationType type) {
+ switch(type) {
+ case CREATED: setText(tr("Created")); break;
+ case UPDATED: setText(tr("Updated")); break;
+ case DELETED: setText(tr("Deleted")); break;
+ }
+ setToolTipText("");
+ }
+
+
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
+ int row, int column) {
+ reset();
+ renderColors(isSelected);
+ switch(column) {
+ case 0:
+ ChangesetModificationType type = (ChangesetModificationType)value;
+ renderModificationType(type);
+ break;
+ case 1:
+ HistoryOsmPrimitive primitive = (HistoryOsmPrimitive)value;
+ renderId(primitive);
+ break;
+ default:
+ /* do nothing */
+ }
+ return this;
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableColumnModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableColumnModel.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableColumnModel.java (revision 2689)
@@ -0,0 +1,51 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableColumn;
+
+import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
+
+/**
+ * The column model for the changeset content
+ *
+ */
+public class ChangesetContentTableColumnModel extends DefaultTableColumnModel {
+
+ protected void createColumns() {
+ TableColumn col = null;
+ ChangesetContentTableCellRenderer renderer = new ChangesetContentTableCellRenderer();
+ // column 0 - type
+ col = new TableColumn(0);
+ col.setHeaderValue("");
+ col.setResizable(true);
+ col.setWidth(50);
+ col.setPreferredWidth(50);
+ col.setMaxWidth(100);
+ col.setCellRenderer(renderer);
+ addColumn(col);
+
+ // column 1 - ID
+ col = new TableColumn(1);
+ col.setHeaderValue(tr("ID"));
+ col.setResizable(true);
+ col.setPreferredWidth(60);
+ col.setMaxWidth(100);
+ col.setCellRenderer(renderer);
+ addColumn(col);
+
+ // column 2 - Name
+ col = new TableColumn(2);
+ col.setHeaderValue(tr("Name"));
+ col.setResizable(true);
+ col.setPreferredWidth(200);
+ col.setCellRenderer(new OsmPrimitivRenderer());
+ addColumn(col);
+ }
+
+ public ChangesetContentTableColumnModel() {
+ createColumns();
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableModel.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableModel.java (revision 2689)
@@ -0,0 +1,164 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.table.AbstractTableModel;
+
+import org.openstreetmap.josm.data.osm.ChangesetDataSet;
+import org.openstreetmap.josm.data.osm.ChangesetDataSet.ChangesetDataSetEntry;
+import org.openstreetmap.josm.data.osm.ChangesetDataSet.ChangesetModificationType;
+import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
+
+/**
+ * This is the table model for the content of a changeset.
+ *
+ */
+public class ChangesetContentTableModel extends AbstractTableModel {
+
+ private final ArrayList data = new ArrayList();
+ private DefaultListSelectionModel selectionModel;
+
+ public ChangesetContentTableModel(DefaultListSelectionModel selectionModel) {
+ this.selectionModel = selectionModel;
+ }
+
+ /**
+ * Replies true if there is at least one selected primitive in the table model
+ *
+ * @return true if there is at least one selected primitive in the table model
+ */
+ public boolean hasSelectedPrimitives() {
+ return selectionModel.getMinSelectionIndex() >= 0;
+ }
+
+ public void setSelectedByIdx(int row) {
+ selectionModel.setSelectionInterval(row, row);
+ }
+
+ /**
+ * Replies the selection model
+ * @return the selection model
+ */
+ public DefaultListSelectionModel getSelectionModel() {
+ return selectionModel;
+ }
+
+ public Set getSelectedPrimitives() {
+ Set ret = new HashSet();
+ for (int i=0;i < data.size();i++) {
+ if (selectionModel.isSelectedIndex(i)) {
+ ret.add(data.get(i).getPrimitive());
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Populates the model with the content of a model. If ds is null, the
+ * table is cleared.
+ *
+ * @param ds the changeset content.
+ */
+ public void populate(ChangesetDataSet ds) {
+ this.data.clear();
+ if (ds == null) {
+ fireTableDataChanged();
+ return;
+ }
+ for (Iterator it = ds.iterator(); it.hasNext();) {
+ data.add(new ChangesetContentEntry(it.next()));
+ }
+ sort();
+ fireTableDataChanged();
+ }
+
+ protected void sort() {
+ Collections.sort(
+ data,
+ new Comparator() {
+ public int compare(ChangesetDataSetEntry c1, ChangesetDataSetEntry c2) {
+ if (c1.getModificationType().equals(c2.getModificationType())) {
+ long id1 = c1.getPrimitive().getId();
+ long id2 = c2.getPrimitive().getId();
+
+ if (id1 == id2)
+ return 0;
+ else if (id1 < id2)
+ return -1;
+ return 1;
+ }
+ switch(c1.getModificationType()) {
+ case CREATED: return -1;
+ case UPDATED:
+ switch(c2.getModificationType()) {
+ case CREATED: return 1;
+ default: return -1;
+ }
+ case DELETED:
+ return 1;
+ }
+ // should not happen
+ return 0;
+ }
+ }
+ );
+ }
+
+ /* -------------------------------------------------------------- */
+ /* interface TableModel */
+ /* -------------------------------------------------------------- */
+ public int getColumnCount() {
+ return 3;
+ }
+
+ public int getRowCount() {
+ return data.size();
+ }
+
+ public Object getValueAt(int row, int col) {
+ switch(col) {
+ case 0: return data.get(row).getModificationType();
+ default: return data.get(row).getPrimitive();
+ }
+ }
+
+ /**
+ * The type used internally to keep information about {@see HistoryOsmPrimitive}
+ * with their {@see ChangesetModificationType}.
+ *
+ */
+ static private class ChangesetContentEntry implements ChangesetDataSetEntry{
+ private ChangesetModificationType modificationType;
+ private HistoryOsmPrimitive primitive;
+
+ public ChangesetContentEntry(ChangesetModificationType modificationType, HistoryOsmPrimitive primitive) {
+ this.modificationType = modificationType;
+ this.primitive = primitive;
+ }
+
+ public ChangesetContentEntry(ChangesetDataSetEntry entry) {
+ this.modificationType = entry.getModificationType();
+ this.primitive = entry.getPrimitive();
+ }
+
+ public ChangesetModificationType getModificationType() {
+ return modificationType;
+ }
+ public void setModificationType(ChangesetModificationType modificationType) {
+ this.modificationType = modificationType;
+ }
+ public HistoryOsmPrimitive getPrimitive() {
+ return primitive;
+ }
+ public void setPrimitive(HistoryOsmPrimitive primitive) {
+ this.primitive = primitive;
+ }
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDetailPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDetailPanel.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDetailPanel.java (revision 2689)
@@ -0,0 +1,487 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trc;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.JToolBar;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.AutoScaleAction;
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.ChangesetCache;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.MapView.EditLayerChangeListener;
+import org.openstreetmap.josm.gui.help.HelpUtil;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * This panel displays the properties of the currently selected changeset in the
+ * {@see ChangesetCacheManager}.
+ *
+ */
+public class ChangesetDetailPanel extends JPanel implements PropertyChangeListener{
+
+ private JTextField tfID;
+ private JTextArea taComment;
+ private JTextField tfOpen;
+ private JTextField tfUser;
+ private JTextField tfCreatedOn;
+ private JTextField tfClosedOn;
+ private DonwloadChangesetContentAction actDownloadChangesetContent;
+ private UpdateChangesetAction actUpdateChangesets;
+ private RemoveFromCacheAction actRemoveFromCache;
+ private SelectInCurrentLayerAction actSelectInCurrentLayer;
+ private ZoomInCurrentLayerAction actZoomInCurrentLayerAction;
+
+ private Changeset current = null;
+
+ protected JPanel buildActionButtonPanel() {
+ JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
+
+ JToolBar tb = new JToolBar(JToolBar.VERTICAL);
+ tb.setFloatable(false);
+
+ // -- remove from cache action
+ tb.add(actRemoveFromCache = new RemoveFromCacheAction());
+ actRemoveFromCache.initProperties(current);
+
+ // -- changeset update
+ tb.add(actUpdateChangesets = new UpdateChangesetAction());
+ actUpdateChangesets.initProperties(current);
+
+ // -- changeset content download
+ tb.add(actDownloadChangesetContent =new DonwloadChangesetContentAction());
+ actDownloadChangesetContent.initProperties(current);
+
+ tb.add(actSelectInCurrentLayer = new SelectInCurrentLayerAction());
+ MapView.addEditLayerChangeListener(actSelectInCurrentLayer);
+
+ tb.add(actZoomInCurrentLayerAction = new ZoomInCurrentLayerAction());
+ MapView.addEditLayerChangeListener(actZoomInCurrentLayerAction);
+
+ addComponentListener(
+ new ComponentAdapter() {
+ @Override
+ public void componentHidden(ComponentEvent e) {
+ // make sure the listener is unregistered when the panel becomes
+ // invisible
+ MapView.removeEditLayerChangeListener(actSelectInCurrentLayer);
+ MapView.removeEditLayerChangeListener(actZoomInCurrentLayerAction);
+ }
+ }
+ );
+
+ pnl.add(tb);
+ return pnl;
+ }
+
+ protected JPanel buildDetailViewPanel() {
+ JPanel pnl = new JPanel(new GridBagLayout());
+
+ GridBagConstraints gc = new GridBagConstraints();
+ gc.anchor = GridBagConstraints.FIRST_LINE_START;
+ gc.insets = new Insets(0,0,2,3);
+
+ //-- id
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ pnl.add(new JLabel(tr("ID:")), gc);
+
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ gc.gridx = 1;
+ pnl.add(tfID = new JTextField(10), gc);
+ tfID.setEditable(false);
+
+ //-- comment
+ gc.gridx = 0;
+ gc.gridy = 1;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ pnl.add(new JLabel(tr("Comment:")), gc);
+
+ gc.fill = GridBagConstraints.BOTH;
+ gc.weightx = 1.0;
+ gc.weighty = 1.0;
+ gc.gridx = 1;
+ pnl.add(taComment= new JTextArea(5,40), gc);
+ taComment.setEditable(false);
+
+ //-- Open/Closed
+ gc.gridx = 0;
+ gc.gridy = 2;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ gc.weighty = 0.0;
+ pnl.add(new JLabel(tr("Open/Closed:")), gc);
+
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.gridx = 1;
+ pnl.add(tfOpen= new JTextField(10), gc);
+ tfOpen.setEditable(false);
+
+ //-- Created by:
+ gc.gridx = 0;
+ gc.gridy = 3;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ pnl.add(new JLabel(tr("Created by:")), gc);
+
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 1.0;
+ gc.gridx = 1;
+ pnl.add(tfUser= new JTextField(""), gc);
+ tfUser.setEditable(false);
+
+ //-- Created On:
+ gc.gridx = 0;
+ gc.gridy = 4;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ pnl.add(new JLabel(tr("Created on:")), gc);
+
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.gridx = 1;
+ pnl.add(tfCreatedOn= new JTextField(20), gc);
+ tfCreatedOn.setEditable(false);
+
+ //-- Closed On:
+ gc.gridx = 0;
+ gc.gridy = 5;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ pnl.add(new JLabel(tr("Closed on:")), gc);
+
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.gridx = 1;
+ pnl.add(tfClosedOn= new JTextField(20), gc);
+ tfClosedOn.setEditable(false);
+
+ return pnl;
+ }
+
+ protected void build() {
+ setLayout(new BorderLayout());
+ setBorder(BorderFactory.createEmptyBorder(3,3,3,3));
+ add(buildDetailViewPanel(), BorderLayout.CENTER);
+ add(buildActionButtonPanel(), BorderLayout.WEST);
+ }
+
+ protected void clearView() {
+ tfID.setText("");
+ taComment.setText("");
+ tfOpen.setText("");
+ tfUser.setText("");
+ tfCreatedOn.setText("");
+ tfClosedOn.setText("");
+ }
+
+ protected void updateView(Changeset cs) {
+ String msg;
+ if (cs == null) return;
+ tfID.setText(Integer.toString(cs.getId()));
+ String comment = cs.get("comment");
+ taComment.setText(comment == null ? "" : comment);
+
+ if (cs.isOpen()) {
+ msg = trc("changeset.state", "Open");
+ } else {
+ msg = trc("changeset.state", "Closed");
+ }
+ tfOpen.setText(msg);
+
+ if (cs.getUser() == null) {
+ msg = tr("anonymous");
+ } else {
+ msg = cs.getUser().getName();
+ }
+ tfUser.setText(msg);
+ SimpleDateFormat sdf = new SimpleDateFormat();
+
+ tfCreatedOn.setText(cs.getCreatedAt() == null ? "" : sdf.format(cs.getCreatedAt()));
+ tfClosedOn.setText(cs.getClosedAt() == null ? "" : sdf.format(cs.getClosedAt()));
+ }
+
+ public ChangesetDetailPanel() {
+ build();
+ }
+
+ protected void setCurrentChangeset(Changeset cs) {
+ current = cs;
+ if (cs == null) {
+ clearView();
+ } else {
+ updateView(cs);
+ }
+ actDownloadChangesetContent.initProperties(current);
+ actUpdateChangesets.initProperties(current);
+ actRemoveFromCache.initProperties(current);
+ actSelectInCurrentLayer.updateEnabledState();
+ actZoomInCurrentLayerAction.updateEnabledState();
+ }
+
+ /* ---------------------------------------------------------------------------- */
+ /* interface PropertyChangeListener */
+ /* ---------------------------------------------------------------------------- */
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (! evt.getPropertyName().equals(ChangesetCacheManagerModel.CHANGESET_IN_DETAIL_VIEW_PROP))
+ return;
+ Changeset cs = (Changeset)evt.getNewValue();
+ setCurrentChangeset(cs);
+ }
+
+ /**
+ * The action for removing the currently selected changeset from the changeset cache
+ */
+ class RemoveFromCacheAction extends AbstractAction {
+ public RemoveFromCacheAction() {
+ putValue(NAME, tr("Remove from cache"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
+ putValue(SHORT_DESCRIPTION, tr("Remove the changeset in the detail view panel from the local cache"));
+ }
+
+ public void actionPerformed(ActionEvent evt) {
+ if (current == null)
+ return;
+ ChangesetCache.getInstance().remove(current);
+ }
+
+ public void initProperties(Changeset cs) {
+ setEnabled(cs != null);
+ }
+ }
+
+ /**
+ * Removes the selected changesets from the local changeset cache
+ *
+ */
+ class DonwloadChangesetContentAction extends AbstractAction{
+ public DonwloadChangesetContentAction() {
+ putValue(NAME, tr("Download content"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","downloadchangesetcontent"));
+ putValue(SHORT_DESCRIPTION, tr("Download the changeset content from the OSM server"));
+ }
+
+ public void actionPerformed(ActionEvent evt) {
+ if (current == null) return;
+ ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(ChangesetDetailPanel.this,current.getId());
+ ChangesetCacheManager.getInstance().runDownloadTask(task);
+ }
+
+ public void initProperties(Changeset cs) {
+ if (cs == null) {
+ setEnabled(false);
+ return;
+ } else {
+ setEnabled(true);
+ }
+ if (cs.getContent() == null) {
+ putValue(NAME, tr("Download content"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","downloadchangesetcontent"));
+ putValue(SHORT_DESCRIPTION, tr("Download the changeset content from the OSM server"));
+ } else {
+ putValue(NAME, tr("Update content"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset","updatechangesetcontent"));
+ putValue(SHORT_DESCRIPTION, tr("Update the changeset content from the OSM server"));
+ }
+ }
+ }
+
+
+ /**
+ * Updates the current changeset from the OSM server
+ *
+ */
+ class UpdateChangesetAction extends AbstractAction{
+ public UpdateChangesetAction() {
+ putValue(NAME, tr("Update changeset"));
+ putValue(SMALL_ICON,ImageProvider.get("dialogs/changeset","updatechangeset"));
+ putValue(SHORT_DESCRIPTION, tr("Update the changeset from the OSM server"));
+ }
+
+ public void actionPerformed(ActionEvent evt) {
+ if (current == null) return;
+ Main.worker.submit(
+ new ChangesetHeaderDownloadTask(
+ ChangesetDetailPanel.this,
+ Collections.singleton(current.getId())
+ )
+ );
+ }
+
+ public void initProperties(Changeset cs) {
+ if (cs == null) {
+ setEnabled(false);
+ return;
+ } else {
+ setEnabled(true);
+ }
+ }
+ }
+
+ /**
+ * Selects the primitives in the content of this changeset in the the current
+ * data layer.
+ *
+ */
+ class SelectInCurrentLayerAction extends AbstractAction implements EditLayerChangeListener{
+
+ public SelectInCurrentLayerAction() {
+ putValue(NAME, tr("Select in layer"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs", "select"));
+ putValue(SHORT_DESCRIPTION, tr("Select the primitives in the content of this changeset in the current data layer"));
+ updateEnabledState();
+ }
+
+ protected void alertNoPrimitivesToSelect(Collection primitives) {
+ HelpAwareOptionPane.showOptionDialog(
+ ChangesetDetailPanel.this,
+ tr("None of the objects in the content of changeset {0} is available in the current
"
+ + "edit layer ''{1}''.",
+ current.getId(),
+ Main.main.getEditLayer().getName()
+ ),
+ tr("Nothing to select"),
+ JOptionPane.WARNING_MESSAGE,
+ HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToSelectInLayer")
+ );
+ }
+
+ public void actionPerformed(ActionEvent arg0) {
+ if (!isEnabled())
+ return;
+ if (Main.main == null || Main.main.getEditLayer() == null) return;
+ OsmDataLayer layer = Main.main.getEditLayer();
+ Set target = new HashSet();
+ for (OsmPrimitive p: layer.data.getNodes()) {
+ if (p.isUsable() && p.getChangesetId() == current.getId()) {
+ target.add(p);
+ }
+ }
+ for (OsmPrimitive p: layer.data.getWays()) {
+ if (p.isUsable() && p.getChangesetId() == current.getId()) {
+ target.add(p);
+ }
+ }
+ for (OsmPrimitive p: layer.data.getRelations()) {
+ if (p.isUsable() && p.getChangesetId() == current.getId()) {
+ target.add(p);
+ }
+ }
+ if (target.isEmpty()) {
+ alertNoPrimitivesToSelect(target);
+ return;
+ }
+ layer.data.setSelected(target);
+ }
+
+ public void updateEnabledState() {
+ if (Main.main == null || Main.main.getEditLayer() == null){
+ setEnabled(false);
+ return;
+ }
+ setEnabled(current != null);
+ }
+
+ public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
+ updateEnabledState();
+ }
+ }
+
+ /**
+ * Zooms to the primitives in the content of this changeset in the the current
+ * data layer.
+ *
+ */
+ class ZoomInCurrentLayerAction extends AbstractAction implements EditLayerChangeListener{
+
+ public ZoomInCurrentLayerAction() {
+ putValue(NAME, tr("Zoom to in layer"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale", "selection"));
+ putValue(SHORT_DESCRIPTION, tr("Zoom to the primitives in the content of this changeset in the current data layer"));
+ updateEnabledState();
+ }
+
+ protected void alertNoPrimitivesToZoomTo() {
+ HelpAwareOptionPane.showOptionDialog(
+ ChangesetDetailPanel.this,
+ tr("None of the objects in the content of changeset {0} is available in the current
"
+ + "edit layer ''{1}''.",
+ current.getId(),
+ Main.main.getEditLayer().getName()
+ ),
+ tr("Nothing to zoom to"),
+ JOptionPane.WARNING_MESSAGE,
+ HelpUtil.ht("/Dialog/ChangesetCacheManager#NothingToZoomTo")
+ );
+ }
+
+ public void actionPerformed(ActionEvent arg0) {
+ if (!isEnabled())
+ return;
+ if (Main.main == null || Main.main.getEditLayer() == null) return;
+ OsmDataLayer layer = Main.main.getEditLayer();
+ Set target = new HashSet();
+ for (OsmPrimitive p: layer.data.getNodes()) {
+ if (p.isUsable() && p.getChangesetId() == current.getId()) {
+ target.add(p);
+ }
+ }
+ for (OsmPrimitive p: layer.data.getWays()) {
+ if (p.isUsable() && p.getChangesetId() == current.getId()) {
+ target.add(p);
+ }
+ }
+ for (OsmPrimitive p: layer.data.getRelations()) {
+ if (p.isUsable() && p.getChangesetId() == current.getId()) {
+ target.add(p);
+ }
+ }
+ if (target.isEmpty()) {
+ alertNoPrimitivesToZoomTo();
+ return;
+ }
+ layer.data.setSelected(target);
+ AutoScaleAction.zoomToSelection();
+ }
+
+ public void updateEnabledState() {
+ if (Main.main == null || Main.main.getEditLayer() == null){
+ setEnabled(false);
+ return;
+ }
+ setEnabled(current != null);
+ }
+
+ public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
+ updateEnabledState();
+ }
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDownloadTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDownloadTask.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDownloadTask.java (revision 2689)
@@ -0,0 +1,12 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import java.util.Set;
+
+import org.openstreetmap.josm.data.osm.Changeset;
+
+public interface ChangesetDownloadTask extends Runnable{
+ Set getDownloadedChangesets();
+ boolean isCanceled();
+ boolean isFailed();
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetHeaderDownloadTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetHeaderDownloadTask.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetHeaderDownloadTask.java (revision 2689)
@@ -0,0 +1,206 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.SwingUtilities;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.ChangesetCache;
+import org.openstreetmap.josm.gui.ExceptionDialogUtil;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.io.OsmServerChangesetReader;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.tools.BugReportExceptionHandler;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+import org.openstreetmap.josm.tools.ExceptionUtil;
+import org.xml.sax.SAXException;
+
+/**
+ * This is an asynchronous task for downloading a collection of changests from the OSM
+ * server.
+ *
+ * The task only downloads the changeset properties without the changeset content. It
+ * updates the global {@see ChangesetCache}.
+ *
+ */
+public class ChangesetHeaderDownloadTask extends PleaseWaitRunnable implements ChangesetDownloadTask{
+
+ /**
+ * Builds a download task from for a collection of changesets.
+ *
+ * Ignores null values and changesets with {@see Changeset#isNew()} == true.
+ *
+ * @param changesets the collection of changesets. Assumes an empty collection if null.
+ * @return the download task
+ */
+ static public ChangesetHeaderDownloadTask buildTaskForChangesets(Collection changesets) {
+ return buildTaskForChangesets(Main.parent, changesets);
+ }
+
+ /**
+ * Builds a download task from for a collection of changesets.
+ *
+ * Ignores null values and changesets with {@see Changeset#isNew()} == true.
+ *
+ * @param parent the parent component relative to which the {@see PleaseWaitDialog} is displayed.
+ * Must not be null.
+ * @param changesets the collection of changesets. Assumes an empty collection if null.
+ * @return the download task
+ * @throws IllegalArgumentException thrown if parent is null
+ */
+ static public ChangesetHeaderDownloadTask buildTaskForChangesets(Component parent, Collection changesets) {
+ CheckParameterUtil.ensureParameterNotNull(parent, "parent");
+ if (changesets == null) {
+ changesets = Collections.emptyList();
+ }
+
+ HashSet ids = new HashSet();
+ for (Changeset cs: changesets) {
+ if (cs == null || cs.isNew()) {
+ continue;
+ }
+ ids.add(cs.getId());
+ }
+ if (parent == null)
+ return new ChangesetHeaderDownloadTask(ids);
+ else
+ return new ChangesetHeaderDownloadTask(parent, ids);
+
+ }
+
+
+ private Set idsToDownload;
+ private OsmServerChangesetReader reader;
+ private boolean canceled;
+ private Exception lastException;
+ private Set downloadedChangesets;
+
+ protected void init(Collection ids) {
+ if (ids == null) {
+ ids = Collections.emptyList();
+ }
+ idsToDownload = new HashSet();
+ if (ids == null || ids.isEmpty())
+ return;
+ for (int id: ids) {
+ if (id <= 0) {
+ continue;
+ }
+ idsToDownload.add(id);
+ }
+ }
+
+ /**
+ * Creates the download task for a collection of changeset ids. Uses a {@see PleaseWaitDialog}
+ * whose parent is {@see Main#parent}.
+ *
+ * Null ids or or ids <= 0 in the id collection are ignored.
+ *
+ * @param ids the collection of ids. Empty collection assumed if null.
+ */
+ public ChangesetHeaderDownloadTask(Collection ids) {
+ // parent for dialog is Main.parent
+ super(tr("Download changesets"), false /* don't ignore exceptions */);
+ init(ids);
+ }
+
+ /**
+ * Creates the download task for a collection of changeset ids. Uses a {@see PleaseWaitDialog}
+ * whose parent is the parent window of dialogParent
.
+ *
+ * Null ids or or ids <= 0 in the id collection are ignored.
+ *
+ * @param dialogParent the parent reference component for the {@see PleaseWaitDialog}. Must not be null.
+ * @param ids the collection of ids. Empty collection assumed if null.
+ * @throws IllegalArgumentException thrown if dialogParent is null
+ */
+ public ChangesetHeaderDownloadTask(Component dialogParent, Collection ids) throws IllegalArgumentException{
+ super(dialogParent,tr("Download changesets"), false /* don't ignore exceptions */);
+ init(ids);
+ }
+
+ @Override
+ protected void cancel() {
+ canceled = true;
+ synchronized (this) {
+ if (reader != null) {
+ reader.cancel();
+ }
+ }
+ }
+
+ @Override
+ protected void finish() {
+ if (canceled)
+ return;
+ if (lastException != null) {
+ ExceptionDialogUtil.explainException(lastException);
+ }
+ Runnable r = new Runnable() {
+ public void run() {
+ ChangesetCache.getInstance().update(downloadedChangesets);
+ }
+ };
+
+ if (SwingUtilities.isEventDispatchThread()) {
+ r.run();
+ } else {
+ try {
+ SwingUtilities.invokeAndWait(r);
+ } catch(InterruptedException e) {
+ e.printStackTrace();
+ } catch(InvocationTargetException e) {
+ Throwable t = e.getTargetException();
+ if (t instanceof RuntimeException) {
+ BugReportExceptionHandler.handleException(t);
+ } else if (t instanceof Exception){
+ ExceptionUtil.explainException(e);
+ } else {
+ BugReportExceptionHandler.handleException(t);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void realRun() throws SAXException, IOException, OsmTransferException {
+ try {
+ synchronized (this) {
+ reader = new OsmServerChangesetReader();
+ }
+ downloadedChangesets = new HashSet();
+ downloadedChangesets.addAll(reader.readChangesets(idsToDownload, getProgressMonitor().createSubTaskMonitor(0, false)));
+ } catch(OsmTransferException e) {
+ if (canceled)
+ // ignore exception if cancelled
+ return;
+ // remember other exceptions
+ lastException = e;
+ }
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /* interface ChangesetDownloadTask */
+ /* ------------------------------------------------------------------------------- */
+ public Set getDownloadedChangesets() {
+ return downloadedChangesets;
+ }
+
+ public boolean isCanceled() {
+ return canceled;
+ }
+
+ public boolean isFailed() {
+ return lastException != null;
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetInSelectionListModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetInSelectionListModel.java (revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetInSelectionListModel.java (revision 2689)
@@ -8,9 +8,8 @@
import org.openstreetmap.josm.data.SelectionChangedListener;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.gui.MapView;
-import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.MapView.EditLayerChangeListener;
import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-public class ChangesetInSelectionListModel extends ChangesetListModel implements SelectionChangedListener, MapView.LayerChangeListener{
+public class ChangesetInSelectionListModel extends ChangesetListModel implements SelectionChangedListener, EditLayerChangeListener{
public ChangesetInSelectionListModel(DefaultListSelectionModel selectionModel) {
@@ -24,15 +23,14 @@
}
+
/* ---------------------------------------------------------------------------- */
/* Interface LayerChangeListener */
/* ---------------------------------------------------------------------------- */
- public void activeLayerChange(Layer oldLayer, Layer newLayer) {
- if (newLayer == null || ! (newLayer instanceof OsmDataLayer)) {
+ public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
+ if (newLayer == null) {
setChangesets(null);
} else {
- initFromPrimitives(((OsmDataLayer) newLayer).data.getSelected());
+ initFromPrimitives((newLayer).data.getSelected());
}
}
- public void layerAdded(Layer newLayer) {}
- public void layerRemoved(Layer oldLayer) {}
}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetTagsPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetTagsPanel.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetTagsPanel.java (revision 2689)
@@ -0,0 +1,58 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import java.awt.BorderLayout;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.gui.tagging.TagEditorModel;
+import org.openstreetmap.josm.gui.tagging.TagTable;
+
+/**
+ * This panel displays the tags of the currently selected changeset in the {@see ChangesetCacheManager}
+ *
+ */
+public class ChangesetTagsPanel extends JPanel implements PropertyChangeListener{
+
+ private TagTable tblTags;
+ private TagEditorModel model;
+
+ protected void build() {
+ setLayout(new BorderLayout());
+ setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+
+ tblTags = new TagTable(model = new TagEditorModel());
+ tblTags.setEnabled(false);
+ add(new JScrollPane(tblTags), BorderLayout.CENTER);
+ }
+
+ public ChangesetTagsPanel() {
+ build();
+ }
+
+ protected void init(Changeset cs) {
+ model.clear();
+ if (cs == null)
+ return;
+ model.initFromTags(cs.getKeys());
+ }
+
+ /* ---------------------------------------------------------------------------- */
+ /* interface PropertyChangeListener */
+ /* ---------------------------------------------------------------------------- */
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (!evt.getPropertyName().equals(ChangesetCacheManagerModel.CHANGESET_IN_DETAIL_VIEW_PROP))
+ return;
+ Changeset cs = (Changeset)evt.getNewValue();
+ if (cs == null) {
+ model.clear();
+ } else {
+ model.initFromPrimitive(cs);
+ }
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetsInActiveDataLayerListModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetsInActiveDataLayerListModel.java (revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetsInActiveDataLayerListModel.java (revision 2689)
@@ -16,6 +16,12 @@
import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
+import org.openstreetmap.josm.gui.MapView.EditLayerChangeListener;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-public class ChangesetsInActiveDataLayerListModel extends ChangesetListModel implements DataSetListener {
+/**
+ * This is the list model for the list of changeset in the current edit layer.
+ *
+ */
+public class ChangesetsInActiveDataLayerListModel extends ChangesetListModel implements DataSetListener, EditLayerChangeListener {
public ChangesetsInActiveDataLayerListModel(DefaultListSelectionModel selectionModel) {
@@ -23,9 +29,10 @@
}
+ /* ------------------------------------------------------------------------------ */
+ /* interface DataSetListener */
+ /* ------------------------------------------------------------------------------ */
public void dataChanged(DataChangedEvent event) {
- initFromPrimitives(event.getPrimitives());
+ initFromDataSet(event.getDataset());
}
-
- public void nodeMoved(NodeMovedEvent event) {/* ignored */}
public void primtivesAdded(PrimitivesAddedEvent event) {
@@ -41,8 +48,4 @@
}
- public void relationMembersChanged(RelationMembersChangedEvent event) {/* ignored */}
-
- public void tagsChanged(TagsChangedEvent event) {/* ignored */}
-
public void otherDatasetChange(AbstractDatasetChangedEvent event) {
if (event instanceof ChangesetIdChangedEvent) {
@@ -53,5 +56,24 @@
}
+ public void nodeMoved(NodeMovedEvent event) {/* ignored */}
+
+ public void relationMembersChanged(RelationMembersChangedEvent event) {/* ignored */}
+
+ public void tagsChanged(TagsChangedEvent event) {/* ignored */}
+
public void wayNodesChanged(WayNodesChangedEvent event) {/* ignored */}
+ /* ------------------------------------------------------------------------------ */
+ /* interface EditLayerListener */
+ /* ------------------------------------------------------------------------------ */
+ public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
+ // just init the model content. Don't register as DataSetListener. The mode
+ // is already registered to receive DataChangedEvents from the current
+ // edit layer
+ if (newLayer != null) {
+ initFromDataSet(newLayer.data);
+ } else {
+ initFromDataSet(null);
+ }
+ }
}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/DownloadChangesetsTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/DownloadChangesetsTask.java (revision 2688)
+++ (revision )
@@ -1,89 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.dialogs.changeset;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.io.IOException;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import javax.swing.SwingUtilities;
-
-import org.openstreetmap.josm.data.osm.Changeset;
-import org.openstreetmap.josm.data.osm.ChangesetCache;
-import org.openstreetmap.josm.gui.ExceptionDialogUtil;
-import org.openstreetmap.josm.gui.PleaseWaitRunnable;
-import org.openstreetmap.josm.io.OsmServerChangesetReader;
-import org.openstreetmap.josm.io.OsmTransferException;
-import org.xml.sax.SAXException;
-
-public class DownloadChangesetsTask extends PleaseWaitRunnable{
-
- private Set idsToDownload;
- private OsmServerChangesetReader reader;
- private boolean cancelled;
- private Exception lastException;
- private List downloadedChangesets;
-
- public DownloadChangesetsTask(Collection ids) {
- super(tr("Download changesets"));
- idsToDownload = new HashSet();
- if (ids == null || ids.isEmpty())
- return;
- for (int id: ids) {
- if (id <= 0) {
- continue;
- }
- idsToDownload.add(id);
- }
- }
-
- @Override
- protected void cancel() {
- cancelled = true;
- synchronized (this) {
- if (reader != null) {
- reader.cancel();
- }
- }
- }
-
- @Override
- protected void finish() {
- if (cancelled)
- return;
- if (lastException != null) {
- ExceptionDialogUtil.explainException(lastException);
- }
- Runnable r = new Runnable() {
- public void run() {
- ChangesetCache.getInstance().update(downloadedChangesets);
- }
- };
-
- if (SwingUtilities.isEventDispatchThread()) {
- r.run();
- } else {
- SwingUtilities.invokeLater(r);
- }
- }
-
- @Override
- protected void realRun() throws SAXException, IOException, OsmTransferException {
- try {
- synchronized (this) {
- reader = new OsmServerChangesetReader();
- }
- downloadedChangesets = reader.readChangesets(idsToDownload, getProgressMonitor().createSubTaskMonitor(0, false));
- } catch(Exception e) {
- if (cancelled)
- // ignore exception if cancelled
- return;
- if (e instanceof RuntimeException)
- throw (RuntimeException)e;
- lastException = e;
- }
- }
-}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/SingleChangesetDownloadPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/SingleChangesetDownloadPanel.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/SingleChangesetDownloadPanel.java (revision 2689)
@@ -0,0 +1,159 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.util.Collections;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.JTextComponent;
+
+import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
+import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * This panel allows to enter the ID of single changeset and to download
+ * the respective changeset.
+ *
+ */
+public class SingleChangesetDownloadPanel extends JPanel {
+
+ private JTextField tfChangsetId;
+ private DownloadAction actDownload;
+ private ChangesetIdValidator valChangesetId;
+
+ protected void build() {
+ setLayout(new FlowLayout(FlowLayout.LEFT,0,0));
+ setBorder(
+ BorderFactory.createCompoundBorder(
+ BorderFactory.createLineBorder(Color.GRAY),
+ BorderFactory.createEmptyBorder(0,3,0,3)
+ )
+ );
+
+ add(new JLabel(tr("Changeset ID: ")));
+ add(tfChangsetId = new JTextField(10));
+ tfChangsetId.setToolTipText(tr("Enter a changset id"));
+ valChangesetId =ChangesetIdValidator.decorate(tfChangsetId);
+ SelectAllOnFocusGainedDecorator.decorate(tfChangsetId);
+
+ actDownload = new DownloadAction();
+ SideButton btn = new SideButton(actDownload);
+ tfChangsetId.addActionListener(actDownload);
+ tfChangsetId.getDocument().addDocumentListener(actDownload);
+ add(btn);
+ }
+
+ public SingleChangesetDownloadPanel() {
+ build();
+ }
+
+ /**
+ * Replies the changeset id entered in this panel. 0 if no changeset id
+ * or an invalid changeset id is currently entered.
+ *
+ * @return the changeset id entered in this panel
+ */
+ public int getChangsetId() {
+ return valChangesetId.getChangesetId();
+ }
+
+ /**
+ * Downloads the single changeset from the OSM API
+ */
+ class DownloadAction extends AbstractAction implements DocumentListener{
+
+ public DownloadAction() {
+ putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "downloadchangesetcontent"));
+ putValue(SHORT_DESCRIPTION, tr("Download the changeset with the specified id, including the changeset content"));
+ updateEnabledState();
+ }
+
+ public void actionPerformed(ActionEvent arg0) {
+ if (!isEnabled())
+ return;
+ int id = getChangsetId();
+ if (id == 0) return;
+ ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(
+ SingleChangesetDownloadPanel.this,
+ Collections.singleton(id)
+ );
+ ChangesetCacheManager.getInstance().runDownloadTask(task);
+ }
+
+ protected void updateEnabledState() {
+ String v = tfChangsetId.getText();
+ if (v == null || v.trim().length() == 0) {
+ setEnabled(false);
+ return;
+ }
+ setEnabled(valChangesetId.isValid());
+ }
+
+ public void changedUpdate(DocumentEvent arg0) {
+ updateEnabledState();
+ }
+
+ public void insertUpdate(DocumentEvent arg0) {
+ updateEnabledState();
+ }
+
+ public void removeUpdate(DocumentEvent arg0) {
+ updateEnabledState();
+ }
+ }
+
+ /**
+ * Validator for a changeset ID entered in a {@see JTextComponent}.
+ *
+ */
+ static private class ChangesetIdValidator extends AbstractTextComponentValidator {
+ static public ChangesetIdValidator decorate(JTextComponent tc) {
+ return new ChangesetIdValidator(tc);
+ }
+
+ public ChangesetIdValidator(JTextComponent tc) {
+ super(tc);
+ }
+
+ @Override
+ public boolean isValid() {
+ String value = getComponent().getText();
+ if (value == null || value.trim().length() == 0)
+ return true;
+ return getChangesetId() > 0;
+ }
+
+ @Override
+ public void validate() {
+ if (!isValid()) {
+ feedbackInvalid(tr("The current value isn't a valid changeset ID. Please enter an integer value > 0"));
+ } else {
+ feedbackValid(tr("Please enter an integer value > 0"));
+ }
+ }
+
+ public int getChangesetId() {
+ String value = getComponent().getText();
+ if (value == null || value.trim().length() == 0) return 0;
+ try {
+ int changesetId = Integer.parseInt(value.trim());
+ if (changesetId > 0) return changesetId;
+ return 0;
+ } catch(NumberFormatException e) {
+ return 0;
+ }
+ }
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/AdvancedChangesetQueryPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/AdvancedChangesetQueryPanel.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/AdvancedChangesetQueryPanel.java (revision 2689)
@@ -0,0 +1,1204 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset.query;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.Scrollable;
+import javax.swing.text.JTextComponent;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.JMultilineLabel;
+import org.openstreetmap.josm.gui.JosmUserIdentityManager;
+import org.openstreetmap.josm.gui.help.HelpUtil;
+import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
+import org.openstreetmap.josm.gui.widgets.BoundingBoxSelectionPanel;
+import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
+import org.openstreetmap.josm.io.ChangesetQuery;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+
+/**
+ * This panel allows to specify a changeset query
+ *
+ */
+public class AdvancedChangesetQueryPanel extends JPanel {
+
+ private JCheckBox cbUserRestriction;
+ private JCheckBox cbOpenAndCloseRestrictions;
+ private JCheckBox cbTimeRestrictions;
+ private JCheckBox cbBoundingBoxRestriction;
+ private UserRestrictionPanel pnlUserRestriction;
+ private OpenAndCloseStateRestrictionPanel pnlOpenAndCloseRestriction;
+ private TimeRestrictionPanel pnlTimeRestriction;
+ private BBoxRestrictionPanel pnlBoundingBoxRestriction;
+
+ protected JPanel buildQueryPanel() {
+ ItemListener stateChangeHandler = new RestrictionGroupStateChangeHandler();
+ JPanel pnl = new QuerySpecificationPanel();
+ pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+ pnl.setLayout(new GridBagLayout());
+ GridBagConstraints gc = new GridBagConstraints();
+
+ // -- select changesets by a specific user
+ //
+ gc.anchor = GridBagConstraints.NORTHWEST;
+ gc.weightx = 0.0;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ pnl.add(cbUserRestriction = new JCheckBox(), gc);
+ cbUserRestriction.addItemListener(stateChangeHandler);
+
+ gc.gridx = 1;
+ gc.weightx = 1.0;
+ pnl.add(new JMultilineLabel(tr("Select changesets owned by specific users")),gc);
+
+ gc.gridy = 1;
+ gc.gridx = 1;
+ gc.weightx = 1.0;
+ pnl.add(pnlUserRestriction = new UserRestrictionPanel(), gc);
+
+ // -- restricting the query to open and closed changesets
+ //
+ gc.gridy = 2;
+ gc.gridx = 0;
+ gc.anchor = GridBagConstraints.NORTHWEST;
+ gc.weightx = 0.0;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ pnl.add(cbOpenAndCloseRestrictions = new JCheckBox(), gc);
+ cbOpenAndCloseRestrictions.addItemListener(stateChangeHandler);
+
+ gc.gridx = 1;
+ gc.weightx = 1.0;
+ pnl.add(new JMultilineLabel(tr("Select changesets depending on whether they are open or closed")),gc);
+
+ gc.gridy = 3;
+ gc.gridx = 1;
+ gc.weightx = 1.0;
+ pnl.add(pnlOpenAndCloseRestriction = new OpenAndCloseStateRestrictionPanel(), gc);
+
+ // -- restricting the query to a specific time
+ //
+ gc.gridy = 4;
+ gc.gridx = 0;
+ gc.anchor = GridBagConstraints.NORTHWEST;
+ gc.weightx = 0.0;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ pnl.add(cbTimeRestrictions = new JCheckBox(), gc);
+ cbTimeRestrictions.addItemListener(stateChangeHandler);
+
+ gc.gridx = 1;
+ gc.weightx = 1.0;
+ pnl.add(new JMultilineLabel(tr("Select changesets based on the date/time they have been created or closed")),gc);
+
+ gc.gridy = 5;
+ gc.gridx = 1;
+ gc.weightx = 1.0;
+ pnl.add(pnlTimeRestriction = new TimeRestrictionPanel(), gc);
+
+
+ // -- restricting the query to a specific bounding box
+ //
+ gc.gridy = 6;
+ gc.gridx = 0;
+ gc.anchor = GridBagConstraints.NORTHWEST;
+ gc.weightx = 0.0;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ pnl.add(cbBoundingBoxRestriction = new JCheckBox(), gc);
+ cbBoundingBoxRestriction.addItemListener(stateChangeHandler);
+
+ gc.gridx = 1;
+ gc.weightx = 1.0;
+ pnl.add(new JMultilineLabel(tr("Select only changesets related to a specific bounding box")),gc);
+
+ gc.gridy = 7;
+ gc.gridx = 1;
+ gc.weightx = 1.0;
+ pnl.add(pnlBoundingBoxRestriction = new BBoxRestrictionPanel(), gc);
+
+
+ gc.gridy = 8;
+ gc.gridx = 0;
+ gc.gridwidth = 2;
+ gc.fill =GridBagConstraints.BOTH;
+ gc.weightx = 1.0;
+ gc.weighty = 1.0;
+ pnl.add(new JPanel(), gc);
+
+ return pnl;
+ }
+
+ protected void build() {
+ setLayout(new BorderLayout());
+ JScrollPane spQueryPanel = new JScrollPane(buildQueryPanel());
+ add(spQueryPanel, BorderLayout.CENTER);
+ spQueryPanel.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ spQueryPanel.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+
+ }
+
+ public AdvancedChangesetQueryPanel() {
+ build();
+ }
+
+ public void startUserInput() {
+ restoreFromSettings();
+ pnlBoundingBoxRestriction.setVisible(cbBoundingBoxRestriction.isSelected());
+ pnlOpenAndCloseRestriction.setVisible(cbOpenAndCloseRestrictions.isSelected());
+ pnlTimeRestriction.setVisible(cbTimeRestrictions.isSelected());
+ pnlUserRestriction.setVisible(cbUserRestriction.isSelected());
+ pnlOpenAndCloseRestriction.startUserInput();
+ pnlUserRestriction.startUserInput();
+ pnlTimeRestriction.startUserInput();
+ }
+
+ public void displayMessageIfInvalid() {
+ if (cbUserRestriction.isSelected()) {
+ if (! pnlUserRestriction.isValidChangesetQuery()) {
+ pnlUserRestriction.displayMessageIfInvalid();
+ }
+ } else if (cbTimeRestrictions.isSelected()) {
+ if (!pnlTimeRestriction.isValidChangesetQuery()) {
+ pnlTimeRestriction.displayMessageIfInvalid();
+ }
+ } else if (cbBoundingBoxRestriction.isSelected()) {
+ if (!pnlBoundingBoxRestriction.isValidChangesetQuery()) {
+ pnlBoundingBoxRestriction.displayMessageIfInvalid();
+ }
+ }
+ }
+
+ /**
+ * Builds the changeset query based on the data entered in the form.
+ *
+ * @return the changeset query. null, if the data entered doesn't represent
+ * a valid changeset query.
+ */
+ public ChangesetQuery buildChangesetQuery() {
+ ChangesetQuery query = new ChangesetQuery();
+ if (cbUserRestriction.isSelected()) {
+ if (! pnlUserRestriction.isValidChangesetQuery())
+ return null;
+ pnlUserRestriction.fillInQuery(query);
+ }
+ if (cbOpenAndCloseRestrictions.isSelected()) {
+ // don't have to check whether it's valid. It always is.
+ pnlOpenAndCloseRestriction.fillInQuery(query);
+ }
+ if (cbBoundingBoxRestriction.isSelected()) {
+ if (!pnlBoundingBoxRestriction.isValidChangesetQuery())
+ return null;
+ pnlBoundingBoxRestriction.fillInQuery(query);
+ }
+ if (cbTimeRestrictions.isSelected()) {
+ if (!pnlTimeRestriction.isValidChangesetQuery())
+ return null;
+ pnlTimeRestriction.fillInQuery(query);
+ }
+ return query;
+ }
+
+ public void rememberSettings() {
+ Main.pref.put("changeset-query.advanced.user-restrictions", cbUserRestriction.isSelected());
+ Main.pref.put("changeset-query.advanced.open-restrictions", cbOpenAndCloseRestrictions.isSelected());
+ Main.pref.put("changeset-query.advanced.time-restrictions", cbTimeRestrictions.isSelected());
+ Main.pref.put("changeset-query.advanced.bbox-restrictions", cbBoundingBoxRestriction.isSelected());
+
+ pnlUserRestriction.rememberSettings();
+ pnlOpenAndCloseRestriction.rememberSettings();
+ pnlTimeRestriction.rememberSettings();
+ }
+
+ public void restoreFromSettings() {
+ cbUserRestriction.setSelected(Main.pref.getBoolean("changeset-query.advanced.user-restrictions", false));
+ cbOpenAndCloseRestrictions.setSelected(Main.pref.getBoolean("changeset-query.advanced.open-restrictions", false));
+ cbTimeRestrictions.setSelected(Main.pref.getBoolean("changeset-query.advanced.time-restrictions", false));
+ cbBoundingBoxRestriction.setSelected(Main.pref.getBoolean("changeset-query.advanced.bbox-restrictions", false));
+ }
+
+ class RestrictionGroupStateChangeHandler implements ItemListener {
+ protected void userRestrictionStateChanged() {
+ if (pnlUserRestriction == null) return;
+ pnlUserRestriction.setVisible(cbUserRestriction.isSelected());
+ }
+
+ protected void openCloseRestrictionStateChanged() {
+ if (pnlOpenAndCloseRestriction == null) return;
+ pnlOpenAndCloseRestriction.setVisible(cbOpenAndCloseRestrictions.isSelected());
+ }
+
+ protected void timeRestrictionsStateChanged() {
+ if (pnlTimeRestriction == null) return;
+ pnlTimeRestriction.setVisible(cbTimeRestrictions.isSelected());
+ }
+
+ protected void boundingBoxRestrictionChanged() {
+ if (pnlBoundingBoxRestriction == null) return;
+ pnlBoundingBoxRestriction.setVisible(cbBoundingBoxRestriction.isSelected());
+ }
+
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getSource() == cbUserRestriction) {
+ userRestrictionStateChanged();
+ } else if (e.getSource() == cbOpenAndCloseRestrictions) {
+ openCloseRestrictionStateChanged();
+ } else if (e.getSource() == cbTimeRestrictions) {
+ timeRestrictionsStateChanged();
+ } else if (e.getSource() == cbBoundingBoxRestriction) {
+ boundingBoxRestrictionChanged();
+ }
+ validate();
+ repaint();
+ }
+ }
+
+ /**
+ * This is the panel for selecting whether the changeset query should be restricted to
+ * open or closed changesets
+ */
+ static private class OpenAndCloseStateRestrictionPanel extends JPanel {
+
+ private JRadioButton rbOpenOnly;
+ private JRadioButton rbClosedOnly;
+ private JRadioButton rbBoth;
+
+ protected void build() {
+ setLayout(new GridBagLayout());
+ setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createEmptyBorder(3,3,3,3),
+ BorderFactory.createCompoundBorder(
+ BorderFactory.createLineBorder(Color.GRAY),
+ BorderFactory.createEmptyBorder(5,5,5,5)
+ )
+ ));
+ GridBagConstraints gc = new GridBagConstraints();
+ gc.anchor = GridBagConstraints.NORTHWEST;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ add(rbOpenOnly = new JRadioButton(), gc);
+
+ gc.gridx = 1;
+ gc.weightx = 1.0;
+ add(new JMultilineLabel(tr("Query open changesets only")), gc);
+
+ gc.gridy = 1;
+ gc.gridx = 0;
+ gc.weightx = 0.0;
+ add(rbClosedOnly = new JRadioButton(), gc);
+
+ gc.gridx = 1;
+ gc.weightx = 1.0;
+ add(new JMultilineLabel(tr("Query closed changesets only")), gc);
+
+ gc.gridy = 2;
+ gc.gridx = 0;
+ gc.weightx = 0.0;
+ add(rbBoth = new JRadioButton(), gc);
+
+ gc.gridx = 1;
+ gc.weightx = 1.0;
+ add(new JMultilineLabel(tr("Query both open and closed changesets")), gc);
+
+ ButtonGroup bgRestrictions = new ButtonGroup();
+ bgRestrictions.add(rbBoth);
+ bgRestrictions.add(rbClosedOnly);
+ bgRestrictions.add(rbOpenOnly);
+ }
+
+ public OpenAndCloseStateRestrictionPanel() {
+ build();
+ }
+
+ public void startUserInput() {
+ restoreFromSettings();
+ }
+
+ public void fillInQuery(ChangesetQuery query) {
+ if (rbBoth.isSelected()) {
+ query.beingClosed(true);
+ query.beingOpen(true);
+ } else if (rbOpenOnly.isSelected()) {
+ query.beingOpen(true);
+ } else if (rbClosedOnly.isSelected()) {
+ query.beingClosed(true);
+ }
+ }
+
+ public void rememberSettings() {
+ String prefRoot = "changeset-query.advanced.open-restrictions";
+ if (rbBoth.isSelected()) {
+ Main.pref.put(prefRoot + ".query-type", "both");
+ } else if (rbOpenOnly.isSelected()) {
+ Main.pref.put(prefRoot + ".query-type", "open");
+ } else if (rbClosedOnly.isSelected()) {
+ Main.pref.put(prefRoot + ".query-type", "closed");
+ }
+ }
+
+ public void restoreFromSettings() {
+ String prefRoot = "changeset-query.advanced.open-restrictions";
+ String v = Main.pref.get(prefRoot + ".query-type", "open");
+ rbBoth.setSelected(v.equals("both"));
+ rbOpenOnly.setSelected(v.equals("open"));
+ rbClosedOnly.setSelected(v.equals("closed"));
+ }
+ }
+
+ /**
+ * This is the panel for selecting whether the query should be restricted to a specific
+ * user
+ *
+ */
+ static private class UserRestrictionPanel extends JPanel {
+ private ButtonGroup bgUserRestrictions;
+ private JRadioButton rbRestrictToMyself;
+ private JRadioButton rbRestrictToUid;
+ private JRadioButton rbRestrictToUserName;
+ private JTextField tfUid;
+ private UidInputFieldValidator valUid;
+ private JTextField tfUserName;
+ private UserNameInputValidator valUserName;
+ private JMultilineLabel lblRestrictedToMyself;
+
+ protected JPanel buildUidInputPanel() {
+ JPanel pnl = new JPanel(new GridBagLayout());
+ GridBagConstraints gc = new GridBagConstraints();
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ gc.insets = new Insets(0,0,0,3);
+ pnl.add(new JLabel(tr("User ID: ")), gc);
+
+ gc.gridx = 1;
+ pnl.add(tfUid = new JTextField(10),gc);
+ SelectAllOnFocusGainedDecorator.decorate(tfUid);
+ valUid = UidInputFieldValidator.decorate(tfUid);
+
+ // grab remaining space
+ gc.gridx = 2;
+ gc.weightx = 1.0;
+ pnl.add(new JPanel(), gc);
+ return pnl;
+ }
+
+ protected JPanel buildUserNameInputPanel() {
+ JPanel pnl = new JPanel(new GridBagLayout());
+ GridBagConstraints gc = new GridBagConstraints();
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ gc.insets = new Insets(0,0,0,3);
+ pnl.add(new JLabel(tr("User name: ")), gc);
+
+ gc.gridx = 1;
+ pnl.add(tfUserName = new JTextField(10),gc);
+ SelectAllOnFocusGainedDecorator.decorate(tfUserName);
+ valUserName = UserNameInputValidator.decorate(tfUserName);
+
+ // grab remaining space
+ gc.gridx = 2;
+ gc.weightx = 1.0;
+ pnl.add(new JPanel(), gc);
+ return pnl;
+ }
+
+ protected void build() {
+ setLayout(new GridBagLayout());
+ setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createEmptyBorder(3,3,3,3),
+ BorderFactory.createCompoundBorder(
+ BorderFactory.createLineBorder(Color.GRAY),
+ BorderFactory.createEmptyBorder(5,5,5,5)
+ )
+ ));
+
+ ItemListener userRestrictionChangeHandler = new UserRestrictionChangedHandler();
+ GridBagConstraints gc = new GridBagConstraints();
+ gc.anchor = GridBagConstraints.NORTHWEST;
+ gc.gridx = 0;
+ gc.fill= GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ add(rbRestrictToMyself = new JRadioButton(), gc);
+ rbRestrictToMyself.addItemListener(userRestrictionChangeHandler);
+
+ gc.gridx = 1;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 1.0;
+ add(lblRestrictedToMyself = new JMultilineLabel(tr("Only changesets owned by myself")), gc);
+
+ gc.gridx = 0;
+ gc.gridy = 1;
+ gc.fill= GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ add(rbRestrictToUid = new JRadioButton(), gc);
+ rbRestrictToUid.addItemListener(userRestrictionChangeHandler);
+
+ gc.gridx = 1;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 1.0;
+ add(new JMultilineLabel(tr("Only changesets owned by the user with the following user ID")),gc);
+
+ gc.gridx = 1;
+ gc.gridy = 2;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 1.0;
+ add(buildUidInputPanel(),gc);
+
+ gc.gridx = 0;
+ gc.gridy = 3;
+ gc.fill= GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ add(rbRestrictToUserName = new JRadioButton(), gc);
+ rbRestrictToUserName.addItemListener(userRestrictionChangeHandler);
+
+ gc.gridx = 1;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 1.0;
+ add(new JMultilineLabel(tr("Only changesets owned by the user with the following user name")),gc);
+
+ gc.gridx = 1;
+ gc.gridy = 4;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 1.0;
+ add(buildUserNameInputPanel(),gc);
+
+ bgUserRestrictions = new ButtonGroup();
+ bgUserRestrictions.add(rbRestrictToMyself);
+ bgUserRestrictions.add(rbRestrictToUid);
+ bgUserRestrictions.add(rbRestrictToUserName);
+ }
+
+ public UserRestrictionPanel() {
+ build();
+ }
+
+ public void startUserInput() {
+ if (JosmUserIdentityManager.getInstance().isAnonymous()) {
+ lblRestrictedToMyself.setText("Only changesets owned by myself (disabled. JOSM is currently run by an anonymous user)");
+ rbRestrictToMyself.setEnabled(false);
+ if (rbRestrictToMyself.isSelected()) {
+ rbRestrictToUid.setSelected(true);
+ }
+ } else {
+ lblRestrictedToMyself.setText("Only changesets owned by myself");
+ rbRestrictToMyself.setEnabled(true);
+ rbRestrictToMyself.setSelected(true);
+ }
+ restoreFromSettings();
+ }
+
+ /**
+ * Sets the query restrictions on query
for changeset owner based
+ * restrictions.
+ *
+ * @param query the query. Must not be null.
+ * @throws IllegalArgumentException thrown if query is null
+ * @throws IllegalStateException thrown if one of the available values for query parameters in
+ * this panel isn't valid
+ *
+ */
+ public void fillInQuery(ChangesetQuery query) throws IllegalStateException, IllegalArgumentException {
+ CheckParameterUtil.ensureParameterNotNull(query, "query");
+ if (rbRestrictToMyself.isSelected()) {
+ JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
+ if (im.isPartiallyIdentified()) {
+ query.forUser(im.getUserName());
+ } else if (im.isFullyIdentified()) {
+ query.forUser(im.getUserId());
+ } else
+ throw new IllegalStateException(tr("Can't restrict changeset query to the current user because the current user is anonymous"));
+ } else if (rbRestrictToUid.isSelected()) {
+ int uid = valUid.getUid();
+ if (uid > 0) {
+ query.forUser(uid);
+ } else
+ throw new IllegalStateException(tr("Current value ''{0}'' for user ID isn''t valid", tfUid.getText()));
+ } else if (rbRestrictToUserName.isSelected()) {
+ if (! valUserName.isValid())
+ throw new IllegalStateException(tr("Can''t restrict the changeset query to the user name ''{0}''", tfUserName.getText()));
+ query.forUser(tfUserName.getText());
+ }
+ }
+
+
+ public boolean isValidChangesetQuery() {
+ if (rbRestrictToUid.isSelected())
+ return valUid.isValid();
+ else if (rbRestrictToUserName.isSelected())
+ return valUserName.isValid();
+ return true;
+ }
+
+ protected void alertInvalidUid() {
+ HelpAwareOptionPane.showOptionDialog(
+ this,
+ tr("Please enter a valid user ID"),
+ tr("Invalid user ID"),
+ JOptionPane.ERROR_MESSAGE,
+ HelpUtil.ht("/Dialog/ChangesetQueryDialog#InvalidUserId")
+ );
+ }
+
+ protected void alertInvalidUserName() {
+ HelpAwareOptionPane.showOptionDialog(
+ this,
+ tr("Please enter a non-empty user name"),
+ tr("Invalid user name"),
+ JOptionPane.ERROR_MESSAGE,
+ HelpUtil.ht("/Dialog/ChangesetQueryDialog#InvalidUserName")
+ );
+ }
+
+ public void displayMessageIfInvalid() {
+ if (rbRestrictToUid.isSelected()) {
+ if (!valUid.isValid()) {
+ alertInvalidUid();
+ }
+ } else if (rbRestrictToUserName.isSelected()) {
+ if (!valUserName.isValid()) {
+ alertInvalidUserName();
+ }
+ }
+ }
+
+ public void rememberSettings() {
+ String prefRoot = "changeset-query.advanced.user-restrictions";
+ if (rbRestrictToMyself.isSelected()) {
+ Main.pref.put(prefRoot + ".query-type", "mine");
+ } else if (rbRestrictToUid.isSelected()) {
+ Main.pref.put(prefRoot + ".query-type", "uid");
+ } else if (rbRestrictToUserName.isSelected()) {
+ Main.pref.put(prefRoot + ".query-type", "username");
+ }
+ Main.pref.put(prefRoot + ".uid", tfUid.getText());
+ Main.pref.put(prefRoot + ".username", tfUserName.getText());
+ }
+
+ public void restoreFromSettings() {
+ String prefRoot = "changeset-query.advanced.user-restrictions";
+ String v = Main.pref.get(prefRoot + ".query-type", "mine");
+ if (v.equals("mine")) {
+ JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
+ if (im.isAnonymous()) {
+ rbRestrictToUid.setSelected(true);
+ } else {
+ rbRestrictToMyself.setSelected(true);
+ }
+ } else if (v.equals("uid")) {
+ rbRestrictToUid.setSelected(true);
+ } else if (v.equals("username")) {
+ rbRestrictToUserName.setSelected(true);
+ }
+ tfUid.setText(Main.pref.get(prefRoot + ".uid", ""));
+ if (!valUid.isValid()) {
+ tfUid.setText("");
+ }
+ tfUserName.setText(Main.pref.get(prefRoot + ".username", ""));
+ }
+
+ class UserRestrictionChangedHandler implements ItemListener {
+ public void itemStateChanged(ItemEvent e) {
+ tfUid.setEnabled(rbRestrictToUid.isSelected());
+ tfUserName.setEnabled(rbRestrictToUserName.isSelected());
+ if (rbRestrictToUid.isSelected()) {
+ tfUid.requestFocusInWindow();
+ } else if (rbRestrictToUserName.isSelected()) {
+ tfUserName.requestFocusInWindow();
+ }
+ }
+ }
+ }
+
+ /**
+ * This is the panel to apply a time restriction to the changeset query
+ */
+ static private class TimeRestrictionPanel extends JPanel {
+
+ private JRadioButton rbClosedAfter;
+ private JRadioButton rbClosedAfterAndCreatedBefore;
+ private JTextField tfClosedAfterDate1;
+ private DateValidator valClosedAfterDate1;
+ private JTextField tfClosedAfterTime1;
+ private TimeValidator valClosedAfterTime1;
+ private JTextField tfClosedAfterDate2;
+ private DateValidator valClosedAfterDate2;
+ private JTextField tfClosedAfterTime2;
+ private TimeValidator valClosedAfterTime2;
+ private JTextField tfCreatedBeforeDate;
+ private DateValidator valCreatedBeforeDate;
+ private JTextField tfCreatedBeforeTime;
+ private TimeValidator valCreatedBeforeTime;
+
+ protected JPanel buildClosedAfterInputPanel() {
+ JPanel pnl = new JPanel(new GridBagLayout());
+ GridBagConstraints gc = new GridBagConstraints();
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ gc.insets = new Insets(0,0,0,3);
+ pnl.add(new JLabel(tr("Date: ")), gc);
+
+ gc.gridx = 1;
+ gc.weightx = 0.7;
+ pnl.add(tfClosedAfterDate1 = new JTextField(),gc);
+ SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterDate1);
+ valClosedAfterDate1 = DateValidator.decorate(tfClosedAfterDate1);
+ tfClosedAfterDate1.setToolTipText(valClosedAfterDate1.getStandardTooltipTextAsHtml());
+
+ gc.gridx = 2;
+ gc.weightx = 0.0;
+ pnl.add(new JLabel("Time: "),gc);
+
+ gc.gridx = 3;
+ gc.weightx = 0.3;
+ pnl.add(tfClosedAfterTime1 = new JTextField(),gc);
+ SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterTime1);
+ valClosedAfterTime1 = TimeValidator.decorate(tfClosedAfterTime1);
+ tfClosedAfterTime1.setToolTipText(valClosedAfterTime1.getStandardTooltipTextAsHtml());
+ return pnl;
+ }
+
+ protected JPanel buildClosedAfterAndCreatedBeforeInputPanel() {
+ JPanel pnl = new JPanel(new GridBagLayout());
+ GridBagConstraints gc = new GridBagConstraints();
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ gc.insets = new Insets(0,0,0,3);
+ pnl.add(new JLabel(tr("Closed after - ")), gc);
+
+ gc.gridx = 1;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ gc.insets = new Insets(0,0,0,3);
+ pnl.add(new JLabel(tr("Date: ")), gc);
+
+ gc.gridx = 2;
+ gc.weightx = 0.7;
+ pnl.add(tfClosedAfterDate2 = new JTextField(),gc);
+ SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterDate2);
+ valClosedAfterDate2 = DateValidator.decorate(tfClosedAfterDate2);
+ tfClosedAfterDate2.setToolTipText(valClosedAfterDate2.getStandardTooltipTextAsHtml());
+ gc.gridx = 3;
+ gc.weightx = 0.0;
+ pnl.add(new JLabel("Time: "),gc);
+
+ gc.gridx = 4;
+ gc.weightx = 0.3;
+ pnl.add(tfClosedAfterTime2 = new JTextField(),gc);
+ SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterTime2);
+ valClosedAfterTime2 = TimeValidator.decorate(tfClosedAfterTime2);
+ tfClosedAfterTime2.setToolTipText(valClosedAfterTime2.getStandardTooltipTextAsHtml());
+
+ gc.gridy = 1;
+ gc.gridx = 0;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ gc.insets = new Insets(0,0,0,3);
+ pnl.add(new JLabel(tr("Created before - ")), gc);
+
+ gc.gridx = 1;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ gc.insets = new Insets(0,0,0,3);
+ pnl.add(new JLabel(tr("Date: ")), gc);
+
+ gc.gridx = 2;
+ gc.weightx = 0.7;
+ pnl.add(tfCreatedBeforeDate = new JTextField(),gc);
+ SelectAllOnFocusGainedDecorator.decorate(tfCreatedBeforeDate);
+ valCreatedBeforeDate = DateValidator.decorate(tfCreatedBeforeDate);
+ tfCreatedBeforeDate.setToolTipText(valCreatedBeforeDate.getStandardTooltipTextAsHtml());
+
+ gc.gridx = 3;
+ gc.weightx = 0.0;
+ pnl.add(new JLabel("Time: "),gc);
+
+ gc.gridx = 4;
+ gc.weightx = 0.3;
+ pnl.add(tfCreatedBeforeTime = new JTextField(),gc);
+ SelectAllOnFocusGainedDecorator.decorate(tfCreatedBeforeTime);
+ valCreatedBeforeTime = TimeValidator.decorate(tfCreatedBeforeTime);
+ tfCreatedBeforeTime.setToolTipText(valCreatedBeforeDate.getStandardTooltipTextAsHtml());
+
+ return pnl;
+ }
+
+ protected void build() {
+ setLayout(new GridBagLayout());
+ setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createEmptyBorder(3,3,3,3),
+ BorderFactory.createCompoundBorder(
+ BorderFactory.createLineBorder(Color.GRAY),
+ BorderFactory.createEmptyBorder(5,5,5,5)
+ )
+ ));
+
+ // -- changesets closed after a specific date/time
+ //
+ GridBagConstraints gc = new GridBagConstraints();
+ gc.anchor = GridBagConstraints.NORTHWEST;
+ gc.gridx = 0;
+ gc.fill= GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ add(rbClosedAfter = new JRadioButton(), gc);
+
+ gc.gridx = 1;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 1.0;
+ add(new JMultilineLabel(tr("Only changesets closed after the following date/time")), gc);
+
+ gc.gridx = 1;
+ gc.gridy = 1;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 1.0;
+ add(buildClosedAfterInputPanel(),gc);
+
+ // -- changesets closed after a specific date/time and created before a specific date time
+ //
+ gc = new GridBagConstraints();
+ gc.anchor = GridBagConstraints.NORTHWEST;
+ gc.gridy = 2;
+ gc.gridx = 0;
+ gc.fill= GridBagConstraints.HORIZONTAL;
+ gc.weightx = 0.0;
+ add(rbClosedAfterAndCreatedBefore = new JRadioButton(), gc);
+
+ gc.gridx = 1;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 1.0;
+ add(new JMultilineLabel(tr("Only changesets closed after and created before a specific date/time")), gc);
+
+ gc.gridx = 1;
+ gc.gridy = 3;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 1.0;
+ add(buildClosedAfterAndCreatedBeforeInputPanel(),gc);
+
+ ButtonGroup bg = new ButtonGroup();
+ bg.add(rbClosedAfter);
+ bg.add(rbClosedAfterAndCreatedBefore);
+
+ ItemListener restrictionChangeHandler = new TimeRestrictionChangedHandler();
+ rbClosedAfter.addItemListener(restrictionChangeHandler);
+ rbClosedAfterAndCreatedBefore.addItemListener(restrictionChangeHandler);
+
+ rbClosedAfter.setSelected(true);
+ }
+
+ public TimeRestrictionPanel() {
+ build();
+ }
+
+ public boolean isValidChangesetQuery() {
+ if (rbClosedAfter.isSelected())
+ return valClosedAfterDate1.isValid() && valClosedAfterTime1.isValid();
+ else if (rbClosedAfterAndCreatedBefore.isSelected())
+ return valClosedAfterDate2.isValid() && valClosedAfterTime2.isValid()
+ && valCreatedBeforeDate.isValid() && valCreatedBeforeTime.isValid();
+ // should not happen
+ return true;
+ }
+
+ class TimeRestrictionChangedHandler implements ItemListener {
+ public void itemStateChanged(ItemEvent e) {
+ tfClosedAfterDate1.setEnabled(rbClosedAfter.isSelected());
+ tfClosedAfterTime1.setEnabled(rbClosedAfter.isSelected());
+
+ tfClosedAfterDate2.setEnabled(rbClosedAfterAndCreatedBefore.isSelected());
+ tfClosedAfterTime2.setEnabled(rbClosedAfterAndCreatedBefore.isSelected());
+ tfCreatedBeforeDate.setEnabled(rbClosedAfterAndCreatedBefore.isSelected());
+ tfCreatedBeforeTime.setEnabled(rbClosedAfterAndCreatedBefore.isSelected());
+ }
+ }
+
+ public void startUserInput() {
+ restoreFromSettings();
+ }
+
+ public void fillInQuery(ChangesetQuery query) throws IllegalStateException{
+ if (!isValidChangesetQuery())
+ throw new IllegalStateException(tr("Can't build changeset query with time based restrictions. Input isn't valid."));
+ if (rbClosedAfter.isSelected()) {
+ GregorianCalendar cal = new GregorianCalendar();
+ Date d1 = valClosedAfterDate1.getDate();
+ Date d2 = valClosedAfterTime1.getDate();
+ cal.setTimeInMillis(d1.getTime() + (d2 == null ? 0 : d2.getTime()));
+ query.closedAfter(cal.getTime());
+ } else if (rbClosedAfterAndCreatedBefore.isSelected()) {
+ GregorianCalendar cal = new GregorianCalendar();
+ Date d1 = valClosedAfterDate2.getDate();
+ Date d2 = valClosedAfterTime2.getDate();
+ cal.setTimeInMillis(d1.getTime() + (d2 == null ? 0 : d2.getTime()));
+ Date d3 = cal.getTime();
+
+ d1 = valCreatedBeforeDate.getDate();
+ d2 = valCreatedBeforeTime.getDate();
+ cal.setTimeInMillis(d1.getTime() + (d2 == null ? 0 : d2.getTime()));
+ Date d4 = cal.getTime();
+
+ query.closedAfterAndCreatedBefore(d3, d4);
+ }
+ }
+
+ public void displayMessageIfInvalid() {
+ if (isValidChangesetQuery()) return;
+ HelpAwareOptionPane.showOptionDialog(
+ this,
+ tr(
+ "Please enter valid date/time values to restrict
"
+ + "the query to a specific time range."
+ ),
+ tr("Invalid date/time values"),
+ JOptionPane.ERROR_MESSAGE,
+ HelpUtil.ht("/Dialog/ChangesetQueryDialog#InvalidDateTimeValues")
+ );
+ }
+
+
+ public void rememberSettings() {
+ String prefRoot = "changeset-query.advanced.time-restrictions";
+ if (rbClosedAfter.isSelected()) {
+ Main.pref.put(prefRoot + ".query-type", "closed-after");
+ } else if (rbClosedAfterAndCreatedBefore.isSelected()) {
+ Main.pref.put(prefRoot + ".query-type", "closed-after-created-before");
+ }
+ Main.pref.put(prefRoot + ".closed-after.date", tfClosedAfterDate1.getText());
+ Main.pref.put(prefRoot + ".closed-after.time", tfClosedAfterTime1.getText());
+ Main.pref.put(prefRoot + ".closed-created.closed.date", tfClosedAfterDate2.getText());
+ Main.pref.put(prefRoot + ".closed-created.closed.time", tfClosedAfterTime2.getText());
+ Main.pref.put(prefRoot + ".closed-created.created.date", tfCreatedBeforeDate.getText());
+ Main.pref.put(prefRoot + ".closed-created.created.time", tfCreatedBeforeTime.getText());
+ }
+
+ public void restoreFromSettings() {
+ String prefRoot = "changeset-query.advanced.open-restrictions";
+ String v = Main.pref.get(prefRoot + ".query-type", "closed-after");
+ rbClosedAfter.setSelected(v.equals("closed-after"));
+ rbClosedAfterAndCreatedBefore.setSelected(v.equals("closed-after-created-before"));
+ if (!rbClosedAfter.isSelected() && !rbClosedAfterAndCreatedBefore.isSelected()) {
+ rbClosedAfter.setSelected(true);
+ }
+ tfClosedAfterDate1.setText(Main.pref.get(prefRoot + ".closed-after.date", ""));
+ tfClosedAfterTime1.setText(Main.pref.get(prefRoot + ".closed-after.time", ""));
+ tfClosedAfterDate2.setText(Main.pref.get(prefRoot + ".closed-created.closed.date", ""));
+ tfClosedAfterTime2.setText(Main.pref.get(prefRoot + ".closed-created.closed.time", ""));
+ tfCreatedBeforeDate.setText(Main.pref.get(prefRoot + ".closed-created.created.date", ""));
+ tfCreatedBeforeTime.setText(Main.pref.get(prefRoot + ".closed-created.created.time", ""));
+ if (!valClosedAfterDate1.isValid()) {
+ tfClosedAfterDate1.setText("");
+ }
+ if (!valClosedAfterTime1.isValid()) {
+ tfClosedAfterTime1.setText("");
+ }
+ if (!valClosedAfterDate2.isValid()) {
+ tfClosedAfterDate2.setText("");
+ }
+ if (!valClosedAfterTime2.isValid()) {
+ tfClosedAfterTime2.setText("");
+ }
+ if (!valCreatedBeforeDate.isValid()) {
+ tfCreatedBeforeDate.setText("");
+ }
+ if (!valCreatedBeforeTime.isValid()) {
+ tfCreatedBeforeTime.setText("");
+ }
+ }
+ }
+
+ static private class BBoxRestrictionPanel extends BoundingBoxSelectionPanel {
+ public BBoxRestrictionPanel() {
+ setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createEmptyBorder(3,3,3,3),
+ BorderFactory.createCompoundBorder(
+ BorderFactory.createLineBorder(Color.GRAY),
+ BorderFactory.createEmptyBorder(5,5,5,5)
+ )
+ ));
+ }
+
+ public boolean isValidChangesetQuery() {
+ return getBoundingBox() != null;
+ }
+
+ public void fillInQuery(ChangesetQuery query) {
+ if (!isValidChangesetQuery())
+ throw new IllegalStateException(tr("Can''t restrict the changeset query to a specific bounding box. The input is invalid."));
+ query.inBbox(getBoundingBox());
+ }
+
+ public void displayMessageIfInvalid() {
+ if (isValidChangesetQuery()) return;
+ HelpAwareOptionPane.showOptionDialog(
+ this,
+ tr(
+ "Please enter valid longitude/latitude values to restrict
" +
+ "the changeset query to a specific bounding box."
+ ),
+ tr("Invalid bounding box"),
+ JOptionPane.ERROR_MESSAGE,
+ HelpUtil.ht("/Dialog/ChangesetQueryDialog#InvalidBoundingBox")
+ );
+ }
+ }
+
+ static private class QuerySpecificationPanel extends JPanel implements Scrollable {
+ public Dimension getPreferredScrollableViewportSize() {
+ return getPreferredSize();
+ }
+
+ public int getScrollableBlockIncrement(Rectangle arg0, int arg1, int arg2) {
+ return 20;
+ }
+
+ public boolean getScrollableTracksViewportHeight() {
+ return false;
+ }
+
+ public boolean getScrollableTracksViewportWidth() {
+ return true;
+ }
+
+ public int getScrollableUnitIncrement(Rectangle arg0, int arg1, int arg2) {
+ return 10;
+ }
+ }
+
+ /**
+ * Validator for user ids entered in in a {@see JTextComponent}.
+ *
+ */
+ static private class UidInputFieldValidator extends AbstractTextComponentValidator {
+ static public UidInputFieldValidator decorate(JTextComponent tc) {
+ return new UidInputFieldValidator(tc);
+ }
+
+ public UidInputFieldValidator(JTextComponent tc) {
+ super(tc);
+ }
+
+ @Override
+ public boolean isValid() {
+ return getUid() > 0;
+ }
+
+ @Override
+ public void validate() {
+ String value = getComponent().getText();
+ if (value == null || value.trim().length() == 0) {
+ feedbackInvalid("");
+ return;
+ }
+ try {
+ int uid = Integer.parseInt(value);
+ if (uid <= 0) {
+ feedbackInvalid(tr("The current value isn't a valid user ID. Please enter an integer value > 0"));
+ return;
+ }
+ } catch(NumberFormatException e) {
+ feedbackInvalid(tr("The current value isn't a valid user ID. Please enter an integer value > 0"));
+ return;
+ }
+ feedbackValid(tr("Please enter an integer value > 0"));
+ }
+
+ public int getUid() {
+ String value = getComponent().getText();
+ if (value == null || value.trim().length() == 0) return 0;
+ try {
+ int uid = Integer.parseInt(value.trim());
+ if (uid > 0) return uid;
+ return 0;
+ } catch(NumberFormatException e) {
+ return 0;
+ }
+ }
+ }
+
+ static private class UserNameInputValidator extends AbstractTextComponentValidator {
+ static public UserNameInputValidator decorate(JTextComponent tc) {
+ return new UserNameInputValidator(tc);
+ }
+
+ public UserNameInputValidator(JTextComponent tc) {
+ super(tc);
+ }
+
+ @Override
+ public boolean isValid() {
+ return getComponent().getText().trim().length() > 0;
+ }
+
+ @Override
+ public void validate() {
+ String value = getComponent().getText();
+ if (value.trim().length() == 0) {
+ feedbackInvalid(tr("The current value isn't a valid user name.
Please enter an non-empty user name."));
+ return;
+ }
+ feedbackValid(tr("Please enter an non-empty user name"));
+ }
+ }
+
+ /**
+ * Validates dates entered as text in in a {@see JTextComponent}. Validates the input
+ * on the fly and gives feedback about whether the date is valid or not.
+ *
+ * Dates can be entered in one of four standard formats defined for the current locale.
+ */
+ static private class DateValidator extends AbstractTextComponentValidator {
+ static public DateValidator decorate(JTextComponent tc) {
+ return new DateValidator(tc);
+ }
+
+ public DateValidator(JTextComponent tc) {
+ super(tc);
+ }
+
+ @Override
+ public boolean isValid() {
+ return getDate() != null;
+ }
+
+ public String getStandardTooltipTextAsHtml() {
+ return "" + getStandardTooltipText() + "";
+ }
+
+ public String getStandardTooltipText() {
+ return tr(
+ "Please enter a date in the usual format for your locale.
"
+ + "Example: {0}
"
+ + "Example: {1}
"
+ + "Example: {2}
"
+ + "Example: {3}
",
+ DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()).format(new Date()),
+ DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()).format(new Date()),
+ DateFormat.getDateInstance(DateFormat.LONG, Locale.getDefault()).format(new Date()),
+ DateFormat.getDateInstance(DateFormat.FULL, Locale.getDefault()).format(new Date())
+ );
+ }
+
+ @Override
+ public void validate() {
+ if (!isValid()) {
+ String msg = "The current value isn't a valid date.
" + getStandardTooltipText()+ "";
+ feedbackInvalid(msg);
+ return;
+ } else {
+ String msg = "" + getStandardTooltipText() + "";
+ feedbackValid(msg);
+ }
+ }
+
+ public Date getDate() {
+ for (int i = 0; i< 4; i++) {
+ try {
+ DateFormat df = null;
+ switch(i) {
+ case 0: df = DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()); break;
+ case 1: df = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()); break;
+ case 2: df = DateFormat.getDateInstance(DateFormat.LONG, Locale.getDefault()); break;
+ case 3: df = DateFormat.getDateInstance(DateFormat.FULL,Locale.getDefault()); break;
+ }
+ return df.parse(getComponent().getText());
+ } catch(ParseException e) {
+ continue;
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Validates time values entered as text in in a {@see JTextComponent}. Validates the input
+ * on the fly and gives feedback about whether the time value is valid or not.
+ *
+ * Time values can be entered in one of four standard formats defined for the current locale.
+ */
+ static private class TimeValidator extends AbstractTextComponentValidator {
+ static public TimeValidator decorate(JTextComponent tc) {
+ return new TimeValidator(tc);
+ }
+
+ public TimeValidator(JTextComponent tc) {
+ super(tc);
+ }
+
+ @Override
+ public boolean isValid() {
+ if (getComponent().getText().trim().length() == 0) return true;
+ return getDate() != null;
+ }
+
+ public String getStandardTooltipTextAsHtml() {
+ return "" + getStandardTooltipText() + "";
+ }
+
+ public String getStandardTooltipText() {
+ return tr(
+ "Please enter a valid time in the usual format for your locale.
"
+ + "Example: {0}
"
+ + "Example: {1}
"
+ + "Example: {2}
"
+ + "Example: {3}
",
+ DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault()).format(new Date()),
+ DateFormat.getTimeInstance(DateFormat.MEDIUM, Locale.getDefault()).format(new Date()),
+ DateFormat.getTimeInstance(DateFormat.LONG, Locale.getDefault()).format(new Date()),
+ DateFormat.getTimeInstance(DateFormat.FULL, Locale.getDefault()).format(new Date())
+ );
+ }
+
+ @Override
+ public void validate() {
+
+ if (!isValid()) {
+ String msg = "The current value isn't a valid time.
" + getStandardTooltipText() + "";
+ feedbackInvalid(msg);
+ return;
+ } else {
+ String msg = "" + getStandardTooltipText() + "";
+ feedbackValid(msg);
+ }
+ }
+
+ public Date getDate() {
+ if (getComponent().getText().trim().length() == 0)
+ return null;
+
+ for (int i = 0; i< 4; i++) {
+ try {
+ DateFormat df = null;
+ switch(i) {
+ case 0: df = DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault()); break;
+ case 1: df = DateFormat.getTimeInstance(DateFormat.MEDIUM, Locale.getDefault()); break;
+ case 2: df = DateFormat.getTimeInstance(DateFormat.LONG, Locale.getDefault()); break;
+ case 3: df = DateFormat.getTimeInstance(DateFormat.FULL,Locale.getDefault()); break;
+ }
+ Date d = df.parse(getComponent().getText());
+ return d;
+ } catch(ParseException e) {
+ continue;
+ }
+ }
+ return null;
+ }
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/BasicChangesetQueryPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/BasicChangesetQueryPanel.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/BasicChangesetQueryPanel.java (revision 2689)
@@ -0,0 +1,260 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset.query;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.JCheckBox;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.gui.JMultilineLabel;
+import org.openstreetmap.josm.gui.JosmUserIdentityManager;
+import org.openstreetmap.josm.gui.widgets.HtmlPanel;
+import org.openstreetmap.josm.io.ChangesetQuery;
+
+/**
+ * This panel presents a list of basic queries for changests.
+ *
+ */
+public class BasicChangesetQueryPanel extends JPanel {
+
+ /**
+ * Enumeration of basic, predefined queries
+ */
+ private static enum BasicQuery {
+ MOST_RECENT_CHANGESETS,
+ MY_OPEN_CHANGESETS,
+ CHANGESETS_IN_MAP_VIEW;
+ }
+
+ private ButtonGroup bgQueries;
+ private Map rbQueries;
+ private Map lblQueries;
+ private JCheckBox cbMyChangesetsOnly;
+ private HtmlPanel pnlInfos;
+
+
+ protected JPanel buildQueriesPanel() {
+ JPanel pnl = new JPanel(new GridBagLayout());
+
+ bgQueries = new ButtonGroup();
+ rbQueries = new HashMap();
+ lblQueries = new HashMap();
+ SelectQueryHandler selectedQueryHandler = new SelectQueryHandler();
+ for (BasicQuery q: BasicQuery.values()) {
+ JRadioButton rb = new JRadioButton();
+ rb.addItemListener(selectedQueryHandler);
+ rbQueries.put(q, rb);
+ bgQueries.add(rb);
+ lblQueries.put(q, new JMultilineLabel(""));
+ }
+
+ GridBagConstraints gc = new GridBagConstraints();
+ // -- most recent changes
+ gc.fill = GridBagConstraints.NONE;
+ gc.anchor = GridBagConstraints.NORTHWEST;
+ gc.insets = new Insets(0,0,5,3);
+ pnl.add(rbQueries.get(BasicQuery.MOST_RECENT_CHANGESETS), gc);
+
+ gc.gridx = 1;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 1.0;
+ pnl.add(lblQueries.get(BasicQuery.MOST_RECENT_CHANGESETS), gc);
+
+ // -- most recent changes
+ gc.gridx = 0;
+ gc.gridy = 1;
+ gc.fill = GridBagConstraints.NONE;
+ gc.weightx = 0.0;
+ pnl.add(rbQueries.get(BasicQuery.MY_OPEN_CHANGESETS), gc);
+
+ gc.gridx = 1;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 1.0;
+ pnl.add(lblQueries.get(BasicQuery.MY_OPEN_CHANGESETS), gc);
+
+ // -- changesets in map view
+ gc.gridx = 0;
+ gc.gridy = 2;
+ gc.fill = GridBagConstraints.NONE;
+ gc.weightx = 0.0;
+ pnl.add(rbQueries.get(BasicQuery.CHANGESETS_IN_MAP_VIEW), gc);
+
+ gc.gridx = 1;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 1.0;
+ pnl.add(lblQueries.get(BasicQuery.CHANGESETS_IN_MAP_VIEW), gc);
+
+ // -- checkbox my changesets only
+ gc.gridx = 0;
+ gc.gridy = 3;
+ gc.gridwidth = 2;
+ gc.insets = new Insets(5,0,3,3);
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.weightx = 1.0;
+ pnl.add(cbMyChangesetsOnly = new JCheckBox(tr("Download my changesets only")), gc);
+ cbMyChangesetsOnly.setToolTipText(tr("Select to restrict the query to your changsets only.
Unselect to include all changesets in the query."));
+
+ // grab remaining space
+ gc.gridx = 0;
+ gc.gridy = 4;
+ gc.gridwidth = 2;
+ gc.insets = new Insets(5,0,3,3);
+ gc.fill = GridBagConstraints.BOTH;
+ gc.weightx = 1.0;
+ gc.weighty = 1.0;
+ pnl.add(new JPanel(), gc);
+
+ return pnl;
+ }
+
+ protected JPanel buildInfoPanel() {
+ pnlInfos = new HtmlPanel();
+ pnlInfos.setText("Please select one the following standard queries."
+ + "Select Download my changesets only"
+ + " if you only want to download changesets created by yourself.
"
+ + "Note that JOSM will download max. 100 changesets."
+ );
+ return pnlInfos;
+ }
+
+ protected void build() {
+ setLayout(new BorderLayout(0,5));
+ setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+ add(buildInfoPanel(), BorderLayout.NORTH);
+ add(buildQueriesPanel(), BorderLayout.CENTER);
+ }
+
+ public BasicChangesetQueryPanel() {
+ build();
+ }
+
+ public void init() {
+ JMultilineLabel lbl = lblQueries.get(BasicQuery.MOST_RECENT_CHANGESETS);
+ lbl.setText("Download the latest changesets");
+
+ // query for open changesets only possible if we have a current user which is at least
+ // partially identified
+ lbl = lblQueries.get(BasicQuery.MY_OPEN_CHANGESETS);
+ if (JosmUserIdentityManager.getInstance().isAnonymous()) {
+ rbQueries.get(BasicQuery.MY_OPEN_CHANGESETS).setEnabled(false);
+ lbl.setText("Download my open changesets
Disabled. Please enter your OSM user name in the preferences first.");
+ } else {
+ rbQueries.get(BasicQuery.MY_OPEN_CHANGESETS).setEnabled(true);
+ lbl.setText("Download my open changesets");
+ }
+
+ // query for changesets in the current map view only if there *is* a current
+ // map view
+ lbl = lblQueries.get(BasicQuery.CHANGESETS_IN_MAP_VIEW);
+ if (Main.map == null || Main.map.mapView == null) {
+ rbQueries.get(BasicQuery.CHANGESETS_IN_MAP_VIEW).setEnabled(false);
+ lbl.setText("Download changesets in the current map view.
Disabled. There is currently no map view active.");
+ } else {
+ rbQueries.get(BasicQuery.CHANGESETS_IN_MAP_VIEW).setEnabled(true);
+ lbl.setText("Download changesets in the current map view");
+ }
+
+ restoreFromPreferences();
+ }
+
+ public void rememberInPreferences() {
+ BasicQuery q = getSelectedQuery();
+ if (q == null) {
+ Main.pref.put("changeset-query.basic.query", null);
+ } else {
+ Main.pref.put("changeset-query.basic.query", q.toString());
+ }
+ Main.pref.put("changeset-query.basic.my-changesets-only", cbMyChangesetsOnly.isSelected());
+ }
+
+ public void restoreFromPreferences() {
+ BasicQuery q;
+ String value = Main.pref.get("changeset-query.basic.query", null);
+ if (value == null) {
+ q = BasicQuery.MOST_RECENT_CHANGESETS;
+ } else {
+ try {
+ q = BasicQuery.valueOf(BasicQuery.class, value);
+ } catch(IllegalArgumentException e) {
+ System.err.println(tr("Warning: unexpected value for preference ''{0}'', got ''{1}''. Resetting to default query.","changeset-query.basic.query", value));
+ q = BasicQuery.MOST_RECENT_CHANGESETS;
+ }
+ }
+ rbQueries.get(q).setSelected(true);
+ boolean mineOnly = Main.pref.getBoolean("changeset-query.basic.my-changesets-only", false);
+ mineOnly = mineOnly || q.equals(BasicQuery.MY_OPEN_CHANGESETS);
+ cbMyChangesetsOnly.setSelected(mineOnly);
+ }
+
+ protected BasicQuery getSelectedQuery() {
+ for (BasicQuery q : BasicQuery.values()) {
+ if (rbQueries.get(q).isSelected())
+ return q;
+ }
+ return null;
+ }
+
+ public ChangesetQuery buildChangesetQuery() {
+ BasicQuery q = getSelectedQuery();
+ ChangesetQuery query = new ChangesetQuery();
+ JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
+ if (q == null)
+ return query;
+ switch(q) {
+ case MOST_RECENT_CHANGESETS:
+ break;
+ case MY_OPEN_CHANGESETS:
+ query = query.beingOpen(true);
+ break;
+ case CHANGESETS_IN_MAP_VIEW:
+ Bounds b = Main.map.mapView.getLatLonBounds(Main.map.mapView.getBounds());
+ query = query.inBbox(b);
+ break;
+ }
+
+ if (cbMyChangesetsOnly.isSelected()) {
+ if (im.isPartiallyIdentified()) {
+ query = query.forUser(im.getUserName());
+ } else if (im.isFullyIdentified()) {
+ query = query.forUser(im.getUserId()).beingOpen(true);
+ } else
+ // anonymous -- should not happen. Message doesn't have to be translated.
+ throw new IllegalStateException("Cannot create changeset query for open changesets of anonymous user");
+ }
+
+ return query;
+ }
+
+ /**
+ * Responds to changes in the selected query
+ *
+ */
+ class SelectQueryHandler implements ItemListener {
+ public void itemStateChanged(ItemEvent e) {
+ BasicQuery q = getSelectedQuery();
+ if (q == null) return;
+ if (q.equals(BasicQuery.MY_OPEN_CHANGESETS)) {
+ cbMyChangesetsOnly.setSelected(true);
+ cbMyChangesetsOnly.setEnabled(false);
+ } else {
+ if (! cbMyChangesetsOnly.isEnabled()) {
+ cbMyChangesetsOnly.setEnabled(true);
+ }
+ }
+ }
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/ChangesetQueryDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/ChangesetQueryDialog.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/ChangesetQueryDialog.java (revision 2689)
@@ -0,0 +1,228 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset.query;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Dialog;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+import javax.swing.KeyStroke;
+
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
+import org.openstreetmap.josm.gui.help.HelpUtil;
+import org.openstreetmap.josm.io.ChangesetQuery;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.WindowGeometry;
+
+/**
+ * This is a modal dialog for entering query criteria to search for changesets.
+ *
+ */
+public class ChangesetQueryDialog extends JDialog {
+
+ private JTabbedPane tpQueryPanels;
+ private BasicChangesetQueryPanel pnlBasicChangesetQueries;
+ private UrlBasedQueryPanel pnlUrlBasedQueries;
+ private AdvancedChangesetQueryPanel pnlAdvancedQueries;
+ private boolean canceled;
+
+ protected JPanel buildContentPanel() {
+ tpQueryPanels = new JTabbedPane();
+ tpQueryPanels.add(pnlBasicChangesetQueries = new BasicChangesetQueryPanel());
+ tpQueryPanels.add(pnlUrlBasedQueries = new UrlBasedQueryPanel());
+ tpQueryPanels.add(pnlAdvancedQueries = new AdvancedChangesetQueryPanel());
+
+ tpQueryPanels.setTitleAt(0, tr("Basic"));
+ tpQueryPanels.setToolTipTextAt(0, tr("Download changesets using a predefined queries"));
+
+ tpQueryPanels.setTitleAt(1, tr("From URL"));
+ tpQueryPanels.setToolTipTextAt(1, tr("Query changesets according to an URL of the OSM server"));
+
+ tpQueryPanels.setTitleAt(2, tr("Advanced"));
+ tpQueryPanels.setToolTipTextAt(2, tr("Query changesets according to advanced query parameters"));
+
+ JPanel pnl = new JPanel(new BorderLayout());
+ pnl.add(tpQueryPanels, BorderLayout.CENTER);
+ return pnl;
+ }
+
+ protected JPanel buildButtonPanel() {
+ JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
+
+ // -- query action
+ pnl.add(new SideButton(new QueryAction()));
+
+ // -- cancel action
+ pnl.add(new SideButton(new CancelAction()));
+
+ // -- help action
+ pnl.add(new SideButton(new ContextSensitiveHelpAction(HelpUtil.ht("/Dialog/ChangesetQuery"))));
+
+ return pnl;
+ }
+
+
+ protected void build() {
+ setTitle(tr("Query changesets"));
+ Container cp = getContentPane();
+ cp.setLayout(new BorderLayout());
+ cp.add(buildContentPanel(), BorderLayout.CENTER);
+ cp.add(buildButtonPanel(), BorderLayout.SOUTH);
+
+ // cancel on ESC
+ getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel");
+ getRootPane().getActionMap().put("cancel", new CancelAction());
+
+ // context sensitive help
+ HelpUtil.setHelpContext(getRootPane(), HelpUtil.ht("/Dialog/ChangesetQuery"));
+
+ addWindowListener(new WindowEventHandler());
+ }
+
+
+ public ChangesetQueryDialog(Dialog parent) {
+ super(parent, true /* modal */);
+ build();
+ }
+
+ public ChangesetQueryDialog(Frame parent) {
+ super(parent, true /* modal */);
+ build();
+ }
+
+ public boolean isCanceled() {
+ return canceled;
+ }
+
+ public void initForUserInput() {
+ pnlBasicChangesetQueries.init();
+ }
+
+ protected void setCanceled(boolean canceled) {
+ this.canceled = canceled;
+ }
+
+ public ChangesetQuery getChangesetQuery() {
+ if (isCanceled())
+ return null;
+ switch(tpQueryPanels.getSelectedIndex()) {
+ case 0:
+ return pnlBasicChangesetQueries.buildChangesetQuery();
+ case 1:
+ return pnlUrlBasedQueries.buildChangesetQuery();
+ case 2:
+ return pnlAdvancedQueries.buildChangesetQuery();
+ default:
+ // FIXME: extend with advanced queries
+ return null;
+ }
+ }
+
+ public void startUserInput() {
+ pnlUrlBasedQueries.startUserInput();
+ pnlAdvancedQueries.startUserInput();
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ if (visible) {
+ new WindowGeometry(
+ getClass().getName() + ".geometry",
+ WindowGeometry.centerInWindow(
+ getParent(),
+ new Dimension(400,400)
+ )
+ ).apply(this);
+ setCanceled(false);
+ startUserInput();
+ } else if (!visible && isShowing()){
+ new WindowGeometry(this).remember(getClass().getName() + ".geometry");
+ pnlAdvancedQueries.rememberSettings();
+ }
+ super.setVisible(visible);
+ }
+
+ class QueryAction extends AbstractAction {
+ public QueryAction() {
+ putValue(NAME, tr("Query"));
+ putValue(SMALL_ICON, ImageProvider.get("dialogs", "search"));
+ putValue(SHORT_DESCRIPTION, tr("Query and download changesets"));
+ }
+
+ protected void alertInvalidChangesetQuery() {
+ HelpAwareOptionPane.showOptionDialog(
+ ChangesetQueryDialog.this,
+ tr("Please enter a valid changeset query URL first."),
+ tr("Illegal changeset query URL"),
+ JOptionPane.WARNING_MESSAGE,
+ HelpUtil.ht("/Dialog/ChangesetQueryDialog#EnterAValidChangesetQueryUrlFirst")
+ );
+ }
+
+ public void actionPerformed(ActionEvent arg0) {
+ switch(tpQueryPanels.getSelectedIndex()) {
+ case 0:
+ // currently, query specifications can't be invalid in the basic query panel.
+ // We select from a couple of predefined queries and there is always a query
+ // selected
+ break;
+ case 1:
+ if (getChangesetQuery() == null) {
+ alertInvalidChangesetQuery();
+ pnlUrlBasedQueries.startUserInput();
+ return;
+ }
+ break;
+
+ case 2:
+ if (getChangesetQuery() == null) {
+ pnlAdvancedQueries.displayMessageIfInvalid();
+ return;
+ }
+ }
+ setCanceled(false);
+ setVisible(false);
+ }
+ }
+
+ class CancelAction extends AbstractAction {
+
+ public CancelAction() {
+ putValue(NAME, tr("Cancel"));
+ putValue(SMALL_ICON, ImageProvider.get("cancel"));
+ putValue(SHORT_DESCRIPTION, tr("Close the dialog and abort querying of changesets"));
+ }
+
+ public void cancel() {
+ setCanceled(true);
+ setVisible(false);
+ }
+
+ public void actionPerformed(ActionEvent arg0) {
+ cancel();
+ }
+ }
+
+ class WindowEventHandler extends WindowAdapter {
+ @Override
+ public void windowClosing(WindowEvent arg0) {
+ new CancelAction().cancel();
+ }
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/ChangesetQueryTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/ChangesetQueryTask.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/ChangesetQueryTask.java (revision 2689)
@@ -0,0 +1,198 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset.query;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.SwingUtilities;
+
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.ChangesetCache;
+import org.openstreetmap.josm.data.osm.UserInfo;
+import org.openstreetmap.josm.gui.JosmUserIdentityManager;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetDownloadTask;
+import org.openstreetmap.josm.io.ChangesetQuery;
+import org.openstreetmap.josm.io.OsmServerChangesetReader;
+import org.openstreetmap.josm.io.OsmServerUserInfoReader;
+import org.openstreetmap.josm.io.OsmTransferCancelledException;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.tools.BugReportExceptionHandler;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+import org.openstreetmap.josm.tools.ExceptionUtil;
+import org.xml.sax.SAXException;
+
+/**
+ * Asynchronous task to send a changeset query to the OSM API.
+ *
+ */
+public class ChangesetQueryTask extends PleaseWaitRunnable implements ChangesetDownloadTask{
+
+ /** the changeset query */
+ private ChangesetQuery query;
+ /** true if the task was canceled */
+ private boolean canceled;
+ /** the set of downloaded changsets */
+ private Set downloadedChangesets;
+ /** the last exception remembered, if any */
+ private Exception lastException;
+ /** the reader object used to read information about the current user from the API */
+ private OsmServerUserInfoReader userInfoReader;
+ /** the reader object used to submit the changeset query to the API */
+ private OsmServerChangesetReader changesetReader;
+
+ /**
+ * Creates the task.
+ *
+ * @param query the query to submit to the OSM server. Must not be null.
+ * @throws IllegalArgumentException thrown if query is null.
+ */
+ public ChangesetQueryTask(ChangesetQuery query) throws IllegalArgumentException {
+ super(tr("Querying and downloading changesets",false /* don't ignore exceptions */));
+ CheckParameterUtil.ensureParameterNotNull(query, "query");
+ this.query = query;
+ }
+
+ /**
+ * Creates the task.
+ *
+ * @param parent the parent component relative to which the {@see PleaseWaitDialog} is displayed.
+ * Must not be null.
+ * @param query the query to submit to the OSM server. Must not be null.
+ * @throws IllegalArgumentException thrown if query is null.
+ * @throws IllegalArgumentException thrown if parent is null
+ */
+ public ChangesetQueryTask(Component parent, ChangesetQuery query) throws IllegalArgumentException {
+ super(parent, tr("Querying and downloading changesets"), false /* don't ignore exceptions */);
+ CheckParameterUtil.ensureParameterNotNull(query, "query");
+ this.query = query;
+ }
+
+ @Override
+ protected void cancel() {
+ canceled = true;
+ synchronized(this) {
+ if (userInfoReader != null) {
+ userInfoReader.cancel();
+ }
+ }
+ synchronized(this) {
+ if (changesetReader != null) {
+ changesetReader.cancel();
+ }
+ }
+ }
+
+ @Override
+ protected void finish() {
+ if (canceled) return;
+ if (lastException != null) {
+ ExceptionUtil.explainException(lastException);
+ return;
+ }
+
+ // update the global changeset cache with the downloaded changesets;
+ // this will trigger change events which views are listening to. They
+ // will update their views accordingly.
+ //
+ // Run on the EDT because UI updates are triggered.
+ //
+ Runnable r = new Runnable() {
+ public void run() {
+ ChangesetCache.getInstance().update(downloadedChangesets);
+ }
+ };
+ if (SwingUtilities.isEventDispatchThread()) {
+ r.run();
+ } else {
+ try {
+ SwingUtilities.invokeAndWait(r);
+ } catch(InterruptedException e) {
+ e.printStackTrace();
+ } catch(InvocationTargetException e) {
+ Throwable t = e.getTargetException();
+ if (t instanceof RuntimeException) {
+ BugReportExceptionHandler.handleException(t);
+ } else if (t instanceof Exception){
+ ExceptionUtil.explainException(e);
+ } else {
+ BugReportExceptionHandler.handleException(t);
+ }
+ }
+ }
+ }
+
+ /**
+ * Tries to fully identify the current JOSM user
+ *
+ * @throws OsmTransferException thrown if something went wrong
+ */
+ protected void fullyIdentifyCurrentUser() throws OsmTransferException {
+ getProgressMonitor().indeterminateSubTask(tr("Determine user id for current user..."));
+
+ synchronized(this) {
+ userInfoReader = new OsmServerUserInfoReader();
+ }
+ UserInfo info = userInfoReader.fetchUserInfo(getProgressMonitor().createSubTaskMonitor(1,false));
+ synchronized(this) {
+ userInfoReader = null;
+ }
+ JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
+ im.setFullyIdentified(im.getUserName(), info);
+ }
+
+ @Override
+ protected void realRun() throws SAXException, IOException, OsmTransferException {
+ try {
+ JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
+ if (query.isRestrictedToPartiallyIdentifiedUser() && im.isCurrentUser(query.getUserName())) {
+ // if we query changesets for the current user, make sure we query against
+ // its user id, not its user name. If necessary, determine the user id
+ // first.
+ //
+ if (im.isPartiallyIdentified() ) {
+ fullyIdentifyCurrentUser();
+ }
+ query = query.forUser(JosmUserIdentityManager.getInstance().getUserId());
+ }
+ if (canceled) return;
+ getProgressMonitor().indeterminateSubTask(tr("Query and download changesets ..."));
+ synchronized(this) {
+ changesetReader= new OsmServerChangesetReader();
+ }
+ downloadedChangesets = new HashSet();
+ downloadedChangesets.addAll(changesetReader.queryChangesets(query, getProgressMonitor().createSubTaskMonitor(0, false)));
+ synchronized (this) {
+ changesetReader = null;
+ }
+ } catch(OsmTransferCancelledException e) {
+ // thrown if user cancel the authentication dialog
+ canceled = true;
+ return;
+ } catch(OsmTransferException e) {
+ if (canceled)
+ return;
+ this.lastException = e;
+ }
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /* interface ChangesetDownloadTask */
+ /* ------------------------------------------------------------------------------- */
+ public Set getDownloadedChangesets() {
+ return downloadedChangesets;
+ }
+
+ public boolean isCanceled() {
+ return canceled;
+ }
+
+ public boolean isFailed() {
+ return lastException != null;
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/UrlBasedQueryPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/UrlBasedQueryPanel.java (revision 2689)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/UrlBasedQueryPanel.java (revision 2689)
@@ -0,0 +1,216 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.changeset.query;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+
+import org.openstreetmap.josm.gui.widgets.HtmlPanel;
+import org.openstreetmap.josm.io.ChangesetQuery;
+import org.openstreetmap.josm.io.OsmApi;
+import org.openstreetmap.josm.io.ChangesetQuery.ChangesetQueryUrlException;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+
+public class UrlBasedQueryPanel extends JPanel {
+
+ private JTextField tfUrl;
+ private JLabel lblValid;
+
+ protected JPanel buildURLPanel() {
+ JPanel pnl = new JPanel(new GridBagLayout());
+ GridBagConstraints gc = new GridBagConstraints();
+ gc.weightx = 0.0;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.insets = new Insets(0,0,0,5);
+ pnl.add(new JLabel(tr("URL: ")), gc);
+
+ gc.gridx = 1;
+ gc.weightx = 1.0;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ pnl.add(tfUrl = new JTextField(), gc);
+ tfUrl.getDocument().addDocumentListener(new ChangetQueryUrlValidator());
+ tfUrl.addFocusListener(
+ new FocusAdapter() {
+ @Override
+ public void focusGained(FocusEvent e) {
+ tfUrl.selectAll();
+ }
+ }
+ );
+
+ gc.gridx = 2;
+ gc.weightx = 0.0;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ pnl.add(lblValid = new JLabel(), gc);
+ lblValid.setPreferredSize(new Dimension(20,20));
+ return pnl;
+ }
+
+ protected JPanel buildHelpPanel() {
+ HtmlPanel pnl = new HtmlPanel();
+ pnl.setText(
+ tr("Please enter or paste an URL to retrieve changesets from the OSM API."
+ + "Examples
"
+ + ""
+ + "Note that changeset queries are currently always submitted to ''{0}'', regardless of the "
+ + "host, port and path of the URL entered beow."
+ + "",
+ OsmApi.getOsmApi().getBaseUrl()
+ )
+ );
+ pnl.getEditorPane().addHyperlinkListener(
+ new HyperlinkListener() {
+ public void hyperlinkUpdate(HyperlinkEvent e) {
+ if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
+ tfUrl.setText(e.getDescription());
+ tfUrl.requestFocusInWindow();
+ }
+ }
+ }
+ );
+ return pnl;
+ }
+
+ protected ChangesetQuery buildChangesetQueryFromUrlQuery(String query) {
+ if (query == null)
+ return new ChangesetQuery();
+ query = query.trim();
+ return null;
+ }
+
+ protected void build() {
+ setLayout(new GridBagLayout());
+ setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+
+ GridBagConstraints gc = new GridBagConstraints();
+ gc.weightx = 1.0;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ gc.insets = new Insets(0,0,10,0);
+ add(buildHelpPanel(),gc);
+
+ gc.gridy = 1;
+ gc.weightx = 1.0;
+ gc.fill = GridBagConstraints.HORIZONTAL;
+ add(buildURLPanel(),gc);
+
+ gc.gridy = 2;
+ gc.weightx = 1.0;
+ gc.weighty = 1.0;
+ gc.fill = GridBagConstraints.BOTH;
+ add(new JPanel(),gc);
+
+ }
+ public UrlBasedQueryPanel() {
+ build();
+ }
+
+ protected boolean isValidChangesetQueryUrl(String text) {
+ return buildChangesetQuery(text) != null;
+ }
+
+ protected ChangesetQuery buildChangesetQuery(String text) {
+ URL url = null;
+ try {
+ url = new URL(text);
+ } catch(MalformedURLException e) {
+ return null;
+ }
+ String path = url.getPath();
+ String query = url.getQuery();
+ if (path == null || ! path.endsWith("/changesets")) return null;
+
+ try {
+ return ChangesetQuery.buildFromUrlQuery(query);
+ } catch(ChangesetQueryUrlException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Replies the {@see ChangesetQuery} specified in this panel. null, if no valid changeset query
+ * is specified.
+ *
+ * @return the changeset query
+ */
+ public ChangesetQuery buildChangesetQuery() {
+ String value = tfUrl.getText().trim();
+ return buildChangesetQuery(value);
+ }
+
+ public void startUserInput() {
+ tfUrl.requestFocusInWindow();
+ }
+
+ /**
+ * Validates text entered in the changeset query URL field on the fly
+ */
+ class ChangetQueryUrlValidator implements DocumentListener {
+ protected String getCurrentFeedback() {
+ String fb = (String)lblValid.getClientProperty("valid");
+ return fb == null ? "none" : fb;
+ }
+ protected void feedbackValid() {
+ if (getCurrentFeedback().equals("valid")) return;
+ lblValid.setIcon(ImageProvider.get("dialogs/changeset", "valid"));
+ lblValid.setToolTipText("");
+ lblValid.putClientProperty("valid", "valid");
+ }
+
+ protected void feedbackInvalid() {
+ if (getCurrentFeedback().equals("invalid")) return;
+ lblValid.setIcon(ImageProvider.get("warning-small"));
+ lblValid.setToolTipText(tr("This changeset query URL is invalid"));
+ lblValid.putClientProperty("valid", "invalid");
+ }
+
+ protected void feedbackNone() {
+ lblValid.setIcon(null);
+ lblValid.putClientProperty("valid", "none");
+ }
+
+ protected void validate() {
+ String value = tfUrl.getText();
+ if (value.trim().equals("")) {
+ feedbackNone();
+ return;
+ }
+ value = value.trim();
+ if (isValidChangesetQueryUrl(value)) {
+ feedbackValid();
+ } else {
+ feedbackInvalid();
+ }
+ }
+ public void changedUpdate(DocumentEvent e) {
+ validate();
+ }
+
+ public void insertUpdate(DocumentEvent e) {
+ validate();
+ }
+
+ public void removeUpdate(DocumentEvent e) {
+ validate();
+ }
+ }
+}
Index: trunk/src/org/openstreetmap/josm/gui/download/SlippyMapChooser.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/download/SlippyMapChooser.java (revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/download/SlippyMapChooser.java (revision 2689)
@@ -7,5 +7,4 @@
import java.awt.BorderLayout;
import java.awt.Color;
-import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
@@ -225,9 +224,9 @@
Math.min(l2.getLat(), l1.getLat()),
Math.min(l1.getLon(), l2.getLon())
- ),
+ ),
new LatLon(
Math.max(l2.getLat(), l1.getLat()),
Math.max(l1.getLon(), l2.getLon()))
- );
+ );
iGui.boundingBoxChanged(b, this);
repaint();
Index: trunk/src/org/openstreetmap/josm/gui/history/HistoryLoadTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/history/HistoryLoadTask.java (revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/history/HistoryLoadTask.java (revision 2689)
@@ -5,4 +5,5 @@
import static org.openstreetmap.josm.tools.I18n.tr;
+import java.awt.Component;
import java.io.IOException;
import java.util.Collection;
@@ -21,4 +22,5 @@
import org.openstreetmap.josm.io.OsmServerHistoryReader;
import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
import org.xml.sax.SAXException;
@@ -51,4 +53,17 @@
public HistoryLoadTask() {
super(tr("Load history"), true);
+ toLoad = new HashSet();
+ }
+
+ /**
+ * Creates a new task
+ *
+ * @param parent the component to be used as reference to find the parent for {@see PleaseWaitDialog}.
+ * Must not be null.
+ * @throws IllegalArgumentException thrown if parent is null
+ */
+ public HistoryLoadTask(Component parent) {
+ super(parent, tr("Load history"), true);
+ CheckParameterUtil.ensureParameterNotNull(parent, "parent");
toLoad = new HashSet();
}
Index: trunk/src/org/openstreetmap/josm/gui/io/DownloadOpenChangesetsTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/DownloadOpenChangesetsTask.java (revision 2688)
+++ trunk/src/org/openstreetmap/josm/gui/io/DownloadOpenChangesetsTask.java (revision 2689)
@@ -99,5 +99,5 @@
return;
reader = new OsmServerChangesetReader();
- ChangesetQuery query = new ChangesetQuery().forUser(model.getUserId()).beingOpen();
+ ChangesetQuery query = new ChangesetQuery().forUser((int)model.getUserId()).beingOpen(true);
changesets = reader.queryChangesets(
query,