drsuapi_SupportedExtensionsExt wild guess to match 0x80a and DsGetNCChangesReq11...
[wireshark-sm.git] / ui / qt / rtp_analysis_dialog.cpp
blobb38d7de01d8d6e91aaf77eefb8f69d0a6a510f95
1 /* rtp_analysis_dialog.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
10 #include "rtp_analysis_dialog.h"
11 #include <ui_rtp_analysis_dialog.h>
13 #include "file.h"
14 #include "frame_tvbuff.h"
16 #include "epan/epan_dissect.h"
17 #include <epan/addr_resolv.h>
18 #include "epan/rtp_pt.h"
20 #include "epan/dfilter/dfilter.h"
22 #include "epan/dissectors/packet-rtp.h"
24 #include <ui/rtp_media.h>
26 #include "ui/help_url.h"
27 #include "ui/simple_dialog.h"
28 #include <wsutil/utf8_entities.h>
30 #include <wsutil/g711.h>
31 #include <wsutil/pint.h>
33 #include <QMessageBox>
34 #include <QPushButton>
35 #include <QVBoxLayout>
36 #include <QHBoxLayout>
37 #include <QLabel>
38 #include <QToolButton>
39 #include <QWidget>
40 #include <QCheckBox>
42 #include <ui/qt/utils/color_utils.h>
43 #include <ui/qt/utils/qt_ui_utils.h>
44 #include "rtp_player_dialog.h"
45 #include <ui/qt/utils/stock_icon.h>
46 #include "main_application.h"
47 #include "ui/qt/widgets/wireshark_file_dialog.h"
50 * @file RTP stream analysis dialog
52 * Displays forward and reverse RTP streams and graphs each stream
55 // To do:
56 // - Progress bar for tapping and saving.
57 // - Add a refresh button and/or action.
58 // - Fixup output file names.
59 // - Add a graph title and legend when saving?
61 enum {
62 packet_col_,
63 sequence_col_,
64 delta_col_,
65 jitter_col_,
66 skew_col_,
67 bandwidth_col_,
68 marker_col_,
69 status_col_
72 static const QRgb color_cn_ = 0xbfbfff;
73 static const QRgb color_rtp_warn_ = 0xffdbbf;
74 static const QRgb color_pt_event_ = 0xefffff;
76 enum { rtp_analysis_type_ = 1000 };
77 class RtpAnalysisTreeWidgetItem : public QTreeWidgetItem
79 public:
80 RtpAnalysisTreeWidgetItem(QTreeWidget *tree, tap_rtp_stat_t *statinfo, packet_info *pinfo, const struct _rtp_info *rtpinfo) :
81 QTreeWidgetItem(tree, rtp_analysis_type_)
83 frame_num_ = pinfo->num;
84 sequence_num_ = rtpinfo->info_seq_num;
85 pkt_len_ = pinfo->fd->pkt_len;
86 flags_ = statinfo->flags;
87 if (flags_ & STAT_FLAG_FIRST) {
88 delta_ = 0.0;
89 jitter_ = 0.0;
90 skew_ = 0.0;
91 } else {
92 delta_ = statinfo->delta;
93 jitter_ = statinfo->jitter;
94 skew_ = statinfo->skew;
96 bandwidth_ = statinfo->bandwidth;
97 marker_ = rtpinfo->info_marker_set ? true : false;
98 ok_ = false;
100 QColor bg_color = QColor();
101 QString status;
103 if (statinfo->pt == PT_CN) {
104 status = "Comfort noise (PT=13, RFC 3389)";
105 bg_color = color_cn_;
106 } else if (statinfo->pt == PT_CN_OLD) {
107 status = "Comfort noise (PT=19, reserved)";
108 bg_color = color_cn_;
109 } else if (statinfo->flags & STAT_FLAG_WRONG_SEQ) {
110 status = "Wrong sequence number";
111 bg_color = ColorUtils::expert_color_error;
112 } else if (statinfo->flags & STAT_FLAG_DUP_PKT) {
113 status = "Suspected duplicate (MAC address) only delta time calculated";
114 bg_color = color_rtp_warn_;
115 } else if (statinfo->flags & STAT_FLAG_REG_PT_CHANGE) {
116 status = QStringLiteral("Payload changed to PT=%1").arg(statinfo->pt);
117 if (statinfo->flags & STAT_FLAG_PT_T_EVENT) {
118 status.append(" telephone/event");
120 bg_color = color_rtp_warn_;
121 } else if (statinfo->flags & STAT_FLAG_WRONG_TIMESTAMP) {
122 status = "Incorrect timestamp";
123 /* color = COLOR_WARNING; */
124 bg_color = color_rtp_warn_;
125 } else if ((statinfo->flags & STAT_FLAG_PT_CHANGE)
126 && !(statinfo->flags & STAT_FLAG_FIRST)
127 && !(statinfo->flags & STAT_FLAG_PT_CN)
128 && (statinfo->flags & STAT_FLAG_FOLLOW_PT_CN)
129 && !(statinfo->flags & STAT_FLAG_MARKER)) {
130 status = "Marker missing?";
131 bg_color = color_rtp_warn_;
132 } else if (statinfo->flags & STAT_FLAG_PT_T_EVENT) {
133 status = QStringLiteral("PT=%1 telephone/event").arg(statinfo->pt);
134 /* XXX add color? */
135 bg_color = color_pt_event_;
136 } else {
137 if (statinfo->flags & STAT_FLAG_MARKER) {
138 bg_color = color_rtp_warn_;
142 if (status.isEmpty()) {
143 ok_ = true;
144 status = UTF8_CHECK_MARK;
147 setText(packet_col_, QString::number(frame_num_));
148 setText(sequence_col_, QString::number(sequence_num_));
149 setText(delta_col_, QString::number(delta_, 'f', prefs.gui_decimal_places3));
150 setText(jitter_col_, QString::number(jitter_, 'f', prefs.gui_decimal_places3));
151 setText(skew_col_, QString::number(skew_, 'f', prefs.gui_decimal_places3));
152 setText(bandwidth_col_, QString::number(bandwidth_, 'f', prefs.gui_decimal_places1));
153 if (marker_) {
154 setText(marker_col_, UTF8_BULLET);
156 setText(status_col_, status);
158 setTextAlignment(packet_col_, Qt::AlignRight);
159 setTextAlignment(sequence_col_, Qt::AlignRight);
160 setTextAlignment(delta_col_, Qt::AlignRight);
161 setTextAlignment(jitter_col_, Qt::AlignRight);
162 setTextAlignment(skew_col_, Qt::AlignRight);
163 setTextAlignment(bandwidth_col_, Qt::AlignRight);
164 setTextAlignment(marker_col_, Qt::AlignCenter);
166 if (bg_color.isValid()) {
167 for (int col = 0; col < columnCount(); col++) {
168 setBackground(col, bg_color);
169 setForeground(col, ColorUtils::expert_color_foreground);
174 uint32_t frameNum() { return frame_num_; }
175 bool frameStatus() { return ok_; }
177 QList<QVariant> rowData() {
178 QString marker_str;
179 QString status_str = ok_ ? "OK" : text(status_col_);
181 if (marker_) marker_str = "SET";
183 return QList<QVariant>()
184 << frame_num_ << sequence_num_ << delta_ << jitter_ << skew_ << bandwidth_
185 << marker_str << status_str;
188 bool operator< (const QTreeWidgetItem &other) const
190 if (other.type() != rtp_analysis_type_) return QTreeWidgetItem::operator< (other);
191 const RtpAnalysisTreeWidgetItem *other_row = static_cast<const RtpAnalysisTreeWidgetItem *>(&other);
193 switch (treeWidget()->sortColumn()) {
194 case (packet_col_):
195 return frame_num_ < other_row->frame_num_;
196 case (sequence_col_):
197 return sequence_num_ < other_row->sequence_num_;
198 case (delta_col_):
199 return delta_ < other_row->delta_;
200 case (jitter_col_):
201 return jitter_ < other_row->jitter_;
202 case (skew_col_):
203 return skew_ < other_row->skew_;
204 case (bandwidth_col_):
205 return bandwidth_ < other_row->bandwidth_;
206 default:
207 break;
210 // Fall back to string comparison
211 return QTreeWidgetItem::operator <(other);
213 private:
214 uint32_t frame_num_;
215 uint32_t sequence_num_;
216 uint32_t pkt_len_;
217 uint32_t flags_;
218 double delta_;
219 double jitter_;
220 double skew_;
221 double bandwidth_;
222 bool marker_;
223 bool ok_;
226 enum {
227 fwd_jitter_graph_,
228 fwd_diff_graph_,
229 fwd_delta_graph_,
230 rev_jitter_graph_,
231 rev_diff_graph_,
232 rev_delta_graph_,
233 num_graphs_
236 RtpAnalysisDialog *RtpAnalysisDialog::pinstance_{nullptr};
237 std::mutex RtpAnalysisDialog::init_mutex_;
238 std::mutex RtpAnalysisDialog::run_mutex_;
240 RtpAnalysisDialog *RtpAnalysisDialog::openRtpAnalysisDialog(QWidget &parent, CaptureFile &cf, QObject *packet_list)
242 std::lock_guard<std::mutex> lock(init_mutex_);
243 if (pinstance_ == nullptr)
245 pinstance_ = new RtpAnalysisDialog(parent, cf);
246 connect(pinstance_, SIGNAL(goToPacket(int)),
247 packet_list, SLOT(goToPacket(int)));
249 return pinstance_;
252 RtpAnalysisDialog::RtpAnalysisDialog(QWidget &parent, CaptureFile &cf) :
253 WiresharkDialog(parent, cf),
254 ui(new Ui::RtpAnalysisDialog),
255 tab_seq(0)
257 ui->setupUi(this);
258 loadGeometry(parent.width() * 4 / 5, parent.height() * 4 / 5);
259 setWindowSubtitle(tr("RTP Stream Analysis"));
260 // Used when tab contains IPs
261 //ui->tabWidget->setStyleSheet("QTabBar::tab { height: 7ex; }");
262 ui->tabWidget->tabBar()->setTabsClosable(true);
264 ui->progressFrame->hide();
266 stream_ctx_menu_.addAction(ui->actionGoToPacket);
267 stream_ctx_menu_.addAction(ui->actionNextProblem);
268 set_action_shortcuts_visible_in_context_menu(stream_ctx_menu_.actions());
270 connect(ui->streamGraph, SIGNAL(mousePress(QMouseEvent*)),
271 this, SLOT(graphClicked(QMouseEvent*)));
273 graph_ctx_menu_.addAction(ui->actionSaveGraph);
275 ui->streamGraph->setContextMenuPolicy(Qt::CustomContextMenu);
276 connect(ui->streamGraph, &QCustomPlot::customContextMenuRequested, this,
277 &RtpAnalysisDialog::showGraphMenu);
279 ui->streamGraph->xAxis->setLabel("Arrival Time");
280 ui->streamGraph->yAxis->setLabel("Value (ms)");
282 QPushButton *prepare_button = ui->buttonBox->addButton(ui->actionPrepareButton->text(), QDialogButtonBox::ActionRole);
283 prepare_button->setToolTip(ui->actionPrepareButton->toolTip());
284 prepare_button->setMenu(ui->menuPrepareFilter);
286 player_button_ = RtpPlayerDialog::addPlayerButton(ui->buttonBox, this);
288 QPushButton *export_btn = ui->buttonBox->addButton(ui->actionExportButton->text(), QDialogButtonBox::ActionRole);
289 export_btn->setToolTip(ui->actionExportButton->toolTip());
291 QMenu *save_menu = new QMenu(export_btn);
292 save_menu->addAction(ui->actionSaveOneCsv);
293 save_menu->addAction(ui->actionSaveAllCsv);
294 save_menu->addSeparator();
295 save_menu->addAction(ui->actionSaveGraph);
296 export_btn->setMenu(save_menu);
298 connect(ui->tabWidget, SIGNAL(currentChanged(int)),
299 this, SLOT(updateWidgets()));
300 connect(ui->tabWidget->tabBar(), SIGNAL(tabCloseRequested(int)),
301 this, SLOT(closeTab(int)));
302 connect(this, SIGNAL(updateFilter(QString, bool)),
303 &parent, SLOT(filterPackets(QString, bool)));
304 connect(this, SIGNAL(rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)),
305 &parent, SLOT(rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)));
306 connect(this, SIGNAL(rtpPlayerDialogAddRtpStreams(QVector<rtpstream_id_t *>)),
307 &parent, SLOT(rtpPlayerDialogAddRtpStreams(QVector<rtpstream_id_t *>)));
308 connect(this, SIGNAL(rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)),
309 &parent, SLOT(rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)));
311 updateWidgets();
313 updateStatistics();
316 RtpAnalysisDialog::~RtpAnalysisDialog()
318 std::lock_guard<std::mutex> lock(init_mutex_);
319 if (pinstance_ != nullptr) {
320 delete ui;
321 for(int i=0; i<tabs_.count(); i++) {
322 deleteTabInfo(tabs_[i]);
323 g_free(tabs_[i]);
325 pinstance_ = nullptr;
329 void RtpAnalysisDialog::deleteTabInfo(tab_info_t *tab_info)
331 delete tab_info->time_vals;
332 delete tab_info->jitter_vals;
333 delete tab_info->diff_vals;
334 delete tab_info->delta_vals;
335 delete tab_info->tab_name;
336 // tab_info->tree_widget was deleted by ui
337 // tab_info->statistics_label was deleted by ui
338 rtpstream_info_free_data(&tab_info->stream);
341 int RtpAnalysisDialog::addTabUI(tab_info_t *new_tab)
343 int new_tab_no;
344 rtpstream_info_calc_t s_calc;
345 rtpstream_info_calculate(&new_tab->stream, &s_calc);
346 new_tab->tab_name = new QString(QStringLiteral("%1:%2 %3\n%4:%5\n(%6)")
347 .arg(s_calc.src_addr_str)
348 .arg(s_calc.src_port)
349 .arg(UTF8_RIGHTWARDS_ARROW)
350 .arg(s_calc.dst_addr_str)
351 .arg(s_calc.dst_port)
352 .arg(int_to_qstring(s_calc.ssrc, 8, 16)));
353 rtpstream_info_calc_free(&s_calc);
355 QWidget *tab = new QWidget();
356 tab->setProperty("tab_data", QVariant::fromValue((void *)new_tab));
357 QHBoxLayout *horizontalLayout = new QHBoxLayout(tab);
358 QVBoxLayout *verticalLayout = new QVBoxLayout();
359 new_tab->statistics_label = new QLabel();
360 //new_tab->statistics_label->setStyleSheet("QLabel { color : blue; }");
361 new_tab->statistics_label->setTextFormat(Qt::RichText);
362 new_tab->statistics_label->setTextInteractionFlags(Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse);
364 verticalLayout->addWidget(new_tab->statistics_label);
366 QSpacerItem *verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
368 verticalLayout->addItem(verticalSpacer);
370 horizontalLayout->addLayout(verticalLayout);
372 new_tab->tree_widget = new QTreeWidget();
373 new_tab->tree_widget->setRootIsDecorated(false);
374 new_tab->tree_widget->setUniformRowHeights(true);
375 new_tab->tree_widget->setItemsExpandable(false);
376 new_tab->tree_widget->setSortingEnabled(true);
377 new_tab->tree_widget->setExpandsOnDoubleClick(false);
379 new_tab->tree_widget->installEventFilter(this);
380 new_tab->tree_widget->setContextMenuPolicy(Qt::CustomContextMenu);
381 new_tab->tree_widget->header()->setSortIndicator(0, Qt::AscendingOrder);
382 connect(new_tab->tree_widget, SIGNAL(customContextMenuRequested(QPoint)),
383 SLOT(showStreamMenu(QPoint)));
384 connect(new_tab->tree_widget, SIGNAL(itemSelectionChanged()),
385 this, SLOT(updateWidgets()));
387 QTreeWidgetItem *ti = new_tab->tree_widget->headerItem();
388 ti->setText(packet_col_, tr("Packet"));
389 ti->setText(sequence_col_, tr("Sequence"));
390 ti->setText(delta_col_, tr("Delta (ms)"));
391 ti->setText(jitter_col_, tr("Jitter (ms)"));
392 ti->setText(skew_col_, tr("Skew"));
393 ti->setText(bandwidth_col_, tr("Bandwidth"));
394 ti->setText(marker_col_, tr("Marker"));
395 ti->setText(status_col_, tr("Status"));
397 QColor color = ColorUtils::graphColor(tab_seq++);
398 ui->tabWidget->setUpdatesEnabled(false);
399 horizontalLayout->addWidget(new_tab->tree_widget);
400 new_tab_no = ui->tabWidget->count() - 1;
401 // Used when tab contains IPs
402 //ui->tabWidget->insertTab(new_tab_no, tab, *new_tab->tab_name);
403 ui->tabWidget->insertTab(new_tab_no, tab, tr("Stream %1").arg(tab_seq - 1));
404 ui->tabWidget->tabBar()->setTabTextColor(new_tab_no, color);
405 ui->tabWidget->tabBar()->setTabToolTip(new_tab_no, *new_tab->tab_name);
406 ui->tabWidget->setUpdatesEnabled(true);
408 QPen pen = QPen(color);
409 QCPScatterStyle jitter_shape;
410 QCPScatterStyle diff_shape;
411 QCPScatterStyle delta_shape;
412 jitter_shape.setShape(QCPScatterStyle::ssCircle);
413 //jitter_shape.setSize(5);
414 diff_shape.setShape(QCPScatterStyle::ssCross);
415 //diff_shape.setSize(5);
416 delta_shape.setShape(QCPScatterStyle::ssTriangle);
417 //delta_shape.setSize(5);
419 new_tab->jitter_graph = ui->streamGraph->addGraph();
420 new_tab->diff_graph = ui->streamGraph->addGraph();
421 new_tab->delta_graph = ui->streamGraph->addGraph();
422 new_tab->jitter_graph->setPen(pen);
423 new_tab->diff_graph->setPen(pen);
424 new_tab->delta_graph->setPen(pen);
425 new_tab->jitter_graph->setScatterStyle(jitter_shape);
426 new_tab->diff_graph->setScatterStyle(diff_shape);
427 new_tab->delta_graph->setScatterStyle(delta_shape);
429 new_tab->graphHorizontalLayout = new QHBoxLayout();
431 new_tab->stream_checkbox = new QCheckBox(tr("Stream %1").arg(tab_seq - 1), ui->graphTab);
432 new_tab->stream_checkbox->setChecked(true);
433 new_tab->stream_checkbox->setIcon(StockIcon::colorIcon(color.rgb(), QPalette::Text));
434 new_tab->graphHorizontalLayout->addWidget(new_tab->stream_checkbox);
435 new_tab->graphHorizontalLayout->addItem(new QSpacerItem(10, 5, QSizePolicy::Expanding, QSizePolicy::Minimum));
436 connect(new_tab->stream_checkbox, SIGNAL(stateChanged(int)),
437 this, SLOT(rowCheckboxChanged(int)));
439 new_tab->jitter_checkbox = new QCheckBox(tr("Stream %1 Jitter").arg(tab_seq - 1), ui->graphTab);
440 new_tab->jitter_checkbox->setChecked(true);
441 new_tab->jitter_checkbox->setIcon(StockIcon::colorIconCircle(color.rgb(), QPalette::Text));
442 new_tab->graphHorizontalLayout->addWidget(new_tab->jitter_checkbox);
443 new_tab->graphHorizontalLayout->addItem(new QSpacerItem(10, 5, QSizePolicy::Expanding, QSizePolicy::Minimum));
444 connect(new_tab->jitter_checkbox, SIGNAL(stateChanged(int)),
445 this, SLOT(singleCheckboxChanged(int)));
447 new_tab->diff_checkbox = new QCheckBox(tr("Stream %1 Difference").arg(tab_seq - 1), ui->graphTab);
448 new_tab->diff_checkbox->setChecked(true);
449 new_tab->diff_checkbox->setIcon(StockIcon::colorIconCross(color.rgb(), QPalette::Text));
450 new_tab->graphHorizontalLayout->addWidget(new_tab->diff_checkbox);
451 new_tab->graphHorizontalLayout->addItem(new QSpacerItem(10, 5, QSizePolicy::Expanding, QSizePolicy::Minimum));
452 connect(new_tab->diff_checkbox, SIGNAL(stateChanged(int)),
453 this, SLOT(singleCheckboxChanged(int)));
455 new_tab->delta_checkbox = new QCheckBox(tr("Stream %1 Delta").arg(tab_seq - 1), ui->graphTab);
456 new_tab->delta_checkbox->setChecked(true);
457 new_tab->delta_checkbox->setIcon(StockIcon::colorIconTriangle(color.rgb(), QPalette::Text));
458 new_tab->graphHorizontalLayout->addWidget(new_tab->delta_checkbox);
459 new_tab->graphHorizontalLayout->addItem(new QSpacerItem(10, 5, QSizePolicy::Expanding, QSizePolicy::Minimum));
460 connect(new_tab->delta_checkbox, SIGNAL(stateChanged(int)),
461 this, SLOT(singleCheckboxChanged(int)));
463 new_tab->graphHorizontalLayout->setStretch(6, 1);
465 ui->layout->addLayout(new_tab->graphHorizontalLayout);
467 return new_tab_no;
470 // Handles all row checkBoxes
471 void RtpAnalysisDialog::rowCheckboxChanged(int checked)
473 QObject *obj = sender();
475 // Find correct tab data
476 for(int i=0; i<tabs_.count(); i++) {
477 tab_info_t *tab = tabs_[i];
478 if (obj == tab->stream_checkbox) {
479 // Set new state for all checkboxes on row
480 Qt::CheckState new_state;
482 if (checked) {
483 new_state = Qt::Checked;
484 } else {
485 new_state = Qt::Unchecked;
487 tab->jitter_checkbox->setCheckState(new_state);
488 tab->diff_checkbox->setCheckState(new_state);
489 tab->delta_checkbox->setCheckState(new_state);
490 break;
495 // Handles all single CheckBoxes
496 void RtpAnalysisDialog::singleCheckboxChanged(int checked)
498 QObject *obj = sender();
500 // Find correct tab data
501 for(int i=0; i<tabs_.count(); i++) {
502 tab_info_t *tab = tabs_[i];
503 if (obj == tab->jitter_checkbox) {
504 tab->jitter_graph->setVisible(checked);
505 updateGraph();
506 break;
507 } else if (obj == tab->diff_checkbox) {
508 tab->diff_graph->setVisible(checked);
509 updateGraph();
510 break;
511 } else if (obj == tab->delta_checkbox) {
512 tab->delta_graph->setVisible(checked);
513 updateGraph();
514 break;
519 void RtpAnalysisDialog::updateWidgets()
521 bool enable_tab = false;
522 bool enable_nav = false;
523 QString hint = err_str_;
525 if ((!file_closed_) &&
526 (tabs_.count() > 0)) {
527 enable_tab = true;
530 if ((!file_closed_) &&
531 (tabs_.count() > 0) &&
532 (ui->tabWidget->currentIndex() < (ui->tabWidget->count()-1))) {
533 enable_nav = true;
536 ui->actionGoToPacket->setEnabled(enable_nav);
537 ui->actionNextProblem->setEnabled(enable_nav);
539 if (enable_nav) {
540 hint.append(tr(" %1 streams, ").arg(tabs_.count() - 1));
541 hint.append(tr(" G: Go to packet, N: Next problem packet"));
544 ui->actionExportButton->setEnabled(enable_tab);
545 ui->actionSaveOneCsv->setEnabled(enable_nav);
546 ui->actionSaveAllCsv->setEnabled(enable_tab);
547 ui->actionSaveGraph->setEnabled(enable_tab);
549 ui->actionPrepareFilterOne->setEnabled(enable_nav);
550 ui->actionPrepareFilterAll->setEnabled(enable_tab);
552 #if defined(QT_MULTIMEDIA_LIB)
553 player_button_->setEnabled(enable_tab);
554 #endif
556 ui->tabWidget->setEnabled(enable_tab);
557 hint.prepend("<small><i>");
558 hint.append("</i></small>");
559 ui->hintLabel->setText(hint);
561 WiresharkDialog::updateWidgets();
564 void RtpAnalysisDialog::on_actionGoToPacket_triggered()
566 tab_info_t *tab_data = getTabInfoForCurrentTab();
567 if (!tab_data) return;
569 QTreeWidget *cur_tree = tab_data->tree_widget;
570 if (!cur_tree || cur_tree->selectedItems().length() < 1) return;
572 QTreeWidgetItem *ti = cur_tree->selectedItems()[0];
573 if (ti->type() != rtp_analysis_type_) return;
575 RtpAnalysisTreeWidgetItem *ra_ti = dynamic_cast<RtpAnalysisTreeWidgetItem *>((RtpAnalysisTreeWidgetItem *)ti);
576 emit goToPacket(ra_ti->frameNum());
579 void RtpAnalysisDialog::on_actionNextProblem_triggered()
581 tab_info_t *tab_data = getTabInfoForCurrentTab();
582 if (!tab_data) return;
584 QTreeWidget *cur_tree = tab_data->tree_widget;
585 if (!cur_tree || cur_tree->topLevelItemCount() < 2) return;
587 // Choose convenience over correctness.
588 if (cur_tree->selectedItems().length() < 1) {
589 cur_tree->setCurrentItem(cur_tree->topLevelItem(0));
592 QTreeWidgetItem *sel_ti = cur_tree->selectedItems()[0];
593 if (sel_ti->type() != rtp_analysis_type_) return;
594 QTreeWidgetItem *test_ti = cur_tree->itemBelow(sel_ti);
595 if (!test_ti) test_ti = cur_tree->topLevelItem(0);
596 while (test_ti != sel_ti) {
597 RtpAnalysisTreeWidgetItem *ra_ti = dynamic_cast<RtpAnalysisTreeWidgetItem *>((RtpAnalysisTreeWidgetItem *)test_ti);
598 if (!ra_ti->frameStatus()) {
599 cur_tree->setCurrentItem(ra_ti);
600 break;
603 test_ti = cur_tree->itemBelow(test_ti);
604 if (!test_ti) test_ti = cur_tree->topLevelItem(0);
608 void RtpAnalysisDialog::on_actionSaveOneCsv_triggered()
610 saveCsv(dir_one_);
613 void RtpAnalysisDialog::on_actionSaveAllCsv_triggered()
615 saveCsv(dir_all_);
618 void RtpAnalysisDialog::on_actionSaveGraph_triggered()
620 ui->tabWidget->setCurrentWidget(ui->graphTab);
622 QString file_name, extension;
623 QDir path(mainApp->openDialogInitialDir());
624 QString pdf_filter = tr("Portable Document Format (*.pdf)");
625 QString png_filter = tr("Portable Network Graphics (*.png)");
626 QString bmp_filter = tr("Windows Bitmap (*.bmp)");
627 // Gaze upon my beautiful graph with lossy artifacts!
628 QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
629 QString filter = QStringLiteral("%1;;%2;;%3;;%4")
630 .arg(pdf_filter)
631 .arg(png_filter)
632 .arg(bmp_filter)
633 .arg(jpeg_filter);
635 QString save_file = path.canonicalPath();
636 if (!file_closed_) {
637 save_file += QStringLiteral("/%1").arg(cap_file_.fileBaseName());
639 file_name = WiresharkFileDialog::getSaveFileName(this, mainApp->windowTitleString(tr("Save Graph As…")),
640 save_file, filter, &extension);
642 if (!file_name.isEmpty()) {
643 bool save_ok = false;
644 // https://www.qcustomplot.com/index.php/support/forum/63
645 // ui->streamGraph->legend->setVisible(true);
646 if (extension.compare(pdf_filter) == 0) {
647 save_ok = ui->streamGraph->savePdf(file_name);
648 } else if (extension.compare(png_filter) == 0) {
649 save_ok = ui->streamGraph->savePng(file_name);
650 } else if (extension.compare(bmp_filter) == 0) {
651 save_ok = ui->streamGraph->saveBmp(file_name);
652 } else if (extension.compare(jpeg_filter) == 0) {
653 save_ok = ui->streamGraph->saveJpg(file_name);
655 // ui->streamGraph->legend->setVisible(false);
656 // else error dialog?
657 if (save_ok) {
658 mainApp->setLastOpenDirFromFilename(file_name);
663 void RtpAnalysisDialog::on_buttonBox_helpRequested()
665 mainApp->helpTopicAction(HELP_TELEPHONY_RTP_ANALYSIS_DIALOG);
668 void RtpAnalysisDialog::tapReset(void *tapinfo_ptr)
670 RtpAnalysisDialog *rtp_analysis_dialog = dynamic_cast<RtpAnalysisDialog *>((RtpAnalysisDialog*)tapinfo_ptr);
671 if (!rtp_analysis_dialog) return;
673 rtp_analysis_dialog->resetStatistics();
676 tap_packet_status RtpAnalysisDialog::tapPacket(void *tapinfo_ptr, packet_info *pinfo, epan_dissect_t *, const void *rtpinfo_ptr, tap_flags_t)
678 RtpAnalysisDialog *rtp_analysis_dialog = dynamic_cast<RtpAnalysisDialog *>((RtpAnalysisDialog*)tapinfo_ptr);
679 if (!rtp_analysis_dialog) return TAP_PACKET_DONT_REDRAW;
681 const struct _rtp_info *rtpinfo = (const struct _rtp_info *)rtpinfo_ptr;
682 if (!rtpinfo) return TAP_PACKET_DONT_REDRAW;
684 /* we ignore packets that are not displayed */
685 if (pinfo->fd->passed_dfilter == 0)
686 return TAP_PACKET_DONT_REDRAW;
687 /* also ignore RTP Version != 2 */
688 else if (rtpinfo->info_version != 2)
689 return TAP_PACKET_DONT_REDRAW;
690 /* is it the forward direction? */
691 else {
692 // Search tab in hash key, if there are multiple tabs with same hash
693 QList<tab_info_t *> tabs = rtp_analysis_dialog->tab_hash_.values(pinfo_rtp_info_to_hash(pinfo, rtpinfo));
694 for (int i = 0; i < tabs.size(); i++) {
695 tab_info_t *tab = tabs.at(i);
696 if (rtpstream_id_equal_pinfo_rtp_info(&tab->stream.id, pinfo, rtpinfo)) {
697 rtp_analysis_dialog->addPacket(tab, pinfo, rtpinfo);
698 break;
703 return TAP_PACKET_DONT_REDRAW;
706 void RtpAnalysisDialog::tapDraw(void *tapinfo_ptr)
708 RtpAnalysisDialog *rtp_analysis_dialog = dynamic_cast<RtpAnalysisDialog *>((RtpAnalysisDialog*)tapinfo_ptr);
709 if (!rtp_analysis_dialog) return;
711 rtp_analysis_dialog->updateStatistics();
714 void RtpAnalysisDialog::resetStatistics()
716 for(int i=0; i<tabs_.count(); i++) {
717 tab_info_t *tab = tabs_[i];
718 memset(&tab->stream.rtp_stats, 0, sizeof(tab->stream.rtp_stats));
720 tab->stream.rtp_stats.first_packet = true;
721 tab->stream.rtp_stats.reg_pt = PT_UNDEFINED;
722 tab->time_vals->clear();
723 tab->jitter_vals->clear();
724 tab->diff_vals->clear();
725 tab->delta_vals->clear();
726 tab->tree_widget->clear();
729 for (int i = 0; i < ui->streamGraph->graphCount(); i++) {
730 ui->streamGraph->graph(i)->data()->clear();
734 void RtpAnalysisDialog::addPacket(tab_info_t *tab, packet_info *pinfo, const _rtp_info *rtpinfo)
736 rtpstream_info_analyse_process(&tab->stream, pinfo, rtpinfo);
737 new RtpAnalysisTreeWidgetItem(tab->tree_widget, &tab->stream.rtp_stats, pinfo, rtpinfo);
738 tab->time_vals->append(tab->stream.rtp_stats.time / 1000);
739 tab->jitter_vals->append(tab->stream.rtp_stats.jitter);
740 tab->diff_vals->append(tab->stream.rtp_stats.diff);
741 tab->delta_vals->append(tab->stream.rtp_stats.delta);
744 void RtpAnalysisDialog::updateStatistics()
746 for(int i=0; i<tabs_.count(); i++) {
747 rtpstream_info_t *stream = &tabs_[i]->stream;
748 rtpstream_info_calc_t s_calc;
749 rtpstream_info_calculate(stream, &s_calc);
751 QString stats_tables = "<html><head><style>td{vertical-align:bottom;}</style></head><body>\n";
752 stats_tables += "<h4>Stream</h4>\n";
753 stats_tables += QStringLiteral("<p>%1:%2 %3")
754 .arg(s_calc.src_addr_str)
755 .arg(s_calc.src_port)
756 .arg(UTF8_RIGHTWARDS_ARROW);
757 stats_tables += QStringLiteral("<br>%1:%2</p>\n")
758 .arg(s_calc.dst_addr_str)
759 .arg(s_calc.dst_port);
760 stats_tables += "<p><table>\n";
761 stats_tables += QStringLiteral("<tr><th align=\"left\">SSRC</th><td>%1</td></tr>")
762 .arg(int_to_qstring(s_calc.ssrc, 8, 16));
763 stats_tables += QStringLiteral("<tr><th align=\"left\">Max Delta</th><td>%1 ms @ %2</td></tr>")
764 .arg(s_calc.max_delta, 0, 'f', prefs.gui_decimal_places3)
765 .arg(s_calc.last_packet_num);
766 stats_tables += QStringLiteral("<tr><th align=\"left\">Max Jitter</th><td>%1 ms</td></tr>")
767 .arg(s_calc.max_jitter, 0, 'f', prefs.gui_decimal_places3);
768 stats_tables += QStringLiteral("<tr><th align=\"left\">Mean Jitter</th><td>%1 ms</td></tr>")
769 .arg(s_calc.mean_jitter, 0, 'f', prefs.gui_decimal_places3);
770 stats_tables += QStringLiteral("<tr><th align=\"left\">Max Skew</th><td>%1 ms</td></tr>")
771 .arg(s_calc.max_skew, 0, 'f', prefs.gui_decimal_places3);
772 stats_tables += QStringLiteral("<tr><th align=\"left\">RTP Packets</th><td>%1</td></tr>")
773 .arg(s_calc.total_nr);
774 stats_tables += QStringLiteral("<tr><th align=\"left\">Expected</th><td>%1</td></tr>")
775 .arg(s_calc.packet_expected);
776 stats_tables += QStringLiteral("<tr><th align=\"left\">Lost</th><td>%1 (%2 %)</td></tr>")
777 .arg(s_calc.lost_num).arg(s_calc.lost_perc, 0, 'f', prefs.gui_decimal_places1);
778 stats_tables += QStringLiteral("<tr><th align=\"left\">Seq Errs</th><td>%1</td></tr>")
779 .arg(s_calc.sequence_err);
780 stats_tables += QStringLiteral("<tr><th align=\"left\">Start at</th><td>%1 s @ %2</td></tr>")
781 .arg(s_calc.start_time_ms, 0, 'f', 6)
782 .arg(s_calc.first_packet_num);
783 stats_tables += QStringLiteral("<tr><th align=\"left\">Duration</th><td>%1 s</td></tr>")
784 .arg(s_calc.duration_ms, 0, 'f', prefs.gui_decimal_places1);
785 stats_tables += QStringLiteral("<tr><th align=\"left\">Clock Drift</th><td>%1 ms</td></tr>")
786 .arg(s_calc.clock_drift_ms, 0, 'f', 0);
787 stats_tables += QStringLiteral("<tr><th align=\"left\">Freq Drift</th><td>%1 Hz (%2 %)</td></tr>") // XXX Terminology?
788 .arg(s_calc.freq_drift_hz, 0, 'f', 0).arg(s_calc.freq_drift_perc, 0, 'f', 2);
789 rtpstream_info_calc_free(&s_calc);
790 stats_tables += "</table></p>\n";
792 tabs_[i]->statistics_label->setText(stats_tables);
794 for (int col = 0; col < tabs_[i]->tree_widget->columnCount() - 1; col++) {
795 tabs_[i]->tree_widget->resizeColumnToContents(col);
798 tabs_[i]->jitter_graph->setData(*tabs_[i]->time_vals, *tabs_[i]->jitter_vals);
799 tabs_[i]->diff_graph->setData(*tabs_[i]->time_vals, *tabs_[i]->diff_vals);
800 tabs_[i]->delta_graph->setData(*tabs_[i]->time_vals, *tabs_[i]->delta_vals);
803 updateGraph();
805 updateWidgets();
808 void RtpAnalysisDialog::updateGraph()
810 for (int i = 0; i < ui->streamGraph->graphCount(); i++) {
811 if (ui->streamGraph->graph(i)->visible()) {
812 ui->streamGraph->graph(i)->rescaleAxes(i > 0);
815 ui->streamGraph->replot();
818 QVector<rtpstream_id_t *>RtpAnalysisDialog::getSelectedRtpIds()
820 QVector<rtpstream_id_t *> stream_ids;
821 for(int i=0; i < tabs_.count(); i++) {
822 stream_ids << &(tabs_[i]->stream.id);
825 return stream_ids;
828 void RtpAnalysisDialog::rtpPlayerReplace()
830 if (tabs_.count() < 1) return;
832 emit rtpPlayerDialogReplaceRtpStreams(getSelectedRtpIds());
835 void RtpAnalysisDialog::rtpPlayerAdd()
837 if (tabs_.count() < 1) return;
839 emit rtpPlayerDialogAddRtpStreams(getSelectedRtpIds());
842 void RtpAnalysisDialog::rtpPlayerRemove()
844 if (tabs_.count() < 1) return;
846 emit rtpPlayerDialogRemoveRtpStreams(getSelectedRtpIds());
849 void RtpAnalysisDialog::saveCsvHeader(QFile *save_file, QTreeWidget *tree)
851 QList<QVariant> row_data;
852 QStringList values;
854 for (int col = 0; col < tree->columnCount(); col++) {
855 row_data << tree->headerItem()->text(col);
857 foreach (QVariant v, row_data) {
858 if (!v.isValid()) {
859 values << "\"\"";
860 } else if (v.userType() == QMetaType::QString) {
861 values << QStringLiteral("\"%1\"").arg(v.toString());
862 } else {
863 values << v.toString();
866 save_file->write(values.join(",").toUtf8());
867 save_file->write("\n");
870 void RtpAnalysisDialog::saveCsvData(QFile *save_file, QTreeWidget *tree)
872 for (int row = 0; row < tree->topLevelItemCount(); row++) {
873 QTreeWidgetItem *ti = tree->topLevelItem(row);
874 if (ti->type() != rtp_analysis_type_) continue;
875 RtpAnalysisTreeWidgetItem *ra_ti = dynamic_cast<RtpAnalysisTreeWidgetItem *>((RtpAnalysisTreeWidgetItem *)ti);
876 QStringList values;
877 foreach (QVariant v, ra_ti->rowData()) {
878 if (!v.isValid()) {
879 values << "\"\"";
880 } else if (v.userType() == QMetaType::QString) {
881 values << QStringLiteral("\"%1\"").arg(v.toString());
882 } else {
883 values << v.toString();
886 save_file->write(values.join(",").toUtf8());
887 save_file->write("\n");
891 // XXX The GTK+ UI saves the length and timestamp.
892 void RtpAnalysisDialog::saveCsv(RtpAnalysisDialog::StreamDirection direction)
894 QString caption;
896 switch (direction) {
897 case dir_one_:
898 caption = tr("Save one stream CSV");
899 break;
900 case dir_all_:
901 default:
902 caption = tr("Save all stream's CSV");
903 break;
906 QString file_path = WiresharkFileDialog::getSaveFileName(
907 this, caption, mainApp->openDialogInitialDir().absoluteFilePath("RTP Packet Data.csv"),
908 tr("Comma-separated values (*.csv)"));
910 if (file_path.isEmpty()) return;
912 QFile save_file(file_path);
913 save_file.open(QFile::WriteOnly);
915 switch (direction) {
916 case dir_one_:
918 tab_info_t *tab_data = getTabInfoForCurrentTab();
919 if (tab_data) {
921 saveCsvHeader(&save_file, tab_data->tree_widget);
923 QString n = QString(*tab_data->tab_name);
924 n.replace("\n"," ");
925 save_file.write("\"");
926 save_file.write(n.toUtf8());
927 save_file.write("\"\n");
928 saveCsvData(&save_file, tab_data->tree_widget);
931 break;
932 case dir_all_:
933 default:
934 if (tabs_.count() > 0) {
935 saveCsvHeader(&save_file, tabs_[0]->tree_widget);
938 for(int i=0; i<tabs_.count(); i++) {
939 QString n = QString(*tabs_[i]->tab_name);
940 n.replace("\n"," ");
941 save_file.write("\"");
942 save_file.write(n.toUtf8());
943 save_file.write("\"\n");
944 saveCsvData(&save_file, tabs_[i]->tree_widget);
945 save_file.write("\n");
947 break;
951 bool RtpAnalysisDialog::eventFilter(QObject *, QEvent *event)
953 if (event->type() != QEvent::KeyPress) return false;
955 QKeyEvent *kevt = static_cast<QKeyEvent *>(event);
957 switch(kevt->key()) {
958 case Qt::Key_G:
959 on_actionGoToPacket_triggered();
960 return true;
961 case Qt::Key_N:
962 on_actionNextProblem_triggered();
963 return true;
964 default:
965 break;
967 return false;
970 void RtpAnalysisDialog::showGraphMenu(const QPoint &pos)
972 graph_ctx_menu_.popup(ui->streamGraph->mapToGlobal(pos));
975 void RtpAnalysisDialog::graphClicked(QMouseEvent*)
977 updateWidgets();
980 void RtpAnalysisDialog::clearLayout(QLayout *layout)
982 if (layout) {
983 QLayoutItem *item;
985 //the key point here is that the layout items are stored inside the layout in a stack
986 while((item = layout->takeAt(0)) != 0) {
987 if (item->widget()) {
988 layout->removeWidget(item->widget());
989 delete item->widget();
992 delete item;
997 void RtpAnalysisDialog::closeTab(int index)
999 // Do not close last tab with graph
1000 if (index != tabs_.count()) {
1001 QWidget *remove_tab = qobject_cast<QWidget *>(ui->tabWidget->widget(index));
1002 tab_info_t *tab = tabs_[index];
1003 tab_hash_.remove(rtpstream_to_hash(&tab->stream), tab);
1004 tabs_.remove(index);
1005 ui->tabWidget->removeTab(index);
1006 ui->streamGraph->removeGraph(tab->jitter_graph);
1007 ui->streamGraph->removeGraph(tab->diff_graph);
1008 ui->streamGraph->removeGraph(tab->delta_graph);
1009 clearLayout(tab->graphHorizontalLayout);
1010 delete remove_tab;
1011 deleteTabInfo(tab);
1012 g_free(tab);
1014 updateGraph();
1018 void RtpAnalysisDialog::showStreamMenu(QPoint pos)
1020 tab_info_t *tab_data = getTabInfoForCurrentTab();
1021 if (!tab_data) return;
1023 QTreeWidget *cur_tree = tab_data->tree_widget;
1024 if (!cur_tree) return;
1026 updateWidgets();
1027 stream_ctx_menu_.popup(cur_tree->viewport()->mapToGlobal(pos));
1030 void RtpAnalysisDialog::replaceRtpStreams(QVector<rtpstream_id_t *> stream_ids)
1032 std::unique_lock<std::mutex> lock(run_mutex_, std::try_to_lock);
1033 if (lock.owns_lock()) {
1034 // Delete existing tabs (from last to first)
1035 if (tabs_.count() > 0) {
1036 for(int i = static_cast<int>(tabs_.count()); i>0; i--) {
1037 closeTab(i-1);
1040 addRtpStreamsPrivate(stream_ids);
1041 } else {
1042 ws_warning("replaceRtpStreams was called while other thread locked it. Current call is ignored, try it later.");
1046 void RtpAnalysisDialog::addRtpStreams(QVector<rtpstream_id_t *> stream_ids)
1048 std::unique_lock<std::mutex> lock(run_mutex_, std::try_to_lock);
1049 if (lock.owns_lock()) {
1050 addRtpStreamsPrivate(stream_ids);
1051 } else {
1052 ws_warning("addRtpStreams was called while other thread locked it. Current call is ignored, try it later.");
1056 void RtpAnalysisDialog::addRtpStreamsPrivate(QVector<rtpstream_id_t *> stream_ids)
1058 int first_tab_no = -1;
1060 setUpdatesEnabled(false);
1061 foreach(rtpstream_id_t *id, stream_ids) {
1062 bool found = false;
1064 QList<tab_info_t *> tabs = tab_hash_.values(rtpstream_id_to_hash(id));
1065 for (int i = 0; i < tabs.size(); i++) {
1066 tab_info_t *tab = tabs.at(i);
1067 if (rtpstream_id_equal(&tab->stream.id, id, RTPSTREAM_ID_EQUAL_SSRC)) {
1068 found = true;
1069 break;
1073 if (!found) {
1074 int cur_tab_no;
1076 tab_info_t *new_tab = g_new0(tab_info_t, 1);
1077 rtpstream_id_copy(id, &(new_tab->stream.id));
1078 new_tab->time_vals = new QVector<double>();
1079 new_tab->jitter_vals = new QVector<double>();
1080 new_tab->diff_vals = new QVector<double>();
1081 new_tab->delta_vals = new QVector<double>();
1082 tabs_ << new_tab;
1083 cur_tab_no = addTabUI(new_tab);
1084 tab_hash_.insert(rtpstream_id_to_hash(id), new_tab);
1085 if (first_tab_no == -1) {
1086 first_tab_no = cur_tab_no;
1090 if (first_tab_no != -1) {
1091 ui->tabWidget->setCurrentIndex(first_tab_no);
1093 setUpdatesEnabled(true);
1094 registerTapListener("rtp", this, NULL, 0, tapReset, tapPacket, tapDraw);
1095 cap_file_.retapPackets();
1096 updateStatistics();
1097 removeTapListeners();
1099 updateGraph();
1102 void RtpAnalysisDialog::removeRtpStreams(QVector<rtpstream_id_t *> stream_ids)
1104 std::unique_lock<std::mutex> lock(run_mutex_, std::try_to_lock);
1105 if (lock.owns_lock()) {
1106 setUpdatesEnabled(false);
1107 foreach(rtpstream_id_t *id, stream_ids) {
1108 QList<tab_info_t *> tabs = tab_hash_.values(rtpstream_id_to_hash(id));
1109 for (int i = 0; i < tabs.size(); i++) {
1110 tab_info_t *tab = tabs.at(i);
1111 if (rtpstream_id_equal(&tab->stream.id, id, RTPSTREAM_ID_EQUAL_SSRC)) {
1112 closeTab(static_cast<int>(tabs_.indexOf(tab)));
1116 setUpdatesEnabled(true);
1118 updateGraph();
1119 } else {
1120 ws_warning("removeRtpStreams was called while other thread locked it. Current call is ignored, try it later.");
1124 tab_info_t *RtpAnalysisDialog::getTabInfoForCurrentTab()
1126 tab_info_t *tab_data;
1128 if (file_closed_) return NULL;
1129 QWidget *cur_tab = qobject_cast<QWidget *>(ui->tabWidget->currentWidget());
1130 if (!cur_tab) return NULL;
1131 tab_data = static_cast<tab_info_t *>(cur_tab->property("tab_data").value<void*>());
1133 return tab_data;
1136 QToolButton *RtpAnalysisDialog::addAnalyzeButton(QDialogButtonBox *button_box, QDialog *dialog)
1138 if (!button_box) return NULL;
1140 QAction *ca;
1141 QToolButton *analysis_button = new QToolButton();
1142 button_box->addButton(analysis_button, QDialogButtonBox::ActionRole);
1143 analysis_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
1144 analysis_button->setPopupMode(QToolButton::MenuButtonPopup);
1146 ca = new QAction(tr("&Analyze"), analysis_button);
1147 ca->setToolTip(tr("Open the analysis window for the selected stream(s)"));
1148 connect(ca, SIGNAL(triggered()), dialog, SLOT(rtpAnalysisReplace()));
1149 analysis_button->setDefaultAction(ca);
1150 // Overrides text striping of shortcut undercode in QAction
1151 analysis_button->setText(ca->text());
1153 QMenu *button_menu = new QMenu(analysis_button);
1154 button_menu->setToolTipsVisible(true);
1155 ca = button_menu->addAction(tr("&Set List"));
1156 ca->setToolTip(tr("Replace existing list in RTP Analysis Dialog with new one"));
1157 connect(ca, SIGNAL(triggered()), dialog, SLOT(rtpAnalysisReplace()));
1158 ca = button_menu->addAction(tr("&Add to List"));
1159 ca->setToolTip(tr("Add new set to existing list in RTP Analysis Dialog"));
1160 connect(ca, SIGNAL(triggered()), dialog, SLOT(rtpAnalysisAdd()));
1161 ca = button_menu->addAction(tr("&Remove from List"));
1162 ca->setToolTip(tr("Remove selected streams from list in RTP Analysis Dialog"));
1163 connect(ca, SIGNAL(triggered()), dialog, SLOT(rtpAnalysisRemove()));
1164 analysis_button->setMenu(button_menu);
1166 return analysis_button;
1169 void RtpAnalysisDialog::on_actionPrepareFilterOne_triggered()
1171 if ((ui->tabWidget->currentIndex() < (ui->tabWidget->count()-1))) {
1172 QVector<rtpstream_id_t *> ids;
1173 ids << &(tabs_[ui->tabWidget->currentIndex()]->stream.id);
1174 QString filter = make_filter_based_on_rtpstream_id(ids);
1175 if (filter.length() > 0) {
1176 emit updateFilter(filter);
1181 void RtpAnalysisDialog::on_actionPrepareFilterAll_triggered()
1183 QVector<rtpstream_id_t *>ids = getSelectedRtpIds();
1184 QString filter = make_filter_based_on_rtpstream_id(ids);
1185 if (filter.length() > 0) {
1186 emit updateFilter(filter);