Vidalia  0.2.17
TorMapWidget.cpp
Go to the documentation of this file.
00001 /*
00002 **  This file is part of Vidalia, and is subject to the license terms in the
00003 **  LICENSE file, found in the top level directory of this distribution. If you
00004 **  did not receive the LICENSE file with this file, you may obtain it from the
00005 **  Vidalia source package distributed by the Vidalia Project at
00006 **  http://www.torproject.org/projects/vidalia.html. No part of Vidalia, 
00007 **  including this file, may be copied, modified, propagated, or distributed 
00008 **  except according to the terms described in the LICENSE file.
00009 */
00010 
00011 /*
00012 ** \file TorMapWidget.cpp
00013 ** \brief Displays Tor servers and circuits on a map of the world
00014 */
00015 
00016 #include "TorMapWidget.h"
00017 #include "TorMapWidgetInputHandler.h"
00018 #include "TorMapWidgetPopupMenu.h"
00019 #include "Vidalia.h"
00020 
00021 #include <MarbleModel.h>
00022 #include <HttpDownloadManager.h>
00023 
00024 #include <QStringList>
00025 
00026 using namespace Marble;
00027 
00028 /** QPens to use for drawing different map elements */
00029 #define CIRCUIT_NORMAL_PEN      QPen(Qt::blue,  2.0)
00030 #define CIRCUIT_SELECTED_PEN    QPen(Qt::green, 3.0)
00031 
00032 
00033 /** Default constructor */
00034 TorMapWidget::TorMapWidget(QWidget *parent)
00035   : MarbleWidget(parent)
00036 {
00037   setMapThemeId("earth/srtm/srtm.dgml");
00038   setShowScaleBar(false);
00039   setShowCrosshairs(false);
00040   setAnimationsEnabled(true);
00041   setCursor(Qt::OpenHandCursor);
00042 
00043   model()->downloadManager()->setDownloadEnabled(false);
00044 
00045   TorMapWidgetInputHandler *handler = new TorMapWidgetInputHandler();
00046   TorMapWidgetPopupMenu *popupMenu  = new TorMapWidgetPopupMenu(this);
00047 
00048   connect(handler, SIGNAL(featureClicked(QPoint,Qt::MouseButton)),
00049           popupMenu, SLOT(featureClicked(QPoint,Qt::MouseButton)));
00050   connect(popupMenu, SIGNAL(displayRouterInfo(QString)),
00051           this, SIGNAL(displayRouterInfo(QString)));
00052 
00053   /* We can't call setInputHandler() until MarbleWidget has called its
00054    * internal _q_initGui() method, which doesn't happen until a
00055    * QTimer::singleShot(0, this, SLOT(_q_initGui())) timer set in its
00056    * constructor times out. So force that event to process now. */ 
00057   vApp->processEvents(QEventLoop::ExcludeUserInputEvents
00058                         | QEventLoop::ExcludeSocketNotifiers);
00059 
00060   setInputHandler(handler);
00061 }
00062 
00063 /** Destructor */
00064 TorMapWidget::~TorMapWidget()
00065 {
00066   clear();
00067 }
00068 
00069 /** Adds a router to the map. */
00070 void
00071 TorMapWidget::addRouter(const RouterDescriptor &desc, const GeoIpRecord &geoip)
00072 {
00073   QString kml;
00074   qreal lon = geoip.longitude();
00075   qreal lat = geoip.latitude();
00076   quint64 bw;
00077   
00078   bw = qMin(desc.averageBandwidth(), desc.burstBandwidth());
00079   bw = qMin(bw, desc.observedBandwidth());
00080 
00081   kml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
00082              "<kml xmlns=\"http://earth.google.com/kml/2.0\">"
00083              "<Document>"
00084              "  <Style id=\"normalPlacemark\">"
00085              "    <IconStyle><Icon><href>:/images/icons/placemark-relay.png</href></Icon></IconStyle>"
00086              "  </Style>"
00087              );
00088 
00089   kml.append("<Placemark>");
00090   kml.append("<styleUrl>#normalPlacemark</styleUrl>");
00091   kml.append(QString("<name>%1</name>").arg(desc.name()));
00092   kml.append(QString("<description>%1</description>").arg(desc.id()));
00093   kml.append(QString("<role>1</role>"));
00094   kml.append(QString("<address>%1</address>").arg(geoip.toString()));
00095   kml.append(QString("<CountryNameCode>%1</CountryNameCode>").arg(geoip.country()));
00096   kml.append(QString("<pop>%1</pop>").arg(10 * bw));
00097   kml.append(QString("<Point>"
00098                      "  <coordinates>%1,%2</coordinates>"
00099                      "</Point>").arg(lon).arg(lat));
00100   kml.append("</Placemark>");
00101   kml.append("</Document></kml>");
00102 
00103   QString id = desc.id();
00104   addPlacemarkData(kml, id);
00105   _routers.insert(id, GeoDataCoordinates(lon, lat, 0.0,
00106                                          GeoDataCoordinates::Degree));
00107 }
00108 
00109 /** Adds a circuit to the map using the given ordered list of router IDs. */
00110 void
00111 TorMapWidget::addCircuit(const CircuitId &circid, const QStringList &path)
00112 {
00113   /* XXX: Is it better to do KML LineString-based circuit drawing here,
00114    *      instead of going with a QPainter-based approach? I gave it a brief
00115    *      try once but failed. It might be worth looking into harder if we
00116    *      want to make circuits selectable on the map too.
00117    */
00118 
00119   /* It doesn't make sense to draw a path of length less than two */
00120   if (path.size() < 2)
00121     return;
00122 
00123   if (_circuits.contains(circid)) {
00124     /* Extend an existing path */
00125     CircuitGeoPath *geoPath = _circuits.value(circid);
00126 
00127     QString router = path.at(path.size()-1);
00128     if (_routers.contains(router))
00129       geoPath->first.append(_routers.value(router));
00130   } else {
00131     /* Construct a new path */
00132     CircuitGeoPath *geoPath = new CircuitGeoPath();
00133     geoPath->second = false; /* initially unselected */
00134 
00135     foreach (QString router, path) {
00136       if (_routers.contains(router))
00137         geoPath->first.append(_routers.value(router));
00138     }
00139     geoPath->first.setTessellationFlags(Tessellate | RespectLatitudeCircle);
00140     _circuits.insert(circid, geoPath);
00141   }
00142 
00143   repaint();
00144 }
00145 
00146 /** Removes a circuit from the map. */
00147 void
00148 TorMapWidget::removeCircuit(const CircuitId &circid)
00149 {
00150   CircuitGeoPath *path = _circuits.take(circid);
00151   if (path)
00152     delete path;
00153 
00154   repaint();
00155 }
00156 
00157 /** Selects and highlights the router on the map. */
00158 void
00159 TorMapWidget::selectRouter(const QString &id)
00160 {
00161 #if 0
00162   if (_routers.contains(id)) {
00163     QPair<QPointF, bool> *routerPair = _routers.value(id);
00164     routerPair->second = true;
00165   }
00166   repaint();
00167 #endif
00168 }
00169 
00170 /** Selects and highlights the circuit with the id <b>circid</b> 
00171  * on the map. */
00172 void
00173 TorMapWidget::selectCircuit(const CircuitId &circid)
00174 {
00175   if (_circuits.contains(circid)) {
00176     CircuitGeoPath *path = _circuits.value(circid);
00177     path->second = true;
00178   }
00179 
00180   repaint();
00181 }
00182 
00183 /** Deselects any highlighted routers or circuits */
00184 void
00185 TorMapWidget::deselectAll()
00186 {
00187 #if 0
00188   /* Deselect all router points */
00189   foreach (QString router, _routers.keys()) {
00190     QPair<QPointF,bool> *routerPair = _routers.value(router);
00191     routerPair->second = false;
00192   }
00193 #endif
00194   /* Deselect all circuit paths */
00195   foreach (CircuitGeoPath *path, _circuits.values()) {
00196     path->second = false;
00197   }
00198 
00199   repaint();
00200 }
00201 
00202 /** Clears the list of routers and removes all the data on the map */
00203 void
00204 TorMapWidget::clear()
00205 {
00206   foreach (QString id, _routers.keys()) {
00207     removePlacemarkKey(id);
00208   }
00209 
00210   foreach (CircuitId circid, _circuits.keys()) {
00211     CircuitGeoPath *path = _circuits.take(circid);
00212     delete path;
00213   }
00214 
00215   repaint();
00216 }
00217  
00218 /** Zooms the map to fit entirely within the constraints of the current
00219  * viewport size. */
00220 void
00221 TorMapWidget::zoomToFit()
00222 {
00223   int width  = size().width();
00224   int height = size().height();
00225 
00226   setRadius(qMin(width, height) / 2);
00227 
00228   /* XXX: Calling setRadius() seems to cause Marble to no longer draw the
00229    *      atmosphere. So, re-enable it. */
00230   setShowAtmosphere(true);
00231 }
00232 
00233 /** Zoom to the circuit on the map with the given <b>circid</b>. */
00234 void
00235 TorMapWidget::zoomToCircuit(const CircuitId &circid)
00236 {
00237 #if 0
00238   if (_circuits.contains(circid)) {
00239     QPair<QPainterPath*,bool> *pair = _circuits.value(circid);
00240     QRectF rect = ((QPainterPath *)pair->first)->boundingRect();
00241     if (!rect.isNull()) {
00242       float zoomLevel = 1.0 - qMax(rect.height()/float(MAP_HEIGHT),
00243                                    rect.width()/float(MAP_WIDTH));
00244 
00245       zoom(rect.center().toPoint(), zoomLevel+0.2);
00246     }
00247   }
00248 #endif
00249 }
00250 
00251 /** Zooms in on the router with the given <b>id</b>. */
00252 void
00253 TorMapWidget::zoomToRouter(const QString &id)
00254 {
00255   if (_routers.contains(id)) {
00256     qreal lon, lat;
00257     GeoDataCoordinates coords = _routers.value(id);
00258     coords.geoCoordinates(lon, lat, GeoDataPoint::Degree);
00259 
00260     zoomView(maximumZoom());
00261     centerOn(lon, lat, true);
00262   }
00263 }
00264 
00265 /** Paints the current circuits and streams on the image. */
00266 void
00267 TorMapWidget::customPaint(GeoPainter *painter)
00268 {
00269   bool selected = false;
00270 
00271   painter->autoMapQuality();
00272   painter->setPen(CIRCUIT_NORMAL_PEN);
00273 
00274   foreach (CircuitGeoPath *path, _circuits.values()) {
00275     if (! path->second && selected) {
00276       painter->setPen(CIRCUIT_NORMAL_PEN);
00277       selected = false;
00278     } else if (path->second && ! selected) {
00279       painter->setPen(CIRCUIT_SELECTED_PEN);
00280       selected = true;
00281     }
00282     painter->drawPolyline(path->first);
00283   }
00284 }
00285