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
10 #include "rtp_analysis_dialog.h"
11 #include <ui_rtp_analysis_dialog.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>
38 #include <QToolButton>
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
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?
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
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
) {
92 delta_
= statinfo
->delta
;
93 jitter_
= statinfo
->jitter
;
94 skew_
= statinfo
->skew
;
96 bandwidth_
= statinfo
->bandwidth
;
97 marker_
= rtpinfo
->info_marker_set
? true : false;
100 QColor bg_color
= QColor();
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
);
135 bg_color
= color_pt_event_
;
137 if (statinfo
->flags
& STAT_FLAG_MARKER
) {
138 bg_color
= color_rtp_warn_
;
142 if (status
.isEmpty()) {
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
));
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() {
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()) {
195 return frame_num_
< other_row
->frame_num_
;
196 case (sequence_col_
):
197 return sequence_num_
< other_row
->sequence_num_
;
199 return delta_
< other_row
->delta_
;
201 return jitter_
< other_row
->jitter_
;
203 return skew_
< other_row
->skew_
;
204 case (bandwidth_col_
):
205 return bandwidth_
< other_row
->bandwidth_
;
210 // Fall back to string comparison
211 return QTreeWidgetItem::operator <(other
);
215 uint32_t sequence_num_
;
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)));
252 RtpAnalysisDialog::RtpAnalysisDialog(QWidget
&parent
, CaptureFile
&cf
) :
253 WiresharkDialog(parent
, cf
),
254 ui(new Ui::RtpAnalysisDialog
),
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
*>)));
316 RtpAnalysisDialog::~RtpAnalysisDialog()
318 std::lock_guard
<std::mutex
> lock(init_mutex_
);
319 if (pinstance_
!= nullptr) {
321 for(int i
=0; i
<tabs_
.count(); i
++) {
322 deleteTabInfo(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
)
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
);
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
;
483 new_state
= Qt::Checked
;
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
);
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
);
507 } else if (obj
== tab
->diff_checkbox
) {
508 tab
->diff_graph
->setVisible(checked
);
511 } else if (obj
== tab
->delta_checkbox
) {
512 tab
->delta_graph
->setVisible(checked
);
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)) {
530 if ((!file_closed_
) &&
531 (tabs_
.count() > 0) &&
532 (ui
->tabWidget
->currentIndex() < (ui
->tabWidget
->count()-1))) {
536 ui
->actionGoToPacket
->setEnabled(enable_nav
);
537 ui
->actionNextProblem
->setEnabled(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
);
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
);
603 test_ti
= cur_tree
->itemBelow(test_ti
);
604 if (!test_ti
) test_ti
= cur_tree
->topLevelItem(0);
608 void RtpAnalysisDialog::on_actionSaveOneCsv_triggered()
613 void RtpAnalysisDialog::on_actionSaveAllCsv_triggered()
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")
635 QString save_file
= path
.canonicalPath();
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?
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? */
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
);
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
);
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
);
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
;
854 for (int col
= 0; col
< tree
->columnCount(); col
++) {
855 row_data
<< tree
->headerItem()->text(col
);
857 foreach (QVariant v
, row_data
) {
860 } else if (v
.userType() == QMetaType::QString
) {
861 values
<< QStringLiteral("\"%1\"").arg(v
.toString());
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
);
877 foreach (QVariant v
, ra_ti
->rowData()) {
880 } else if (v
.userType() == QMetaType::QString
) {
881 values
<< QStringLiteral("\"%1\"").arg(v
.toString());
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
)
898 caption
= tr("Save one stream CSV");
902 caption
= tr("Save all stream's CSV");
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
);
918 tab_info_t
*tab_data
= getTabInfoForCurrentTab();
921 saveCsvHeader(&save_file
, tab_data
->tree_widget
);
923 QString n
= QString(*tab_data
->tab_name
);
925 save_file
.write("\"");
926 save_file
.write(n
.toUtf8());
927 save_file
.write("\"\n");
928 saveCsvData(&save_file
, tab_data
->tree_widget
);
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
);
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");
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()) {
959 on_actionGoToPacket_triggered();
962 on_actionNextProblem_triggered();
970 void RtpAnalysisDialog::showGraphMenu(const QPoint
&pos
)
972 graph_ctx_menu_
.popup(ui
->streamGraph
->mapToGlobal(pos
));
975 void RtpAnalysisDialog::graphClicked(QMouseEvent
*)
980 void RtpAnalysisDialog::clearLayout(QLayout
*layout
)
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();
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
);
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;
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
--) {
1040 addRtpStreamsPrivate(stream_ids
);
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
);
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
) {
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
)) {
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>();
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();
1097 removeTapListeners();
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);
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*>());
1136 QToolButton
*RtpAnalysisDialog::addAnalyzeButton(QDialogButtonBox
*button_box
, QDialog
*dialog
)
1138 if (!button_box
) return NULL
;
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
);