[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[vidalia-svn] r3883: Fixed an oopsie. (vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin)



Author: tyree731
Date: 2009-06-24 18:27:15 -0400 (Wed, 24 Jun 2009)
New Revision: 3883

Added:
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitItem.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitItem.h
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitListWidget.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitListWidget.h
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIp.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIp.h
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCache.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCache.h
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCacheItem.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCacheItem.h
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpRequest.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpRequest.h
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResolver.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResolver.h
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResponse.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResponse.h
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/NetViewer.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/NetViewer.h
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/NetViewer.ui
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterDescriptorView.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterDescriptorView.h
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterInfoDialog.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterInfoDialog.h
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterInfoDialog.ui
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListItem.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListItem.h
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListWidget.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListWidget.h
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/StreamItem.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/StreamItem.h
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapImageView.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapImageView.h
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidget.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidget.h
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetInputHandler.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetInputHandler.h
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetPopupMenu.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetPopupMenu.h
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/ZImageView.cpp
   vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/ZImageView.h
Log:
Fixed an oopsie.


Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitItem.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitItem.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitItem.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,76 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file CircuitItem.cpp
+** \version $Id$
+** \brief Item representing a Tor circuit and its status
+*/
+
+#include "CircuitItem.h"
+#include "CircuitListWidget.h"
+
+
+/** Constructor */
+CircuitItem::CircuitItem(const Circuit &circuit)
+{
+  /* Update the displayed text */
+  update(circuit);
+}
+
+/** Updates the status and path of this circuit item. */
+void
+CircuitItem::update(const Circuit &circuit)
+{
+  QString displayedPath;
+
+  /* Save the Circuit object */
+  _circuit = circuit;
+  
+  /* Use a semi-meaningful value if the path is empty */
+  displayedPath = circuit.length() > 0 ? circuit.routerNames().join(",")
+                                       : tr("<Path Empty>");
+
+  /* Update the column fields */
+  setText(CircuitListWidget::ConnectionColumn, displayedPath);
+  setToolTip(CircuitListWidget::ConnectionColumn, displayedPath);
+  setText(CircuitListWidget::StatusColumn, circuit.statusString());
+  setToolTip(CircuitListWidget::StatusColumn, circuit.statusString());
+}
+
+/** Adds a stream as a child of this circuit. */
+void
+CircuitItem::addStream(StreamItem *stream)
+{
+  addChild(stream);
+}
+
+/** Removes the stream item from this circuit and frees its memory */
+void
+CircuitItem::removeStream(StreamItem *stream)
+{
+  int index = indexOfChild(stream);
+  if (index > -1) {
+    delete takeChild(index);
+  }
+}
+
+/** Returns a list of all stream items on this circuit. */
+QList<StreamItem *>
+CircuitItem::streams() const
+{
+  QList<StreamItem *> streams;
+  int n = childCount();
+  for (int i = 0; i < n; i++) {
+    streams << (StreamItem *)child(i);
+  }
+  return streams;
+}
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitItem.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitItem.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitItem.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitItem.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,55 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file CircuitItem.h
+** \version $Id$
+** \brief List item representing a Tor circuit and its status
+*/
+
+#ifndef _CIRCUITITEM_H
+#define _CIRCUITITEM_H
+
+#include "StreamItem.h"
+
+#include "Circuit.h"
+#include "Stream.h"
+
+#include <QApplication>
+#include <QTreeWidgetItem>
+
+
+class CircuitItem : public QTreeWidgetItem
+{
+  Q_DECLARE_TR_FUNCTIONS(CircuitItem)
+
+public:
+  /** Default constructor */
+  CircuitItem(const Circuit &circuit);
+ 
+  /** Adds a stream to this circuit item */
+  void addStream(StreamItem *stream);
+  /** Removes the stream item from the list and frees its memory. */
+  void removeStream(StreamItem *stream);
+  /** Updates the status of this circuit item using the given circuit. */
+  void update(const Circuit &circuit);
+  /** Returns the ID for this circuit. */
+  CircuitId id() const { return _circuit.id(); }
+  /** Returns the Circuit object for this item. */
+  Circuit circuit() const { return _circuit; }
+  /** Returns a list of all stream items on this circuit. */
+  QList<StreamItem *> streams() const;
+  
+private:
+  Circuit _circuit; /**< Circuit associated with this item. */
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitItem.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitListWidget.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitListWidget.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitListWidget.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,363 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file CircuitListWidget.cpp
+** \version $Id$
+** \brief Collection of Tor circuits as CircuitItems
+*/
+
+#include "config.h"
+#include "CircuitListWidget.h"
+#include <vidalia/Vidalia.h>
+
+#include <QPoint>
+#include <QTimer>
+
+#define IMG_CLOSE   ":/images/22x22/edit-delete.png"
+#define IMG_ZOOM    ":/images/22x22/page-zoom.png"
+
+#define CLOSED_CIRCUIT_REMOVE_DELAY     3000
+#define FAILED_CIRCUIT_REMOVE_DELAY     5000
+#define CLOSED_STREAM_REMOVE_DELAY      3000
+#define FAILED_STREAM_REMOVE_DELAY      4000
+
+
+/** Default constructor. */
+CircuitListWidget::CircuitListWidget(QWidget *parent)
+: QTreeWidget(parent)
+{
+  /* Create and initialize columns */
+  setHeaderLabels(QStringList() << tr("Connection") << tr("Status"));
+
+  /* Find out when a circuit has been selected */
+  connect(this, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
+          this, SLOT(onSelectionChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
+  connect(this, SIGNAL(customContextMenuRequested(QPoint)),
+          this, SLOT(customContextMenuRequested(QPoint)));
+
+  /* Respond to the Delete key by closing whatever circuits or streams are
+   * selected. */
+  vApp->createShortcut(QKeySequence::Delete, this, this,
+                       SLOT(closeSelectedConnections()));
+}
+
+/** Called when the user changes the UI translation. */
+void
+CircuitListWidget::retranslateUi()
+{
+  setHeaderLabels(QStringList() << tr("Connection") << tr("Status"));
+  for (int i = 0; i < topLevelItemCount(); i++) {
+    CircuitItem *circuitItem = dynamic_cast<CircuitItem *>(topLevelItem(i));
+    circuitItem->update(circuitItem->circuit());
+
+    foreach (StreamItem *streamItem, circuitItem->streams()) {
+      streamItem->update(streamItem->stream());
+    }
+  }
+}
+
+/** Called when the user requests a context menu on a circuit or stream in the
+ * list and displays a context menu appropriate for whichever type of item is
+ * currently selected. */
+void
+CircuitListWidget::customContextMenuRequested(const QPoint &pos)
+{
+  QMenu menu(this);
+
+  /* Find out which item was right-clicked */
+  QTreeWidgetItem *item = itemAt(pos);
+  if (!item)
+    return;
+    
+  if (!item->parent()) {
+    /* A circuit was selected */
+    CircuitItem *circuitItem = dynamic_cast<CircuitItem *>(item);
+    if (!circuitItem)
+      return;
+
+    /* Set up the circuit context menu */
+    QAction *zoomAct  = new QAction(QIcon(IMG_ZOOM),
+                                    tr("Zoom to Circuit"), this);
+    QAction *closeAct = new QAction(QIcon(IMG_CLOSE),
+                                    tr("Close Circuit (Del)"), this);
+#if defined(USE_MARBLE)
+    zoomAct->setEnabled(circuitItem->circuit().status() == Circuit::Built);
+    menu.addAction(zoomAct);
+    menu.addSeparator();
+#endif
+    menu.addAction(closeAct);
+      
+    /* Display the context menu and find out which (if any) action was
+     * selected */
+    QAction* action = menu.exec(mapToGlobal(pos));
+    if (action == closeAct)
+      emit closeCircuit(circuitItem->id());
+    else if (action == zoomAct)
+      emit zoomToCircuit(circuitItem->id());
+  } else {
+    /* A stream was selected */
+    StreamItem *streamItem = dynamic_cast<StreamItem *>(item);
+    if (!streamItem)
+      return;
+ 
+    /* Set up the stream context menu */
+    QAction *closeAct = new QAction(QIcon(IMG_CLOSE),
+                                    tr("Close Stream (Del)"), this);
+    menu.addAction(closeAct);
+
+    /* Display the context menu and find out which (if any) action was
+     * selected */
+    QAction* action = menu.exec(mapToGlobal(pos));
+    if (action == closeAct)
+      emit closeStream(streamItem->id());
+  }
+}
+
+/** Closes all selected circuits or streams. */
+void
+CircuitListWidget::closeSelectedConnections()
+{
+  QList<QTreeWidgetItem *> items = selectedItems();
+  foreach (QTreeWidgetItem *item, items) {
+    if (!item->parent()) {
+      CircuitItem *circuitItem = dynamic_cast<CircuitItem *>(item);
+      if (circuitItem)
+        emit closeCircuit(circuitItem->id());
+    } else {
+      StreamItem *streamItem = dynamic_cast<StreamItem *>(item);
+      if (streamItem)
+        emit closeStream(streamItem->id());
+    }
+  }
+}
+
+/** Adds a <b>circuit</b> to the list. If the circuit already exists in the
+ * list, the status and path will be updated. */
+void
+CircuitListWidget::addCircuit(const Circuit &circuit)
+{
+  /* Check to see if the circuit already exists in the tree */
+  CircuitItem *item = findCircuitItem(circuit.id());
+  
+  if (!item) {
+    /* Add the new circuit */
+    item = new CircuitItem(circuit);
+    addTopLevelItem(item);
+  } else {
+    /* Circuit already exists, so update its status and path */
+    item->update(circuit);
+  }
+
+  /* If the circuit is closed or dead, schedule it for removal */
+  Circuit::Status status = circuit.status();
+  if (status == Circuit::Closed) {
+    scheduleCircuitRemoval(item, CLOSED_CIRCUIT_REMOVE_DELAY);
+  } else if (status == Circuit::Failed) {
+    scheduleCircuitRemoval(item, FAILED_CIRCUIT_REMOVE_DELAY);
+  }
+}
+
+/** Adds a stream to the list. If the stream already exists in the list, the
+ * status and path will be updated. */
+void
+CircuitListWidget::addStream(const Stream &stream)
+{
+  /* Check to see if the stream already exists in the tree */
+  StreamItem *item = findStreamItem(stream.id());
+
+  if (!item) {
+    CircuitItem *circuit = findCircuitItem(stream.circuitId());
+    /* New stream, so try to find its circuit and add it */
+    if (circuit) {
+      circuit->addStream(new StreamItem(stream));
+      expandItem(circuit);
+    }
+  } else {
+    /* Stream already exists, so just update its status */
+    item->update(stream);
+
+    /* If the stream is closed or dead, schedule it for removal */
+    Stream::Status status = stream.status();
+    if (status == Stream::Closed) {
+      scheduleStreamRemoval(item, CLOSED_STREAM_REMOVE_DELAY);
+    } else if (status == Stream::Failed) {
+      scheduleStreamRemoval(item, FAILED_STREAM_REMOVE_DELAY);
+    }
+  }
+}
+
+/** Schedules the given circuit to be removed after the specified timeout. */
+void
+CircuitListWidget::scheduleCircuitRemoval(CircuitItem *circuit, int delay)
+{
+  if (!_circuitRemovalList.contains(circuit)) {
+    _circuitRemovalList << circuit;
+    QTimer::singleShot(delay, this, SLOT(removeCircuit()));
+  } 
+}
+
+/** Schedules the given stream to be removed after the specified timeout. */
+void
+CircuitListWidget::scheduleStreamRemoval(StreamItem *stream, int delay)
+{
+  if (!_streamRemovalList.contains(stream)) {
+    _streamRemovalList << stream;
+    QTimer::singleShot(delay, this, SLOT(removeStream()));
+  } 
+}
+
+/** Removes the first circuit scheduled to be removed. */
+void
+CircuitListWidget::removeCircuit()
+{
+  if (!_circuitRemovalList.isEmpty()) {
+    CircuitItem *circuitItem = _circuitRemovalList.takeFirst();
+    Circuit circuit = circuitItem->circuit();
+    removeCircuit(circuitItem);
+    emit circuitRemoved(circuit.id());
+  }
+}
+
+/** Removes the given circuit item and all streams on that circuit. */
+void
+CircuitListWidget::removeCircuit(CircuitItem *circuit)
+{
+  if (circuit) {
+    /* Remove all streams (if any) on this circuit. */
+    QList<StreamItem *> streams = circuit->streams();
+    foreach (StreamItem *stream, streams) {
+      /* Check if this stream was scheduled for removal already */
+      if (_streamRemovalList.contains(stream)) {
+        /* If this stream was already scheduled for removal, replace its pointer
+         * with 0, so it doesn't get removed twice. */
+        int index = _streamRemovalList.indexOf(stream);
+        _streamRemovalList.replace(index, (StreamItem *)0);
+      }
+      
+      /* Remove the stream item from the circuit */
+      circuit->removeStream(stream);
+    }
+    /* Remove the circuit item itself */
+    delete takeTopLevelItem(indexOfTopLevelItem(circuit));
+  }
+}
+
+/** Removes the first stream scheduled to be removed. */
+void
+CircuitListWidget::removeStream()
+{
+  if (!_streamRemovalList.isEmpty()) {
+    StreamItem *stream = _streamRemovalList.takeFirst();
+    removeStream(stream);
+  }
+}
+
+/** Removes the given stream item. */
+void
+CircuitListWidget::removeStream(StreamItem *stream)
+{
+  if (stream) {
+    /* Try to get the stream's parent (a circuit item) */ 
+    CircuitItem *circuit = (CircuitItem *)stream->parent();
+    if (circuit) {
+      /* Remove the stream from the circuit and delete the item */
+      circuit->removeStream(stream);
+    } else {
+      /* It isn't on a circuit, so just delete the stream */
+      delete stream;
+    }
+  }
+}
+
+/** Clears all circuits and streams from the list. */
+void
+CircuitListWidget::clearCircuits()
+{
+  QTreeWidget::clear();
+  _circuitRemovalList.clear();
+  _streamRemovalList.clear();
+}
+
+/** Finds the circuit with the given ID and returns a pointer to that
+ * circuit's item in the list. */
+CircuitItem*
+CircuitListWidget::findCircuitItem(const CircuitId &circid)
+{
+  int numCircs = topLevelItemCount();
+  for (int i = 0; i < numCircs; i++) {
+    CircuitItem *circuit = (CircuitItem *)topLevelItem(i);
+    if (circid == circuit->id()) {
+      return circuit;
+    }
+  }
+  return 0;
+}
+
+/** Finds the stream with the given ID and returns a pointer to that stream's
+ * item in the list. */
+StreamItem*
+CircuitListWidget::findStreamItem(const StreamId &streamid)
+{
+  int numCircs = topLevelItemCount();
+  int numStreams;
+  
+  for (int i = 0; i < numCircs; i++) {
+    CircuitItem *circuit = (CircuitItem *)topLevelItem(i);
+    numStreams = circuit->childCount();
+  
+    for (int j = 0; j < numStreams; j++) {
+      StreamItem *stream = (StreamItem *)circuit->child(j);
+      if (streamid == stream->id()) {
+        return stream;
+      }
+    }
+  }
+  return 0;
+}
+
+/** Called when the current item selection has changed. */
+void
+CircuitListWidget::onSelectionChanged(QTreeWidgetItem *cur, 
+                                      QTreeWidgetItem *prev)
+{
+  Q_UNUSED(prev);
+
+  if (cur) {
+    Circuit circuit;
+    
+    if (!cur->parent()) {
+      /* User selected a CircuitItem, so just grab the Circuit */
+      circuit = ((CircuitItem *)cur)->circuit();
+    } else {
+      /* User selected a StreamItem, so get its parent and then the Circuit */
+      CircuitItem *circItem = (CircuitItem *)cur->parent();
+      circuit = circItem->circuit();
+    }
+
+    /* If this circuit has a path, then emit it so we can highlight it */
+    emit circuitSelected(circuit);
+  }
+}
+
+/** Returns a list of circuits currently in the widget. */
+CircuitList
+CircuitListWidget::circuits()
+{
+  int numCircs = topLevelItemCount();
+  CircuitList circs;
+  
+  for (int i = 0; i < numCircs; i++) {
+    CircuitItem *circ = (CircuitItem *)topLevelItem(i);
+    circs << circ->circuit();
+  }
+  return circs;
+}
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitListWidget.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitListWidget.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitListWidget.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitListWidget.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,106 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file CircuitListWidget.h
+** \version $Id$
+** \brief Collection of Tor circuits as CircuitItems
+*/
+
+#ifndef _CIRCUITLISTWIDGET_H
+#define _CIRCUITLISTWIDGET_H
+
+#include "CircuitItem.h"
+#include "StreamItem.h"
+
+#include <QTreeWidget>
+#include <QList>
+#include <QMenu>
+#include <QAction>
+#include <QMouseEvent>
+
+
+class CircuitListWidget : public QTreeWidget
+{
+  Q_OBJECT
+  
+public:
+  /** Circuit list columns. */
+  enum Columns {
+    ConnectionColumn = 0, /**< Column for either the circuit or stream */
+    StatusColumn = 1      /**< Status of the connection. */
+  };
+  
+  /** Default constructor */
+  CircuitListWidget(QWidget *parent = 0);
+
+  /** Adds a circuit to the list. If the circuit already exists in the list,
+   * the status and path will be updated. */
+  void addCircuit(const Circuit &circuit);
+  /** Adds a stream to the list. If the stream already exists in the list, the
+   * status and path will be updated. */
+  void addStream(const Stream &stream);
+  /** Returns a list of circuits currently in the widget. */
+  QList<Circuit> circuits();
+  /** Called when the user changes the UI translation. */
+  void retranslateUi();
+
+signals:
+  /** Emitted when a circuit item is selected. */
+  void circuitSelected(Circuit circuit);
+  /** Emitted when a circuit is removed from the list. */
+  void circuitRemoved(CircuitId circid);
+  /** Emitted when the user selects a circuit to be closed. */
+  void closeCircuit(CircuitId circid);
+  /** Emitted when the user selects a stream to be closed. */
+  void closeStream(StreamId streamid);
+  /** Emitted when the user selects a circuit to zoom to. */
+  void zoomToCircuit(CircuitId circid);
+  
+public slots:
+  /** Clears all circuits and streams from the list. */
+  void clearCircuits();
+
+private slots:
+  /** Removes the first circuit scheduled to be removed.*/
+  void removeCircuit(); 
+  /** Removes the first stream scheduled to be removed. */
+  void removeStream();
+  /** Called when the current item selectio has changed. */
+  void onSelectionChanged(QTreeWidgetItem *cur, QTreeWidgetItem *prev);
+  /** Called when the user requests a context menu on a circuit or stream in
+   * the list and displays a context menu appropriate for whichever type of
+   * item is currently selected. */
+  void customContextMenuRequested(const QPoint &pos);
+  /** Closes all selected circuits or streams. */
+  void closeSelectedConnections();
+
+private:
+  /** Removes the given circuit item and all streams on that circuit. */
+  void removeCircuit(CircuitItem *circuit);
+  /** Removes the given stream item. */
+  void removeStream(StreamItem *stream);
+  /** Finds the circuit with the given ID. */
+  CircuitItem* findCircuitItem(const CircuitId &circid);
+  /** Finds the stream with the given ID. */
+  StreamItem* findStreamItem(const StreamId &streamid);
+  /** Schedules the given circuit item to be removed after the given timeout. */
+  void scheduleCircuitRemoval(CircuitItem *circuit, int delay);
+  /** Schedules a stream to be removed after the given timeout. */
+  void scheduleStreamRemoval(StreamItem *stream, int delay);
+
+  /** List of circuit items to be removed. */
+  QList<CircuitItem *> _circuitRemovalList;
+  /** List of stream items to be removed. */
+  QList<StreamItem *> _streamRemovalList;
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/CircuitListWidget.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIp.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIp.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIp.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,75 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file GeoIp.cpp
+** \version $Id$
+** \brief Associates an IP with a geographic location
+*/
+
+#include "GeoIp.h"
+
+#include <QStringList>
+
+/** Verifies a latitude is between -90.0 and 90.0 degrees. */
+#define IS_VALID_LATITUDE(x)    (((x) >= -90.0) && ((x) <= 90.0))
+/** Verifies a longitude is between -180.0 and 180.0 degrees. */
+#define IS_VALID_LONGITUDE(x)   (((x) >= -180.0) && ((x) <= 180.0))
+
+
+GeoIp::GeoIp()
+{
+  _latitude  = 0.0;
+  _longitude = 0.0;
+}
+
+GeoIp::GeoIp(const QHostAddress &ip, float latitude, float longitude,
+             const QString &city, const QString &region,
+             const QString &country, const QString &countryCode) 
+{
+  _ip = ip;
+  _latitude = latitude;
+  _longitude = longitude;
+  _city = city;
+  _region = region;
+  _country = country;
+  _countryCode = countryCode;
+}
+
+bool
+GeoIp::isValid() const
+{
+  return (! _ip.isNull()
+            && IS_VALID_LATITUDE(_latitude)
+            && IS_VALID_LONGITUDE(_longitude));
+}
+
+QString
+GeoIp::toString() const
+{
+  QStringList location;
+
+  /* Add the city name (if present) */
+  if (!_city.isEmpty())
+    location << _city;
+
+  /* Add the full state or region name (if present) */
+  if (!_region.isEmpty() && _region != _city)
+      location << _region;
+
+  /* Add the country name or the country code (if present) */
+  if (!_country.isEmpty())
+    location << _country;
+  else if (!_countryCode.isEmpty())
+    location << _countryCode;
+
+  return location.join(", ");
+}
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIp.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIp.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIp.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIp.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,96 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file GeoIp.h
+** \version $Id$
+** \brief Associates an IP with a geographic location
+*/
+
+#ifndef _GEOIP_H
+#define _GEOIP_H
+
+#include <QHash>
+#include <QString>
+#include <QHostAddress>
+
+
+class GeoIp
+{
+public:
+  /** Default constructor. Creates an empty GeoIp object.
+   */
+  GeoIp();
+
+  GeoIp(const QHostAddress &ip, float latitude, float longitude,
+        const QString &city = QString(),
+        const QString &region = QString(),
+        const QString &country = QString(),
+        const QString &countryCode = QString());
+
+  /** Returns the IP address associated with this GeoIP object.
+   */
+  QHostAddress ip() const { return _ip; }
+
+  /** Returns the latitude portion of the geographic coordinates associated
+   * with this IP address or range of IP addresses.
+   */
+  float latitude() const { return _latitude; }
+
+  /** Returns the longitude portion of the geographic coordinates associated
+   * with this IP address or range of IP addresses.
+   */
+  float longitude() const { return _longitude; }
+
+  /** Returns the name of the city associated with this IP address, if known.
+   * Otherwise, returns an empty QString.
+   */
+  QString city() const { return _city; }
+
+  /** Returns the full region name (e.g., state) in which this IP address 
+   * resides, if known. Otherwise, returns an empty QString.
+   */
+  QString region() const { return _region; }
+
+  /** Returns the full name of the country associated with this IP address
+   * or range of IP addresses, if known. Otherwise, returns an empty QString.
+   */
+  QString country() const { return _country; }
+
+  /** Returns the ISO 3166-1 alpha-2 two-letter country code of the country
+   * associated with this IP address or range of IP addresses, if known.
+   * Otherwise, returns an empty QString.
+   */
+  QString countryCode() const { return _countryCode; }
+
+  /** Returns a human-readable string of city, region(state), and country.
+   * Some fields may be absent if they are not known. If no fields are known,
+   * this will return an empty QString.
+   */
+  QString toString() const;
+
+  /** Returns true if the GeoIp object is valid. A valid GeoIp object must
+   * have valid IP address, valid latitude and longitude coordinates and a 
+   * two-letter country code.
+   */
+  bool isValid() const;
+
+private:
+  QHostAddress _ip; /**< IP address for this location. */
+  float _latitude;  /**< Latitudinal coordinate for this IP's location. */
+  float _longitude; /**< Longitudinal coordinate for this IP's location. */
+  QString _city;    /**< City in which this IP lives. */
+  QString _region;   /**< State or district in which this IP lives. */
+  QString _country; /**< Country in which this IP lives. */
+  QString _countryCode; /**< ISO-3166-1 alpha-2 country code. */
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIp.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCache.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCache.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCache.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,160 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file GeoIpCache.cpp
+** \version $Id$
+** \brief Caches the results of previous GeoIP requests
+*/
+
+#include "GeoIpCache.h"
+#include "GeoIpCacheItem.h"
+#include "GeoIp.h"
+#include <vidalia/Vidalia.h>
+
+#include "file.h"
+#include "stringutil.h"
+
+#include <QFile>
+#include <QDir>
+#include <QString>
+#include <QDateTime>
+#include <QTextStream>
+#include <QHostAddress>
+
+
+GeoIpCache::GeoIpCache(QObject *parent)
+  : QObject(parent)
+{
+  loadFromDisk();
+}
+
+QString
+GeoIpCache::cacheFileName() const
+{
+  return (Vidalia::dataDirectory() + "/geoip-cache");
+}
+
+bool
+GeoIpCache::saveToDisk(QString *errmsg)
+{
+  /* Make sure we have a data directory. */
+  if (!create_path(Vidalia::dataDirectory())) {
+    return false;
+  }
+  
+  /* Try to open a temporary cache file for writing */
+  QFile tmpCacheFile(cacheFileName() + ".tmp");
+  if (!tmpCacheFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
+    return err(errmsg, tmpCacheFile.errorString());
+  }
+
+  /* Write the cache entries to the file. */
+  QTextStream cache(&tmpCacheFile);
+  foreach (GeoIpCacheItem cacheItem, _cache) {
+    /* Save the cache item if it's not too old. */
+    if (!cacheItem.isExpired()) {
+      cache << cacheItem.toCacheString() << endl;
+    }
+  }
+
+  QFile cacheFile(cacheFileName());
+  /* Check if an previous cache file exists. */
+  if (cacheFile.exists()) {
+    /* A previous cache file exists, so try to remove it */
+    if (!cacheFile.remove()) {
+      return err(errmsg, cacheFile.errorString());
+    }
+  }
+  /* Rename the temporary file into place. */
+  if (!tmpCacheFile.rename(cacheFile.fileName())) {
+    return err(errmsg, tmpCacheFile.errorString());
+  }
+  return true;
+}
+
+bool
+GeoIpCache::loadFromDisk(QString *errmsg)
+{
+  QFile cacheFile(cacheFileName());
+
+  if (cacheFile.exists()) {
+    /* Try to open the cache file */
+    if (!cacheFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
+      return err(errmsg, cacheFile.errorString());
+    }
+    
+    /* Read the cached items from the cache file */
+    QTextStream cache(&cacheFile);
+    QString line = cache.readLine();
+    while (! line.isNull()) {
+      /* Create a GeoIpCacheItem from the line and save it */
+      GeoIpCacheItem item = GeoIpCacheItem::fromCacheString(line);
+      if (item.isValid() && ! item.isExpired())
+        addToCache(item);
+
+      line = cache.readLine();
+    }
+    vInfo("Parsed %1 GeoIP entries from '%2'").arg(_cache.size())
+                                              .arg(cacheFileName());
+  }
+  return true;
+}
+
+void
+GeoIpCache::addToCache(const GeoIp &geoip)
+{
+  /* Create a "range" consisting of only a single IP address. */
+  if (! contains(geoip.ip()))
+    addToCache(geoip.ip(), geoip.ip(), geoip);
+}
+
+void
+GeoIpCache::addToCache(const QHostAddress &from, const QHostAddress &to,
+                       const GeoIp &geoip)
+{
+  /* New cache entries expire 30 days from the time they are first cached. */
+  QDateTime expires = QDateTime::currentDateTime().toUTC().addDays(30);
+  
+  /* Create a new GeoIpCacheItem and add it to the cache */
+  addToCache(GeoIpCacheItem(from, to, geoip, expires));
+}
+
+void
+GeoIpCache::addToCache(const GeoIpCacheItem &ci)
+{
+  if (! ci.isValid() || ci.isExpired())
+    return;
+
+  /* The key for the cache is the last IP address in the range of IP addresses
+   * covered by the GeoIpCacheItem. We do this because QMap::upperBound() and 
+   * QMap::lowerBound() return an iterator to the next item higher than the
+   * search value (an IP address) if an exact match is not found.
+   */
+  _cache.insert(ci.ipRangeEnd().toIPv4Address(), ci);
+}
+
+GeoIp
+GeoIpCache::geoIpForAddress(const QHostAddress &ip)
+{
+  GeoIpCacheMap::iterator i = _cache.upperBound(ip.toIPv4Address());
+  if (i != _cache.end() && (*i).contains(ip)) 
+    return (*i).toGeoIp(ip);
+  return GeoIp();
+}
+
+bool
+GeoIpCache::contains(const QHostAddress &ip)
+{
+  GeoIpCacheMap::iterator i = _cache.upperBound(ip.toIPv4Address());
+  return (i != _cache.end() && (*i).contains(ip));
+}
+
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCache.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCache.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCache.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCache.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,87 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file GeoIpCache.h
+** \version $Id$
+** \brief Caches the results of previous GeoIP requests
+*/
+
+#ifndef _GEOIPCACHE_H
+#define _GEOIPCACHE_H
+
+#include "GeoIpCacheItem.h"
+
+#include <QObject>
+#include <QMap>
+
+class GeoIp;
+class QString;
+class QHostAddress;
+
+typedef QMap<quint32, GeoIpCacheItem> GeoIpCacheMap;
+
+
+class GeoIpCache : public QObject
+{
+  Q_OBJECT
+
+public:
+  /** Default constructor. */
+  GeoIpCache(QObject *parent = 0);
+  
+  /** Writes the current cache to disk. Returns true if the cache file was
+   * successfully saved to disk. Otherwise, returns false and sets
+   * <b>errmsg</b> to a string describing the error encountered, if
+   * <b>errmsg</b> is not null.
+   */
+  bool saveToDisk(QString *errmsg = 0);
+  
+  /** Reads the cache in from disk. Returns true if the cache file was
+   * successfully read. Otherwise, returns false and sets <b>errmsg</b> to
+   * a string describing the error encountered, if <b>errmsg</b> is not null.
+   */
+  bool loadFromDisk(QString *errmsg = 0);
+  
+  /** Returns the location currently used for the cache file.
+   */
+  QString cacheFileName() const;
+
+  /** Caches the geographic information in <b>geoip</b> associated with a
+   * single IP address.
+   */
+  void addToCache(const GeoIp &geoip);
+
+  /** Caches the geographic information in <b>geoip</b> associated with a
+   * range of IP addresses, from <b>from</b> to <b>to</b> (inclusive).
+   */
+  void addToCache(const QHostAddress &from, const QHostAddress &to,
+                  const GeoIp &geoip);
+
+  /** Returns a GeoIp object for the given <b>ip</b> from cache. If no cached
+   * information is known for <b>ip</b>, an empty GeoIp object is returned.
+   */
+  GeoIp geoIpForAddress(const QHostAddress &ip);
+
+  /** Returns true if the cache contains geographic location information for
+   * <b>ip</b>. Otherwise, returns false.
+   */
+  bool contains(const QHostAddress &ip);
+
+private:
+  /** Adds the GeoIpCacheItem <b>ci</b> to the cache. */
+  void addToCache(const GeoIpCacheItem &ci);
+
+  /**< List of cached GeoIp objects. */
+  GeoIpCacheMap _cache;  
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCache.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCacheItem.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCacheItem.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCacheItem.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,183 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file GeoIpCacheItem.cpp
+** \version $Id$
+** \brief Cached result of a single IP-to-geolocation result
+*/
+
+#include "GeoIpCacheItem.h"
+#include "GeoIp.h"
+
+#include "stringutil.h"
+
+#include <QString>
+#include <QDateTime>
+#include <QStringList>
+
+#define CACHE_KEY_FROM_IP       "FROM"
+#define CACHE_KEY_TO_IP         "TO"
+#define CACHE_KEY_EXPIRES       "EXPIRES"
+#define CACHE_KEY_LATITUDE      "LAT"
+#define CACHE_KEY_LONGITUDE     "LON"
+#define CACHE_KEY_CITY          "CITY"
+#define CACHE_KEY_REGION        "REGION"
+#define CACHE_KEY_COUNTRY       "COUNTRY"
+#define CACHE_KEY_COUNTRY_CODE  "CC"
+
+
+GeoIpCacheItem::GeoIpCacheItem()
+{
+  _fromIp = 0;
+  _toIp = 0;
+}
+
+GeoIpCacheItem::GeoIpCacheItem(const QHostAddress &from, const QHostAddress &to,
+                               const GeoIp &geoip, const QDateTime &expires)
+{
+  _fromIp = from.toIPv4Address();
+  _toIp = to.toIPv4Address();
+  _expires = expires;
+
+  _fields.insert(CACHE_KEY_LATITUDE, geoip.latitude());
+  _fields.insert(CACHE_KEY_LONGITUDE, geoip.longitude());
+  if (! geoip.city().isEmpty())
+    _fields.insert(CACHE_KEY_CITY, geoip.city());
+  if (! geoip.region().isEmpty())
+    _fields.insert(CACHE_KEY_REGION, geoip.region());
+  if (! geoip.country().isEmpty())
+    _fields.insert(CACHE_KEY_COUNTRY, geoip.country());
+  if (! geoip.countryCode().isEmpty())
+    _fields.insert(CACHE_KEY_COUNTRY_CODE, geoip.countryCode());
+}
+
+QHostAddress
+GeoIpCacheItem::ipRangeStart() const
+{
+  return QHostAddress(_fromIp);
+}
+
+QHostAddress
+GeoIpCacheItem::ipRangeEnd() const
+{
+  return QHostAddress(_toIp);
+}
+
+bool
+GeoIpCacheItem::contains(const QHostAddress &ip) const
+{
+  quint32 ipv4 = ip.toIPv4Address();
+
+  return (ipv4 >= _fromIp && ipv4 <= _toIp);
+}
+
+bool
+GeoIpCacheItem::isValid() const
+{
+  return (_expires.isValid()
+            && ! QHostAddress(_fromIp).isNull()
+            && ! QHostAddress(_toIp).isNull()
+            && _fromIp <= _toIp
+            && _fields.contains(CACHE_KEY_LATITUDE)
+            && _fields.contains(CACHE_KEY_LONGITUDE));
+}
+
+bool
+GeoIpCacheItem::isExpired() const
+{
+  return (_expires < QDateTime::currentDateTime().toUTC());
+}
+
+GeoIp
+GeoIpCacheItem::toGeoIp(const QHostAddress &ip) const
+{
+  if (this->contains(ip))
+    return GeoIp(ip,
+                 _fields.value(CACHE_KEY_LATITUDE).toDouble(),
+                 _fields.value(CACHE_KEY_LONGITUDE).toDouble(),
+                 _fields.value(CACHE_KEY_CITY).toString(),
+                 _fields.value(CACHE_KEY_REGION).toString(),
+                 _fields.value(CACHE_KEY_COUNTRY).toString(),
+                 _fields.value(CACHE_KEY_COUNTRY_CODE).toString());
+  return GeoIp();
+}
+
+QString
+GeoIpCacheItem::toCacheString() const
+{
+  QStringList keyvals;
+
+  keyvals << QString(CACHE_KEY_FROM_IP"=%1").arg(QHostAddress(_fromIp).toString());
+  keyvals << QString(CACHE_KEY_TO_IP"=%1").arg(QHostAddress(_toIp).toString());
+  keyvals << QString(CACHE_KEY_EXPIRES"=\"%1\"").arg(_expires.toString(Qt::ISODate));
+
+  foreach (QString key, _fields.keys()) {
+    QString value = _fields.value(key).toString();
+    if (value.contains(" ")) {
+      value.replace("\\", "\\\\");
+      value.replace("\"", "\\\"");
+      value = "\"" + value + "\"";
+    }
+    keyvals << key + "=" + value;
+  }
+  return keyvals.join(" ");
+}
+
+GeoIpCacheItem
+GeoIpCacheItem::fromCacheString(const QString &line)
+{
+  GeoIpCacheItem ci;
+  bool ok;
+
+  QHash<QString,QString> keyvals = string_parse_keyvals(line, &ok);
+  if (! ok)
+    return GeoIpCacheItem();
+  
+  /* Get the range of IP addresses associated with this cache entry */
+  QHostAddress fromIp(keyvals.value(CACHE_KEY_FROM_IP));
+  QHostAddress toIp(keyvals.value(CACHE_KEY_TO_IP));
+  if (fromIp.isNull() || toIp.isNull())
+    return GeoIpCacheItem();
+  ci._fromIp = fromIp.toIPv4Address();
+  ci._toIp = toIp.toIPv4Address();
+
+  /* Extract the expiration timestamp of this entry */
+  ci._expires = QDateTime::fromString(keyvals.value(CACHE_KEY_EXPIRES),
+                                   Qt::ISODate);
+  if (! ci._expires.isValid())
+    ci._expires = QDateTime::currentDateTime().toUTC().addDays(30);
+  
+  
+  /* Make sure we have valid geographic coordinates */
+  float latitude = keyvals.value(CACHE_KEY_LATITUDE).toFloat(&ok);
+  if (! ok)
+    return GeoIpCacheItem();
+  ci._fields.insert(CACHE_KEY_LATITUDE, latitude);
+
+  float longitude = keyvals.value(CACHE_KEY_LONGITUDE).toFloat(&ok);
+  if (! ok)
+    return GeoIpCacheItem();
+  ci._fields.insert(CACHE_KEY_LONGITUDE, longitude);
+  
+  /* Each of these fields is optional */
+  if (keyvals.contains(CACHE_KEY_CITY))
+    ci._fields.insert(CACHE_KEY_CITY, keyvals.value(CACHE_KEY_CITY));
+  if (keyvals.contains(CACHE_KEY_REGION))
+    ci._fields.insert(CACHE_KEY_REGION, keyvals.value(CACHE_KEY_REGION));
+  if (keyvals.contains(CACHE_KEY_COUNTRY))
+    ci._fields.insert(CACHE_KEY_COUNTRY, keyvals.value(CACHE_KEY_COUNTRY));
+  if (keyvals.contains(CACHE_KEY_COUNTRY_CODE))
+    ci._fields.insert(CACHE_KEY_COUNTRY_CODE,
+                      keyvals.value(CACHE_KEY_COUNTRY_CODE));
+
+  return ci;
+}
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCacheItem.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCacheItem.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCacheItem.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCacheItem.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,97 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file GeoIpCacheItem.h
+** \version $Id$
+** \brief Cached result of a single IP-to-geolocation result
+*/
+
+#ifndef _GEOIPCACHEITEM_H
+#define _GEOIPCACHEITEM_H
+
+#include <QHash>
+#include <QString>
+#include <QVariant>
+#include <QDateTime>
+
+class GeoIp;
+class QHostAddress;
+
+
+class GeoIpCacheItem
+{
+public:
+  /** Default constructor
+   */
+  GeoIpCacheItem();
+
+  /** Constructor.
+   */
+  GeoIpCacheItem(const QHostAddress &from, const QHostAddress &to,
+                 const GeoIp &geoIp, const QDateTime &expires);
+
+  /** Returns the first IP address in the range of IP addresses associated
+   * with this GeoIpCacheItem.
+   */
+  QHostAddress ipRangeStart() const;
+
+  /** Returns the last IP address in the range of IP addresses associated
+   * with this GeoIpCacheItem.
+   */
+  QHostAddress ipRangeEnd() const;
+
+  /** Returns true if <b>ip</b> is within the range of IP addresses associated
+   * with this GeoIpCacheItem.
+   * \sa ipRangeStart()
+   * \sa ipRangeEnd()
+   */
+  bool contains(const QHostAddress &ip) const;
+
+  /** Returns true if this GeoIpCacheItem object contains valid values for
+   * all required fields.
+   */
+  bool isValid() const;
+
+  /** Returns true if the cache item is too old to be considered accurate.
+   * Cached GeoIp responses are considered valid for thirty days after they
+   * are first added to the cache.
+   */
+  bool isExpired() const;
+
+  /** Returns a GeoIp object for <b>ip</b>, populated with the cached
+   * geographic information stored by this GeoIpCacheObject. If <b>ip</b>
+   * is not within the range of IP addresses associated with this object,
+   * an empty GeoIp object is returned.
+   * \sa contains
+   */
+  GeoIp toGeoIp(const QHostAddress &ip) const;
+
+  /** Formats the fields contained in this GeoIpCacheItem as a string
+   * suitable for writing to a cache file.
+   */
+  QString toCacheString() const;
+  
+  /** Parses <b>cacheLine</b> and constructs a new GeoIpCacheItem object
+   * with the parsed values. The format of <b>cacheLine</b> must follow the
+   * format as produced by toCacheString().
+   * \sa toCacheString()
+   */
+  static GeoIpCacheItem fromCacheString(const QString &cacheLine);
+
+private:
+  quint32 _fromIp;
+  quint32 _toIp;
+  QDateTime _expires;  /**< Time this item was cached. */
+  QHash<QString,QVariant> _fields;
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpCacheItem.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpRequest.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpRequest.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpRequest.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,80 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file GeoIpRequest.cpp
+** \version $Id$
+** \brief A formatted request for GeoIP information for one or more IPs
+*/
+
+#include "GeoIpRequest.h"
+#include "ZlibByteArray.h"
+
+#include <QString>
+#include <QHostAddress>
+#include <QHttpRequestHeader>
+
+
+/** Creates an HTTP POST header for this request, based on the 
+ * Host, Page, and content-length values. */
+QHttpRequestHeader
+GeoIpRequest::createHeader() const
+{
+  QHttpRequestHeader header("POST", _page, 1, 1);
+  
+  if (!_host.isEmpty())
+    header.setValue("Host", _host);
+  header.setContentType("application/x-www-form-urlencoded");
+  header.setContentLength(_request.length());
+  header.setValue("Connection", "close");
+
+  if (ZlibByteArray::isZlibAvailable()) {
+    QString acceptEncodings = "deflate, x-deflate";
+    if (ZlibByteArray::isGzipSupported())
+      acceptEncodings += ", gzip, x-gzip";
+    header.setValue("Accept-Encoding", acceptEncodings);
+  }
+
+  return header;
+}
+
+/** Sets the list of IPs whose geo information we want to request. */
+void
+GeoIpRequest::setRequest(const QList<QHostAddress> &ips)
+{
+  _request = "format=long&ip=";
+  int ipcount = ips.size();
+
+  /* Add each IP to a comma-delimited list. */
+  for (int i = 0; i < ipcount; i++) {
+    _request.append(ips.at(i).toString());
+    if (i < ipcount-1) {
+      _request.append(",");
+    }
+  }
+  _ips = ips;
+}
+
+/** Formats the request as an HTTP POST request. */
+QByteArray
+GeoIpRequest::request() const
+{
+  /* Create the header and append the request content. */
+  QString request = createHeader().toString() + _request;
+  return request.toAscii();
+}
+
+/** Returns true if this request contains <b>ip</b>. */
+bool
+GeoIpRequest::contains(const QHostAddress &ip) const
+{
+  return _ips.contains(ip);
+}
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpRequest.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpRequest.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpRequest.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpRequest.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,62 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file GeoIpRequest.h
+** \version $Id$
+** \brief A formatted request for GeoIP information for one or more IPs
+*/
+
+#ifndef _GEOIPREQUEST_H
+#define _GEOIPREQUEST_H
+
+#include <QList>
+#include <QString>
+#include <QHttpRequestHeader>
+
+class QHostAddress;
+class QByteArray;
+
+
+class GeoIpRequest
+{
+public:
+  /** Constructor */
+  GeoIpRequest(int id) : _id(id) {}
+  
+  /** Sets the Host: field in this request's header. */
+  void setHost(const QString &host) { _host = host; }
+  /** Sets the page path in this request's header. */
+  void setPage(const QString &page) { _page = page; }
+  /** Sets the list of IPs whose geo information we want to request. */
+  void setRequest(const QList<QHostAddress> &ips);
+  /** Returns true if this request contains <b>ip</b>. */
+  bool contains(const QHostAddress &ip) const;
+
+  /** Returns the request's identifier. */
+  int id() const { return _id; }
+  /** Returns the number of IP addresses contained in this request. */
+  int size() const { return _ips.size(); }
+  /** Formats the request as an HTTP POST request */
+  QByteArray request() const;
+  
+private:
+  /** Creates an HTTP header for this request. */
+  QHttpRequestHeader createHeader() const;
+  
+  int _id;          /**< Request identifier */
+  QString _host;    /**< Host: field value. */
+  QString _page;    /**< Page giving us the geo ip information. */
+  QString _request; /**< Formatted Geo IP request string. */
+  QList<QHostAddress> _ips; /**< List of IP addresses in this request. */
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpRequest.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResolver.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResolver.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResolver.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,283 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file GeoIpResolver.cpp
+** \version $Id$
+** \brief Requests GeoIP information and caches the result
+*/
+
+#include "GeoIpResolver.h"
+#include "GeoIpRequest.h"
+#include "GeoIpResponse.h"
+#include "GeoIp.h"
+#include "Vidalia.h"
+
+#include "stringutil.h"
+#include "TorSslSocket.h"
+
+/** Host for the GeoIP information. */ 
+#define GEOIP_HOST    "geoips.vidalia-project.net"
+/** The SSL GeoIP service runs on port 443. */
+#define GEOIP_SSL_PORT  443
+/** Page that we request the GeoIP information from. */
+#define GEOIP_PAGE    "/cgi-bin/geoip.py"
+
+
+/** Default constructor. */
+GeoIpResolver::GeoIpResolver(QObject *parent)
+  : QObject(parent)
+{
+  _socksAddr = QHostAddress::LocalHost;
+  _socksPort = 9050;
+  _cache = new GeoIpCache(this);
+}
+
+/** Sets the address and port of Tor, through which GeoIP requests will be
+ * made. */
+void
+GeoIpResolver::setSocksHost(const QHostAddress &addr, quint16 port)
+{
+  _socksAddr = addr;
+  _socksPort = port;
+}
+
+/** Resolves <b>ip</b> to geographic information if it is cached. A resolved()
+ * signal will be emitted and true returned if we have cached geographic
+ * information for <b>ip</b>. Otherwise, this returns false. */
+bool
+GeoIpResolver::resolveFromCache(const QHostAddress &ip)
+{
+  if (_cache->contains(ip)) {
+    emit resolved(-1, QList<GeoIp>() << _cache->geoIpForAddress(ip));
+    return true;
+  }
+  return false;
+}
+
+/** Resolves a list of IPs to a geographic location, but only those which are
+ * cached. Returns a list of IPs that were not in the cache. */
+QList<QHostAddress>
+GeoIpResolver::resolveFromCache(const QList<QHostAddress> &ips)
+{
+  QList<GeoIp> cached;
+
+  /* Build a list of which IPs have cached GeoIp information */
+  foreach (QHostAddress ip, ips) {
+    if (_cache->contains(ip))
+      cached << _cache->geoIpForAddress(ip);
+  }
+
+  /* If any were cached, emit their results now */
+  if (cached.size() > 0) {
+    vInfo("Resolved %1 GeoIP entries from cache.").arg(cached.size());
+    emit resolved(-1, cached);
+  }
+  return ips;
+}
+
+/** Resolves a single IP to a geographic location. */
+int
+GeoIpResolver::resolve(const QHostAddress &ip)
+{
+  return resolve(QList<QHostAddress>() << ip);
+}
+
+/** Called when the socket has connected to the Geo IP host. */
+void
+GeoIpResolver::connected()
+{
+  /* Find the socket and request for whoever called this slot */ 
+  QAbstractSocket *socket = dynamic_cast<QAbstractSocket *>(sender());
+  if (!_requestList.contains(socket)) {
+    return;
+  }
+  GeoIpRequest *req = static_cast<GeoIpRequest *>(_requestList.value(socket));
+
+  vInfo("Connected to the GeoIP host. Sending request for %1 uncached "
+        "GeoIP entries. (request id %2)").arg(req->size()).arg(req->id());
+
+  /* Send the request */
+  socket->write(req->request());
+}
+
+/** Called when the socket has disconnected from the Geo IP host. */
+void
+GeoIpResolver::disconnected()
+{
+  /* Find the socket and request for whoever called this slot */ 
+  QAbstractSocket *socket = dynamic_cast<QAbstractSocket *>(sender());
+  if (!_requestList.contains(socket)) {
+    return;
+  }
+  GeoIpRequest *req = static_cast<GeoIpRequest *>(_requestList.take(socket));
+
+  /* Read and parse the response header */
+  GeoIpResponse response = GeoIpResponse(socket->readAll());
+
+  /* Check the response code and see what we got */
+  if (response.statusCode() == 200) {
+    /* We got a 200 OK, so get the Geo IP information from the response body
+     * and cache the results. */
+    parseGeoIpResponse(response.content(), req);
+  } else {
+    /* We failed to get the Geo IP information, so emit resolveFailed and
+     * include the HTTP status message. */
+    vWarn("GeoIP resolution failed (request id %1): %2").arg(req->id())
+                                             .arg(response.statusMessage());
+    emit resolveFailed(req->id(), response.statusMessage());
+  }
+  /* Close the socket and clean up */
+  socket->close();
+  delete socket;
+  delete req;
+}
+
+void
+GeoIpResolver::parseGeoIpResponse(const QByteArray &response,
+                                  GeoIpRequest *request)
+{
+  QList<GeoIp> geoIpList;
+  QHash<QString,QString> keyvals;
+  QHostAddress ip, from, to;
+  QString city, region, country, cc;
+  float latitude, longitude;
+  GeoIp geoIp;
+  int numCached = 0;
+  bool ok;
+
+  QStringList lines = QString(response).split("\n", QString::SkipEmptyParts);
+  foreach (QString line, lines) {
+    /* Split the key=value formatted GeoIP record into keys and values */
+    QHash<QString,QString> keyvals = string_parse_keyvals(line.trimmed(), &ok);
+    if (! ok)
+      goto err;
+
+    /* Extract each of the required fields from the GeoIP record */
+    ip = QHostAddress(keyvals.value("IP"));
+    if (ip.isNull())
+      goto err;
+    latitude = keyvals.value("LAT").toFloat(&ok);
+    if (! ok)
+      goto err;
+    longitude = keyvals.value("LON").toFloat(&ok);
+    if (! ok)
+      goto err;
+
+    /* Each of these fields is optional */
+    city    = keyvals.value("CITY");
+    region  = keyvals.value("REGION");
+    country = keyvals.value("COUNTRY");
+    cc      = keyvals.value("CC");
+    
+    geoIp = GeoIp(ip, latitude, longitude, city, region, country, cc);
+    if (! geoIp.isValid())
+      goto err;
+
+    if (request->contains(ip)) {
+      if (! _cache->contains(ip)) {
+        from = QHostAddress(keyvals.value("FROM"));
+        to   = QHostAddress(keyvals.value("TO"));
+        if (! from.isNull() && ! to.isNull())
+          _cache->addToCache(from, to, geoIp);
+        else
+          _cache->addToCache(geoIp);
+        numCached++;
+      }
+
+      geoIpList << geoIp;
+      continue;
+
+err:
+      vInfo("Ignored improperly formatted GeoIP record (request id %1): %2")
+                                               .arg(line).arg(request->id());
+    } else {
+      /* This item wasn't requested, so just log it and ignore. */
+      vWarn("Received a GeoIP entry for IP address %1 that was not included "
+            "in the initial request. (request id %2)").arg(ip)
+                                                      .arg(request->id());
+    }
+  }
+  /* If new results were cached, save them to disk */
+  if (numCached > 0)
+    _cache->saveToDisk();
+  
+  /* Emit the results */
+  vInfo("Parsed %1 entries from the GeoIP response. (request id %2)")
+                                   .arg(geoIpList.size()).arg(request->id());
+  emit resolved(request->id(), geoIpList);  
+}
+
+/** Called when an error has occurred requesting Geo IP information. */
+void
+GeoIpResolver::socketError(const QString &errorString)
+{
+  /* Find the socket and request for whoever called this slot */ 
+  QAbstractSocket *socket = dynamic_cast<QAbstractSocket *>(sender());
+  if (!_requestList.contains(socket)) {
+    return;
+  }
+  
+  /* We expect a remote host to close the socket, because that's how the HTTP
+   * server tells us he's done talking to us. */
+  if (socket->error() != QAbstractSocket::RemoteHostClosedError) {
+    /* Emit the failure and clean up */
+    GeoIpRequest *req = static_cast<GeoIpRequest *>(_requestList.take(socket));
+    emit resolveFailed(req->id(), errorString);
+    socket->abort();
+    vWarn("GeoIP request socket error (request id %1): %2").arg(req->id())
+                                                           .arg(errorString);
+    delete socket;
+    delete req;
+  }
+}
+
+/** Creates an HTTP request for Geo IP information. */
+GeoIpRequest*
+GeoIpResolver::createRequest(const QList<QHostAddress> &ips)
+{
+  static int id = -1;
+  GeoIpRequest *request = new GeoIpRequest(++id);
+  request->setHost(GEOIP_HOST);
+  request->setPage(GEOIP_PAGE);
+  request->setRequest(ips);
+  return request;
+}
+
+/** Resolves a list of IPs to a geographic location. */
+int
+GeoIpResolver::resolve(const QList<QHostAddress> &ips)
+{
+  /* Resolve the cached IPs and get a list of IPs that still need to be
+   * resolved to a lat and long. */
+  QList<QHostAddress> uncached = resolveFromCache(ips);
+  if (! uncached.size())
+    return -1;
+
+  /* Create a socket used to request the geo ip information. */
+  TorSslSocket *socket = new TorSslSocket(_socksAddr, _socksPort);
+  connect(socket, SIGNAL(connectedToRemoteHost()), this, SLOT(connected()),
+          Qt::QueuedConnection);
+  connect(socket, SIGNAL(socketError(QString)), 
+          this,   SLOT(socketError(QString)),
+          Qt::QueuedConnection);
+  connect(socket, SIGNAL(disconnected()), this, SLOT(disconnected()),
+          Qt::QueuedConnection);
+  GeoIpRequest *request = createRequest(uncached);
+  _requestList.insert(socket, request);
+  
+  /* Connect so we can send our request and return the request ID. */
+  vInfo("Opening an SSL connection to the GeoIP host at %1:%2 (request id %3)")
+                        .arg(GEOIP_HOST).arg(GEOIP_SSL_PORT).arg(request->id());
+  socket->connectToRemoteHost(GEOIP_HOST, GEOIP_SSL_PORT, true);
+
+  return request->id();
+}
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResolver.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResolver.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResolver.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResolver.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,86 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+G** \file GeoIpResolver.h
+** \version $Id$
+** \brief Requests GeoIP information and caches the result
+*/
+
+#ifndef _GEOIPRESOLVER_H
+#define _GEOIPRESOLVER_H
+
+#include "GeoIpCache.h"
+
+#include <QObject>
+#include <QList>
+#include <QHash>
+#include <QHostAddress>
+
+class GeoIp;
+class GeoIpRequest;
+class GeoIpResponse;
+class QString;
+class QAbstractSocket;
+
+
+class GeoIpResolver : public QObject
+{
+  Q_OBJECT
+
+public:
+  /** Default constructor. */
+  GeoIpResolver(QObject *parent = 0);
+  
+  /** Sets the address and port of Tor, through which GeoIP requests will be
+   * made. */
+  void setSocksHost(const QHostAddress &addr, quint16 port);
+  /** Resolves a single IP to a geographic location. */
+  int resolve(const QHostAddress &ip);
+  /** Resolves a list of IPs to a geographic location. */
+  int resolve(const QList<QHostAddress> &ips);
+  /** Resolves <b>ip</b> to geographic information only if it is cached. */
+  bool resolveFromCache(const QHostAddress &ip);
+  /** Resolves a list of IPs to a geographic location, but only those which
+   * are cached. Returns a list of which IPs were not cached. */
+  QList<QHostAddress> resolveFromCache(const QList<QHostAddress> &ips);
+
+signals:
+  /** Emitted when a list of IPs have been resolved to lat/long. */
+  void resolved(int id, const QList<GeoIp> &geoips);
+  /** Emitted when a resolve has failed. */
+  void resolveFailed(int id, const QString &errorString);
+
+private slots:
+  /** Called when the socket has connected to the Geo IP host. */
+  void connected();
+  /** Called when the socket has disconnected from the Geo IP host. */
+  void disconnected();
+  /** Called when an error has occurred getting the Geo IP information. */
+  void socketError(const QString &errorString);
+
+private:
+  /** Creates an HTTP request for Geo IP information. */
+  GeoIpRequest* createRequest(const QList<QHostAddress> &ips);
+
+  void parseGeoIpResponse(const QByteArray &response, GeoIpRequest *request);
+
+  /**< Cached GeoIp objects. */
+  GeoIpCache*  _cache;
+  /**< List of sockets used for requests. */
+  QHash<QAbstractSocket *,GeoIpRequest*> _requestList;
+  /** Tor's SocksListenAddress. */
+  QHostAddress _socksAddr;
+  /** Tor's SocksPort. */
+  quint16 _socksPort;
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResolver.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResponse.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResponse.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResponse.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,137 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file GeoIpResponse.cpp
+** \version $Id$
+** \brief Parses a response to a previous GeoIP request
+*/
+
+#include "GeoIpResponse.h"
+#include "GeoIp.h"
+#include "Vidalia.h"
+
+#include "ZlibByteArray.h"
+
+#include <QByteArray>
+#include <QStringList>
+#include <QHttpResponseHeader>
+
+/** Status code for a successful HTTP request. */
+#define STATUS_HTTP_OK                 200
+/** Status code for content encoding errors. */
+#define STATUS_CONTENT_ENCODING_ERR    601
+/** Status code for transfer encoding errors. */
+#define STATUS_TRANSFER_ENCODING_ERR   602
+
+
+GeoIpResponse::GeoIpResponse(const QByteArray &response)
+{
+  QString errmsg;
+  
+  /* Parse out the header */
+  int headerPos = response.indexOf("\r\n\r\n");
+  _header = QHttpResponseHeader(QString(response.mid(0, headerPos)));
+
+  /* Parse out the Geo IP information, if any was included. */
+  if (headerPos > 0 && _header.statusCode() == STATUS_HTTP_OK) {
+    _content = response.mid(headerPos+4);
+ 
+    if (_header.hasKey("Transfer-Encoding")) {
+      QString encoding = _header.value("Transfer-Encoding");
+      if (encoding == "chunked") {
+        _content = decodeChunked(_content);
+        if (_content.isEmpty()) {
+          _header.setStatusLine(STATUS_TRANSFER_ENCODING_ERR,
+            QString("Failed to decode chunked response"));
+          return;
+        }
+      } else {
+        _header.setStatusLine(STATUS_TRANSFER_ENCODING_ERR,
+          QString("Unknown transfer encoding '%1'").arg(encoding));
+        return;
+      }
+    }
+ 
+    if (_header.hasKey("Content-Encoding")) {
+      ZlibByteArray::CompressionMethod method;
+      QString encoding = _header.value("Content-Encoding");
+      if (encoding == "gzip" || encoding == "x-gzip") {
+        method = ZlibByteArray::Gzip;
+      } else if (encoding == "deflate" || encoding == "x-deflate") {
+        method = ZlibByteArray::Zlib;
+      } else if (encoding == "text/plain") {
+        method = ZlibByteArray::None;
+      } else {
+        _header.setStatusLine(STATUS_CONTENT_ENCODING_ERR,
+          QString("Unknown content encoding '%1'").arg(encoding));
+        return;
+      }
+ 
+      _content = ZlibByteArray::uncompress(_content, method, &errmsg);
+      if (_content.isEmpty()) {
+        _header.setStatusLine(STATUS_CONTENT_ENCODING_ERR,
+          QString("Content decoding using method '%1' failed: %2")
+                                       .arg(encoding).arg(errmsg));
+        return;
+      }
+    }
+  }
+}
+
+QByteArray
+GeoIpResponse::decodeChunked(const QByteArray &chunked)
+{
+  QByteArray unchunked;
+  QString sizeString;
+  int eol, chunkedlen, chunksize, offset = 0;
+  bool ok;
+ 
+  chunkedlen = chunked.length();
+  while (offset < chunkedlen) {
+    eol = chunked.indexOf("\r\n", offset);
+    if (eol < 0)
+      return QByteArray();
+    sizeString = QString::fromAscii(chunked.mid(offset, eol-offset));
+    offset = eol + 2; /* Skip past the CRLF */
+    
+    if (sizeString.indexOf(";") >= 0)
+      sizeString.truncate(sizeString.indexOf(";")); 
+    chunksize = sizeString.toInt(&ok, 16);
+    if (!ok || chunksize > chunkedlen - offset)
+      return QByteArray();
+    if (!chunksize)
+      break; /* Last chunk. Ignore the trailer. */
+    
+    unchunked.append(chunked.mid(offset, chunksize));
+    offset += chunksize;
+    offset += 2; /* CRLF after each chunk */
+  }
+  return unchunked;
+}
+
+int
+GeoIpResponse::statusCode() const
+{
+  return _header.statusCode();
+}
+
+QString
+GeoIpResponse::statusMessage() const
+{
+  return _header.reasonPhrase();
+}
+
+QByteArray
+GeoIpResponse::content() const
+{
+  return _content;
+}
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResponse.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResponse.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResponse.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResponse.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,58 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file GeoIpResponse.h
+** \version $Id$
+** \brief Parses a response to a previous GeoIP request
+*/
+
+#ifndef _GEOIPRESPONSE_H
+#define _GEOIPRESPONSE_H
+
+#include <QList>
+#include <QByteArray>
+#include <QHttpResponseHeader>
+
+class GeoIp;
+class QString;
+class QStringList;
+
+
+class GeoIpResponse
+{
+public:
+  /** Constructor. Parses the response data for an HTTP header and Geo IP
+   * information.  */
+  GeoIpResponse(const QByteArray &response);
+
+  /** Returns the HTTP status code for this response.
+   */
+  int statusCode() const;
+
+  /** Returns the HTTP status message for this response.
+   */
+  QString statusMessage() const;
+
+  /** Returns the Geo IP information contained in this response.
+   */
+  QByteArray content() const;
+  
+private:
+  /** Decodes a <b>chunked</b> transfer encoding. Returns the unchunked 
+   * result on success, or an empty QByteArray if decoding fails. */
+  QByteArray decodeChunked(const QByteArray &chunked);
+  
+  QHttpResponseHeader _header; /**< HTTP response header. */
+  QByteArray _content; /**< Geo IP information in this response. */
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/GeoIpResponse.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/NetViewer.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/NetViewer.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/NetViewer.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,593 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file NetViewer.cpp
+** \version $Id$
+** \brief Displays a map of the Tor network and the user's circuits
+*/
+
+#include "NetViewer.h"
+#include "RouterInfoDialog.h"
+#include "RouterListItem.h"
+#include "Vidalia.h"
+#include "VMessageBox.h"
+
+#include <QMessageBox>
+#include <QHeaderView>
+
+#define IMG_MOVE    ":/images/22x22/move-map.png"
+#define IMG_ZOOMIN  ":/images/22x22/zoom-in.png"
+#define IMG_ZOOMOUT ":/images/22x22/zoom-out.png"
+
+/** Number of milliseconds to wait after the arrival of the last descriptor whose
+ * IP needs to be resolved to geographic information, in case more descriptors
+ * arrive. Then we can simply lump the IPs into a single request. */
+#define MIN_RESOLVE_QUEUE_DELAY   (10*1000)
+/** Maximum number of milliseconds to wait after the arrival of the first
+ * IP address into the resolve queue, before we flush the entire queue. */
+#define MAX_RESOLVE_QUEUE_DELAY   (30*1000)
+
+
+/** Constructor. Loads settings from VidaliaSettings.
+ * \param parent The parent widget of this NetViewer object.\
+ */
+NetViewer::NetViewer()
+  : VidaliaPanel()
+{
+  /* Invoke Qt Designer generated QObject setup routine */
+  ui.setupUi(this);
+
+#if defined(Q_WS_MAC)
+  ui.actionHelp->setShortcut(QString("Ctrl+?"));
+#endif
+
+  /* Pressing 'Esc' or 'Ctrl+W' will close the window */
+  ui.actionClose->setShortcut(QString("Esc"));
+  Vidalia::createShortcut("Ctrl+W", this, ui.actionClose, SLOT(trigger()));
+
+  /* Get the TorControl object */
+  _torControl = Vidalia::torControl();
+  _torControl->setEvent(TorEvents::NewDescriptor, this, true);
+  _torControl->setEvent(TorEvents::CircuitStatus, this, true);
+  _torControl->setEvent(TorEvents::StreamStatus,  this, true);
+  _torControl->setEvent(TorEvents::AddressMap,    this, true);
+
+  /* Change the column widths of the tree widgets */
+  ui.treeRouterList->header()->
+    resizeSection(RouterListWidget::StatusColumn, 25);
+  ui.treeRouterList->header()->
+    resizeSection(RouterListWidget::CountryColumn, 25);
+  ui.treeCircuitList->header()->
+    resizeSection(CircuitListWidget::ConnectionColumn, 235);
+
+  /* Create the TorMapWidget and add it to the dialog */
+#if defined(USE_MARBLE)
+  _map = new TorMapWidget();
+  connect(_map, SIGNAL(displayRouterInfo(QString)),
+          this, SLOT(displayRouterInfo(QString)));
+  connect(ui.actionZoomFullScreen, SIGNAL(triggered()),
+          this, SLOT(toggleFullScreen()));
+  Vidalia::createShortcut("ESC", _map, this, SLOT(toggleFullScreen()));
+#else
+  _map = new TorMapImageView();
+  ui.actionZoomFullScreen->setVisible(false);
+#endif
+  ui.gridLayout->addWidget(_map);
+
+
+  /* Connect zoom buttons to TorMapWidget zoom slots */
+  connect(ui.actionZoomIn, SIGNAL(triggered()), this, SLOT(zoomIn()));
+  connect(ui.actionZoomOut, SIGNAL(triggered()), this, SLOT(zoomOut()));
+  connect(ui.actionZoomToFit, SIGNAL(triggered()), _map, SLOT(zoomToFit()));
+
+  /* Create the timer that will be used to update the router list once every
+   * hour. We still receive the NEWDESC event to get new descriptors, but this
+   * needs to be called to get rid of any descriptors that were removed. */
+  _refreshTimer.setInterval(60*60*1000);
+  connect(&_refreshTimer, SIGNAL(timeout()), this, SLOT(refresh()));
+  
+  /* Set up the timers used to group together GeoIP requests for new
+   * descriptors arriving within MIN_RESOLVE_QUEUE_DELAY milliseconds, but no
+   * more than MAX_RESOLVE_QUEUE_DELAY milliseconds of each other. */
+  _minResolveQueueTimer.setSingleShot(true);
+  connect(&_minResolveQueueTimer, SIGNAL(timeout()), this, SLOT(resolve()));
+  _maxResolveQueueTimer.setSingleShot(true);
+  connect(&_maxResolveQueueTimer, SIGNAL(timeout()), this, SLOT(resolve()));
+
+  /* Connect the necessary slots and signals */
+  connect(ui.actionHelp, SIGNAL(triggered()), this, SLOT(help()));
+  connect(ui.actionRefresh, SIGNAL(triggered()), this, SLOT(refresh()));
+  connect(ui.treeRouterList, SIGNAL(routerSelected(QList<RouterDescriptor>)),
+	        this, SLOT(routerSelected(QList<RouterDescriptor>)));
+  connect(ui.treeRouterList, SIGNAL(zoomToRouter(QString)),
+          _map, SLOT(zoomToRouter(QString)));
+  connect(ui.treeCircuitList, SIGNAL(circuitSelected(Circuit)),
+          this, SLOT(circuitSelected(Circuit)));
+  connect(ui.treeCircuitList, SIGNAL(circuitRemoved(CircuitId)),
+          _map, SLOT(removeCircuit(CircuitId)));
+  connect(ui.treeCircuitList, SIGNAL(zoomToCircuit(CircuitId)),
+          _map, SLOT(zoomToCircuit(CircuitId)));
+  connect(ui.treeCircuitList, SIGNAL(closeCircuit(CircuitId)),
+          _torControl, SLOT(closeCircuit(CircuitId)));
+  connect(ui.treeCircuitList, SIGNAL(closeStream(StreamId)),
+          _torControl, SLOT(closeStream(StreamId)));
+
+  /* Respond to changes in the status of the control connection */
+  connect(_torControl, SIGNAL(authenticated()), this, SLOT(onAuthenticated()));
+  connect(_torControl, SIGNAL(disconnected()), this, SLOT(onDisconnected())); 
+
+  /* Connect the slot to find out when geoip information has arrived */
+  connect(&_geoip, SIGNAL(resolved(int, QList<GeoIp>)), 
+             this,   SLOT(resolved(int, QList<GeoIp>)));
+}
+
+/** Called when the user changes the UI translation. */
+void
+NetViewer::retranslateUi()
+{
+  ui.retranslateUi(this);
+  ui.treeRouterList->retranslateUi();
+  ui.treeCircuitList->retranslateUi();
+
+  if (ui.treeRouterList->selectedItems().size()) {
+    QList<RouterDescriptor> routers;
+    foreach (QTreeWidgetItem *item, ui.treeRouterList->selectedItems()) {
+      routers << dynamic_cast<RouterListItem *>(item)->descriptor();
+    }
+    ui.textRouterInfo->display(routers);
+  } else if (ui.treeCircuitList->selectedItems().size()) {
+    QList<RouterDescriptor> routers;
+    QTreeWidgetItem *item = ui.treeCircuitList->selectedItems()[0];
+    Circuit circuit = dynamic_cast<CircuitItem*>(item)->circuit();
+    foreach (QString id, circuit.routerIDs()) {
+      RouterListItem *item = ui.treeRouterList->findRouterById(id);
+      if (item)
+        routers.append(item->descriptor());
+    }
+    ui.textRouterInfo->display(routers);
+  }
+}
+
+/** Display the network map window. If there are geoip requests waiting in the
+ * queue, start the queue timers now. */
+void
+NetViewer::showWindow()
+{
+  if (!_resolveQueue.isEmpty()) {
+    _minResolveQueueTimer.start(MIN_RESOLVE_QUEUE_DELAY);
+    _maxResolveQueueTimer.start(MAX_RESOLVE_QUEUE_DELAY);
+  }
+  show();
+}
+
+/** Loads data into map, lists and starts timer when we get connected*/
+void
+NetViewer::onAuthenticated()
+{
+  _geoip.setSocksHost(_torControl->getSocksAddress(),
+                      _torControl->getSocksPort());
+  refresh();
+  _refreshTimer.start();
+  ui.actionRefresh->setEnabled(true);
+}
+
+/** Clears map, lists and stops timer when we get disconnected */
+void
+NetViewer::onDisconnected()
+{
+  clear();
+  _refreshTimer.stop();
+  ui.actionRefresh->setEnabled(false);
+}
+
+/** Custom event handler. Catches the new descriptor events. */
+void
+NetViewer::customEvent(QEvent *event)
+{
+  int type = event->type();
+  
+  if (type == CustomEventType::NewDescriptorEvent) {
+    /* New router descriptor, so load it and add it to the list */
+    NewDescriptorEvent *nde = (NewDescriptorEvent *)event;
+    newDescriptors(nde->descriptorIDs());
+  } else if (type == CustomEventType::CircuitEvent) {
+    /* New or updated circuit information */
+    CircuitEvent *ce = (CircuitEvent *)event;
+    addCircuit(ce->circuit());
+  } else if (type == CustomEventType::StreamEvent) {
+    /* New or updated stream information */
+    StreamEvent *se = (StreamEvent *)event;
+    addStream(se->stream());
+  } else if (type == CustomEventType::AddressMapEvent) {
+    /* New or updated address mapping. We store the reverse of the new
+     * mapping, so we can go from an IP address back to a hostname. */
+    AddressMapEvent *ae = (AddressMapEvent *)event;
+    _addressMap.add(ae->to(), ae->from(), ae->expires());
+  }
+
+  /* Update the world map */
+  _map->update();
+}
+
+/** Reloads the lists of routers, circuits that Tor knows about */
+void
+NetViewer::refresh()
+{
+  /* Don't let the user refresh while we're refreshing. */
+  ui.actionRefresh->setEnabled(false);
+
+  /* Clear the data */
+  clear();
+
+  /* Load router information */
+  loadNetworkStatus();
+  /* Load existing address mappings */
+  loadAddressMap();
+  /* Load Circuits and Streams information */
+  loadConnections();
+
+  /* Ok, they can refresh again. */
+  ui.actionRefresh->setEnabled(true);
+} 
+
+/** Clears the lists and the map */
+void
+NetViewer::clear()
+{
+  /* Clear the resolve queue and map */
+  _resolveMap.clear();
+  _resolveQueue.clear();
+  /* Clear the network map */
+  _map->clear();
+  _map->update();
+  /* Clear the address map */
+  _addressMap.clear();
+  /* Clear the lists of routers, circuits, and streams */
+  ui.treeRouterList->clearRouters();
+  ui.treeCircuitList->clearCircuits();
+  ui.textRouterInfo->clear();
+}
+
+/** Loads a list of all current address mappings. */
+void
+NetViewer::loadAddressMap()
+{
+  /* We store the reverse address mappings, so we can go from a numeric value
+   * back to a likely more meaningful hostname to display for the user. */
+  _addressMap = _torControl->getAddressMap().reverse();
+}
+
+/** Loads a list of all current circuits and streams. */
+void
+NetViewer::loadConnections()
+{
+  /* Load all circuits */
+  CircuitList circuits = _torControl->getCircuits();
+  foreach (Circuit circuit, circuits) {
+    addCircuit(circuit);
+  }
+  /* Now load all streams */
+  StreamList streams = _torControl->getStreams();
+  foreach (Stream stream, streams) {
+    addStream(stream);
+  }
+
+  /* Update the map */
+  _map->update();
+}
+
+/** Adds <b>circuit</b> to the map and the list */
+void
+NetViewer::addCircuit(const Circuit &circuit)
+{
+  /* Add the circuit to the list of all current circuits */
+  ui.treeCircuitList->addCircuit(circuit);
+  /* Plot the circuit on the map */
+  _map->addCircuit(circuit.id(), circuit.routerIDs());
+}
+
+/** Adds <b>stream</b> to its associated circuit on the list of all circuits. */
+void
+NetViewer::addStream(const Stream &stream)
+{
+  QString target = stream.targetAddress();
+  QHostAddress addr(target);
+  
+  /* If the stream's target has an IP address instead of a host name,
+   * check our cache for an existing reverse address mapping. */
+  if (!addr.isNull() && _addressMap.isMapped(target)) {
+    /* Replace the IP address in the stream event with the original 
+     * hostname */
+    ui.treeCircuitList->addStream(
+      Stream(stream.id(), stream.status(), stream.circuitId(),
+             _addressMap.mappedTo(target), stream.targetPort()));
+  } else {
+    ui.treeCircuitList->addStream(stream);
+  }
+}
+
+/** Called when the user selects the "Help" action from the toolbar. */
+void
+NetViewer::help()
+{
+#if 0
+  emit helpRequested("netview");
+#endif
+}
+
+/** Retrieves a list of all running routers from Tor and their descriptors,
+ * and adds them to the RouterListWidget. */
+void
+NetViewer::loadNetworkStatus()
+{
+  NetworkStatus networkStatus = _torControl->getNetworkStatus();
+  foreach (RouterStatus rs, networkStatus) {
+    if (!rs.isRunning())
+      continue;
+
+    RouterDescriptor rd = _torControl->getRouterDescriptor(rs.id());
+    if (!rd.isEmpty())
+      addRouter(rd);
+  }
+}
+
+/** Adds a router to our list of servers and retrieves geographic location
+ * information for the server. */
+void
+NetViewer::addRouter(const RouterDescriptor &rd)
+{
+  /* Add the descriptor to the list of server */
+  ui.treeRouterList->addRouter(rd);
+
+  /* Add this IP to a list of addresses whose geographic location we'd like to
+   * find, but not for special purpose descriptors (e.g., bridges). This
+   * check is only valid for Tor >= 0.2.0.13-alpha. */
+  if (_torControl->getTorVersion() >= 0x020013) {
+    DescriptorAnnotations annotations =
+      _torControl->getDescriptorAnnotations(rd.id());
+    if (!annotations.contains("purpose"))
+      addToResolveQueue(rd.ip(), rd.id());
+  } else {
+    addToResolveQueue(rd.ip(), rd.id());
+  }
+}
+
+/** Called when a NEWDESC event arrives. Retrieves new router descriptors
+ * for the router identities given in <b>ids</b> and updates the router
+ * list and network map. */
+void
+NetViewer::newDescriptors(const QStringList &ids)
+{
+  foreach (QString id, ids) {
+    RouterDescriptor rd = _torControl->getRouterDescriptor(id);
+    if (!rd.isEmpty())
+      addRouter(rd); /* Updates the existing entry */
+  }
+}
+
+/** Adds an IP address to the resolve queue and updates the queue timers. */
+void
+NetViewer::addToResolveQueue(const QHostAddress &ip, const QString &id)
+{
+  QString ipstr = ip.toString();
+  if (!_resolveMap.values(ipstr).contains(id)) {
+    /* Remember which server ids belong to which IP addresses */
+    _resolveMap.insertMulti(ipstr, id);
+  }
+ 
+  if (!_resolveQueue.contains(ip) && !_geoip.resolveFromCache(ip)) {
+    /* Add the IP to the queue of IPs waiting for geographic information  */
+    _resolveQueue << ip;
+ 
+    /* Wait MIN_RESOLVE_QUEUE_DELAY after the last item inserted into the
+     * queue, before sending the resolve request. */
+    _minResolveQueueTimer.start(MIN_RESOLVE_QUEUE_DELAY);
+    
+    /* Do not wait longer than MAX_RESOLVE_QUEUE_DELAY from the time the first
+     * item is inserted into the queue, before flushing and resolving the
+     * queue. */
+    if (_resolveQueue.size() == 1) {
+      _maxResolveQueueTimer.start(MAX_RESOLVE_QUEUE_DELAY);
+    }
+  }
+}
+
+/** Called when the user selects a circuit from the circuit and streams
+ * list. */
+void
+NetViewer::circuitSelected(const Circuit &circuit)
+{
+  /* Clear any selected items. */
+  ui.treeRouterList->deselectAll();
+  ui.textRouterInfo->clear();
+  _map->deselectAll();
+
+  /* Select the items on the map and in the list */
+  _map->selectCircuit(circuit.id());
+
+  QList<RouterDescriptor> routers;
+
+  foreach (QString id, circuit.routerIDs()) {
+    /* Try to find and select each router in the path */
+    RouterListItem *item = ui.treeRouterList->findRouterById(id);
+    if (item)
+      routers.append(item->descriptor());
+  }
+
+  ui.textRouterInfo->display(routers);
+}
+
+/** Called when the user selects one or more routers from the router list. */
+void
+NetViewer::routerSelected(const QList<RouterDescriptor> &routers)
+{
+  _map->deselectAll();
+  ui.textRouterInfo->clear();
+  ui.textRouterInfo->display(routers);
+
+  /* XXX: Ideally we would also be able to select multiple pinpoints on the
+   *      map. But our current map sucks and you can't even tell when one is
+   *      selected anyway. Worry about this when we actually get to Marble.
+   */
+  if (routers.size() == 1)
+    _map->selectRouter(routers[0].id());
+}
+
+/** If there are any IPs in the resolve queue, do the request now. */
+void
+NetViewer::resolve()
+{
+  if (!_resolveQueue.isEmpty()) {
+    /* Send the request now if either the network map is visible, or the
+     * request is for more than a quarter of the servers in the list. */
+    if (isVisible() || 
+        (_resolveQueue.size() >= ui.treeRouterList->topLevelItemCount()/4)) {
+      vInfo("Sending GeoIP request for %1 IP addresses.")
+                               .arg(_resolveQueue.size());
+      /* Flush the resolve queue and stop the timers */
+      _geoip.resolve(_resolveQueue);
+      _resolveQueue.clear();
+    }
+  }
+  /* Stop the queue timers. Only one should be active since the other is what
+   * called this slot, but calling stop() on a stopped timer does not hurt. */
+  _minResolveQueueTimer.stop();
+  _maxResolveQueueTimer.stop();
+}
+
+/** Called when a list of GeoIp information has been resolved. */
+void
+NetViewer::resolved(int id, const QList<GeoIp> &geoips)
+{
+  Q_UNUSED(id);
+
+  QString ip;
+  RouterListItem *router;
+ 
+  foreach (GeoIp geoip, geoips) {
+    /* Find all routers that are at this IP address */
+    ip = geoip.ip().toString();
+    QList<QString> ids = _resolveMap.values(ip);
+    _resolveMap.remove(ip);
+      
+    /* Update their geographic location information with the results of this
+     * GeoIP query. */
+    foreach (QString id, ids) {
+      router = ui.treeRouterList->findRouterById(id);
+      if (router) {
+        /* Save the location information in the descriptor */
+        router->setLocation(geoip);
+        /* Plot the router on the map */
+        _map->addRouter(router->descriptor(), geoip);
+      }
+    }
+  }
+
+  /* Update the circuit lines */
+  foreach (Circuit circuit, ui.treeCircuitList->circuits()) {
+    _map->addCircuit(circuit.id(), circuit.routerIDs());
+  }
+
+  /* Repaint the map */
+  _map->update();
+}
+
+/** Called when the user selects a router on the network map. Displays a 
+ * dialog with detailed information for the router specified by
+ * <b>id</b>.*/
+void
+NetViewer::displayRouterInfo(const QString &id)
+{
+  RouterInfoDialog dlg(_map->isFullScreen() ? static_cast<QWidget*>(_map) 
+                                            : static_cast<QWidget*>(this));
+
+  /* Fetch the specified router's descriptor */
+  QStringList rd = _torControl->getRouterDescriptorText(id);
+  if (rd.isEmpty()) {
+    VMessageBox::warning(this, tr("Relay Not Found"),
+                         tr("No details on the selected relay are available."),
+                         VMessageBox::Ok);
+    return;
+  }
+
+  /* Fetch the router's network status information */
+  RouterStatus rs = _torControl->getRouterStatus(id);
+
+  dlg.setRouterInfo(rd, rs);
+
+  /* Populate the UI with information learned from a previous GeoIP request */
+  RouterListItem *item = ui.treeRouterList->findRouterById(id);
+  if (item)
+    dlg.setLocation(item->location());
+  else
+    dlg.setLocation(tr("Unknown"));
+
+  dlg.exec();
+}
+
+/* XXX: The following zoomIn() and zoomOut() slots are a hack. MarbleWidget
+ *      does have zoomIn() and zoomOut() slots to which we could connect the
+ *      buttons, but these slots currently don't force a repaint. So to see
+ *      the zoom effect, the user has to click on the map after clicking one
+ *      of the zoom buttons. Instead, we use the zoomViewBy() method, which
+ *      DOES force a repaint.
+ */
+/** Called when the user clicks the "Zoom In" button. */
+void
+NetViewer::zoomIn()
+{
+#if defined(USE_MARBLE)
+  _map->zoomViewBy(40);
+#else
+  _map->zoomIn();
+#endif
+}
+
+/** Called when the user clicks the "Zoom Out" button. */
+void
+NetViewer::zoomOut()
+{
+#if defined(USE_MARBLE)
+  _map->zoomViewBy(-40);
+#else
+  _map->zoomOut();
+#endif
+}
+
+/** Called when the user clicks "Full Screen" or presses Escape on the map.
+ * Toggles the map between normal and a full screen viewing modes. */
+void
+NetViewer::toggleFullScreen()
+{
+  if (_map->isFullScreen()) {
+    /* Disabling full screen mode. Put the map back in its container. */
+    ui.gridLayout->addWidget(_map);
+    _map->setWindowState(_map->windowState() & ~Qt::WindowFullScreen);
+  } else {
+    /* Enabling full screen mode. Remove the map from the QGridLayout
+     * container and set its window state to full screen. */
+    _map->setParent(0);
+    _map->setWindowState(_map->windowState() | Qt::WindowFullScreen);
+    _map->show();
+  }
+}
+
+QString
+NetViewer::tabLabel() const
+{
+  return QString("Network Map");
+}
+
+QIcon
+NetViewer::tabIcon() const
+{
+  return QIcon();
+}


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/NetViewer.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/NetViewer.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/NetViewer.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/NetViewer.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,151 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file NetViewer.h
+** \version $Id$
+** \brief Displays a map of the Tor network and the user's circuits
+*/
+
+#ifndef _NETVIEWER_H
+#define _NETVIEWER_H
+
+#include "config.h"
+#include "ui_NetViewer.h"
+#include "VidaliaPanel.h"
+#include "GeoIpResolver.h"
+
+#if defined(USE_MARBLE)
+#include "TorMapWidget.h"
+#else
+#include "TorMapImageView.h"
+#endif
+
+#include "TorControl.h"
+
+#include <QIcon>
+#include <QMainWindow>
+#include <QString>
+#include <QStringList>
+#include <QEvent>
+#include <QTimer>
+#include <QHash>
+
+
+class NetViewer : public VidaliaPanel
+{
+  Q_OBJECT
+
+public:
+  /** Default constructor */
+  NetViewer();
+
+public slots:
+  /** Displays the network map window. */
+  void showWindow();
+  /** Loads a list of current circuits and streams. */
+  void loadConnections();
+  /** Adds <b>circuit</b> to the list and the map */
+  void addCircuit(const Circuit &circuit);
+  /** Adds <b>stream</b> to the list of circuits, under the appropriate
+   * circuit. */
+  void addStream(const Stream &stream);
+  /** Clears all known information */
+  void clear();
+
+protected:
+  /** Called to deliver a NEWDESC event from Tor. */
+  void customEvent(QEvent *event);
+  /** Called when the user changes the UI translation. */
+  void retranslateUi();
+
+private slots:
+  /** Called when the user selects the "Help" action on the toolbar. */
+  void help();
+  /** Called when the user selects the "Refresh" action on the toolbar */
+  void refresh();
+  /** Called when the user selects a circuit on the circuit list */
+  void circuitSelected(const Circuit &circuit);
+  /** Called when an IP has been resolved to geographic information. */
+  void resolved(int id, const QList<GeoIp> &geoips);
+  /** Called when the user selects one or more routers in the list. */
+  void routerSelected(const QList<RouterDescriptor> &routers);
+  /** Handles when we get connected to Tor network */
+  void onAuthenticated();
+  /** Handles when we get disconnected from Tor network */
+  void onDisconnected();
+  /** Resolves IP addresses in the resolve queue to geographic information. */
+  void resolve();
+  /** Called when the user selects a router on the network map. Displays a 
+   * dialog with detailed information for the router specified by
+   * <b>id</b>.*/
+  void displayRouterInfo(const QString &id);
+  /** Called when the user clicks the "Zoom In" button. */
+  void zoomIn();
+  /** Called when the user clicks the "Zoom Out" button. */
+  void zoomOut();
+  /** Called when the user clicks "Full Screen" or presses Escape on the map.
+   * Toggles the map between normal and a full screen viewing modes. */
+  void toggleFullScreen();
+
+  /** Hook for displaying text in a panel's tab. */
+  QString tabLabel() const;
+  /** Hook for displaying an icon in a panel's tab. */
+  QIcon tabIcon() const;
+
+private:
+  /** Adds an IP address to the resolve queue and updates the queue timers. */
+  void addToResolveQueue(const QHostAddress &ip, const QString &id);
+  /** Retrieves a list of all running routers from Tor and their descriptors,
+   * and adds them to the RouterListWidget. */
+  void loadNetworkStatus();
+  /** Loads a list of address mappings from Tor. */
+  void loadAddressMap();
+  /** Adds a router to our list of servers and retrieves geographic location
+   * information for the server. */
+  void addRouter(const RouterDescriptor &rd);
+  /** Called when a NEWDESC event arrives. Retrieves new router descriptors
+   * for the router identities given in <b>ids</b> and updates the router list
+   * and network map. */
+  void newDescriptors(const QStringList &ids);
+
+  /** TorControl object used to talk to Tor. */
+  TorControl* _torControl;
+  /** Timer that fires once an hour to update the router list. */
+  QTimer _refreshTimer;
+  /** GeoIpResolver used to geolocate routers by IP address. */
+  GeoIpResolver _geoip;
+  /** Queue for IPs pending resolution to geographic information. */
+  QList<QHostAddress> _resolveQueue;
+  /** Maps pending GeoIP requests to server IDs. */
+  QHash<QString, QString> _resolveMap;
+  /** Stores a list of address mappings from Tor. */
+  AddressMap _addressMap;
+  /** Timer used to delay GeoIP requests for MIN_RESOLVE_QUEUE_DELAY
+   * milliseconds after we've inserted the last item into the queue. */
+  QTimer _minResolveQueueTimer;
+  /** Timer used to limit the delay of GeoIP requests to
+   * MAX_RESOLVE_QUEUE_DELAY milliseconds after inserting the first item 
+   * into the queue. */
+  QTimer _maxResolveQueueTimer;
+ 
+  /** Widget that displays the Tor network map. */
+#if defined(USE_MARBLE)
+  TorMapWidget* _map;
+#else
+  TorMapImageView* _map;
+#endif
+
+  /** Qt Designer generated object **/
+  Ui::NetViewer ui;
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/NetViewer.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/NetViewer.ui
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/NetViewer.ui	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/NetViewer.ui	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,393 @@
+<ui version="4.0" >
+ <class>NetViewer</class>
+ <widget class="QMainWindow" name="NetViewer" >
+  <property name="geometry" >
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>844</width>
+    <height>596</height>
+   </rect>
+  </property>
+  <property name="sizePolicy" >
+   <sizepolicy>
+    <hsizetype>5</hsizetype>
+    <vsizetype>5</vsizetype>
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="contextMenuPolicy" >
+   <enum>Qt::NoContextMenu</enum>
+  </property>
+  <property name="windowTitle" >
+   <string>Tor Network Map</string>
+  </property>
+  <property name="windowIcon" >
+   <iconset resource="../res/vidalia.qrc" >:/images/32x32/applications-internet.png</iconset>
+  </property>
+  <property name="toolButtonStyle" >
+   <enum>Qt::ToolButtonTextUnderIcon</enum>
+  </property>
+  <widget class="QWidget" name="centralwidget" >
+   <layout class="QVBoxLayout" >
+    <property name="margin" >
+     <number>9</number>
+    </property>
+    <property name="spacing" >
+     <number>6</number>
+    </property>
+    <item>
+     <widget class="QSplitter" name="splitter" >
+      <property name="contextMenuPolicy" >
+       <enum>Qt::NoContextMenu</enum>
+      </property>
+      <property name="orientation" >
+       <enum>Qt::Horizontal</enum>
+      </property>
+      <property name="childrenCollapsible" >
+       <bool>false</bool>
+      </property>
+      <widget class="RouterListWidget" name="treeRouterList" >
+       <property name="sizePolicy" >
+        <sizepolicy>
+         <hsizetype>5</hsizetype>
+         <vsizetype>7</vsizetype>
+         <horstretch>1</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="minimumSize" >
+        <size>
+         <width>175</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="contextMenuPolicy" >
+        <enum>Qt::DefaultContextMenu</enum>
+       </property>
+       <property name="statusTip" >
+        <string/>
+       </property>
+       <property name="indentation" >
+        <number>0</number>
+       </property>
+       <property name="sortingEnabled" >
+        <bool>true</bool>
+       </property>
+       <property name="selectionMode" >
+        <enum>QAbstractItemView::ExtendedSelection</enum>
+       </property>
+      </widget>
+      <widget class="QSplitter" name="splitter3" >
+       <property name="sizePolicy" >
+        <sizepolicy>
+         <hsizetype>5</hsizetype>
+         <vsizetype>5</vsizetype>
+         <horstretch>100</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="contextMenuPolicy" >
+        <enum>Qt::NoContextMenu</enum>
+       </property>
+       <property name="orientation" >
+        <enum>Qt::Vertical</enum>
+       </property>
+       <property name="childrenCollapsible" >
+        <bool>false</bool>
+       </property>
+       <widget class="QFrame" name="frmMap" >
+        <property name="sizePolicy" >
+         <sizepolicy>
+          <hsizetype>7</hsizetype>
+          <vsizetype>7</vsizetype>
+          <horstretch>100</horstretch>
+          <verstretch>100</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="minimumSize" >
+         <size>
+          <width>400</width>
+          <height>300</height>
+         </size>
+        </property>
+        <property name="contextMenuPolicy" >
+         <enum>Qt::NoContextMenu</enum>
+        </property>
+        <property name="frameShape" >
+         <enum>QFrame::StyledPanel</enum>
+        </property>
+        <property name="frameShadow" >
+         <enum>QFrame::Raised</enum>
+        </property>
+        <layout class="QHBoxLayout" >
+         <property name="margin" >
+          <number>9</number>
+         </property>
+         <property name="spacing" >
+          <number>6</number>
+         </property>
+         <item>
+          <layout class="QGridLayout" >
+           <property name="margin" >
+            <number>0</number>
+           </property>
+           <property name="spacing" >
+            <number>6</number>
+           </property>
+          </layout>
+         </item>
+        </layout>
+       </widget>
+       <widget class="QSplitter" name="splitter2" >
+        <property name="sizePolicy" >
+         <sizepolicy>
+          <hsizetype>7</hsizetype>
+          <vsizetype>1</vsizetype>
+          <horstretch>100</horstretch>
+          <verstretch>1</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="contextMenuPolicy" >
+         <enum>Qt::NoContextMenu</enum>
+        </property>
+        <property name="orientation" >
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="childrenCollapsible" >
+         <bool>false</bool>
+        </property>
+        <widget class="CircuitListWidget" name="treeCircuitList" >
+         <property name="sizePolicy" >
+          <sizepolicy>
+           <hsizetype>5</hsizetype>
+           <vsizetype>5</vsizetype>
+           <horstretch>100</horstretch>
+           <verstretch>100</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize" >
+          <size>
+           <width>300</width>
+           <height>0</height>
+          </size>
+         </property>
+         <property name="contextMenuPolicy" >
+          <enum>Qt::CustomContextMenu</enum>
+         </property>
+         <property name="statusTip" >
+          <string/>
+         </property>
+         <property name="sortingEnabled" >
+          <bool>false</bool>
+         </property>
+        </widget>
+        <widget class="RouterDescriptorView" name="textRouterInfo" >
+         <property name="sizePolicy" >
+          <sizepolicy>
+           <hsizetype>5</hsizetype>
+           <vsizetype>5</vsizetype>
+           <horstretch>100</horstretch>
+           <verstretch>100</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="contextMenuPolicy" >
+          <enum>Qt::DefaultContextMenu</enum>
+         </property>
+         <property name="readOnly" >
+          <bool>true</bool>
+         </property>
+        </widget>
+       </widget>
+      </widget>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QStatusBar" name="statusbar" />
+  <widget class="QToolBar" name="toolBar" >
+   <property name="contextMenuPolicy" >
+    <enum>Qt::NoContextMenu</enum>
+   </property>
+   <property name="movable" >
+    <bool>false</bool>
+   </property>
+   <property name="orientation" >
+    <enum>Qt::Horizontal</enum>
+   </property>
+   <attribute name="toolBarArea" >
+    <number>4</number>
+   </attribute>
+   <addaction name="actionRefresh" />
+   <addaction name="separator" />
+   <addaction name="actionZoomIn" />
+   <addaction name="actionZoomOut" />
+   <addaction name="actionZoomToFit" />
+   <addaction name="actionZoomFullScreen" />
+   <addaction name="separator" />
+   <addaction name="actionHelp" />
+   <addaction name="actionClose" />
+  </widget>
+  <action name="actionRefresh" >
+   <property name="enabled" >
+    <bool>false</bool>
+   </property>
+   <property name="icon" >
+    <iconset resource="../res/vidalia.qrc" >:/images/32x32/view-refresh.png</iconset>
+   </property>
+   <property name="text" >
+    <string>Refresh</string>
+   </property>
+   <property name="toolTip" >
+    <string>Refresh the list of Tor relays and connections</string>
+   </property>
+   <property name="statusTip" >
+    <string>Refresh the list of Tor relays and connections</string>
+   </property>
+   <property name="shortcut" >
+    <string>Ctrl+R</string>
+   </property>
+  </action>
+  <action name="actionHelp" >
+   <property name="icon" >
+    <iconset resource="../res/vidalia.qrc" >:/images/32x32/system-help.png</iconset>
+   </property>
+   <property name="text" >
+    <string>Help</string>
+   </property>
+   <property name="toolTip" >
+    <string>Show the network map help</string>
+   </property>
+   <property name="statusTip" >
+    <string>Show network map help</string>
+   </property>
+   <property name="shortcut" >
+    <string>F1</string>
+   </property>
+  </action>
+  <action name="actionClose" >
+   <property name="icon" >
+    <iconset resource="../res/vidalia.qrc" >:/images/32x32/window-close.png</iconset>
+   </property>
+   <property name="text" >
+    <string>Close</string>
+   </property>
+   <property name="toolTip" >
+    <string>Close the network map</string>
+   </property>
+   <property name="statusTip" >
+    <string>Close the network map</string>
+   </property>
+   <property name="shortcut" >
+    <string>Esc</string>
+   </property>
+  </action>
+  <action name="actionZoomIn" >
+   <property name="icon" >
+    <iconset resource="../res/vidalia.qrc" >:/images/32x32/zoom-in.png</iconset>
+   </property>
+   <property name="text" >
+    <string>Zoom In</string>
+   </property>
+   <property name="toolTip" >
+    <string>Zoom in on the network map</string>
+   </property>
+   <property name="statusTip" >
+    <string>Zoom in on the network map</string>
+   </property>
+   <property name="shortcut" >
+    <string>+</string>
+   </property>
+  </action>
+  <action name="actionZoomOut" >
+   <property name="icon" >
+    <iconset resource="../res/vidalia.qrc" >:/images/32x32/zoom-out.png</iconset>
+   </property>
+   <property name="text" >
+    <string>Zoom Out</string>
+   </property>
+   <property name="toolTip" >
+    <string>Zoom out on the network map</string>
+   </property>
+   <property name="statusTip" >
+    <string>Zoom out on the network map</string>
+   </property>
+   <property name="shortcut" >
+    <string>-</string>
+   </property>
+  </action>
+  <action name="actionZoomToFit" >
+   <property name="icon" >
+    <iconset resource="../res/vidalia.qrc" >:/images/32x32/zoom-fit-best.png</iconset>
+   </property>
+   <property name="text" >
+    <string>Zoom To Fit</string>
+   </property>
+   <property name="toolTip" >
+    <string>Zooms to fit all currently displayed circuits</string>
+   </property>
+   <property name="statusTip" >
+    <string>Zooms to fit all currently displayed circuits</string>
+   </property>
+   <property name="shortcut" >
+    <string>Ctrl+Z</string>
+   </property>
+  </action>
+  <action name="actionZoomFullScreen" >
+   <property name="icon" >
+    <iconset resource="../res/vidalia.qrc" >:/images/32x32/view-fullscreen.png</iconset>
+   </property>
+   <property name="text" >
+    <string>Full Screen</string>
+   </property>
+   <property name="toolTip" >
+    <string>View the network map as a full screen window</string>
+   </property>
+   <property name="statusTip" >
+    <string>View the network map as a full screen window</string>
+   </property>
+   <property name="shortcut" >
+    <string>Ctrl+F</string>
+   </property>
+  </action>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>RouterDescriptorView</class>
+   <extends>QTextEdit</extends>
+   <header>RouterDescriptorView.h</header>
+  </customwidget>
+  <customwidget>
+   <class>RouterListWidget</class>
+   <extends>QTreeWidget</extends>
+   <header>RouterListWidget.h</header>
+  </customwidget>
+  <customwidget>
+   <class>CircuitListWidget</class>
+   <extends>QTreeWidget</extends>
+   <header>CircuitListWidget.h</header>
+  </customwidget>
+ </customwidgets>
+ <resources>
+  <include location="../res/vidalia.qrc" />
+ </resources>
+ <connections>
+  <connection>
+   <sender>actionClose</sender>
+   <signal>triggered()</signal>
+   <receiver>NetViewer</receiver>
+   <slot>close()</slot>
+   <hints>
+    <hint type="sourcelabel" >
+     <x>-1</x>
+     <y>-1</y>
+    </hint>
+    <hint type="destinationlabel" >
+     <x>403</x>
+     <y>322</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/NetViewer.ui
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterDescriptorView.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterDescriptorView.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterDescriptorView.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,143 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file RouterDescriptorView.cpp
+** \version $Id$
+** \brief Formats and displays a router descriptor as HTML
+*/
+
+#include "RouterDescriptorView.h"
+#include "Vidalia.h"
+
+#include "html.h"
+#include "stringutil.h"
+
+#include <QMenu>
+#include <QIcon>
+#include <QTextCursor>
+#include <QClipboard>
+#include <QShortcut>
+#include <QTextDocumentFragment>
+
+#define IMG_COPY      ":/images/22x22/edit-copy.png"
+
+
+/** Default constructor. */
+RouterDescriptorView::RouterDescriptorView(QWidget *parent)
+: QTextEdit(parent)
+{
+  /* Steal QTextEdit's default "Copy" shortcut, since we want to do some
+   * tweaking of the selected text before putting it on the clipboard. */
+  QShortcut *shortcut = new QShortcut(QKeySequence::Copy, this,
+                                      SLOT(copySelectedText()));
+  Q_UNUSED(shortcut);
+}
+
+/** Displays a context menu for the user when they right-click on the
+ * widget. */
+void
+RouterDescriptorView::contextMenuEvent(QContextMenuEvent *event)
+{
+  QMenu *menu = new QMenu();
+
+  QAction *copyAction = new QAction(QIcon(IMG_COPY), tr("Copy"), menu);
+  copyAction->setShortcut(QKeySequence::Copy);
+  connect(copyAction, SIGNAL(triggered()), this, SLOT(copySelectedText()));
+
+  if (textCursor().selectedText().isEmpty())
+    copyAction->setEnabled(false);
+
+  menu->addAction(copyAction);
+  menu->exec(event->globalPos());
+  delete menu;
+}
+
+/** Copies any selected text to the clipboard. */
+void
+RouterDescriptorView::copySelectedText()
+{ 
+  QString selectedText = textCursor().selection().toPlainText();
+  selectedText.replace(":\n", ": ");
+  vApp->clipboard()->setText(selectedText);
+}
+
+/** Adjusts the displayed uptime to include time since the router's descriptor
+ * was last published. */
+quint64
+RouterDescriptorView::adjustUptime(quint64 uptime, QDateTime published)
+{
+  QDateTime now = QDateTime::currentDateTime().toUTC();
+  
+  if (now < published) {
+    return uptime;
+  }
+  return (uptime + (now.toTime_t() - published.toTime_t()));
+}
+
+/** Displays all router descriptors in the given list. */
+void
+RouterDescriptorView::display(QList<RouterDescriptor> rdlist)
+{
+  RouterDescriptor rd;
+  QString html = "<html><body>";
+  
+  for (int r = 0; r < rdlist.size(); r++) { 
+    rd = rdlist.at(r);
+    if (rd.isEmpty())
+      continue;
+    
+    /* Router name and status */
+    html.append(p(b(rd.name()) + " (" + i(rd.status()) + ")"));
+
+    /* IP and platform */
+    html.append("<table>");
+    
+    /* If we have location information, show that first. */
+    if (!rd.location().isEmpty()) {
+      html.append(trow(tcol(b(tr("Location:"))) + tcol(rd.location())));
+    }
+    
+    /* Add the IP address and router platform information */
+    html.append(trow(tcol(b(tr("IP Address:"))) + tcol(rd.ip().toString())));
+    html.append(trow(tcol(b(tr("Platform:")))   + tcol(rd.platform())));
+
+    /* If the router is online, then show the uptime and bandwidth stats. */
+    if (!rd.offline()) {
+      html.append(trow(tcol(b(tr("Bandwidth:")))  + 
+                       tcol(string_format_bandwidth(rd.observedBandwidth()))));
+      html.append(trow(tcol(b(tr("Uptime:")))   + 
+                       tcol(string_format_uptime(
+                              adjustUptime(rd.uptime(), rd.published())))));
+    }
+
+    /* Date the router was published */
+    html.append(trow(tcol(b(tr("Last Updated:")))  +
+                     tcol(string_format_datetime(rd.published()) + " GMT")));
+
+    html.append("</table>");
+
+    /* If there are multiple descriptors, and this isn't is the last one 
+     * then separate them with a short horizontal line. */
+    if (r+1 != rdlist.size()) {
+      html.append("<center><hr width=\"50%\"/></center>");
+    }
+  }
+  html.append("</body></html>");
+  setHtml(html); 
+}
+
+/** Displays the given router descriptor. */
+void
+RouterDescriptorView::display(RouterDescriptor rd)
+{
+  display(QList<RouterDescriptor>() << rd);
+}
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterDescriptorView.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterDescriptorView.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterDescriptorView.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterDescriptorView.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,56 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file RouterDescriptorView.h
+** \version $Id$
+** \brief Formats and displays a router descriptor as HTML
+*/
+
+#ifndef _ROUTERDESCRIPTORVIEW_H
+#define _ROUTERDESCRIPTORVIEW_H
+
+#include "RouterDescriptor.h"
+
+#include <QObject>
+#include <QTextEdit>
+#include <QList>
+#include <QContextMenuEvent>
+
+
+class RouterDescriptorView : public QTextEdit
+{
+  Q_OBJECT
+
+public:
+  /** Default constructor. */
+  RouterDescriptorView(QWidget *parent = 0);
+
+public slots:
+  /** Shows the given router descriptor. */
+  void display(RouterDescriptor rd);
+  /** Shows all router descriptors in the given list. */
+  void display(QList<RouterDescriptor> rdlist);
+  /** Copies any selected text to the clipboard. */
+  void copySelectedText();
+
+protected:
+  /** Displays a context menu for the user when they right-click on the
+   * widget. */
+  virtual void contextMenuEvent(QContextMenuEvent *event);
+
+private:
+  /** Adjusts the displayed uptime to include time since the
+   * router's descriptor was last published. */
+  quint64 adjustUptime(quint64 uptime, QDateTime published);
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterDescriptorView.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterInfoDialog.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterInfoDialog.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterInfoDialog.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,79 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file RouterInfoDialog.cpp
+** \version $Id$
+** \brief Displays detailed information about a particular router
+*/
+
+#include "RouterInfoDialog.h"
+
+#include "stringutil.h"
+
+
+RouterInfoDialog::RouterInfoDialog(QWidget *parent)
+  : QDialog(parent)
+{
+  ui.setupUi(this);
+}
+
+quint64
+RouterInfoDialog::adjustUptime(quint64 uptime, const QDateTime &published)
+{
+  QDateTime now = QDateTime::currentDateTime().toUTC();
+
+  if (now < published)
+    return uptime;
+
+  return (uptime + (now.toTime_t() - published.toTime_t()));
+}
+
+void
+RouterInfoDialog::setRouterInfo(const QStringList &desc,
+                                const RouterStatus &status)
+{
+  RouterDescriptor rd(desc);
+
+  ui.lblName->setText(rd.name());
+  ui.lblIPAddress->setText(rd.ip().toString());
+  ui.lblPlatform->setText(rd.platform());
+  ui.lblBandwidth->setText(string_format_bandwidth(rd.observedBandwidth()));
+  ui.lblLastUpdated->setText(string_format_datetime(rd.published()) + " GMT");
+  ui.lblUptime->setText(string_format_uptime(adjustUptime(rd.uptime(),
+                                                          rd.published())));
+
+  if (rd.hibernating()) {
+    ui.lblStatus->setText(tr("Hibernating"));
+  } else if (status.isValid()) {
+    if (status.flags() & RouterStatus::Running)
+      ui.lblStatus->setText(tr("Online"));
+    else
+      ui.lblStatus->setText(tr("Offline"));
+  } else {
+    ui.lblStatus->setText(tr("Unknown"));
+  }
+
+  if (! rd.contact().isEmpty()) {
+    ui.lblContact->setText(rd.contact());
+  } else {
+    ui.lblContact->setVisible(false);
+    ui.lblContactLabel->setVisible(false);
+  }
+
+  ui.textDescriptor->setPlainText(desc.join("\n"));
+}
+
+void
+RouterInfoDialog::setLocation(const QString &location)
+{
+  ui.lblLocation->setText(location);
+}
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterInfoDialog.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterInfoDialog.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterInfoDialog.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterInfoDialog.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,59 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file RouterInfoDialog.h
+** \version $Id$
+** \brief Displays detailed information about a particular router
+*/
+
+
+#ifndef _ROUTERINFODIALOG_H
+#define _ROUTERINFODIALOG_H
+
+#include "ui_RouterInfoDialog.h"
+
+#include "RouterStatus.h"
+#include "RouterDescriptor.h"
+
+#include <QDialog>
+
+
+class RouterInfoDialog : public QDialog
+{
+  Q_OBJECT
+
+public:
+  /** Default constructor.
+   */
+  RouterInfoDialog(QWidget *parent = 0);
+
+  /** Populates the dialog's UI with information parsed from the router
+   * descriptor <b>desc</b> and the router status information in
+   * <b>status</b>.
+   */
+  void setRouterInfo(const QStringList &desc, const RouterStatus &status);
+
+  /** Sets the geographic location information displayed in the dialog to
+   * <b>location</b>.
+   */
+  void setLocation(const QString &location);
+
+private:
+  /** Adjusts <b>uptime</b> to be the greater of either <b>published</b> or
+   * <b>uptime</b> plus the number of seconds elapsed since <b>published</b>.
+   */
+  quint64 adjustUptime(quint64 uptime, const QDateTime &published);
+
+  Ui::RouterInfoDialog ui;
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterInfoDialog.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterInfoDialog.ui
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterInfoDialog.ui	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterInfoDialog.ui	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,449 @@
+<ui version="4.0" >
+ <class>RouterInfoDialog</class>
+ <widget class="QDialog" name="RouterInfoDialog" >
+  <property name="geometry" >
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>427</width>
+    <height>382</height>
+   </rect>
+  </property>
+  <property name="windowTitle" >
+   <string>Relay Details</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout" >
+   <item>
+    <widget class="QTabWidget" name="tabWidget" >
+     <property name="tabShape" >
+      <enum>QTabWidget::Rounded</enum>
+     </property>
+     <property name="currentIndex" >
+      <number>0</number>
+     </property>
+     <property name="usesScrollButtons" >
+      <bool>false</bool>
+     </property>
+     <widget class="QWidget" name="tabSummary" >
+      <attribute name="title" >
+       <string>Summary</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_2" >
+       <item row="0" column="0" >
+        <widget class="QLabel" name="lblNameLabel" >
+         <property name="sizePolicy" >
+          <sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font" >
+          <font>
+           <weight>75</weight>
+           <bold>true</bold>
+          </font>
+         </property>
+         <property name="text" >
+          <string>Name:</string>
+         </property>
+         <property name="alignment" >
+          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="1" >
+        <widget class="QLabel" name="lblName" >
+         <property name="sizePolicy" >
+          <sizepolicy vsizetype="Preferred" hsizetype="MinimumExpanding" >
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text" >
+          <string/>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="0" >
+        <widget class="QLabel" name="lblStatusLabel" >
+         <property name="sizePolicy" >
+          <sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font" >
+          <font>
+           <weight>75</weight>
+           <bold>true</bold>
+          </font>
+         </property>
+         <property name="text" >
+          <string>Status:</string>
+         </property>
+         <property name="alignment" >
+          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="1" >
+        <widget class="QLabel" name="lblStatus" >
+         <property name="sizePolicy" >
+          <sizepolicy vsizetype="Preferred" hsizetype="MinimumExpanding" >
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text" >
+          <string/>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="0" >
+        <widget class="QLabel" name="lblLocationLabel" >
+         <property name="sizePolicy" >
+          <sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font" >
+          <font>
+           <weight>75</weight>
+           <bold>true</bold>
+          </font>
+         </property>
+         <property name="text" >
+          <string>Location:</string>
+         </property>
+         <property name="alignment" >
+          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="1" >
+        <widget class="QLabel" name="lblLocation" >
+         <property name="sizePolicy" >
+          <sizepolicy vsizetype="Preferred" hsizetype="MinimumExpanding" >
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text" >
+          <string/>
+         </property>
+        </widget>
+       </item>
+       <item row="3" column="0" >
+        <widget class="QLabel" name="lblIPAddressLabel" >
+         <property name="sizePolicy" >
+          <sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font" >
+          <font>
+           <weight>75</weight>
+           <bold>true</bold>
+          </font>
+         </property>
+         <property name="text" >
+          <string>IP Address:</string>
+         </property>
+         <property name="alignment" >
+          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+         </property>
+        </widget>
+       </item>
+       <item row="3" column="1" >
+        <widget class="QLabel" name="lblIPAddress" >
+         <property name="sizePolicy" >
+          <sizepolicy vsizetype="Preferred" hsizetype="MinimumExpanding" >
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text" >
+          <string/>
+         </property>
+        </widget>
+       </item>
+       <item row="4" column="0" >
+        <widget class="QLabel" name="lblPlatformLabel" >
+         <property name="sizePolicy" >
+          <sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font" >
+          <font>
+           <weight>75</weight>
+           <bold>true</bold>
+          </font>
+         </property>
+         <property name="text" >
+          <string>Platform:</string>
+         </property>
+         <property name="alignment" >
+          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+         </property>
+        </widget>
+       </item>
+       <item row="4" column="1" >
+        <widget class="QLabel" name="lblPlatform" >
+         <property name="sizePolicy" >
+          <sizepolicy vsizetype="Preferred" hsizetype="MinimumExpanding" >
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize" >
+          <size>
+           <width>0</width>
+           <height>0</height>
+          </size>
+         </property>
+         <property name="text" >
+          <string/>
+         </property>
+         <property name="wordWrap" >
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+       <item row="5" column="0" >
+        <widget class="QLabel" name="lblBandwidthLabel" >
+         <property name="sizePolicy" >
+          <sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font" >
+          <font>
+           <weight>75</weight>
+           <bold>true</bold>
+          </font>
+         </property>
+         <property name="text" >
+          <string>Bandwidth:</string>
+         </property>
+         <property name="alignment" >
+          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+         </property>
+        </widget>
+       </item>
+       <item row="5" column="1" >
+        <widget class="QLabel" name="lblBandwidth" >
+         <property name="sizePolicy" >
+          <sizepolicy vsizetype="Preferred" hsizetype="MinimumExpanding" >
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text" >
+          <string/>
+         </property>
+        </widget>
+       </item>
+       <item row="6" column="0" >
+        <widget class="QLabel" name="lblUptimeLabel" >
+         <property name="sizePolicy" >
+          <sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font" >
+          <font>
+           <weight>75</weight>
+           <bold>true</bold>
+          </font>
+         </property>
+         <property name="text" >
+          <string>Uptime:</string>
+         </property>
+         <property name="alignment" >
+          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+         </property>
+        </widget>
+       </item>
+       <item row="6" column="1" >
+        <widget class="QLabel" name="lblUptime" >
+         <property name="sizePolicy" >
+          <sizepolicy vsizetype="Preferred" hsizetype="MinimumExpanding" >
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text" >
+          <string/>
+         </property>
+        </widget>
+       </item>
+       <item row="8" column="0" >
+        <widget class="QLabel" name="lblContactLabel" >
+         <property name="sizePolicy" >
+          <sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font" >
+          <font>
+           <weight>75</weight>
+           <bold>true</bold>
+          </font>
+         </property>
+         <property name="text" >
+          <string>Contact:</string>
+         </property>
+         <property name="alignment" >
+          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+         </property>
+        </widget>
+       </item>
+       <item row="8" column="1" >
+        <widget class="QLabel" name="lblContact" >
+         <property name="sizePolicy" >
+          <sizepolicy vsizetype="Preferred" hsizetype="MinimumExpanding" >
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text" >
+          <string/>
+         </property>
+         <property name="wordWrap" >
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+       <item row="10" column="0" >
+        <spacer name="verticalSpacer" >
+         <property name="orientation" >
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0" >
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="9" column="0" >
+        <widget class="QLabel" name="lblLastUpdatedLabel" >
+         <property name="sizePolicy" >
+          <sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font" >
+          <font>
+           <weight>75</weight>
+           <bold>true</bold>
+          </font>
+         </property>
+         <property name="text" >
+          <string>Last Updated:</string>
+         </property>
+         <property name="alignment" >
+          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+         </property>
+        </widget>
+       </item>
+       <item row="9" column="1" >
+        <widget class="QLabel" name="lblLastUpdated" >
+         <property name="sizePolicy" >
+          <sizepolicy vsizetype="Preferred" hsizetype="MinimumExpanding" >
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text" >
+          <string/>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tabDescriptor" >
+      <attribute name="title" >
+       <string>Descriptor</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout" >
+       <item row="0" column="0" >
+        <widget class="QTextEdit" name="textDescriptor" >
+         <property name="font" >
+          <font>
+           <family>Courier New</family>
+           <pointsize>10</pointsize>
+          </font>
+         </property>
+         <property name="lineWrapMode" >
+          <enum>QTextEdit::NoWrap</enum>
+         </property>
+         <property name="acceptRichText" >
+          <bool>false</bool>
+         </property>
+         <property name="textInteractionFlags" >
+          <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox" >
+     <property name="orientation" >
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons" >
+      <set>QDialogButtonBox::Close</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>RouterInfoDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel" >
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel" >
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>RouterInfoDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel" >
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel" >
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterInfoDialog.ui
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListItem.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListItem.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListItem.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,150 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file RouterListItem.cpp
+** \version $Id$
+** \brief Item representing a single router and status in a RouterListWidget
+*/
+
+#include "RouterListItem.h"
+#include "RouterListWidget.h"
+
+#include <QHeaderView>
+
+#define STATUS_COLUMN   (RouterListWidget::StatusColumn)
+#define COUNTRY_COLUMN  (RouterListWidget::CountryColumn)
+#define NAME_COLUMN     (RouterListWidget::NameColumn)
+
+#define IMG_NODE_OFFLINE    ":/images/icons/node-unresponsive.png"
+#define IMG_NODE_SLEEPING   ":/images/icons/node-hibernating.png"
+#define IMG_NODE_NO_BW      ":/images/icons/node-bw-none.png"
+#define IMG_NODE_LOW_BW     ":/images/icons/node-bw-low.png"
+#define IMG_NODE_MED_BW     ":/images/icons/node-bw-med.png"
+#define IMG_NODE_HIGH_BW    ":/images/icons/node-bw-high.png"
+#define IMG_FLAG_UNKNOWN    ":/images/flags/unknown.png"
+
+
+/** Default constructor. */
+RouterListItem::RouterListItem(RouterListWidget *list, RouterDescriptor rd)
+  : QTreeWidgetItem()
+{
+  _list = list;
+  _rd   = 0;
+  _country = "~"; /* Force items with no country to the bottom */
+  setIcon(COUNTRY_COLUMN, QIcon(IMG_FLAG_UNKNOWN));
+  update(rd);
+}
+
+/** Destructor. */
+RouterListItem::~RouterListItem()
+{
+  if (_rd)
+    delete _rd;
+}
+
+/** Updates the router descriptor for this item. */
+void
+RouterListItem::update(const RouterDescriptor &rd)
+{
+  QIcon statusIcon;
+  if (_rd) {
+    delete _rd;
+  }
+  _rd = new RouterDescriptor(rd);
+  
+  /* Determine the status value (used for sorting) and icon */
+  if (_rd->offline()) {
+    _statusValue = -1;
+    statusIcon = QIcon(IMG_NODE_OFFLINE);
+    setToolTip(STATUS_COLUMN, tr("Offline"));
+  } else if (_rd->hibernating()) {
+    _statusValue = 0;
+    statusIcon = QIcon(IMG_NODE_SLEEPING);
+    setToolTip(STATUS_COLUMN, tr("Hibernating"));
+  } else {
+    _statusValue = (qint64)_rd->observedBandwidth();
+    if (_statusValue >= 400*1024) {
+      statusIcon = QIcon(IMG_NODE_HIGH_BW);
+    } else if (_statusValue >= 60*1024) {
+      statusIcon = QIcon(IMG_NODE_MED_BW);
+    } else if (_statusValue >= 20*1024) {
+      statusIcon = QIcon(IMG_NODE_LOW_BW);
+    } else {
+      statusIcon = QIcon(IMG_NODE_NO_BW);
+    }
+    setToolTip(STATUS_COLUMN, tr("%1 KB/s").arg(_statusValue/1024));
+  }
+  
+  /* Make the new information visible */
+  setIcon(STATUS_COLUMN, statusIcon);
+  setText(NAME_COLUMN, _rd->name());
+  setToolTip(NAME_COLUMN, QString(_rd->name() + "\r\n" + _rd->platform()));
+}
+
+/** Sets the location information for this item's router descriptor. */
+void
+RouterListItem::setLocation(const GeoIp &geoip)
+{
+  QPixmap flag(":/images/flags/" + geoip.countryCode().toLower() + ".png");
+  if (!flag.isNull()) {
+    setIcon(COUNTRY_COLUMN, QIcon(flag));
+  }
+  setToolTip(COUNTRY_COLUMN, geoip.toString());
+  
+  if (_rd)
+    _rd->setLocation(geoip.toString());
+  _country = geoip.countryCode();
+}
+
+/** Overload the comparison operator. */
+bool
+RouterListItem::operator<(const QTreeWidgetItem &other) const
+{
+  const RouterListItem *a = this;
+  const RouterListItem *b = (RouterListItem *)&other;
+ 
+  if (_list) {
+    Qt::SortOrder order = _list->header()->sortIndicatorOrder();
+    switch (_list->sortColumn()) {
+      case RouterListWidget::StatusColumn:
+        /* Numeric comparison based on status and/or bandwidth */
+        if (a->_statusValue == b->_statusValue) {
+          if (order == Qt::AscendingOrder)
+            return (a->name().toLower() > b->name().toLower());
+          else
+            return (a->name().toLower() < b->name().toLower());
+        }
+        return (a->_statusValue < b->_statusValue);
+      case RouterListWidget::CountryColumn:
+        /* Compare based on country code */
+        if (a->_country == b->_country) {
+          if (order == Qt::AscendingOrder)
+            return (a->_statusValue > b->_statusValue);
+          else
+            return (a->_statusValue < b->_statusValue);
+        }
+        return (a->_country < b->_country);
+      case RouterListWidget::NameColumn:
+        /* Case-insensitive comparison based on router name */
+        if (a->name().toLower() == b->name().toLower()) {
+          if (order == Qt::AscendingOrder)
+            return (a->_statusValue > b->_statusValue);
+          else
+            return (a->_statusValue < b->_statusValue);
+        }
+        return (a->name().toLower() < b->name().toLower());
+      default:
+        break;
+    }
+  }
+  return QTreeWidgetItem::operator<(other);
+}
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListItem.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListItem.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListItem.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListItem.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,66 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file RouterListItem.h
+** \version $Id$
+** \brief Item representing a single router and status in a RouterListWidget
+*/
+
+#ifndef _ROUTERLISTITEM_H
+#define _ROUTERLISTITEM_H
+
+#include "RouterDescriptor.h"
+#include "RouterListWidget.h"
+#include "GeoIp.h"
+
+#include <QCoreApplication>
+#include <QTreeWidgetItem>
+#include <QString>
+
+class RouterListWidget;
+
+
+class RouterListItem : public QTreeWidgetItem
+{
+  Q_DECLARE_TR_FUNCTIONS(RouterListItem)
+
+public:
+  /** Default constructor. */
+  RouterListItem(RouterListWidget *list, RouterDescriptor rd);
+  /** Destructor. */
+  ~RouterListItem();
+
+  /** Updates this router item using a new descriptor. */
+  void update(const RouterDescriptor &rd);
+  /** Returns the router's ID. */
+  QString id() const { return _rd->id(); }
+  /** Returns the router's name. */
+  QString name() const { return _rd->name(); }
+  /** Returns the descriptor for this router. */
+  RouterDescriptor descriptor() const { return *_rd; }
+  /** Sets the location information for this router item. */
+  void setLocation(const GeoIp &geoip);
+  /** Returns the location information set for this router item. */
+  QString location() const { return _rd->location(); }
+
+  /** Overload the comparison operator. */
+  virtual bool operator<(const QTreeWidgetItem &other) const;
+
+private:
+  RouterDescriptor* _rd;   /**< Descriptor for this router item. */
+  RouterListWidget* _list; /**< The list for this list item. */
+  qint64 _statusValue;     /**< Value used to sort items by status. */
+  QString _country;        /**< Country in which this router is likely
+                                located. */
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListItem.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListWidget.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListWidget.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListWidget.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,240 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file RouterListWidget.cpp
+** \version $Id$
+** \brief Displays a list of Tor servers and their status
+*/
+
+#include "RouterListWidget.h"
+#include "RouterListItem.h"
+#include "Vidalia.h"
+
+#include <QHeaderView>
+#include <QClipboard>
+
+#define IMG_ZOOM   ":/images/22x22/page-zoom.png"
+#define IMG_COPY   ":/images/22x22/edit-copy.png"
+
+
+RouterListWidget::RouterListWidget(QWidget *parent)
+  : QTreeWidget(parent)
+{
+  /* Create and initialize columns */
+  setHeaderLabels(QStringList() << QString("")
+                                << QString("")
+                                << tr("Relay"));
+
+  /* Sort by descending server bandwidth */
+  sortItems(StatusColumn, Qt::DescendingOrder);
+
+  /* Find out when the selected item has changed. */
+  connect(this, SIGNAL(itemSelectionChanged()), 
+          this, SLOT(onSelectionChanged()));
+}
+
+/** Called when the user changes the UI translation. */
+void
+RouterListWidget::retranslateUi()
+{
+  setHeaderLabels(QStringList() << QString("")
+                                << QString("")
+                                << tr("Relay"));
+}
+
+/** Called when the user requests a context menu for a router in the list. A
+ * context menu will be displayed providing a list of actions, including
+ * zooming in on the server. */
+void
+RouterListWidget::contextMenuEvent(QContextMenuEvent *event)
+{
+  QAction *action;
+  QMenu *menu, *copyMenu;
+  QList<QTreeWidgetItem *> selected;
+
+  selected = selectedItems();
+  if (! selected.size())
+    return;
+
+  menu = new QMenu();
+  copyMenu = menu->addMenu(QIcon(IMG_COPY), tr("Copy"));
+  action = copyMenu->addAction(tr("Nickname"));
+  connect(action, SIGNAL(triggered()), this, SLOT(copySelectedNicknames()));
+
+  action = copyMenu->addAction(tr("Fingerprint"));
+  connect(action, SIGNAL(triggered()), this, SLOT(copySelectedFingerprints()));
+
+  action = menu->addAction(QIcon(IMG_ZOOM), tr("Zoom to Relay"));
+  if (selected.size() > 1)
+    action->setEnabled(false);
+  else
+    connect(action, SIGNAL(triggered()), this, SLOT(zoomToSelectedRelay()));
+
+  menu->exec(event->globalPos());
+  delete menu;
+}
+
+/** Copies the nicknames for all currently selected relays to the clipboard.
+ * Nicknames are formatted as a comma-delimited list, suitable for doing
+ * dumb things with your torrc. */
+void
+RouterListWidget::copySelectedNicknames()
+{
+  QString text;
+
+  foreach (QTreeWidgetItem *item, selectedItems()) {
+    RouterListItem *relay = dynamic_cast<RouterListItem *>(item);
+    if (relay)
+      text.append(relay->name() + ",");
+  }
+  if (text.length()) {
+    text.remove(text.length()-1, 1);
+    vApp->clipboard()->setText(text);
+  }
+}
+
+/** Copies the fingerprints for all currently selected relays to the
+ * clipboard. Fingerprints are formatted as a comma-delimited list, suitable
+ * for doing dumb things with your torrc. */
+void
+RouterListWidget::copySelectedFingerprints()
+{
+  QString text;
+
+  foreach (QTreeWidgetItem *item, selectedItems()) {
+    RouterListItem *relay = dynamic_cast<RouterListItem *>(item);
+    if (relay)
+      text.append("$" + relay->id() + ",");
+  }
+  if (text.length()) {
+    text.remove(text.length()-1, 1);
+    vApp->clipboard()->setText(text);
+  }
+}
+
+/** Emits a zoomToRouter() signal containing the fingerprint of the
+ * currently selected relay. */
+void
+RouterListWidget::zoomToSelectedRelay()
+{
+  QList<QTreeWidgetItem *> selected = selectedItems();
+  if (selected.size() != 1)
+    return;
+
+  RouterListItem *relay = dynamic_cast<RouterListItem *>(selected[0]);
+  if (relay)
+    emit zoomToRouter(relay->id());
+}
+
+/** Deselects all currently selected routers. */
+void
+RouterListWidget::deselectAll()
+{
+  QList<QTreeWidgetItem *> selected = selectedItems();
+  foreach (QTreeWidgetItem *item, selected) {
+    setItemSelected(item, false);
+  }
+}
+
+/** Clear the list of router items. */
+void
+RouterListWidget::clearRouters()
+{
+  _idmap.clear();
+  QTreeWidget::clear();
+  setStatusTip(tr("%1 relays online").arg(0));
+}
+
+/** Called when the user selects a router from the list. This will search the
+ * list for a router whose names starts with the key pressed. */
+void
+RouterListWidget::keyPressEvent(QKeyEvent *event)
+{
+  int index;
+  
+  QString key = event->text();
+  if (!key.isEmpty() && key.at(0).isLetterOrNumber()) {
+    /* A text key was pressed, so search for routers that begin with that key. */
+    QList<QTreeWidgetItem *> list = findItems(QString("^[%1%2].*$")
+                                                  .arg(key.toUpper())
+                                                  .arg(key.toLower()),
+                                               Qt::MatchRegExp|Qt::MatchWrap,
+                                               NameColumn);
+    if (list.size() > 0) {
+      QList<QTreeWidgetItem *> s = selectedItems();
+      
+      /* A match was found, so deselect any previously selected routers,
+       * select the new match, and make sure it's visible. If there was
+       * already a router selected that started with the search key, go to the
+       * next match in the list. */
+      deselectAll();
+      index = (!s.size() ? 0 : (list.indexOf(s.at(0)) + 1) % list.size());
+
+      /* Select the item and scroll to it */
+      setItemSelected(list.at(index), true);
+      scrollToItem(list.at(index));
+    }
+    event->accept();
+  } else {
+    /* It was something we don't understand, so hand it to the parent class */
+    QTreeWidget::keyPressEvent(event);
+  }
+}
+
+/** Finds the list item whose key ID matches <b>id</b>. Returns 0 if not 
+ * found. */
+RouterListItem*
+RouterListWidget::findRouterById(QString id)
+{
+  if (_idmap.contains(id)) {
+    return _idmap.value(id);
+  }
+  return 0;
+}
+
+/** Adds a router descriptor to the list. */
+void
+RouterListWidget::addRouter(RouterDescriptor rd)
+{
+  QString id = rd.id();
+  if (id.isEmpty())
+    return;
+
+  RouterListItem *item = findRouterById(id);
+  if (item) {
+    item->update(rd);
+  } else {
+    item = new RouterListItem(this, rd);
+    addTopLevelItem(item);
+    _idmap.insert(id, item);
+  }
+
+  /* Set our status tip to the number of servers in the list */
+  setStatusTip(tr("%1 relays online").arg(topLevelItemCount()));
+}
+
+/** Called when the selected items have changed. This emits the 
+ * routerSelected() signal with the descriptor for the selected router.
+ */
+void
+RouterListWidget::onSelectionChanged()
+{
+  QList<RouterDescriptor> descriptors;
+
+  foreach (QTreeWidgetItem *item, selectedItems()) {
+    RouterListItem *relay = dynamic_cast<RouterListItem *>(item);
+    if (relay)
+      descriptors << relay->descriptor();
+  }
+  if (descriptors.count() > 0)
+    emit routerSelected(descriptors);
+}
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListWidget.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListWidget.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListWidget.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListWidget.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,97 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file RouterListWidget.h
+** \version $Id$
+** \brief Displays a list of Tor servers and their status
+*/
+
+#ifndef _ROUTERLISTWIDGET_H
+#define _ROUTERLISTWIDGET_H
+
+#include "RouterDescriptor.h"
+
+#include <QHash>
+#include <QList>
+#include <QMenu>
+#include <QObject>
+#include <QAction>
+#include <QKeyEvent>
+#include <QTreeWidget>
+#include <QHostAddress>
+#include <QMouseEvent>
+
+class RouterListItem;
+
+class RouterListWidget : public QTreeWidget
+{
+  Q_OBJECT
+
+public:
+  /** Columns in the list. */
+  enum Columns {
+    StatusColumn  = 0,  /**< Status column, indicating bandwidth. */
+    CountryColumn = 1,  /**< Router's country flag. */
+    NameColumn    = 2,  /**< Router's name. */
+  };
+
+  /** Default constructor. */
+  RouterListWidget(QWidget *parent = 0);
+
+  /** Adds a new descriptor the list. */
+  void addRouter(RouterDescriptor rd);
+  /** Finds the list item whose key ID matches <b>id</b>. Returns 0 if not 
+   * found. */
+  RouterListItem* findRouterById(QString id);
+  /** Deselects all currently selected routers. */
+  void deselectAll();
+  /** Called when the user changes the UI translation. */
+  void retranslateUi();
+
+signals:
+  /** Emitted when the user selects a router from the list. */
+  void routerSelected(QList<RouterDescriptor> rd);
+  /** Emitted when the user selects a router to zoom in on. */
+  void zoomToRouter(QString id);
+
+public slots:
+  /** Clears the list of router items. */
+  void clearRouters();
+
+private slots:
+  /** Called when the user clicks on an item in the list. */
+  void onSelectionChanged();
+  /** Copies the nicknames for all currently selected relays to the clipboard.
+   * Nicknames are formatted as a comma-delimited list, suitable for doing
+   * dumb things with your torrc. */
+  void copySelectedNicknames();
+  /** Copies the fingerprints for all currently selected relays to the
+   * clipboard. Fingerprints are formatted as a comma-delimited list, suitable
+   * for doing dumb things with your torrc. */
+  void copySelectedFingerprints();
+  /** Emits a zoomToRouter() signal containing the fingerprint of the
+   * currently selected relay. */
+  void zoomToSelectedRelay();
+
+protected:
+  /** Called when the user presses a key while the list has focus. */
+  void keyPressEvent(QKeyEvent *event);
+  /** Displays a context menu for the user when they right-click on the
+   * widget. */
+  virtual void contextMenuEvent(QContextMenuEvent *event);
+
+private:
+  /** Maps a server ID to that server's list item. */
+  QHash<QString,RouterListItem*> _idmap;
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/RouterListWidget.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/StreamItem.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/StreamItem.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/StreamItem.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,38 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file StreamItem.cpp
+** \version $Id$
+** \brief Item representing a stream through Tor and its status
+*/
+
+#include "StreamItem.h"
+#include "CircuitListWidget.h"
+
+
+/** Constructor */
+StreamItem::StreamItem(const Stream &stream)
+{
+  /* Update the status and target */
+  update(stream);
+}
+
+/** Updates the status of this stream item. */
+void
+StreamItem::update(const Stream &stream)
+{
+  _stream = stream;
+  setText(CircuitListWidget::ConnectionColumn, stream.target());
+  setToolTip(CircuitListWidget::ConnectionColumn, stream.target());
+  setText(CircuitListWidget::StatusColumn, stream.statusString());
+  setToolTip(CircuitListWidget::StatusColumn, stream.statusString());
+}
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/StreamItem.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/StreamItem.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/StreamItem.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/StreamItem.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,42 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file StreamItem.h
+** \version $Id$
+** \brief Item representing a stream through Tor and its status
+*/
+
+#ifndef _STREAMITEM_H
+#define _STREAMITEM_H
+
+#include "Stream.h"
+
+#include <QTreeWidgetItem>
+
+
+class StreamItem : public QTreeWidgetItem
+{
+public:
+  /** Constructor */
+  StreamItem(const Stream &stream);
+
+  Stream stream() const { return _stream; }
+  /** Updates the status of this stream item. */
+  void update(const Stream &stream);
+  /** Returns the ID of the stream associated with this tree item. */
+  StreamId id() const { return _stream.id(); }
+  
+private:
+  Stream _stream;
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/StreamItem.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapImageView.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapImageView.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapImageView.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,333 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file TorMapImageView.cpp
+** \version $Id$
+** \brief Displays Tor servers and circuits on a map of the world
+*/
+
+#include "config.h"
+#include "TorMapImageView.h"
+
+#include <QStringList>
+
+#if defined(__sgi) && defined(_COMPILER_VERSION) && _COMPILER_VERSION >= 730
+#include <math.h>
+#else
+#include <cmath>
+#endif
+
+#define IMG_WORLD_MAP   ":/images/map/world-map.png"
+
+/** QPens to use for drawing different map elements */
+#define PEN_ROUTER        QPen(QColor("#ff030d"), 1.0)
+#define PEN_CIRCUIT       QPen(Qt::yellow, 0.5)
+#define PEN_SELECTED      QPen(Qt::green, 2.0)
+
+/** Size of the map image */
+#define IMG_WIDTH       1000
+#define IMG_HEIGHT      507
+
+/** Border between the edge of the image and the actual map */
+#define MAP_TOP         2
+#define MAP_BOTTOM      2
+#define MAP_RIGHT       5
+#define MAP_LEFT        5
+#define MAP_WIDTH       (IMG_WIDTH-MAP_LEFT-MAP_RIGHT)
+#define MAP_HEIGHT      (IMG_HEIGHT-MAP_TOP-MAP_BOTTOM)
+
+/** Map offset from zero longitude */
+#define MAP_ORIGIN       -10
+
+/** Minimum allowable size for this widget */
+#define MIN_SIZE        QSize(512,256)
+
+/** Robinson projection table */
+/** Length of the parallel of latitude */
+static float  plen[] = {
+    1.0000, 0.9986, 0.9954, 0.9900,
+    0.9822, 0.9730, 0.9600, 0.9427,
+    0.9216, 0.8962, 0.8679, 0.8350,
+    0.7986, 0.7597, 0.7186, 0.6732,
+    0.6213, 0.5722, 0.5322
+  };
+
+/** Distance of corresponding parallel from equator */ 
+static float  pdfe[] = {
+    0.0000, 0.0620, 0.1240, 0.1860,
+    0.2480, 0.3100, 0.3720, 0.4340,
+    0.4958, 0.5571, 0.6176, 0.6769,
+    0.7346, 0.7903, 0.8435, 0.8936,
+    0.9394, 0.9761, 1.0000
+  };
+
+/** Default constructor */
+TorMapImageView::TorMapImageView(QWidget *parent)
+: ZImageView(parent)
+{
+  QImage map(IMG_WORLD_MAP);
+  setImage(map);
+}
+
+/** Destructor */
+TorMapImageView::~TorMapImageView()
+{
+  clear();
+}
+
+/** Adds a router to the map. */
+void
+TorMapImageView::addRouter(const RouterDescriptor &desc, const GeoIp &geoip)
+{
+  QString id = desc.id();
+  QPointF routerCoord = toMapSpace(geoip.latitude(), geoip.longitude());
+  
+  /* Add data the hash of known routers, and plot the point on the map */
+  if (_routers.contains(id))
+    _routers.value(id)->first = routerCoord;
+  else
+    _routers.insert(id, new QPair<QPointF,bool>(routerCoord, false));
+}
+
+/** Adds a circuit to the map using the given ordered list of router IDs. */
+void
+TorMapImageView::addCircuit(const CircuitId &circid, const QStringList &path)
+{
+  QPainterPath *circPainterPath = new QPainterPath;
+  
+  /* Build the new circuit */
+  for (int i = 0; i < path.size()-1; i++) {
+    QString fromNode = path.at(i);
+    QString toNode = path.at(i+1);
+   
+    /* Add the coordinates of the hops to the circuit */
+    if (_routers.contains(fromNode) && _routers.contains(toNode)) {
+      /* Find the two endpoints for this path segment */
+      QPointF fromPos = _routers.value(fromNode)->first;
+      QPointF endPos = _routers.value(toNode)->first;
+      
+      /* Draw the path segment */ 
+      circPainterPath->moveTo(fromPos);
+      circPainterPath->lineTo(endPos);
+      circPainterPath->moveTo(endPos);
+    }
+  }
+  
+  /** Add the data to the hash of known circuits and plot the circuit on the map */
+  if (_circuits.contains(circid)) {
+    /* This circuit is being updated, so just update the path, making sure we
+     * free the memory allocated to the old one. */
+    QPair<QPainterPath*,bool> *circuitPair = _circuits.value(circid);
+    delete circuitPair->first;
+    circuitPair->first = circPainterPath;
+  } else {
+    /* This is a new path, so just add it to our list */
+    _circuits.insert(circid, new QPair<QPainterPath*,bool>(circPainterPath,false));
+  }
+}
+
+/** Removes a circuit from the map. */
+void
+TorMapImageView::removeCircuit(const CircuitId &circid)
+{
+  QPair<QPainterPath*,bool> *circ = _circuits.take(circid);
+  QPainterPath *circpath = circ->first;
+  if (circpath) {
+    delete circpath;
+  }
+  delete circ;
+}
+
+/** Selects and highlights the router on the map. */
+void
+TorMapImageView::selectRouter(const QString &id)
+{
+  if (_routers.contains(id)) {
+    QPair<QPointF, bool> *routerPair = _routers.value(id);
+    routerPair->second = true;
+  }
+  repaint();
+}
+
+/** Selects and highlights the circuit with the id <b>circid</b> 
+ * on the map. */
+void
+TorMapImageView::selectCircuit(const CircuitId &circid)
+{
+  if (_circuits.contains(circid)) {
+    QPair<QPainterPath*, bool> *circuitPair = _circuits.value(circid);
+    circuitPair->second = true;
+  }
+  repaint();
+}
+
+/** Deselects any highlighted routers or circuits */
+void
+TorMapImageView::deselectAll()
+{
+  /* Deselect all router points */
+  foreach (QString router, _routers.keys()) {
+    QPair<QPointF,bool> *routerPair = _routers.value(router);
+    routerPair->second = false;
+  }
+  /* Deselect all circuit paths */
+  foreach (CircuitId circid, _circuits.keys()) {
+    QPair<QPainterPath*,bool> *circuitPair = _circuits.value(circid);
+    circuitPair->second = false;
+  }
+}
+
+/** Clears the list of routers and removes all the data on the map */
+void
+TorMapImageView::clear()
+{
+  /* Clear out all the router points and free their memory */
+  foreach (QString router, _routers.keys()) {
+    delete _routers.take(router);
+  }
+  /* Clear out all the circuit paths and free their memory */
+  foreach (CircuitId circid, _circuits.keys()) {
+    QPair<QPainterPath*,bool> *circuitPair = _circuits.take(circid);
+    delete circuitPair->first;
+    delete circuitPair;
+  }
+}
+  
+/** Draws the routers and paths onto the map image. */
+void
+TorMapImageView::paintImage(QPainter *painter)
+{
+  painter->setRenderHint(QPainter::Antialiasing);
+  
+  /* Draw the router points */
+  foreach(QString router, _routers.keys()) {
+    QPair<QPointF,bool> *routerPair = _routers.value(router);
+    painter->setPen((routerPair->second ? PEN_SELECTED : PEN_ROUTER)); 
+    painter->drawPoint(routerPair->first);
+  }
+  /* Draw the circuit paths */
+  foreach(CircuitId circid, _circuits.keys()) {
+    QPair<QPainterPath*,bool> *circuitPair = _circuits.value(circid);
+    painter->setPen((circuitPair->second ? PEN_SELECTED : PEN_CIRCUIT));
+    painter->drawPath(*(circuitPair->first));
+  }
+}
+
+/** Converts world space coordinates into map space coordinates */
+QPointF
+TorMapImageView::toMapSpace(float latitude, float longitude)
+{
+  float width  = MAP_WIDTH;
+  float height = MAP_HEIGHT;
+  float deg = width / 360.0;
+  longitude += MAP_ORIGIN;
+
+  float lat;
+  float lon;
+  
+  lat = floor(longitude * (deg * lerp(abs(int(latitude)), plen))
+	      + width/2 + MAP_LEFT);
+  
+  if (latitude < 0) {
+    lon = floor((height/2) + (lerp(abs(int(latitude)), pdfe) * (height/2))
+		+ MAP_TOP);
+  } else {
+    lon = floor((height/2) - (lerp(abs(int(latitude)), pdfe) * (height/2))
+		+ MAP_TOP);
+  }
+
+  return QPointF(lat, lon);
+}
+  
+/** Linearly interpolates using the values in the Robinson projection table */
+float
+TorMapImageView::lerp(float input, float *table)
+{
+  int x = int(floor(input / 5));
+
+  return ((table[x+1] - table[x]) / 
+	  (((x+1)*5) - (x*5))) * (input - x*5) + table[x];
+}
+
+/** Returns the minimum size of the widget */
+QSize
+TorMapImageView::minimumSizeHint() const
+{
+  return MIN_SIZE;
+}
+
+/** Zooms to fit all currently displayed circuits on the map. If there are no
+ * circuits on the map, the viewport will be returned to its default position
+ * (zoomed all the way out and centered). */
+void
+TorMapImageView::zoomToFit()
+{
+  QRectF rect = circuitBoundingBox();
+  
+  if (rect.isNull()) {
+    /* If there are no circuits, zoom all the way out */
+    resetZoomPoint();
+    zoom(0.0);
+  } else {
+    /* Zoom in on the displayed circuits */
+    float zoomLevel = 1.0 - qMax(rect.height()/float(MAP_HEIGHT),
+                                 rect.width()/float(MAP_WIDTH));
+    
+    zoom(rect.center().toPoint(), zoomLevel+0.2);
+  }
+}
+
+/** Zoom to the circuit on the map with the given <b>circid</b>. */
+void
+TorMapImageView::zoomToCircuit(const CircuitId &circid)
+{
+  if (_circuits.contains(circid)) {
+    QPair<QPainterPath*,bool> *pair = _circuits.value(circid);
+    QRectF rect = ((QPainterPath *)pair->first)->boundingRect();
+    if (!rect.isNull()) {
+      float zoomLevel = 1.0 - qMax(rect.height()/float(MAP_HEIGHT),
+                                   rect.width()/float(MAP_WIDTH));
+
+      zoom(rect.center().toPoint(), zoomLevel+0.2);
+    }
+  }
+}
+
+/** Zooms in on the router with the given <b>id</b>. */
+void
+TorMapImageView::zoomToRouter(const QString &id)
+{
+  QPair<QPointF,bool> *routerPair;
+  
+  if (_routers.contains(id)) {
+    deselectAll();
+    routerPair = _routers.value(id);
+    routerPair->second = true;  /* Set the router point to "selected" */
+    zoom(routerPair->first.toPoint(), 1.0); 
+  }
+}
+
+/** Computes a bounding box around all currently displayed circuit paths on
+ * the map. */
+QRectF
+TorMapImageView::circuitBoundingBox()
+{
+  QRectF rect;
+
+  /* Compute the union of bounding rectangles for all circuit paths */
+  foreach (CircuitId circid, _circuits.keys()) {
+    QPair<QPainterPath*,bool> *pair = _circuits.value(circid);
+    QPainterPath *circuit = pair->first;
+    rect = rect.unite(circuit->boundingRect());
+  }
+  return rect;
+}
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapImageView.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapImageView.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapImageView.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapImageView.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,87 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file TorMapImageView.h
+** \version $Id$
+** \brief Displays Tor servers and circuits on a map of the world
+*/
+
+#ifndef _TORMAPIMAGEVIEW_H
+#define _TORMAPIMAGEVIEW_H
+
+#include "ZImageView.h"
+#include "GeoIp.h"
+
+#include "RouterDescriptor.h"
+#include "Circuit.h"
+
+#include <QHash>
+#include <QPair>
+#include <QPainter>
+#include <QPainterPath>
+
+
+class TorMapImageView : public ZImageView
+{
+  Q_OBJECT
+
+public:
+  /** Default constructor. */
+  TorMapImageView(QWidget *parent = 0);
+  /** Destructor. */
+  ~TorMapImageView();
+
+  /** Plots the given router on the map using the given coordinates. */
+  void addRouter(const RouterDescriptor &desc, const GeoIp &geoip);
+  /** Plots the given circuit on the map. */
+  void addCircuit(const CircuitId &circid, const QStringList &path);
+  /** Selects and hightlights a router on the map. */
+  void selectRouter(const QString &id);
+  /** Selects and highlights a circuit on the map. */
+  void selectCircuit(const CircuitId &circid);
+  /** Returns the minimum size of the widget */
+  QSize minimumSizeHint() const;
+
+public slots:
+  /** Removes a circuit from the map. */
+  void removeCircuit(const CircuitId &circid);
+  /** Deselects all the highlighted circuits and routers */
+  void deselectAll();
+  /** Clears the known routers and removes all the data from the map */
+  void clear();
+  /** Zooms to fit all currently displayed circuits on the map. */
+  void zoomToFit();
+  /** Zoom to a particular router on the map. */
+  void zoomToRouter(const QString &id);
+  /** Zoom to the circuit on the map with the given <b>circid</b>. */
+  void zoomToCircuit(const CircuitId &circid);
+
+protected:
+  /** Paints the current circuits and streams on the image. */
+  virtual void paintImage(QPainter *painter);
+
+private:
+  /** Converts world space coordinates into map space coordinates */
+  QPointF toMapSpace(float latitude, float longitude);
+  /** Linearly interpolates using the values in the projection table */
+  float lerp(float input, float *table);
+  /** Computes a bounding box around all currently displayed circuit paths on
+   * the map. */
+  QRectF circuitBoundingBox();
+  
+  /** Stores map locations for tor routers */
+  QHash<QString, QPair<QPointF,bool>* > _routers;
+  /** Stores circuit information */
+  QHash<CircuitId, QPair<QPainterPath *,bool>* > _circuits;
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapImageView.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidget.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidget.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidget.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,290 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file TorMapWidget.cpp
+** \version $Id$
+** \brief Displays Tor servers and circuits on a map of the world
+*/
+
+#include "TorMapWidget.h"
+#include "TorMapWidgetInputHandler.h"
+#include "TorMapWidgetPopupMenu.h"
+#include "Vidalia.h"
+
+#include <QStringList>
+
+using namespace Marble;
+
+/** QPens to use for drawing different map elements */
+#define CIRCUIT_NORMAL_PEN      QPen(Qt::blue,  2.0)
+#define CIRCUIT_SELECTED_PEN    QPen(Qt::green, 3.0)
+
+
+/** Default constructor */
+TorMapWidget::TorMapWidget(QWidget *parent)
+  : MarbleWidget(parent)
+{
+  setMapThemeId("earth/srtm/srtm.dgml");
+  setShowScaleBar(false);
+  setShowCrosshairs(false);
+  setAnimationsEnabled(true);
+  setCursor(Qt::OpenHandCursor);
+
+  TorMapWidgetInputHandler *handler = new TorMapWidgetInputHandler();
+  TorMapWidgetPopupMenu *popupMenu  = new TorMapWidgetPopupMenu(this);
+
+  connect(handler, SIGNAL(featureClicked(QPoint,Qt::MouseButton)),
+          popupMenu, SLOT(featureClicked(QPoint,Qt::MouseButton)));
+  connect(popupMenu, SIGNAL(displayRouterInfo(QString)),
+          this, SIGNAL(displayRouterInfo(QString)));
+
+  /* We can't call setInputHandler() until MarbleWidget has called its
+   * internal _q_initGui() method, which doesn't happen until a
+   * QTimer::singleShot(0, this, SLOT(_q_initGui())) timer set in its
+   * constructor times out. So force that event to process now. */ 
+  vApp->processEvents(QEventLoop::ExcludeUserInputEvents
+                        | QEventLoop::ExcludeSocketNotifiers);
+
+  setInputHandler(handler);
+}
+
+/** Destructor */
+TorMapWidget::~TorMapWidget()
+{
+  clear();
+}
+
+/** Adds a router to the map. */
+void
+TorMapWidget::addRouter(const RouterDescriptor &desc, const GeoIp &geoip)
+{
+  QString kml;
+  qreal lon = geoip.longitude();
+  qreal lat = geoip.latitude();
+  quint64 bw;
+  
+  bw = qMin(desc.averageBandwidth(), desc.burstBandwidth());
+  bw = qMin(bw, desc.observedBandwidth());
+
+  kml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+             "<kml xmlns=\"http://earth.google.com/kml/2.0\";>"
+             "<Document>"
+             "  <Style id=\"normalPlacemark\">"
+             "    <IconStyle><Icon><href>:/images/icons/placemark-relay.png</href></Icon></IconStyle>"
+             "  </Style>"
+             );
+
+  kml.append("<Placemark>");
+  kml.append("<styleUrl>#normalPlacemark</styleUrl>");
+  kml.append(QString("<name>%1</name>").arg(desc.name()));
+  kml.append(QString("<description>%1</description>").arg(desc.id()));
+  kml.append(QString("<role>1</role>"));
+  kml.append(QString("<address>%1</address>").arg(geoip.toString()));
+  kml.append(QString("<CountryNameCode>%1</CountryNameCode>").arg(geoip.country()));
+  kml.append(QString("<pop>%1</pop>").arg(10 * bw));
+  kml.append(QString("<Point>"
+                     "  <coordinates>%1,%2</coordinates>"
+                     "</Point>").arg(lon).arg(lat));
+  kml.append("</Placemark>");
+  kml.append("</Document></kml>");
+
+  QString id = desc.id();
+  addPlaceMarkData(kml, id);
+  _routers.insert(id, GeoDataCoordinates(lon, lat, 0.0,
+                                         GeoDataCoordinates::Degree));
+}
+
+/** Adds a circuit to the map using the given ordered list of router IDs. */
+void
+TorMapWidget::addCircuit(const CircuitId &circid, const QStringList &path)
+{
+  /* XXX: Is it better to do KML LineString-based circuit drawing here,
+   *      instead of going with a QPainter-based approach? I gave it a brief
+   *      try once but failed. It might be worth looking into harder if we
+   *      want to make circuits selectable on the map too.
+   */
+
+  /* It doesn't make sense to draw a path of length less than two */
+  if (path.size() < 2)
+    return;
+
+  if (_circuits.contains(circid)) {
+    /* Extend an existing path */
+    CircuitGeoPath *geoPath = _circuits.value(circid);
+
+    QString router = path.at(path.size()-1);
+    if (_routers.contains(router)) {
+      GeoDataCoordinates coords = _routers.value(router);
+      geoPath->first.append(new GeoDataCoordinates(coords));
+    }
+  } else {
+    /* Construct a new path */
+    CircuitGeoPath *geoPath = new CircuitGeoPath();
+    geoPath->second = false; /* initially unselected */
+
+    foreach (QString router, path) {
+      if (_routers.contains(router)) {
+        GeoDataCoordinates coords = _routers.value(router);
+        geoPath->first.append(new GeoDataCoordinates(coords));
+      }      
+    }
+    geoPath->first.setTessellationFlags(Tessellate | RespectLatitudeCircle);
+    _circuits.insert(circid, geoPath);
+  }
+
+  repaint();
+}
+
+/** Removes a circuit from the map. */
+void
+TorMapWidget::removeCircuit(const CircuitId &circid)
+{
+  CircuitGeoPath *path = _circuits.take(circid);
+  if (path) {
+    GeoDataLineString coords = path->first;
+    qDeleteAll(coords.begin(), coords.end());
+    delete path;
+  }
+
+  repaint();
+}
+
+/** Selects and highlights the router on the map. */
+void
+TorMapWidget::selectRouter(const QString &id)
+{
+#if 0
+  if (_routers.contains(id)) {
+    QPair<QPointF, bool> *routerPair = _routers.value(id);
+    routerPair->second = true;
+  }
+  repaint();
+#endif
+}
+
+/** Selects and highlights the circuit with the id <b>circid</b> 
+ * on the map. */
+void
+TorMapWidget::selectCircuit(const CircuitId &circid)
+{
+  if (_circuits.contains(circid)) {
+    CircuitGeoPath *path = _circuits.value(circid);
+    path->second = true;
+  }
+
+  repaint();
+}
+
+/** Deselects any highlighted routers or circuits */
+void
+TorMapWidget::deselectAll()
+{
+#if 0
+  /* Deselect all router points */
+  foreach (QString router, _routers.keys()) {
+    QPair<QPointF,bool> *routerPair = _routers.value(router);
+    routerPair->second = false;
+  }
+#endif
+  /* Deselect all circuit paths */
+  foreach (CircuitGeoPath *path, _circuits.values()) {
+    path->second = false;
+  }
+
+  repaint();
+}
+
+/** Clears the list of routers and removes all the data on the map */
+void
+TorMapWidget::clear()
+{
+  foreach (QString id, _routers.keys()) {
+    removePlaceMarkKey(id);
+  }
+
+  foreach (CircuitId circid, _circuits.keys()) {
+    CircuitGeoPath *path = _circuits.take(circid);
+    GeoDataLineString coords = path->first;
+    qDeleteAll(coords.begin(), coords.end());
+    delete path;
+  }
+
+  repaint();
+}
+ 
+/** Zooms the map to fit entirely within the constraints of the current
+ * viewport size. */
+void
+TorMapWidget::zoomToFit()
+{
+  int width  = size().width();
+  int height = size().height();
+
+  setRadius(qMin(width, height) / 2);
+
+  /* XXX: Calling setRadius() seems to cause Marble to no longer draw the
+   *      atmosphere. So, re-enable it. */
+  setShowAtmosphere(true);
+}
+
+/** Zoom to the circuit on the map with the given <b>circid</b>. */
+void
+TorMapWidget::zoomToCircuit(const CircuitId &circid)
+{
+#if 0
+  if (_circuits.contains(circid)) {
+    QPair<QPainterPath*,bool> *pair = _circuits.value(circid);
+    QRectF rect = ((QPainterPath *)pair->first)->boundingRect();
+    if (!rect.isNull()) {
+      float zoomLevel = 1.0 - qMax(rect.height()/float(MAP_HEIGHT),
+                                   rect.width()/float(MAP_WIDTH));
+
+      zoom(rect.center().toPoint(), zoomLevel+0.2);
+    }
+  }
+#endif
+}
+
+/** Zooms in on the router with the given <b>id</b>. */
+void
+TorMapWidget::zoomToRouter(const QString &id)
+{
+  if (_routers.contains(id)) {
+    qreal lon, lat;
+    GeoDataCoordinates coords = _routers.value(id);
+    coords.geoCoordinates(lon, lat, GeoDataPoint::Degree);
+
+    zoomView(maximumZoom());
+    centerOn(lon, lat, true);
+  }
+}
+
+/** Paints the current circuits and streams on the image. */
+void
+TorMapWidget::customPaint(GeoPainter *painter)
+{
+  bool selected = false;
+
+  painter->autoMapQuality();
+  painter->setPen(CIRCUIT_NORMAL_PEN);
+
+  foreach (CircuitGeoPath *path, _circuits.values()) {
+    if (! path->second && selected) {
+      painter->setPen(CIRCUIT_NORMAL_PEN);
+      selected = false;
+    } else if (path->second && ! selected) {
+      painter->setPen(CIRCUIT_SELECTED_PEN);
+      selected = true;
+    }
+    painter->drawPolyline(path->first);
+  }
+}
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidget.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidget.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidget.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidget.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,89 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file TorMapWidget.h
+** \version $Id$
+** \brief Displays Tor servers and circuits on a map of the world
+*/
+
+#ifndef _TORMAPWIDGET_H
+#define _TORMAPWIDGET_H
+
+#include "RouterDescriptor.h"
+#include "GeoIp.h"
+
+#include "Circuit.h"
+#include "Stream.h"
+
+#include <MarbleWidget.h>
+#include <GeoPainter.h>
+#include <GeoDataCoordinates.h>
+#include <GeoDataLineString.h>
+
+#include <QHash>
+#include <QPair>
+#include <QPainterPath>
+
+typedef QPair<Marble::GeoDataLineString, bool> CircuitGeoPath;
+
+
+class TorMapWidget : public Marble::MarbleWidget
+{
+  Q_OBJECT
+
+public:
+  /** Default constructor. */
+  TorMapWidget(QWidget *parent = 0);
+  /** Destructor. */
+  ~TorMapWidget();
+
+  /** Plots the given router on the map using the given coordinates. */
+  void addRouter(const RouterDescriptor &desc, const GeoIp &geoip);
+  /** Plots the given circuit on the map. */
+  void addCircuit(const CircuitId &circid, const QStringList &path);
+  /** Selects and hightlights a router on the map. */
+  void selectRouter(const QString &id);
+  /** Selects and highlights a circuit on the map. */
+  void selectCircuit(const CircuitId &circid);
+
+public slots:
+  /** Removes a circuit from the map. */
+  void removeCircuit(const CircuitId &circid);
+  /** Deselects all the highlighted circuits and routers */
+  void deselectAll();
+  /** Clears the known routers and removes all the data from the map */
+  void clear();
+  /** Zooms the map to fit entirely within the constraints of the current
+   * viewport size. */
+  void zoomToFit();
+  /** Zoom to a particular router on the map. */
+  void zoomToRouter(const QString &id);
+  /** Zoom to the circuit on the map with the given <b>circid</b>. */
+  void zoomToCircuit(const CircuitId &circid);
+
+signals:
+  /** Emitted when the user selects a router placemark on the map. <b>id</b>
+   * contain's the selected router's fingerprint. */
+  void displayRouterInfo(const QString &id);
+
+protected:
+  /** Paints the current circuits and streams on the image. */
+  virtual void customPaint(Marble::GeoPainter *painter);
+
+private:
+  /** Stores placemark IDs for Tor routers. */
+  QHash<QString, Marble::GeoDataCoordinates> _routers;
+  /** Stores circuit information */
+  QHash<CircuitId, CircuitGeoPath*> _circuits;
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidget.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetInputHandler.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetInputHandler.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetInputHandler.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,138 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+#include "TorMapWidgetInputHandler.h"
+
+#include <MarbleWidget.h>
+#include <MarbleMap.h>
+#include <MarbleModel.h>
+#include <ViewParams.h>
+#include <ViewportParams.h>
+
+#include <QTimer>
+#include <QMouseEvent>
+#include <QWheelEvent>
+#include <QPersistentModelIndex>
+
+using namespace Marble;
+
+
+/** Amount to zoom in or out when responding to mouse double clicks. This
+ * value was taken from MarbleMap.cpp.
+ */
+#define MAP_ZOOM_STEP   40
+
+/** Number of units the mouse must be clicked and dragged before it will
+ * force a map rotation and repaint.
+*/
+#define MIN_DRAG_THRESHOLD 3
+
+
+TorMapWidgetInputHandler::TorMapWidgetInputHandler()
+  : MarbleWidgetInputHandler()
+{
+}
+
+bool
+TorMapWidgetInputHandler::eventFilter(QObject *obj, QEvent *e)
+{
+  Q_UNUSED(obj);
+
+  QWheelEvent *wheelEvent = 0;
+  QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(e);
+
+  switch (e->type()) {
+    case QEvent::MouseButtonPress:
+      _mousePressedX = mouseEvent->x();
+      _mousePressedY = mouseEvent->y();
+      _mousePressedLon = m_widget->centerLongitude();
+      _mousePressedLat = m_widget->centerLatitude();
+
+      if (pointHasFeatures(mouseEvent->pos()))
+        emit featureClicked(mouseEvent->pos(), mouseEvent->button());
+      else
+        m_widget->setCursor(Qt::ClosedHandCursor);
+      break;
+
+    case QEvent::MouseButtonRelease:
+      if (! pointHasFeatures(mouseEvent->pos()))
+        m_widget->setCursor(Qt::OpenHandCursor);
+      else
+        m_widget->setCursor(Qt::PointingHandCursor);
+      break;
+
+    case QEvent::MouseMove:
+      if (mouseEvent->buttons() & Qt::LeftButton) {
+        // Pan the map if the left button is pressed while dragging
+        int dx = mouseEvent->x() - _mousePressedX;
+        int dy = mouseEvent->y() - _mousePressedY;
+
+        if (abs(dx) <= MIN_DRAG_THRESHOLD && abs(dy) <= MIN_DRAG_THRESHOLD)
+          return true;
+        m_widget->setCursor(Qt::ClosedHandCursor);
+
+        qreal dir = 1;
+        if (m_widget->projection() == Spherical) {
+          if (m_widget->map()->viewParams()->viewport()->polarity() > 0) {
+            if (mouseEvent->y() < (-m_widget->northPoleY() + m_widget->height()/2))
+              dir = -1;
+          } else {
+            if (mouseEvent->y() > (+m_widget->northPoleY() + m_widget->height()/2))
+              dir = -1;
+          }
+        }
+
+        qreal radius = (qreal)(m_widget->radius());
+        qreal lon = (qreal)(_mousePressedLon) - 90.0 * dir * dx / radius;
+        qreal lat = (qreal)(_mousePressedLat) + 90.0 * dy / radius;
+        m_widget->centerOn(lon, lat, false);
+
+        return true;
+      } else {
+        // Change the mouse cursor if we're hovering over a relay placemark
+        if (pointHasFeatures(mouseEvent->pos()) > 0)
+          m_widget->setCursor(Qt::PointingHandCursor);
+        else
+          m_widget->setCursor(Qt::OpenHandCursor);
+      }
+      break;
+
+    case QEvent::MouseButtonDblClick:
+      // Adjust the zoom level on the map
+      if (mouseEvent->button() == Qt::LeftButton) {
+        m_widget->zoomViewBy(MAP_ZOOM_STEP);
+        return true;
+      } else if (mouseEvent->button() == Qt::RightButton) {
+        m_widget->zoomViewBy(-MAP_ZOOM_STEP);
+        return true;
+      }
+      break;
+
+    case QEvent::Wheel:
+      // Adjust the zoom level on the map
+      m_widget->setViewContext(Marble::Animation);
+
+      wheelEvent = static_cast<QWheelEvent*>(e);
+      m_widget->zoomViewBy((int)(wheelEvent->delta() / 3));
+      m_mouseWheelTimer->start(400);
+      return true;
+
+    default:
+      break;
+  }
+  return MarbleWidgetInputHandler::eventFilter(obj, e);
+}
+
+bool
+TorMapWidgetInputHandler::pointHasFeatures(const QPoint &point) const
+{
+  return (m_widget->model()->whichFeatureAt(point).size() > 0);
+}
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetInputHandler.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetInputHandler.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetInputHandler.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetInputHandler.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,55 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+#ifndef _TORMAPWIDGETINPUTHANDLER_H
+#define _TORMAPWIDGETINPUTHANDLER_H
+
+#include "MarbleWidgetInputHandler.h"
+
+#include <QEvent>
+#include <QObject>
+#include <QPoint>
+
+
+class TorMapWidgetInputHandler : public Marble::MarbleWidgetInputHandler
+{
+  Q_OBJECT
+
+public:
+  /** Default constructor.
+   */
+  TorMapWidgetInputHandler();
+
+signals:
+  /** Emitted when the user clicks on a map feature located at <b>point</b>.
+   * <b>button</b> indicates which mouse button was clicked.
+   */
+  void featureClicked(const QPoint &point, Qt::MouseButton button);
+
+protected:
+  /** Filter and handles event <b>e</b> that was sent to widget <b>obj</b>.
+   * <b>obj</b> is always a MarbleWidget object.
+   */
+  virtual bool eventFilter(QObject *obj, QEvent *e);
+
+private:
+  /** Returns true if the map has one or more features located at the screen
+   * position <b>point</b>.
+   */
+  bool pointHasFeatures(const QPoint &point) const;
+
+  int   _mousePressedX;
+  int   _mousePressedY;
+  qreal _mousePressedLon;
+  qreal _mousePressedLat;
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetInputHandler.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetPopupMenu.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetPopupMenu.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetPopupMenu.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,90 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file TorMapWidgetPopupMenu.cpp
+** \version $Id$
+** \brief Popup menu displayed when the user mouse clicks on a map placemark
+*/
+
+#include "TorMapWidgetPopupMenu.h"
+#include "Vidalia.h"
+
+#include <MarbleModel.h>
+#include <MarblePlacemarkModel.h>
+
+#include <QChar>
+#include <QVector>
+#include <QModelIndex>
+
+using namespace Marble;
+
+
+TorMapWidgetPopupMenu::TorMapWidgetPopupMenu(TorMapWidget *widget)
+  : QObject(widget),
+    _widget(widget)
+{
+  _leftClickMenu = new QMenu(widget);
+  connect(_leftClickMenu, SIGNAL(triggered(QAction*)),
+          this, SLOT(relaySelected(QAction*)));
+}
+
+void
+TorMapWidgetPopupMenu::featureClicked(const QPoint &pos, Qt::MouseButton btn)
+{
+  switch (btn) {
+    case Qt::LeftButton:
+      featureLeftClicked(pos);
+      break;
+
+    case Qt::RightButton:
+      break;
+
+    default:
+      break;
+  }
+}
+
+void
+TorMapWidgetPopupMenu::featureLeftClicked(const QPoint &pos)
+{
+  QVector<QModelIndex>::const_iterator it;
+  QVector<QModelIndex> features = _widget->model()->whichFeatureAt(pos);
+  QString name, id;
+  int numRelays = 0;
+
+  _leftClickMenu->clear();
+  for (it = features.constBegin(); it != features.constEnd(); ++it) {
+    QChar role = (*it).data(MarblePlacemarkModel::GeoTypeRole).toChar();
+    if (role == '1') {
+      /* Normal Tor Relay */
+      name = (*it).data().toString();
+      id   = (*it).data(MarblePlacemarkModel::DescriptionRole).toString();
+
+      QAction *action = _leftClickMenu->addAction(name);
+      action->setData(id);
+      numRelays++;
+    }
+  }
+
+  if (numRelays == 1)
+    emit displayRouterInfo(id);
+  else if (numRelays > 1)
+    _leftClickMenu->popup(_widget->mapToGlobal(pos));
+}
+
+void
+TorMapWidgetPopupMenu::relaySelected(QAction *action)
+{
+  QString id = action->data().toString();
+  if (! id.isEmpty())
+    emit displayRouterInfo(id);
+}
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetPopupMenu.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetPopupMenu.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetPopupMenu.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetPopupMenu.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,81 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file TorMapWidgetPopupMenu.h
+** \version $Id$
+** \brief Popup menu displayed when the user mouse clicks on a map placemark
+*/
+
+#ifndef _TORMAPWIDGETPOPUPMENU_H
+#define _TORMAPWIDGETPOPUPMENU_H
+
+#include "TorMapWidget.h"
+
+#include <QObject>
+#include <QPoint>
+#include <QString>
+#include <QMenu>
+
+
+class TorMapWidgetPopupMenu : public QObject
+{
+  Q_OBJECT
+
+public:
+  /** Constructor. <b>widget</b> is the parent map widget on which the popup
+   * menu will be displayed.
+   */
+  TorMapWidgetPopupMenu(TorMapWidget *widget);
+
+public slots:
+  /** Called when the user clicks on one or more map features located at mouse
+   * position <b>pos</b>. <b>button</b> specifies the mouse button clicked.
+   * A popup menu will be displayed depending on which mouse button was
+   * clicked.
+   *
+   * \sa featureLeftClicked
+   */
+  void featureClicked(const QPoint &pos, Qt::MouseButton button);
+
+signals:
+  /** Emitted when the user selects the router placemark whose fingerprint
+   * is <b>id</b>.
+   */
+  void displayRouterInfo(const QString &id);
+
+protected:
+  /** Called when the user left-clicks on one or more placemarks at mouse
+   * position <b>pos</b>. If only one relay placemark exists at <b>pos</b>,
+   * then the displayRouterInfo() signal will be emitted. Otherwise, a
+   * popup menu will be displayed listing all placemarks at this location.
+   *
+   * \sa featureLeftClicked
+   */
+  virtual void featureLeftClicked(const QPoint &pos);
+
+private slots:
+  /** Called when the user selects a relay from the popup menu used to
+   * disambiguate a location with multiple relay placemarks.
+   */
+  void relaySelected(QAction *action);
+
+private:
+  /** The parent map widget on which the popup menu is displayed.
+   */
+  TorMapWidget *_widget;
+
+  /** Menu displayed when the user left-clicks on one or more placemarks.
+   */
+  QMenu *_leftClickMenu;
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/TorMapWidgetPopupMenu.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/ZImageView.cpp
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/ZImageView.cpp	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/ZImageView.cpp	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,374 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file ZImageView.cpp
+** \version $Id$
+** \brief Displays an image and allows zooming and panning
+*/
+
+#include "ZImageView.h"
+
+#include <QPainter>
+#include <QMouseEvent>
+
+#include <cmath>
+
+#if QT_VERSION >= 0x040200
+#define CURSOR_NORMAL           QCursor(Qt::OpenHandCursor)
+#define CURSOR_MOUSE_PRESS      QCursor(Qt::ClosedHandCursor)
+#else
+#define CURSOR_NORMAL           QCursor(Qt::CrossCursor)
+#define CURSOR_MOUSE_PRESS      QCursor(Qt::SizeAllCursor)
+#endif
+
+
+/** Constructor. */
+ZImageView::ZImageView(QWidget *parent)
+  : QWidget(parent)
+{
+  /* Initialize members */
+  _zoom = 0.0;
+  _desiredX = 0.0;
+  _desiredY = 0.0;
+  _maxZoomFactor = 2.0;
+  _padding = 60;
+
+  setCursor(CURSOR_NORMAL);
+  updateViewport();
+  resetZoomPoint();
+  repaint();
+}
+
+/** Sets the displayed image. */
+void
+ZImageView::setImage(QImage& img)
+{
+  _image = img.copy();
+  updateViewport();
+  resetZoomPoint();
+
+  if (isVisible()) {
+    repaint();
+  }
+}
+
+/** Draws the scaled image on the widget. */
+void
+ZImageView::drawScaledImage()
+{
+  if (!isVisible()) {
+    return;
+  }
+
+  QBrush background(QColor("#fdfdfd"));
+  if (_image.isNull()) {
+    QPainter p(this);
+    p.fillRect(rect(), background);
+    return;
+  }
+
+  QRect sRect = rect();
+  QRect iRect = _image.rect();
+  QRect r = _view;
+
+  // Think of the _view as being overlaid on the image.  The _view has the same
+  // aspect ratio as the screen, so we cut the _view region out of the _image
+  // and scale it to the screen dimensions and paint it.
+
+  // There is a slight catch in that the _view may be larger than the image in 
+  // one or both directions.  In that case, we need to reduce the _view region
+  // to lie within the image, then paint the background around it.  Copying
+  // a region from an image where the region is bigger than the image results
+  // in the parts outside the image being black, which is not what we want.
+
+  // The view has the same aspect ratio as the screen, so the vertical and 
+  // horizontal scale factors will be equal.
+
+  double scaleFactor = double(sRect.width()) / double(_view.width());
+
+  // Constrain r to lie entirely within the image.
+  if (r.top() < 0) {
+    r.setTop(0);
+  }
+  if (iRect.bottom() < r.bottom()) {
+    r.setBottom(iRect.bottom());
+  }
+  if (r.left() < 0) {
+    r.setLeft(0);
+  }
+  if (iRect.right() < r.right()) {
+    r.setRight(iRect.right());
+  }
+
+  // Figure out the size that the 'r' region will be when drawn to the screen.
+  QSize scaleTo(int(double(r.width()) * scaleFactor), 
+		int(double(r.height()) * scaleFactor));
+
+  /** Make a copy of the image so we don't ruin the original */
+  QImage i = _image.copy();
+  
+  /** Create a QPainter that draws directly on the copied image and call the
+   * virtual function to draw whatever the subclasses need to on the image. */
+  QPainter painter;
+  painter.begin(&i);
+  paintImage(&painter);
+  painter.end();
+
+  /** Rescale the image copy */
+  i = i.copy(r).scaled(scaleTo,
+		     Qt::KeepAspectRatioByExpanding,
+		     Qt::SmoothTransformation);
+
+  int extraWidth = int(double(sRect.width() - i.width()) / 2.0);
+  int extraHeight = int(double(sRect.height() - i.height()) / 2.0);
+
+  // We don't want to paint the background
+  // because this isn't double buffered and that would flicker.
+  // We could double buffer it, but that would cost ~3 MB of memory.
+  
+  QPainter p(this);
+  if (extraWidth > 0) {
+    p.fillRect(0, 0, extraWidth, sRect.height(), background);
+    p.fillRect(sRect.width() - extraWidth, 0,
+	       sRect.width(), sRect.height(), background);
+  }
+
+  if (extraHeight > 0) {
+    p.fillRect(0, 0, sRect.width(), extraHeight, background);
+    p.fillRect(0, sRect.height() - extraHeight,
+	       sRect.width(), sRect.height(), background);
+  }
+
+  // Finally, paint the image copy.
+  p.drawImage(extraWidth, extraHeight, i);
+}
+	
+/** Updates the displayed viewport. */
+void
+ZImageView::updateViewport(int screendx, int screendy)
+{
+  /* The gist of this is to find the biggest and smallest possible viewports,
+   * then use the _zoom factor to interpolate between them.  Also pan the 
+   * viewport, but constrain each dimension to lie within the image or to be 
+   * centered if the image is too small in that direction. */
+
+  QRect sRect = rect();
+  QRect iRect = _image.rect();
+
+  float sw = float(sRect.width());
+  float sh = float(sRect.height());
+  float iw = float(iRect.width());
+  float ih = float(iRect.height());
+	
+  // Get the initial max and min sizes for the viewport.  These won't have the 
+  // correct aspect ratio.  They will actually be the least upper bound and 
+  // greatest lower bound of the set containing the screen and image rects.
+  float maxw = float(std::max<int>(sRect.width(), iRect.width())) + _padding;
+  float maxh = float(std::max<int>(sRect.height(), iRect.height())) + _padding;
+  float minw = std::ceil(float(sRect.width()) / _maxZoomFactor);
+  float minh = std::ceil(float(sRect.height()) / _maxZoomFactor);
+
+  // Now that we have the glb and the lub, we expand/shrink them until
+  // the aspect ratio is that of the screen.
+  float aspect = sw / sh;
+
+  // Fix the max rect.
+  float newmaxh = maxh;
+  float newmaxw = aspect * newmaxh;
+  if (newmaxw < maxw) {
+    newmaxw = maxw;
+    newmaxh = maxw / aspect;
+  }
+
+  // Fix the min rect.
+  float newminh = minh;
+  float newminw = aspect * newminh;
+  if (minw < newminw) {
+    newminw = minw;
+    newminh = newminw / aspect;
+  }
+	
+  // Now interpolate between max and min.
+  float vw = (1.0f - _zoom) * (newmaxw - newminw) + newminw;
+  float vh = (1.0f - _zoom) * (newmaxh - newminh) + newminh;
+
+  _view.setWidth(int(vw));
+  _view.setHeight(int(vh));
+
+  // Now pan the view
+
+  // Convert the pan delta from screen coordinates to view coordinates.
+  float vdx = vw * (float(screendx) / sw);
+  float vdy = vh * (float(screendy) / sh);
+	
+  // Constrain the center of the viewport to the image rect.
+  _desiredX = qBound(0.0f, _desiredX + vdx, iw);
+  _desiredY = qBound(0.0f, _desiredY + vdy, ih);
+  _view.moveCenter(QPoint(int(_desiredX), int(_desiredY)));
+
+  QPoint viewCenter = _view.center();
+  float vx = viewCenter.x();
+  float vy = viewCenter.y();
+
+  // The viewport may be wider than the height and/or width.  In that case,
+  // center the view over the image in the appropriate directions.
+  //
+  // If the viewport is smaller than the image in either direction, then make
+  // sure the edge of the viewport isn't past the edge of the image.
+	
+  vdx = 0;
+  vdy = 0;
+   
+  if (iw <= vw) {
+    vdx = (iw / 2.0f) - vx;  // Center horizontally.
+  } else {
+    // Check that the edge of the view isn't past the edge of the image.
+    float vl = float(_view.left());
+    float vr = float(_view.right());
+    if (vl < 0) {
+      vdx = -vl;
+    } else if (vr > iw) {
+      vdx = iw - vr;
+    }
+  }
+    
+  if (ih <= vh) {
+    vdy = (ih / 2.0f) - vy; // Center vertically.
+  } else {
+    // Check that the edge of the view isn't past the edge of the image.
+    float vt = float(_view.top());
+    float vb = float(_view.bottom());
+    if (vt < 0) {
+      vdy = -vt;
+    } else if (vb > ih) {
+      vdy = ih - vb;
+    }
+  }
+
+  _view.translate(int(vdx), int(vdy));
+}
+
+/** Resets the zoom point back to the center of the viewport. */
+void
+ZImageView::resetZoomPoint()
+{
+  QPoint viewCenter = _view.center();
+  _desiredX = viewCenter.x();
+  _desiredY = viewCenter.y();
+}
+
+/** Handles repainting this widget by updating the viewport and drawing the
+ * scaled image. */
+void
+ZImageView::paintEvent(QPaintEvent*)
+{
+  updateViewport();
+  drawScaledImage();
+}
+
+/** Sets the current zoom percentage to the given value and scrolls the
+ * viewport to center the given point. */
+void
+ZImageView::zoom(QPoint zoomAt, float pct)
+{
+  _desiredX = zoomAt.x();
+  _desiredY = zoomAt.y();
+  zoom(pct);
+}
+
+/** Sets the current zoom percentage to the given value. */
+void
+ZImageView::zoom(float pct)
+{
+  _zoom = qBound(0.0f, pct, 1.0f);
+  repaint();
+}
+
+/** Zooms into the image by 10% */
+void
+ZImageView::zoomIn()
+{
+  zoom(_zoom + .1);
+}
+
+/** Zooms away from the image by 10% */
+void
+ZImageView::zoomOut()
+{
+  zoom(_zoom - .1);
+}
+
+/** Responds to the user pressing a mouse button. */
+void
+ZImageView::mousePressEvent(QMouseEvent *e)
+{
+  e->accept();
+  setCursor(CURSOR_MOUSE_PRESS);
+  _mouseX = e->x();
+  _mouseY = e->y();
+}
+
+/** Responds to the user releasing a mouse button. */
+void 
+ZImageView::mouseReleaseEvent(QMouseEvent *e)
+{
+  e->accept();
+  setCursor(CURSOR_NORMAL);
+  updateViewport();
+  resetZoomPoint();
+}
+
+/** Responds to the user double-clicking a mouse button on the image. A left
+ * double-click zooms in on the image and a right double-click zooms out.
+ * Zooming is centered on the location of the double-click. */
+void
+ZImageView::mouseDoubleClickEvent(QMouseEvent *e)
+{
+  e->accept();
+  
+  QPoint center = rect().center(); 
+  int dx = e->x() - center.x();
+  int dy = e->y() - center.y();
+  updateViewport(dx, dy);
+  resetZoomPoint();
+
+  Qt::MouseButton btn = e->button();
+  if (btn == Qt::LeftButton)
+    zoomIn();
+  else if (btn == Qt::RightButton)
+    zoomOut();
+}
+
+/** Responds to the user moving the mouse. */
+void
+ZImageView::mouseMoveEvent(QMouseEvent *e)
+{
+  e->accept();
+  int dx = _mouseX - e->x();
+  int dy = _mouseY - e->y();
+  _mouseX = e->x();
+  _mouseY = e->y();
+
+  updateViewport(dx, dy);
+  if (0.001 <= _zoom) {
+    repaint();
+  }
+}
+
+void
+ZImageView::wheelEvent(QWheelEvent *e)
+{
+  if (e->delta() > 0) {
+    zoomIn();
+  } else {
+    zoomOut();
+  }
+}


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/ZImageView.cpp
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/ZImageView.h
===================================================================
--- vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/ZImageView.h	                        (rev 0)
+++ vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/ZImageView.h	2009-06-24 22:27:15 UTC (rev 3883)
@@ -0,0 +1,88 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file ZImageView.h
+** \version $Id$
+** \brief Displays an image and allows zooming and panning
+*/
+
+#ifndef ZIMAGEVIEW_H
+#define ZIMAGEVIEW_H
+
+#include <QImage>
+#include <QPixmap>
+#include <QWidget>
+
+
+class ZImageView : public QWidget
+{
+  Q_OBJECT
+
+public:
+  /** Default constructor. */
+  ZImageView(QWidget *parent = 0);
+  /** Sets the displayed image. */
+  void setImage(QImage& pixmap);
+  
+public slots:
+  /** Resets the center zoom point back to the center of the viewport. */
+  void resetZoomPoint();
+  /** Sets the current zoom level to the given percent. */
+  void zoom(float pct);
+  /** Sets the current zoom level to the given percent and scrolls the window
+   * to place the specified point in the middle. */
+  void zoom(QPoint zoomAt, float pct);
+  /** Zooms into the displayed image by 5% */
+  void zoomIn();
+  /** Zooms away from the displayed image by 5% */
+  void zoomOut();
+
+protected:
+  /** Virtual method to let subclasses paint on the image before it's scaled. */
+  virtual void paintImage(QPainter *painter) { Q_UNUSED(painter); }
+  /** Updates the viewport and repaints the displayed image. */
+  virtual void paintEvent(QPaintEvent*);
+  /** Handles the user pressing a mouse button. */
+  virtual void mousePressEvent(QMouseEvent* e);
+  /** Handles the user releasing a mouse button. */
+  virtual void mouseReleaseEvent(QMouseEvent* e);
+  /** Handles the user moving the mouse. */
+  virtual void mouseMoveEvent(QMouseEvent* e);
+  /** Handles the user double-clicking a mouse button. */
+  virtual void mouseDoubleClickEvent(QMouseEvent *e);
+  /** Handles the wheel events. */
+  virtual void wheelEvent(QWheelEvent *e);
+
+  /** Update the viewport.  This will set _view to a region that,
+   *  when copied from the image and scaled to the screen size, will
+   *  show what is expected.  The _view may be larger in one or more
+   *  directions than the image, and you must deal with the 
+   *  non-overlapping regions. */
+  void updateViewport(int screendx=0, int screendy=0);
+  /** Redraws the scaled image in the viewport. */
+  void drawScaledImage();
+  
+private:
+  float _zoom;     /**< The current zoom level. */
+  QImage _image;   /**< The displayed image. */
+  float _padding;  /**< Amount of padding to use on the side of the image. */
+  float _maxZoomFactor;  /**< Maximum amount to zoom into the image. */
+
+  int  _mouseX;     /**< The x-coordinate of the current mouse position. */
+  int  _mouseY;     /**< The y-coordinate of the current mouse position. */
+  
+  QRect _view;      /**< The displayed viewport. */
+  float _desiredX;  /**< The X value we desire (???). */
+  float _desiredY;  /**< The Y value we desire (???). */
+};
+
+#endif
+


Property changes on: vidalia/branches/extension-api/src/vidalia/NetworkMapPlugin/ZImageView.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native