Vidalia
0.2.15
|
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 CircuitListWidget.cpp 00013 ** \brief Collection of Tor circuits as CircuitItems 00014 */ 00015 00016 #include "config.h" 00017 #include "CircuitListWidget.h" 00018 #include "Vidalia.h" 00019 00020 #include <QPoint> 00021 #include <QTimer> 00022 00023 #define IMG_CLOSE ":/images/22x22/edit-delete.png" 00024 #define IMG_ZOOM ":/images/22x22/page-zoom.png" 00025 00026 #define CLOSED_CIRCUIT_REMOVE_DELAY 3000 00027 #define FAILED_CIRCUIT_REMOVE_DELAY 5000 00028 #define CLOSED_STREAM_REMOVE_DELAY 3000 00029 #define FAILED_STREAM_REMOVE_DELAY 4000 00030 00031 00032 /** Default constructor. */ 00033 CircuitListWidget::CircuitListWidget(QWidget *parent) 00034 : QTreeWidget(parent) 00035 { 00036 /* Create and initialize columns */ 00037 setHeaderLabels(QStringList() << tr("Connection") << tr("Status")); 00038 00039 /* Find out when a circuit has been selected */ 00040 connect(this, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), 00041 this, SLOT(onSelectionChanged(QTreeWidgetItem*,QTreeWidgetItem*))); 00042 connect(this, SIGNAL(customContextMenuRequested(QPoint)), 00043 this, SLOT(customContextMenuRequested(QPoint))); 00044 00045 /* Respond to the Delete key by closing whatever circuits or streams are 00046 * selected. */ 00047 vApp->createShortcut(QKeySequence::Delete, this, this, 00048 SLOT(closeSelectedConnections())); 00049 } 00050 00051 /** Called when the user changes the UI translation. */ 00052 void 00053 CircuitListWidget::retranslateUi() 00054 { 00055 setHeaderLabels(QStringList() << tr("Connection") << tr("Status")); 00056 for (int i = 0; i < topLevelItemCount(); i++) { 00057 CircuitItem *circuitItem = dynamic_cast<CircuitItem *>(topLevelItem(i)); 00058 circuitItem->update(circuitItem->circuit()); 00059 00060 foreach (StreamItem *streamItem, circuitItem->streams()) { 00061 streamItem->update(streamItem->stream()); 00062 } 00063 } 00064 } 00065 00066 /** Called when the user requests a context menu on a circuit or stream in the 00067 * list and displays a context menu appropriate for whichever type of item is 00068 * currently selected. */ 00069 void 00070 CircuitListWidget::customContextMenuRequested(const QPoint &pos) 00071 { 00072 QMenu menu(this); 00073 00074 /* Find out which item was right-clicked */ 00075 QTreeWidgetItem *item = itemAt(pos); 00076 if (!item) 00077 return; 00078 00079 if (!item->parent()) { 00080 /* A circuit was selected */ 00081 CircuitItem *circuitItem = dynamic_cast<CircuitItem *>(item); 00082 if (!circuitItem) 00083 return; 00084 00085 /* Set up the circuit context menu */ 00086 QAction *zoomAct = new QAction(QIcon(IMG_ZOOM), 00087 tr("Zoom to Circuit"), this); 00088 QAction *closeAct = new QAction(QIcon(IMG_CLOSE), 00089 tr("Close Circuit (Del)"), this); 00090 #if defined(USE_MARBLE) 00091 zoomAct->setEnabled(circuitItem->circuit().status() == Circuit::Built); 00092 menu.addAction(zoomAct); 00093 menu.addSeparator(); 00094 #endif 00095 menu.addAction(closeAct); 00096 00097 /* Display the context menu and find out which (if any) action was 00098 * selected */ 00099 QAction* action = menu.exec(mapToGlobal(pos)); 00100 if (action == closeAct) 00101 emit closeCircuit(circuitItem->id()); 00102 else if (action == zoomAct) 00103 emit zoomToCircuit(circuitItem->id()); 00104 } else { 00105 /* A stream was selected */ 00106 StreamItem *streamItem = dynamic_cast<StreamItem *>(item); 00107 if (!streamItem) 00108 return; 00109 00110 /* Set up the stream context menu */ 00111 QAction *closeAct = new QAction(QIcon(IMG_CLOSE), 00112 tr("Close Stream (Del)"), this); 00113 menu.addAction(closeAct); 00114 00115 /* Display the context menu and find out which (if any) action was 00116 * selected */ 00117 QAction* action = menu.exec(mapToGlobal(pos)); 00118 if (action == closeAct) 00119 emit closeStream(streamItem->id()); 00120 } 00121 } 00122 00123 /** Closes all selected circuits or streams. */ 00124 void 00125 CircuitListWidget::closeSelectedConnections() 00126 { 00127 QList<QTreeWidgetItem *> items = selectedItems(); 00128 foreach (QTreeWidgetItem *item, items) { 00129 if (!item->parent()) { 00130 CircuitItem *circuitItem = dynamic_cast<CircuitItem *>(item); 00131 if (circuitItem) 00132 emit closeCircuit(circuitItem->id()); 00133 } else { 00134 StreamItem *streamItem = dynamic_cast<StreamItem *>(item); 00135 if (streamItem) 00136 emit closeStream(streamItem->id()); 00137 } 00138 } 00139 } 00140 00141 /** Adds a <b>circuit</b> to the list. If the circuit already exists in the 00142 * list, the status and path will be updated. */ 00143 void 00144 CircuitListWidget::addCircuit(const Circuit &circuit) 00145 { 00146 /* Check to see if the circuit already exists in the tree */ 00147 CircuitItem *item = findCircuitItem(circuit.id()); 00148 00149 if (!item) { 00150 /* Add the new circuit */ 00151 item = new CircuitItem(circuit); 00152 addTopLevelItem(item); 00153 } else { 00154 /* Circuit already exists, so update its status and path */ 00155 item->update(circuit); 00156 } 00157 00158 /* If the circuit is closed or dead, schedule it for removal */ 00159 Circuit::Status status = circuit.status(); 00160 if (status == Circuit::Closed) { 00161 scheduleCircuitRemoval(item, CLOSED_CIRCUIT_REMOVE_DELAY); 00162 } else if (status == Circuit::Failed) { 00163 scheduleCircuitRemoval(item, FAILED_CIRCUIT_REMOVE_DELAY); 00164 } 00165 } 00166 00167 /** Adds a stream to the list. If the stream already exists in the list, the 00168 * status and path will be updated. */ 00169 void 00170 CircuitListWidget::addStream(const Stream &stream) 00171 { 00172 /* Check to see if the stream already exists in the tree */ 00173 StreamItem *item = findStreamItem(stream.id()); 00174 00175 if (!item) { 00176 CircuitItem *circuit = findCircuitItem(stream.circuitId()); 00177 /* New stream, so try to find its circuit and add it */ 00178 if (circuit) { 00179 circuit->addStream(new StreamItem(stream)); 00180 expandItem(circuit); 00181 } 00182 } else { 00183 /* Stream already exists, so just update its status */ 00184 item->update(stream); 00185 00186 /* If the stream is closed or dead, schedule it for removal */ 00187 Stream::Status status = stream.status(); 00188 if (status == Stream::Closed) { 00189 scheduleStreamRemoval(item, CLOSED_STREAM_REMOVE_DELAY); 00190 } else if (status == Stream::Failed) { 00191 scheduleStreamRemoval(item, FAILED_STREAM_REMOVE_DELAY); 00192 } 00193 } 00194 } 00195 00196 /** Schedules the given circuit to be removed after the specified timeout. */ 00197 void 00198 CircuitListWidget::scheduleCircuitRemoval(CircuitItem *circuit, int delay) 00199 { 00200 if (!_circuitRemovalList.contains(circuit)) { 00201 _circuitRemovalList << circuit; 00202 QTimer::singleShot(delay, this, SLOT(removeCircuit())); 00203 } 00204 } 00205 00206 /** Schedules the given stream to be removed after the specified timeout. */ 00207 void 00208 CircuitListWidget::scheduleStreamRemoval(StreamItem *stream, int delay) 00209 { 00210 if (!_streamRemovalList.contains(stream)) { 00211 _streamRemovalList << stream; 00212 QTimer::singleShot(delay, this, SLOT(removeStream())); 00213 } 00214 } 00215 00216 /** Removes the first circuit scheduled to be removed. */ 00217 void 00218 CircuitListWidget::removeCircuit() 00219 { 00220 if (!_circuitRemovalList.isEmpty()) { 00221 CircuitItem *circuitItem = _circuitRemovalList.takeFirst(); 00222 Circuit circuit = circuitItem->circuit(); 00223 removeCircuit(circuitItem); 00224 emit circuitRemoved(circuit.id()); 00225 } 00226 } 00227 00228 /** Removes the given circuit item and all streams on that circuit. */ 00229 void 00230 CircuitListWidget::removeCircuit(CircuitItem *circuit) 00231 { 00232 if (circuit) { 00233 /* Remove all streams (if any) on this circuit. */ 00234 QList<StreamItem *> streams = circuit->streams(); 00235 foreach (StreamItem *stream, streams) { 00236 /* Check if this stream was scheduled for removal already */ 00237 if (_streamRemovalList.contains(stream)) { 00238 /* If this stream was already scheduled for removal, replace its pointer 00239 * with 0, so it doesn't get removed twice. */ 00240 int index = _streamRemovalList.indexOf(stream); 00241 _streamRemovalList.replace(index, (StreamItem *)0); 00242 } 00243 00244 /* Remove the stream item from the circuit */ 00245 circuit->removeStream(stream); 00246 } 00247 /* Remove the circuit item itself */ 00248 delete takeTopLevelItem(indexOfTopLevelItem(circuit)); 00249 } 00250 } 00251 00252 /** Removes the first stream scheduled to be removed. */ 00253 void 00254 CircuitListWidget::removeStream() 00255 { 00256 if (!_streamRemovalList.isEmpty()) { 00257 StreamItem *stream = _streamRemovalList.takeFirst(); 00258 removeStream(stream); 00259 } 00260 } 00261 00262 /** Removes the given stream item. */ 00263 void 00264 CircuitListWidget::removeStream(StreamItem *stream) 00265 { 00266 if (stream) { 00267 /* Try to get the stream's parent (a circuit item) */ 00268 CircuitItem *circuit = (CircuitItem *)stream->parent(); 00269 if (circuit) { 00270 /* Remove the stream from the circuit and delete the item */ 00271 circuit->removeStream(stream); 00272 } else { 00273 /* It isn't on a circuit, so just delete the stream */ 00274 delete stream; 00275 } 00276 } 00277 } 00278 00279 /** Clears all circuits and streams from the list. */ 00280 void 00281 CircuitListWidget::clearCircuits() 00282 { 00283 QTreeWidget::clear(); 00284 _circuitRemovalList.clear(); 00285 _streamRemovalList.clear(); 00286 } 00287 00288 /** Finds the circuit with the given ID and returns a pointer to that 00289 * circuit's item in the list. */ 00290 CircuitItem* 00291 CircuitListWidget::findCircuitItem(const CircuitId &circid) 00292 { 00293 int numCircs = topLevelItemCount(); 00294 for (int i = 0; i < numCircs; i++) { 00295 CircuitItem *circuit = (CircuitItem *)topLevelItem(i); 00296 if (circid == circuit->id()) { 00297 return circuit; 00298 } 00299 } 00300 return 0; 00301 } 00302 00303 /** Finds the stream with the given ID and returns a pointer to that stream's 00304 * item in the list. */ 00305 StreamItem* 00306 CircuitListWidget::findStreamItem(const StreamId &streamid) 00307 { 00308 int numCircs = topLevelItemCount(); 00309 int numStreams; 00310 00311 for (int i = 0; i < numCircs; i++) { 00312 CircuitItem *circuit = (CircuitItem *)topLevelItem(i); 00313 numStreams = circuit->childCount(); 00314 00315 for (int j = 0; j < numStreams; j++) { 00316 StreamItem *stream = (StreamItem *)circuit->child(j); 00317 if (streamid == stream->id()) { 00318 return stream; 00319 } 00320 } 00321 } 00322 return 0; 00323 } 00324 00325 /** Called when the current item selection has changed. */ 00326 void 00327 CircuitListWidget::onSelectionChanged(QTreeWidgetItem *cur, 00328 QTreeWidgetItem *prev) 00329 { 00330 Q_UNUSED(prev); 00331 00332 if (cur) { 00333 Circuit circuit; 00334 00335 if (!cur->parent()) { 00336 /* User selected a CircuitItem, so just grab the Circuit */ 00337 circuit = ((CircuitItem *)cur)->circuit(); 00338 } else { 00339 /* User selected a StreamItem, so get its parent and then the Circuit */ 00340 CircuitItem *circItem = (CircuitItem *)cur->parent(); 00341 circuit = circItem->circuit(); 00342 } 00343 00344 /* If this circuit has a path, then emit it so we can highlight it */ 00345 emit circuitSelected(circuit); 00346 } 00347 } 00348 00349 /** Returns a list of circuits currently in the widget. */ 00350 CircuitList 00351 CircuitListWidget::circuits() 00352 { 00353 int numCircs = topLevelItemCount(); 00354 CircuitList circs; 00355 00356 for (int i = 0; i < numCircs; i++) { 00357 CircuitItem *circ = (CircuitItem *)topLevelItem(i); 00358 circs << circ->circuit(); 00359 } 00360 return circs; 00361 } 00362