Vidalia  0.3.1
TorMapWidget.cpp
Go to the documentation of this file.
1 /*
2 ** This file is part of Vidalia, and is subject to the license terms in the
3 ** LICENSE file, found in the top level directory of this distribution. If you
4 ** did not receive the LICENSE file with this file, you may obtain it from the
5 ** Vidalia source package distributed by the Vidalia Project at
6 ** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7 ** including this file, may be copied, modified, propagated, or distributed
8 ** except according to the terms described in the LICENSE file.
9 */
10 
11 /*
12 ** \file TorMapWidget.cpp
13 ** \brief Displays Tor servers and circuits on a map of the world
14 */
15 
16 #include "TorMapWidget.h"
18 #include "TorMapWidgetPopupMenu.h"
19 #include "Vidalia.h"
20 
21 #include <MarbleModel.h>
22 #include <HttpDownloadManager.h>
23 
24 #include <QStringList>
25 
26 using namespace Marble;
27 
28 /** QPens to use for drawing different map elements */
29 #define CIRCUIT_NORMAL_PEN QPen(Qt::blue, 2.0)
30 #define CIRCUIT_SELECTED_PEN QPen(Qt::green, 3.0)
31 
32 
33 /** Default constructor */
35  : MarbleWidget(parent)
36 {
37  setMapThemeId("earth/srtm/srtm.dgml");
38  setShowScaleBar(false);
39  setShowCrosshairs(false);
40  setAnimationsEnabled(true);
41  setCursor(Qt::OpenHandCursor);
42 
43  model()->downloadManager()->setDownloadEnabled(false);
44 
46  TorMapWidgetPopupMenu *popupMenu = new TorMapWidgetPopupMenu(this);
47 
48  connect(handler, SIGNAL(featureClicked(QPoint,Qt::MouseButton)),
49  popupMenu, SLOT(featureClicked(QPoint,Qt::MouseButton)));
50  connect(popupMenu, SIGNAL(displayRouterInfo(QString)),
51  this, SIGNAL(displayRouterInfo(QString)));
52 
53  /* We can't call setInputHandler() until MarbleWidget has called its
54  * internal _q_initGui() method, which doesn't happen until a
55  * QTimer::singleShot(0, this, SLOT(_q_initGui())) timer set in its
56  * constructor times out. So force that event to process now. */
57  vApp->processEvents(QEventLoop::ExcludeUserInputEvents
58  | QEventLoop::ExcludeSocketNotifiers);
59 
60  setInputHandler(handler);
61 }
62 
63 /** Destructor */
65 {
66  clear();
67 }
68 
69 /** Adds a router to the map. */
70 void
72 {
73  QString kml;
74  qreal lon = geoip.longitude();
75  qreal lat = geoip.latitude();
76  quint64 bw;
77 
78  bw = qMin(desc.averageBandwidth(), desc.burstBandwidth());
79  bw = qMin(bw, desc.observedBandwidth());
80 
81  kml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
82  "<kml xmlns=\"http://earth.google.com/kml/2.0\">"
83  "<Document>"
84  " <Style id=\"normalPlacemark\">"
85  " <IconStyle><Icon><href>:/images/icons/placemark-relay.png</href></Icon></IconStyle>"
86  " </Style>"
87  );
88 
89  kml.append("<Placemark>");
90  kml.append("<styleUrl>#normalPlacemark</styleUrl>");
91  kml.append(QString("<name>%1</name>").arg(desc.name()));
92  kml.append(QString("<description>%1</description>").arg(desc.id()));
93  kml.append(QString("<role>1</role>"));
94  kml.append(QString("<address>%1</address>").arg(geoip.toString()));
95  kml.append(QString("<CountryNameCode>%1</CountryNameCode>").arg(geoip.country()));
96  kml.append(QString("<pop>%1</pop>").arg(10 * bw));
97  kml.append(QString("<Point>"
98  " <coordinates>%1,%2</coordinates>"
99  "</Point>").arg(lon).arg(lat));
100  kml.append("</Placemark>");
101  kml.append("</Document></kml>");
102 
103  QString id = desc.id();
104  addPlacemarkData(kml, id);
105  _routers.insert(id, GeoDataCoordinates(lon, lat, 0.0,
106  GeoDataCoordinates::Degree));
107 }
108 
109 /** Adds a circuit to the map using the given ordered list of router IDs. */
110 void
111 TorMapWidget::addCircuit(const CircuitId &circid, const QStringList &path)
112 {
113  /* XXX: Is it better to do KML LineString-based circuit drawing here,
114  * instead of going with a QPainter-based approach? I gave it a brief
115  * try once but failed. It might be worth looking into harder if we
116  * want to make circuits selectable on the map too.
117  */
118 
119  /* It doesn't make sense to draw a path of length less than two */
120  if (path.size() < 2)
121  return;
122 
123  if (_circuits.contains(circid)) {
124  /* Extend an existing path */
125  CircuitGeoPath *geoPath = _circuits.value(circid);
126 
127  QString router = path.at(path.size()-1);
128  if (_routers.contains(router))
129  geoPath->first.append(_routers.value(router));
130  } else {
131  /* Construct a new path */
132  CircuitGeoPath *geoPath = new CircuitGeoPath();
133  geoPath->second = false; /* initially unselected */
134 
135  foreach (QString router, path) {
136  if (_routers.contains(router))
137  geoPath->first.append(_routers.value(router));
138  }
139  geoPath->first.setTessellationFlags(Tessellate | RespectLatitudeCircle);
140  _circuits.insert(circid, geoPath);
141  }
142 
143  repaint();
144 }
145 
146 /** Removes a circuit from the map. */
147 void
149 {
150  CircuitGeoPath *path = _circuits.take(circid);
151  if (path)
152  delete path;
153 
154  repaint();
155 }
156 
157 /** Selects and highlights the router on the map. */
158 void
159 TorMapWidget::selectRouter(const QString &id)
160 {
161 #if 0
162  if (_routers.contains(id)) {
163  QPair<QPointF, bool> *routerPair = _routers.value(id);
164  routerPair->second = true;
165  }
166  repaint();
167 #endif
168 }
169 
170 /** Selects and highlights the circuit with the id <b>circid</b>
171  * on the map. */
172 void
174 {
175  if (_circuits.contains(circid)) {
176  CircuitGeoPath *path = _circuits.value(circid);
177  path->second = true;
178  }
179 
180  repaint();
181 }
182 
183 /** Deselects any highlighted routers or circuits */
184 void
186 {
187 #if 0
188  /* Deselect all router points */
189  foreach (QString router, _routers.keys()) {
190  QPair<QPointF,bool> *routerPair = _routers.value(router);
191  routerPair->second = false;
192  }
193 #endif
194  /* Deselect all circuit paths */
195  foreach (CircuitGeoPath *path, _circuits.values()) {
196  path->second = false;
197  }
198 
199  repaint();
200 }
201 
202 /** Clears the list of routers and removes all the data on the map */
203 void
205 {
206  foreach (QString id, _routers.keys()) {
207  removePlacemarkKey(id);
208  }
209 
210  foreach (CircuitId circid, _circuits.keys()) {
211  CircuitGeoPath *path = _circuits.take(circid);
212  delete path;
213  }
214 
215  repaint();
216 }
217 
218 /** Zooms the map to fit entirely within the constraints of the current
219  * viewport size. */
220 void
222 {
223  int width = size().width();
224  int height = size().height();
225 
226  setRadius(qMin(width, height) / 2);
227 
228  /* XXX: Calling setRadius() seems to cause Marble to no longer draw the
229  * atmosphere. So, re-enable it. */
230  setShowAtmosphere(true);
231 }
232 
233 /** Zoom to the circuit on the map with the given <b>circid</b>. */
234 void
236 {
237 #if 0
238  if (_circuits.contains(circid)) {
239  QPair<QPainterPath*,bool> *pair = _circuits.value(circid);
240  QRectF rect = ((QPainterPath *)pair->first)->boundingRect();
241  if (!rect.isNull()) {
242  float zoomLevel = 1.0 - qMax(rect.height()/float(MAP_HEIGHT),
243  rect.width()/float(MAP_WIDTH));
244 
245  zoom(rect.center().toPoint(), zoomLevel+0.2);
246  }
247  }
248 #endif
249 }
250 
251 /** Zooms in on the router with the given <b>id</b>. */
252 void
253 TorMapWidget::zoomToRouter(const QString &id)
254 {
255  if (_routers.contains(id)) {
256  qreal lon, lat;
257  GeoDataCoordinates coords = _routers.value(id);
258  coords.geoCoordinates(lon, lat, GeoDataPoint::Degree);
259 
260  zoomView(maximumZoom());
261  centerOn(lon, lat, true);
262  }
263 }
264 
265 /** Paints the current circuits and streams on the image. */
266 void
267 TorMapWidget::customPaint(GeoPainter *painter)
268 {
269  bool selected = false;
270 
271  painter->autoMapQuality();
272  painter->setPen(CIRCUIT_NORMAL_PEN);
273 
274  foreach (CircuitGeoPath *path, _circuits.values()) {
275  if (! path->second && selected) {
276  painter->setPen(CIRCUIT_NORMAL_PEN);
277  selected = false;
278  } else if (path->second && ! selected) {
279  painter->setPen(CIRCUIT_SELECTED_PEN);
280  selected = true;
281  }
282  painter->drawPolyline(path->first);
283  }
284 }
285