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