summaryrefslogtreecommitdiff
path: root/ui/qt/rtp_stream_dialog.cpp
diff options
context:
space:
mode:
authorGerald Combs <gerald@wireshark.org>2015-01-14 17:25:56 -0800
committerGerald Combs <gerald@wireshark.org>2015-01-30 06:48:32 +0000
commit2bf7878e8a7455fe656bb07e9a7d42e6ac4d87fd (patch)
tree3a0c99831311c43017d1d9b3336856e4a956c353 /ui/qt/rtp_stream_dialog.cpp
parent6824cee6c4b5f7c00b9dc4e9013aaa936b18b739 (diff)
downloadwireshark-2bf7878e8a7455fe656bb07e9a7d42e6ac4d87fd.tar.gz
Qt: Add the RTP Streams dialog.
Add keyboard shortcuts. Note that not all of the buttons made it from GTK+. Add a "Go to setup frame" option. Move rtp_streams.c from ui/gtk to ui. Add a help URL for RTP analysis (which needs to be split into streams + analysis). Fix RTP stream packet marking. Change-Id: Ifb8192ff701a933422509233d76461a46e459f4f Reviewed-on: https://code.wireshark.org/review/6852 Petri-Dish: Gerald Combs <gerald@wireshark.org> Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org> Reviewed-by: Gerald Combs <gerald@wireshark.org>
Diffstat (limited to 'ui/qt/rtp_stream_dialog.cpp')
-rw-r--r--ui/qt/rtp_stream_dialog.cpp636
1 files changed, 636 insertions, 0 deletions
diff --git a/ui/qt/rtp_stream_dialog.cpp b/ui/qt/rtp_stream_dialog.cpp
new file mode 100644
index 0000000000..7a502d5585
--- /dev/null
+++ b/ui/qt/rtp_stream_dialog.cpp
@@ -0,0 +1,636 @@
+/* rtp_stream_dialog.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "rtp_stream_dialog.h"
+#include "ui_rtp_stream_dialog.h"
+
+#include "file.h"
+
+#include "epan/addr_resolv.h"
+#include <epan/rtp_pt.h>
+
+#include "ui/utf8_entities.h"
+
+#include "qt_ui_utils.h"
+#include "wireshark_application.h"
+
+#include <QAction>
+#include <QClipboard>
+#include <QFileDialog>
+#include <QKeyEvent>
+#include <QPushButton>
+#include <QTextStream>
+#include <QTreeWidgetItem>
+#include <QTreeWidgetItemIterator>
+
+#include "tango_colors.h"
+
+/*
+ * @file RTP stream dialog
+ *
+ * Displays a list of RTP streams with the following information:
+ * - UDP 4-tuple
+ * - SSRC
+ * - Payload type
+ * - Stats: Packets, lost, max delta, max jitter, mean jitter
+ * - Problems
+ *
+ * Finds reverse streams
+ * "Save As" rtpdump
+ * Mark packets
+ * Go to the setup frame
+ * Prepare filter
+ * Copy As CSV and YAML
+ * Analyze
+ */
+
+// To do:
+// - Add more statistics to the hint text (e.g. lost packets).
+// - Add more statistics to the main list (e.g. stream duration)
+
+const int src_addr_col_ = 0;
+const int src_port_col_ = 1;
+const int dst_addr_col_ = 2;
+const int dst_port_col_ = 3;
+const int ssrc_col_ = 4;
+const int payload_col_ = 5;
+const int packets_col_ = 6;
+const int lost_col_ = 7;
+const int max_delta_col_ = 8;
+const int max_jitter_col_ = 9;
+const int mean_jitter_col_ = 10;
+const int status_col_ = 11;
+
+Q_DECLARE_METATYPE(rtp_stream_info_t*)
+
+class RtpStreamTreeWidgetItem : public QTreeWidgetItem
+{
+public:
+ RtpStreamTreeWidgetItem(QTreeWidget *tree, rtp_stream_info_t *stream_info) : QTreeWidgetItem(tree) {
+ setData(0, Qt::UserRole, qVariantFromValue(stream_info));
+ drawData();
+ }
+
+ void drawData() {
+ rtp_stream_info_t *stream_info = data(0, Qt::UserRole).value<rtp_stream_info_t*>();
+ if (!stream_info) {
+ return;
+ }
+ setText(src_addr_col_, address_to_display_qstring(&stream_info->src_addr));
+ setText(src_port_col_, QString::number(stream_info->src_port));
+ setText(dst_addr_col_, address_to_display_qstring(&stream_info->dest_addr));
+ setText(dst_port_col_, QString::number(stream_info->dest_port));
+ setText(ssrc_col_, QString("0x%1").arg(stream_info->ssrc, 0, 16));
+
+ if (stream_info->payload_type_name != NULL) {
+ setText(payload_col_, stream_info->payload_type_name);
+ } else {
+ setText(payload_col_, val_to_str_ext(stream_info->payload_type,
+ &rtp_payload_type_short_vals_ext,
+ "Unknown (%u)"));
+ }
+
+ setText(packets_col_, QString::number(stream_info->packet_count));
+
+ guint32 expected;
+ double pct_loss;
+ expected = (stream_info->rtp_stats.stop_seq_nr + stream_info->rtp_stats.cycles*65536)
+ - stream_info->rtp_stats.start_seq_nr + 1;
+ lost_ = expected - stream_info->rtp_stats.total_nr;
+ if (expected) {
+ pct_loss = (double)(lost_*100.0)/(double)expected;
+ } else {
+ pct_loss = 0;
+ }
+
+ setText(lost_col_, QObject::tr("%1 (%L2%)").arg(lost_).arg(QString::number(pct_loss, 'f', 1)));
+ setText(max_delta_col_, QString::number(stream_info->rtp_stats.max_delta, 'f', 3)); // This is RTP. Do we need nanoseconds?
+ setText(max_jitter_col_, QString::number(stream_info->rtp_stats.max_jitter, 'f', 3));
+ setText(mean_jitter_col_, QString::number(stream_info->rtp_stats.mean_jitter, 'f', 3));
+
+ if (stream_info->problem) {
+ setText(status_col_, UTF8_BULLET);
+ setTextAlignment(status_col_, Qt::AlignCenter);
+ for (int i = 0; i < columnCount(); i++) {
+ setBackgroundColor(i, ws_css_warn_background);
+ setTextColor(i, ws_css_warn_text);
+ }
+ }
+ }
+ // Return a QString, int, double, or invalid QVariant representing the raw column data.
+ QVariant colData(int col) const {
+ rtp_stream_info_t *stream_info = data(0, Qt::UserRole).value<rtp_stream_info_t*>();
+ if (!stream_info) {
+ return QVariant();
+ }
+
+ switch(col) {
+ case src_addr_col_:
+ case dst_addr_col_:
+ case payload_col_: // XXX Return numeric value?
+ return text(col);
+ case src_port_col_:
+ return stream_info->src_port;
+ case dst_port_col_:
+ return stream_info->dest_port;
+ case ssrc_col_:
+ return stream_info->ssrc;
+ case packets_col_:
+ return stream_info->packet_count;
+ case lost_col_:
+ return lost_;
+ case max_delta_col_:
+ return stream_info->rtp_stats.max_delta;
+ case max_jitter_col_:
+ return stream_info->rtp_stats.max_jitter;
+ case mean_jitter_col_:
+ return stream_info->rtp_stats.mean_jitter;
+ case status_col_:
+ return stream_info->problem ? "Problem" : "";
+ default:
+ break;
+ }
+ return QVariant();
+ }
+
+ bool operator< (const QTreeWidgetItem &other) const
+ {
+ rtp_stream_info_t *this_stream_info = data(0, Qt::UserRole).value<rtp_stream_info_t*>();
+ rtp_stream_info_t *other_stream_info = other.data(0, Qt::UserRole).value<rtp_stream_info_t*>();
+ if (!this_stream_info || !other_stream_info) {
+ return false;
+ }
+ const RtpStreamTreeWidgetItem &other_rstwi = dynamic_cast<const RtpStreamTreeWidgetItem&>(other);
+
+ switch (treeWidget()->sortColumn()) {
+ case src_addr_col_:
+ return cmp_address(&(this_stream_info->src_addr), &(other_stream_info->src_addr)) < 0;
+ case src_port_col_:
+ return this_stream_info->src_port < other_stream_info->src_port;
+ case dst_addr_col_:
+ return cmp_address(&(this_stream_info->dest_addr), &(other_stream_info->dest_addr)) < 0;
+ case dst_port_col_:
+ return this_stream_info->dest_port < other_stream_info->dest_port;
+ case ssrc_col_:
+ return this_stream_info->ssrc < other_stream_info->ssrc;
+ case payload_col_:
+ return this_stream_info->payload_type < other_stream_info->payload_type; // XXX Compare payload_type_name instead?
+ case packets_col_:
+ return this_stream_info->packet_count < other_stream_info->packet_count;
+ case lost_col_:
+ return lost_ < other_rstwi.lost_;
+ case max_delta_col_:
+ return this_stream_info->rtp_stats.max_delta < other_stream_info->rtp_stats.max_delta;
+ case max_jitter_col_:
+ return this_stream_info->rtp_stats.max_jitter < other_stream_info->rtp_stats.max_jitter;
+ case mean_jitter_col_:
+ return this_stream_info->rtp_stats.mean_jitter < other_stream_info->rtp_stats.mean_jitter;
+ default:
+ break;
+ }
+
+ // Fall back to string comparison
+ return QTreeWidgetItem::operator <(other);
+ }
+
+private:
+ guint32 lost_;
+};
+
+RtpStreamDialog::RtpStreamDialog(QWidget &parent, CaptureFile &cf) :
+ WiresharkDialog(parent, cf),
+ ui(new Ui::RtpStreamDialog),
+ need_redraw_(false)
+{
+ ui->setupUi(this);
+ setWindowSubtitle(tr("RTP Streams"));
+ ui->streamTreeWidget->installEventFilter(this);
+
+ // XXX Use recent settings instead
+ resize(parent.width() * 4 / 5, parent.height() * 2 / 3);
+
+ ctx_menu_.addAction(ui->actionSelectNone);
+ ctx_menu_.addAction(ui->actionFindReverse);
+ ctx_menu_.addAction(ui->actionGoToSetup);
+ ctx_menu_.addAction(ui->actionMarkPackets);
+ ctx_menu_.addAction(ui->actionPrepareFilter);
+ ctx_menu_.addAction(ui->actionExportAsRtpDump);
+ ctx_menu_.addAction(ui->actionCopyAsCsv);
+ ctx_menu_.addAction(ui->actionCopyAsYaml);
+ ctx_menu_.addAction(ui->actionAnalyze);
+ ui->streamTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(ui->streamTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
+ SLOT(showStreamMenu(QPoint)));
+
+ // Some GTK+ buttons have been left out intentionally in order to
+ // reduce clutter. Do you have a strong and informed opinion about
+ // this? Perhaps you should volunteer to maintain this code!
+ find_reverse_button_ = ui->buttonBox->addButton(ui->actionFindReverse->text(), QDialogButtonBox::ApplyRole);
+ find_reverse_button_->setToolTip(ui->actionFindReverse->toolTip());
+ prepare_button_ = ui->buttonBox->addButton(ui->actionPrepareFilter->text(), QDialogButtonBox::ApplyRole);
+ prepare_button_->setToolTip(ui->actionPrepareFilter->toolTip());
+ export_button_ = ui->buttonBox->addButton(tr("Export..."), QDialogButtonBox::ApplyRole);
+ export_button_->setToolTip(ui->actionExportAsRtpDump->toolTip());
+ copy_button_ = ui->buttonBox->addButton(tr("Copy"), QDialogButtonBox::ApplyRole);
+ analyze_button_ = ui->buttonBox->addButton(ui->actionAnalyze->text(), QDialogButtonBox::ApplyRole);
+ analyze_button_->setToolTip(ui->actionAnalyze->toolTip());
+
+ QMenu *copy_menu = new QMenu();
+ QAction *ca;
+ ca = copy_menu->addAction(tr("as CSV"));
+ ca->setToolTip(ui->actionCopyAsCsv->toolTip());
+ connect(ca, SIGNAL(triggered()), this, SLOT(on_actionCopyAsCsv_triggered()));
+ ca = copy_menu->addAction(tr("as YAML"));
+ ca->setToolTip(ui->actionCopyAsYaml->toolTip());
+ connect(ca, SIGNAL(triggered()), this, SLOT(on_actionCopyAsYaml_triggered()));
+ copy_button_->setMenu(copy_menu);
+
+ /* Register the tap listener */
+ memset(&tapinfo_, 0, sizeof(rtpstream_tapinfo_t));
+ tapinfo_.tap_draw = tapDraw;
+ tapinfo_.tap_mark_packet = tapMarkPacket;
+ tapinfo_.tap_data = this;
+ tapinfo_.mode = TAP_ANALYSE;
+
+ register_tap_listener_rtp_stream(&tapinfo_);
+ /* Scan for RTP streams (redissect all packets) */
+ rtpstream_scan(&tapinfo_, cf.capFile());
+
+ updateWidgets();
+}
+
+RtpStreamDialog::~RtpStreamDialog()
+{
+ delete ui;
+ remove_tap_listener_rtp_stream(&tapinfo_);
+}
+
+bool RtpStreamDialog::eventFilter(QObject *obj, QEvent *event)
+{
+ Q_UNUSED(obj)
+ if (ui->streamTreeWidget->hasFocus() && event->type() == QEvent::KeyPress) {
+ QKeyEvent &keyEvent = static_cast<QKeyEvent&>(*event);
+ switch(keyEvent.key()) {
+ case Qt::Key_G:
+ on_actionGoToSetup_triggered();
+ return true;
+ case Qt::Key_M:
+ on_actionMarkPackets_triggered();
+ return true;
+ case Qt::Key_P:
+ on_actionPrepareFilter_triggered();
+ return true;
+ case Qt::Key_R:
+ on_actionFindReverse_triggered();
+ return true;
+ case Qt::Key_A:
+ // XXX "Shift+Ctrl+A" is a fairly standard shortcut for "select none".
+ // However, the main window uses this for displaying the profile dialog.
+// if (keyEvent.modifiers() == (Qt::ControlModifier | Qt::ShiftModifier))
+// on_actionSelectNone_triggered();
+// return true;
+ break;
+ default:
+ break;
+ }
+ }
+ return false;
+}
+
+void RtpStreamDialog::tapDraw(void *tapinfo_ptr)
+{
+ rtpstream_tapinfo_t *tapinfo = (rtpstream_tapinfo_t *) tapinfo_ptr;
+
+ RtpStreamDialog *rtp_stream_dialog = static_cast<RtpStreamDialog *>(tapinfo->tap_data);
+ if (rtp_stream_dialog) {
+ rtp_stream_dialog->updateStreams();
+ }
+}
+
+void RtpStreamDialog::tapMarkPacket(rtpstream_tapinfo_t *tapinfo, frame_data *fd)
+{
+ if (!tapinfo) return;
+
+ RtpStreamDialog *rtp_stream_dialog = static_cast<RtpStreamDialog *>(tapinfo->tap_data);
+ if (rtp_stream_dialog) {
+ rtp_stream_dialog->need_redraw_ = true;
+ cf_mark_frame(rtp_stream_dialog->cap_file_.capFile(), fd);
+ rtp_stream_dialog->need_redraw_ = true;
+ }
+}
+
+void RtpStreamDialog::updateStreams()
+{
+ GList *cur_stream = g_list_nth(tapinfo_.strinfo_list, ui->streamTreeWidget->topLevelItemCount());
+
+ // Add any missing items
+ while (cur_stream && cur_stream->data) {
+ rtp_stream_info_t *stream_info = (rtp_stream_info_t*) cur_stream->data;
+ new RtpStreamTreeWidgetItem(ui->streamTreeWidget, stream_info);
+ cur_stream = g_list_next(cur_stream);
+ }
+
+ // Recalculate values
+ QTreeWidgetItemIterator iter(ui->streamTreeWidget);
+ while (*iter) {
+ RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(*iter);
+ rsti->drawData();
+ ++iter;
+ }
+
+ // Resize columns
+ for (int i = 0; i < ui->streamTreeWidget->columnCount(); i++) {
+ ui->streamTreeWidget->resizeColumnToContents(i);
+ }
+
+ ui->streamTreeWidget->setSortingEnabled(true);
+
+ updateWidgets();
+
+ if (need_redraw_) {
+ emit packetsMarked();
+ need_redraw_ = false;
+ }
+}
+
+void RtpStreamDialog::updateWidgets()
+{
+ bool selected = ui->streamTreeWidget->selectedItems().count() > 0;
+
+ QString hint = "<small><i>";
+ hint += tr("%1 streams").arg(ui->streamTreeWidget->topLevelItemCount());
+
+ if (selected) {
+ int tot_packets = 0;
+ foreach(QTreeWidgetItem *ti, ui->streamTreeWidget->selectedItems()) {
+ rtp_stream_info_t *stream_info = ti->data(0, Qt::UserRole).value<rtp_stream_info_t *>();
+ if (stream_info) {
+ tot_packets += stream_info->packet_count;
+ }
+ }
+ hint += tr(", %1 selected, %2 total packets")
+ .arg(ui->streamTreeWidget->selectedItems().count())
+ .arg(tot_packets);
+ }
+
+ hint += ". Right-click for more options.";
+ hint += "</i></small>";
+ ui->hintLabel->setText(hint);
+
+ bool enable = selected && !file_closed_;
+ bool has_data = ui->streamTreeWidget->topLevelItemCount() > 0;
+
+ find_reverse_button_->setEnabled(enable);
+ prepare_button_->setEnabled(enable);
+ export_button_->setEnabled(enable);
+ copy_button_->setEnabled(has_data);
+ analyze_button_->setEnabled(false); // XXX No dialog
+
+ ui->actionFindReverse->setEnabled(enable);
+ ui->actionGoToSetup->setEnabled(enable);
+ ui->actionMarkPackets->setEnabled(enable);
+ ui->actionPrepareFilter->setEnabled(enable);
+ ui->actionExportAsRtpDump->setEnabled(enable);
+ ui->actionCopyAsCsv->setEnabled(has_data);
+ ui->actionCopyAsYaml->setEnabled(has_data);
+ ui->actionAnalyze->setEnabled(false); // XXX No dialog
+}
+
+QList<QVariant> RtpStreamDialog::streamRowData(int row) const
+{
+ QList<QVariant> row_data;
+
+ if (row >= ui->streamTreeWidget->topLevelItemCount()) {
+ return row_data;
+ }
+
+ for (int col = 0; col < ui->streamTreeWidget->columnCount(); col++) {
+ if (row < 0) {
+ row_data << ui->streamTreeWidget->headerItem()->text(col);
+ } else {
+ RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(row));
+ if (rsti) {
+ row_data << rsti->colData(col);
+ }
+ }
+ }
+ return row_data;
+}
+
+void RtpStreamDialog::captureFileClosing()
+{
+ remove_tap_listener_rtp_stream(&tapinfo_);
+ WiresharkDialog::captureFileClosing();
+}
+
+void RtpStreamDialog::showStreamMenu(QPoint pos)
+{
+ ctx_menu_.popup(ui->streamTreeWidget->viewport()->mapToGlobal(pos));
+}
+
+void RtpStreamDialog::on_actionAnalyze_triggered()
+{
+
+}
+
+void RtpStreamDialog::on_actionCopyAsCsv_triggered()
+{
+ QString csv;
+ QTextStream stream(&csv, QIODevice::Text);
+ for (int row = -1; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
+ QStringList rdsl;
+ foreach (QVariant v, streamRowData(row)) {
+ if (!v.isValid()) {
+ rdsl << "\"\"";
+ } else if ((int) v.type() == (int) QMetaType::QString) {
+ rdsl << QString("\"%1\"").arg(v.toString());
+ } else {
+ rdsl << v.toString();
+ }
+ }
+ stream << rdsl.join(",") << endl;
+ }
+ wsApp->clipboard()->setText(stream.readAll());
+}
+
+void RtpStreamDialog::on_actionCopyAsYaml_triggered()
+{
+ QString yaml;
+ QTextStream stream(&yaml, QIODevice::Text);
+ stream << "---" << endl;
+ for (int row = -1; row < ui->streamTreeWidget->topLevelItemCount(); row ++) {
+ stream << "-" << endl;
+ foreach (QVariant v, streamRowData(row)) {
+ stream << " - " << v.toString() << endl;
+ }
+ }
+ wsApp->clipboard()->setText(stream.readAll());
+}
+
+void RtpStreamDialog::on_actionExportAsRtpDump_triggered()
+{
+ if (file_closed_ || ui->streamTreeWidget->selectedItems().count() < 1) return;
+
+ // XXX If the user selected multiple frames is this the one we actually want?
+ QTreeWidgetItem *ti = ui->streamTreeWidget->selectedItems()[0];
+ rtp_stream_info_t *stream_info = ti->data(0, Qt::UserRole).value<rtp_stream_info_t *>();
+ if (stream_info) {
+ QString file_name;
+ QDir path(wsApp->lastOpenDir());
+ QString save_file = path.canonicalPath() + "/" + cap_file_.fileTitle();
+ QString extension;
+ file_name = QFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save RTPDump As" UTF8_HORIZONTAL_ELLIPSIS)),
+ save_file, "RTPDump Format (*.rtpdump)", &extension);
+
+ if (file_name.length() > 0) {
+ gchar *dest_file = qstring_strdup(file_name);
+ gboolean save_ok = rtpstream_save(&tapinfo_, cap_file_.capFile(), stream_info, dest_file);
+ g_free(dest_file);
+ // else error dialog?
+ if (save_ok) {
+ path = QDir(file_name);
+ wsApp->setLastOpenDir(path.canonicalPath().toUtf8().constData());
+ }
+ }
+
+ }
+}
+
+void RtpStreamDialog::on_actionFindReverse_triggered()
+{
+ if (ui->streamTreeWidget->selectedItems().count() < 1) return;
+
+ // Gather up our selected streams...
+ QList<rtp_stream_info_t *> selected_streams;
+ foreach(QTreeWidgetItem *ti, ui->streamTreeWidget->selectedItems()) {
+ rtp_stream_info_t *stream_info = ti->data(0, Qt::UserRole).value<rtp_stream_info_t *>();
+ if (stream_info) {
+ selected_streams << stream_info;
+ }
+ }
+
+ // ...and compare them to our unselected streams.
+ QTreeWidgetItemIterator iter(ui->streamTreeWidget, QTreeWidgetItemIterator::Unselected);
+ while (*iter) {
+ rtp_stream_info_t *stream = (*iter)->data(0, Qt::UserRole).value<rtp_stream_info_t*>();
+ if (stream) {
+ foreach (rtp_stream_info_t *fwd_stream, selected_streams) {
+ if (rtp_stream_info_is_reverse(fwd_stream, stream)) {
+ (*iter)->setSelected(true);
+ }
+ }
+ }
+ ++iter;
+ }
+}
+
+void RtpStreamDialog::on_actionGoToSetup_triggered()
+{
+ if (ui->streamTreeWidget->selectedItems().count() < 1) return;
+ // XXX If the user selected multiple frames is this the one we actually want?
+ QTreeWidgetItem *ti = ui->streamTreeWidget->selectedItems()[0];
+ rtp_stream_info_t *stream_info = ti->data(0, Qt::UserRole).value<rtp_stream_info_t *>();
+ if (stream_info) {
+ emit goToPacket(stream_info->setup_frame_number);
+ }
+}
+
+void RtpStreamDialog::on_actionMarkPackets_triggered()
+{
+ if (ui->streamTreeWidget->selectedItems().count() < 1) return;
+ rtp_stream_info_t *stream_a, *stream_b = NULL;
+
+ stream_a = ui->streamTreeWidget->selectedItems()[0]->data(0, Qt::UserRole).value<rtp_stream_info_t*>();
+ if (ui->streamTreeWidget->selectedItems().count() > 1)
+ stream_b = ui->streamTreeWidget->selectedItems()[1]->data(0, Qt::UserRole).value<rtp_stream_info_t*>();
+
+ if (stream_a == NULL && stream_b == NULL) return;
+
+ // XXX Mark the setup frame as well?
+ need_redraw_ = false;
+ rtpstream_mark(&tapinfo_, cap_file_.capFile(), stream_a, stream_b);
+ updateWidgets();
+}
+
+void RtpStreamDialog::on_actionPrepareFilter_triggered()
+{
+ if (ui->streamTreeWidget->selectedItems().count() < 1) return;
+
+ // Gather up our selected streams...
+ QStringList stream_filters;
+ foreach(QTreeWidgetItem *ti, ui->streamTreeWidget->selectedItems()) {
+ rtp_stream_info_t *stream_info = ti->data(0, Qt::UserRole).value<rtp_stream_info_t *>();
+ if (stream_info) {
+ QString ip_proto = stream_info->src_addr.type == AT_IPv6 ? "ipv6" : "ip";
+ stream_filters << QString("(%1.src==%2 && udp.srcport==%3 && %1.dst==%4 && udp.dstport==%5 && rtp.ssrc==0x%6)")
+ .arg(ip_proto) // %1
+ .arg(address_to_qstring(&stream_info->src_addr)) // %2
+ .arg(stream_info->src_port) // %3
+ .arg(address_to_qstring(&stream_info->dest_addr)) // %4
+ .arg(stream_info->dest_port) // %5
+ .arg(stream_info->ssrc, 0, 16);
+ }
+ }
+ if (stream_filters.length() > 0) {
+ QString filter = stream_filters.join(" || ");
+ remove_tap_listener_rtp_stream(&tapinfo_);
+ emit updateFilter(filter);
+ }
+}
+
+void RtpStreamDialog::on_actionSelectNone_triggered()
+{
+ ui->streamTreeWidget->clearSelection();
+}
+
+void RtpStreamDialog::on_streamTreeWidget_itemSelectionChanged()
+{
+ updateWidgets();
+}
+
+void RtpStreamDialog::on_buttonBox_clicked(QAbstractButton *button)
+{
+ if (button == prepare_button_) {
+ on_actionPrepareFilter_triggered();
+ } else if (button == export_button_) {
+ on_actionExportAsRtpDump_triggered();
+ } else if (button == analyze_button_) {
+
+ }
+}
+
+void RtpStreamDialog::on_buttonBox_helpRequested()
+{
+ wsApp->helpTopicAction(HELP_RTP_ANALYSIS_DIALOG);
+}
+
+/*
+ * Editor modelines
+ *
+ * Local Variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * ex: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */