Vidalia  0.3.1
RouterListWidget.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 RouterListWidget.cpp
13 ** \brief Displays a list of Tor servers and their status
14 */
15 
16 #include "RouterListWidget.h"
17 #include "RouterListItem.h"
18 #include "Vidalia.h"
19 
20 #include <QHeaderView>
21 #include <QClipboard>
22 
23 #define IMG_ZOOM ":/images/22x22/page-zoom.png"
24 #define IMG_COPY ":/images/22x22/edit-copy.png"
25 
26 
28  : QTreeWidget(parent)
29 {
30  /* Create and initialize columns */
31  setHeaderLabels(QStringList() << QString("")
32  << QString("")
33  << tr("Relay"));
34 
35  /* Sort by descending server bandwidth */
36  sortItems(StatusColumn, Qt::DescendingOrder);
37 
38  /* Find out when the selected item has changed. */
39  connect(this, SIGNAL(itemSelectionChanged()),
40  this, SLOT(onSelectionChanged()));
41 }
42 
43 /** Called when the user changes the UI translation. */
44 void
46 {
47  setHeaderLabels(QStringList() << QString("")
48  << QString("")
49  << tr("Relay"));
50 }
51 
52 /** Called when the user requests a context menu for a router in the list. A
53  * context menu will be displayed providing a list of actions, including
54  * zooming in on the server. */
55 void
56 RouterListWidget::contextMenuEvent(QContextMenuEvent *event)
57 {
58  QAction *action;
59  QMenu *menu, *copyMenu;
60  QList<QTreeWidgetItem *> selected;
61 
62  selected = selectedItems();
63  if (! selected.size())
64  return;
65 
66  menu = new QMenu();
67  copyMenu = menu->addMenu(QIcon(IMG_COPY), tr("Copy"));
68  action = copyMenu->addAction(tr("Nickname"));
69  connect(action, SIGNAL(triggered()), this, SLOT(copySelectedNicknames()));
70 
71  action = copyMenu->addAction(tr("Fingerprint"));
72  connect(action, SIGNAL(triggered()), this, SLOT(copySelectedFingerprints()));
73 
74  action = menu->addAction(QIcon(IMG_ZOOM), tr("Zoom to Relay"));
75  if (selected.size() > 1)
76  action->setEnabled(false);
77  else
78  connect(action, SIGNAL(triggered()), this, SLOT(zoomToSelectedRelay()));
79 
80  menu->exec(event->globalPos());
81  delete menu;
82 }
83 
84 /** Copies the nicknames for all currently selected relays to the clipboard.
85  * Nicknames are formatted as a comma-delimited list, suitable for doing
86  * dumb things with your torrc. */
87 void
89 {
90  QString text;
91 
92  foreach (QTreeWidgetItem *item, selectedItems()) {
93  RouterListItem *relay = dynamic_cast<RouterListItem *>(item);
94  if (relay)
95  text.append(relay->name() + ",");
96  }
97  if (text.length()) {
98  text.remove(text.length()-1, 1);
99  vApp->clipboard()->setText(text);
100  }
101 }
102 
103 /** Copies the fingerprints for all currently selected relays to the
104  * clipboard. Fingerprints are formatted as a comma-delimited list, suitable
105  * for doing dumb things with your torrc. */
106 void
108 {
109  QString text;
110 
111  foreach (QTreeWidgetItem *item, selectedItems()) {
112  RouterListItem *relay = dynamic_cast<RouterListItem *>(item);
113  if (relay)
114  text.append("$" + relay->id() + ",");
115  }
116  if (text.length()) {
117  text.remove(text.length()-1, 1);
118  vApp->clipboard()->setText(text);
119  }
120 }
121 
122 /** Emits a zoomToRouter() signal containing the fingerprint of the
123  * currently selected relay. */
124 void
126 {
127  QList<QTreeWidgetItem *> selected = selectedItems();
128  if (selected.size() != 1)
129  return;
130 
131  RouterListItem *relay = dynamic_cast<RouterListItem *>(selected[0]);
132  if (relay)
133  emit zoomToRouter(relay->id());
134 }
135 
136 /** Deselects all currently selected routers. */
137 void
139 {
140  QList<QTreeWidgetItem *> selected = selectedItems();
141  foreach (QTreeWidgetItem *item, selected) {
142  setItemSelected(item, false);
143  }
144 }
145 
146 /** Clear the list of router items. */
147 void
149 {
150  _idmap.clear();
151  QTreeWidget::clear();
152  setStatusTip(tr("%1 relays online").arg(0));
153 }
154 
155 /** Called when the search of a router is triggered by the signal
156  * textChanged from the search field. */
157 void
158 RouterListWidget::onRouterSearch(const QString routerNickname)
159 {
160  if (!currentIndex().data().toString().toLower().startsWith(routerNickname.toLower()))
161  {
162  searchNextRouter(routerNickname);
163  } else {
164  /* If item at currentIndex() isn't visible, make it visible. */
165  scrollToItem(itemFromIndex(currentIndex()));
166  }
167 }
168 
169 /** Selects the following router whose name starts by routerNickname. */
170 void
171 RouterListWidget::searchNextRouter(const QString routerNickname)
172 {
173  /* currentIndex().row() = -1 if no item is selected. */
174  int startIndex = currentIndex().row() + 1;
175  /* Search for a router whose name start with routerNickname. Case-insensitive search. */
176  QModelIndexList qmIndList = model()->match(model()->index(startIndex, NameColumn),
177  Qt::DisplayRole,
178  QVariant(routerNickname),
179  1,
180  (Qt::MatchStartsWith | Qt::MatchWrap));
181  if (qmIndList.count() > 0) {
182  setCurrentIndex(qmIndList.at(0));
183  /* If item at currentIndex() was already selected but not visible,
184  * make it visible. */
185  scrollToItem(itemFromIndex(currentIndex()));
186  }
187 }
188 
189 /** Called when the user selects a router from the list. This will search the
190  * list for a router whose names starts with the key pressed. */
191 void
193 {
194  int index;
195 
196  QString key = event->text();
197  if (!key.isEmpty() && key.at(0).isLetterOrNumber()) {
198  /* A text key was pressed, so search for routers that begin with that key. */
199  QList<QTreeWidgetItem *> list = findItems(QString("^[%1%2].*$")
200  .arg(key.toUpper())
201  .arg(key.toLower()),
202  Qt::MatchRegExp|Qt::MatchWrap,
203  NameColumn);
204  if (list.size() > 0) {
205  QList<QTreeWidgetItem *> s = selectedItems();
206 
207  /* A match was found, so deselect any previously selected routers,
208  * select the new match, and make sure it's visible. If there was
209  * already a router selected that started with the search key, go to the
210  * next match in the list. */
211  deselectAll();
212  index = (!s.size() ? 0 : (list.indexOf(s.at(0)) + 1) % list.size());
213 
214  /* Select the item and scroll to it */
215  setItemSelected(list.at(index), true);
216  scrollToItem(list.at(index));
217  }
218  event->accept();
219  } else {
220  /* It was something we don't understand, so hand it to the parent class */
221  QTreeWidget::keyPressEvent(event);
222  }
223 }
224 
225 /** Finds the list item whose key ID matches <b>id</b>. Returns 0 if not
226  * found. */
229 {
230  if (_idmap.contains(id)) {
231  return _idmap.value(id);
232  }
233  return 0;
234 }
235 
236 /** Adds a router descriptor to the list. */
239 {
240  QString id = rd.id();
241  if (id.isEmpty())
242  return 0;
243 
244  RouterListItem *item = findRouterById(id);
245  if (item) {
246  item->update(rd);
247  } else {
248  item = new RouterListItem(this, rd);
249  addTopLevelItem(item);
250  _idmap.insert(id, item);
251  }
252 
253  /* Set our status tip to the number of servers in the list */
254  setStatusTip(tr("%1 relays online").arg(topLevelItemCount()));
255 
256  return item;
257 }
258 
259 /** Called when the selected items have changed. This emits the
260  * routerSelected() signal with the descriptor for the selected router.
261  */
262 void
264 {
265  QList<RouterDescriptor> descriptors;
266 
267  foreach (QTreeWidgetItem *item, selectedItems()) {
268  RouterListItem *relay = dynamic_cast<RouterListItem *>(item);
269  if (relay)
270  descriptors << relay->descriptor();
271  }
272  if (descriptors.count() > 0)
273  emit routerSelected(descriptors);
274 }
275 
RouterListItem * addRouter(const RouterDescriptor &rd)
void keyPressEvent(QKeyEvent *event)
stop errmsg QVariant
void routerSelected(QList< RouterDescriptor > rd)
void searchNextRouter(const QString routerNickname)
#define IMG_ZOOM
void update(const RouterDescriptor &rd)
QHash< QString, RouterListItem * > _idmap
QString id() const
QString name() const
stop errmsg connect(const QHostAddress &address, quint16 port)
#define IMG_COPY
virtual void contextMenuEvent(QContextMenuEvent *event)
RouterListItem * findRouterById(QString id)
void zoomToRouter(QString id)
RouterDescriptor descriptor() const
RouterListWidget(QWidget *parent=0)
#define vApp
Definition: Vidalia.h:37
QString id() const
void onRouterSearch(const QString routerNickname)