1 /* voip_calls_dialog.cpp
2  *
3  * Wireshark - Network traffic analyzer
4  * By Gerald Combs <gerald@wireshark.org>
5  * Copyright 1998 Gerald Combs
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  */
print_var(void)9 
10 #include "voip_calls_dialog.h"
11 #include <ui_voip_calls_dialog.h>
12 
13 #include "file.h"
14 
15 #include "epan/addr_resolv.h"
16 #include "epan/dissectors/packet-h225.h"
17 
18 #include "ui/rtp_stream.h"
19 #include "ui/rtp_stream_id.h"
20 
21 #include <ui/qt/utils/qt_ui_utils.h>
22 #include "rtp_player_dialog.h"
23 #include "sequence_dialog.h"
24 #include <ui/qt/utils/stock_icon.h>
25 #include "wireshark_application.h"
26 #include <ui/qt/models/voip_calls_info_model.h>
27 
28 #include <QClipboard>
29 #include <QContextMenuEvent>
30 #include <QToolButton>
31 
32 // To do:
33 // - More context menu items
34 //   - Don't select on right click
35 // - Player
36 // - Add a screenshot to the user's guide
37 // - Add filter for quickly searching through list?
38 
39 // Bugs:
40 // - Preparing a filter overwrites the existing filter. The GTK+ UI appends.
41 //   We'll probably have to add an "append" parameter to MainWindow::filterPackets.
42 
43 enum { voip_calls_type_ = 1000 };
44 
45 VoipCallsDialog *VoipCallsDialog::pinstance_voip_{nullptr};
46 VoipCallsDialog *VoipCallsDialog::pinstance_sip_{nullptr};
47 std::mutex VoipCallsDialog::mutex_;
48 
49 VoipCallsDialog *VoipCallsDialog::openVoipCallsDialogVoip(QWidget &parent, CaptureFile &cf, QObject *packet_list)
50 {
51     std::lock_guard<std::mutex> lock(mutex_);
52     if (pinstance_voip_ == nullptr)
53     {
54         pinstance_voip_ = new VoipCallsDialog(parent, cf, false);
55         connect(pinstance_voip_, SIGNAL(goToPacket(int)),
56                 packet_list, SLOT(goToPacket(int)));
57     }
58     return pinstance_voip_;
59 }
60 
61 VoipCallsDialog *VoipCallsDialog::openVoipCallsDialogSip(QWidget &parent, CaptureFile &cf, QObject *packet_list)
62 {
63     std::lock_guard<std::mutex> lock(mutex_);
64     if (pinstance_sip_ == nullptr)
65     {
66         pinstance_sip_ = new VoipCallsDialog(parent, cf, true);
67         connect(pinstance_sip_, SIGNAL(goToPacket(int)),
68                 packet_list, SLOT(goToPacket(int)));
69     }
70     return pinstance_sip_;
71 }
72 
73 VoipCallsDialog::VoipCallsDialog(QWidget &parent, CaptureFile &cf, bool all_flows) :
74     WiresharkDialog(parent, cf),
75     all_flows_(all_flows),
76     ui(new Ui::VoipCallsDialog),
77     parent_(parent),
78     voip_calls_tap_listeners_removed_(false)
79 {
80     ui->setupUi(this);
81     loadGeometry(parent.width() * 4 / 5, parent.height() * 2 / 3);
82     ui->callTreeView->installEventFilter(this);
83 
84     // Create the model that stores the actual data and the proxy model that is
85     // responsible for sorting and filtering data in the display.
86     call_infos_model_ = new VoipCallsInfoModel(this);
87     cache_model_ = new CacheProxyModel(this);
88     cache_model_->setSourceModel(call_infos_model_);
89     sorted_model_ = new VoipCallsInfoSortedModel(this);
90     sorted_model_->setSourceModel(cache_model_);
91     ui->callTreeView->setModel(sorted_model_);
92 
93     connect(ui->callTreeView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
94             this, SLOT(updateWidgets()));
95     ui->callTreeView->sortByColumn(VoipCallsInfoModel::StartTime, Qt::AscendingOrder);
96     setWindowSubtitle(all_flows_ ? tr("SIP Flows") : tr("VoIP Calls"));
97 
98     sequence_button_ = ui->buttonBox->addButton(ui->actionFlowSequence->text(), QDialogButtonBox::ActionRole);
99     sequence_button_->setToolTip(ui->actionFlowSequence->toolTip());
100     prepare_button_ = ui->buttonBox->addButton(ui->actionPrepareFilter->text(), QDialogButtonBox::ActionRole);
101     prepare_button_->setToolTip(ui->actionPrepareFilter->toolTip());
102     player_button_ = RtpPlayerDialog::addPlayerButton(ui->buttonBox, this);
103 
104     connect (ui->todCheckBox, &QAbstractButton::toggled, this, &VoipCallsDialog::switchTimeOfDay);
105 
106     copy_button_ = ui->buttonBox->addButton(ui->actionCopyButton->text(), QDialogButtonBox::ActionRole);
107     copy_button_->setToolTip(ui->actionCopyButton->toolTip());
108     QMenu *copy_menu = new QMenu(copy_button_);
109     QAction *ca;
110     ca = copy_menu->addAction(tr("as CSV"));
111     connect(ca, SIGNAL(triggered()), this, SLOT(copyAsCSV()));
112     ca = copy_menu->addAction(tr("as YAML"));
113     connect(ca, SIGNAL(triggered()), this, SLOT(copyAsYAML()));
114     copy_button_->setMenu(copy_menu);
115     connect(&cap_file_, SIGNAL(captureEvent(CaptureEvent)),
116             this, SLOT(captureEvent(CaptureEvent)));
117 
118     connect(this, SIGNAL(rtpStreamsDialogSelectRtpStreams(QVector<rtpstream_id_t *>)), &parent_, SLOT(rtpStreamsDialogSelectRtpStreams(QVector<rtpstream_id_t *>)));
119     connect(this, SIGNAL(rtpStreamsDialogDeselectRtpStreams(QVector<rtpstream_id_t *>)), &parent_, SLOT(rtpStreamsDialogDeselectRtpStreams(QVector<rtpstream_id_t *>)));
120 
121     memset (&tapinfo_, 0, sizeof(tapinfo_));
122     tapinfo_.tap_packet = tapPacket;
123     tapinfo_.tap_reset = tapReset;
124     tapinfo_.tap_draw = tapDraw;
125     tapinfo_.tap_data = this;
126     tapinfo_.callsinfos = g_queue_new();
127     tapinfo_.h225_cstype = H225_OTHER;
128     tapinfo_.fs_option = all_flows_ ? FLOW_ALL : FLOW_ONLY_INVITES; /* flow show option */
129     tapinfo_.graph_analysis = sequence_analysis_info_new();
130     tapinfo_.graph_analysis->name = "voip";
131     sequence_info_ = new SequenceInfo(tapinfo_.graph_analysis);
132     shown_callsinfos_ = g_queue_new();
133 
134     voip_calls_init_all_taps(&tapinfo_);
135     if (cap_file_.isValid() && cap_file_.capFile()->dfilter) {
136         // Activate display filter checking
137         tapinfo_.apply_display_filter = true;
138         ui->displayFilterCheckBox->setChecked(true);
139     }
140 
141     connect(this, SIGNAL(updateFilter(QString, bool)),
142             &parent, SLOT(filterPackets(QString, bool)));
143     connect(&parent, SIGNAL(displayFilterSuccess(bool)),
144             this, SLOT(displayFilterSuccess(bool)));
145     connect(this, SIGNAL(rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)),
146             &parent, SLOT(rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)));
147     connect(this, SIGNAL(rtpPlayerDialogAddRtpStreams(QVector<rtpstream_id_t *>)),
148             &parent, SLOT(rtpPlayerDialogAddRtpStreams(QVector<rtpstream_id_t *>)));
149     connect(this, SIGNAL(rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)),
150             &parent, SLOT(rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)));
151 
152     updateWidgets();
153 
154     if (cap_file_.isValid()) {
155         tapinfo_.session = cap_file_.capFile()->epan;
156         cap_file_.delayedRetapPackets();
157     }
158 }
159 
160 bool VoipCallsDialog::eventFilter(QObject *, QEvent *event)
161 {
162     if (ui->callTreeView->hasFocus() && event->type() == QEvent::KeyPress) {
163         QKeyEvent &keyEvent = static_cast<QKeyEvent&>(*event);
164         switch(keyEvent.key()) {
165             case Qt::Key_I:
166                 if (keyEvent.modifiers() == Qt::ControlModifier) {
167                     // Ctrl+I
168                     on_actionSelectInvert_triggered();
169                     return true;
170                 }
171                 break;
172             case Qt::Key_A:
173                 if (keyEvent.modifiers() == Qt::ControlModifier) {
174                     // Ctrl+A
175                     on_actionSelectAll_triggered();
176                     return true;
177                 } else if (keyEvent.modifiers() == (Qt::ShiftModifier | Qt::ControlModifier)) {
178                     // Ctrl+Shift+A
179                     on_actionSelectNone_triggered();
180                     return true;
181                 }
182                 break;
183             case Qt::Key_S:
184                 on_actionSelectRtpStreams_triggered();
185                 break;
186             case Qt::Key_D:
187                 on_actionDeselectRtpStreams_triggered();
188                 break;
189             default:
190                 break;
191         }
192     }
193     return false;
194 }
195 
196 VoipCallsDialog::~VoipCallsDialog()
197 {
198     std::lock_guard<std::mutex> lock(mutex_);
199     delete ui;
200 
201     voip_calls_reset_all_taps(&tapinfo_);
202     if (!voip_calls_tap_listeners_removed_) {
203         voip_calls_remove_all_tap_listeners(&tapinfo_);
204         voip_calls_tap_listeners_removed_ = true;
205     }
206     sequence_info_->unref();
207     g_queue_free(tapinfo_.callsinfos);
208     // We don't need to clear shown_callsinfos_ data, it was shared
209     // with tapinfo_.callsinfos and was cleared
210     // during voip_calls_reset_all_taps
211     g_queue_free(shown_callsinfos_);
212     if (all_flows_) {
213         pinstance_sip_ = nullptr;
214     } else {
215         pinstance_voip_ = nullptr;
216     }
217 }
218 
219 void VoipCallsDialog::removeTapListeners()
220 {
221     if (!voip_calls_tap_listeners_removed_) {
222         voip_calls_remove_all_tap_listeners(&tapinfo_);
223         voip_calls_tap_listeners_removed_ = true;
224     }
225     WiresharkDialog::removeTapListeners();
226 }
227 
228 void VoipCallsDialog::captureFileClosing()
229 {
230     // The time formatting is currently provided by VoipCallsInfoModel, but when
231     // the cache is active, the ToD cannot be modified.
232     cache_model_->setSourceModel(NULL);
233     if (!voip_calls_tap_listeners_removed_) {
234         voip_calls_remove_all_tap_listeners(&tapinfo_);
235         voip_calls_tap_listeners_removed_ = true;
236     }
237     tapinfo_.session = NULL;
238 
239     WiresharkDialog::captureFileClosing();
240 }
241 
242 void VoipCallsDialog::captureFileClosed()
243 {
244     // The time formatting is currently provided by VoipCallsInfoModel, but when
245     // the cache is active, the ToD cannot be modified.
246     ui->todCheckBox->setEnabled(false);
247     ui->displayFilterCheckBox->setEnabled(false);
248 
249     WiresharkDialog::captureFileClosed();
250 }
251 
252 void VoipCallsDialog::contextMenuEvent(QContextMenuEvent *event)
253 {
254     bool selected = ui->callTreeView->selectionModel()->hasSelection();
255 
256     if (! selected)
257         return;
258 
259     QMenu popupMenu;
260     QAction *action;
261 
262     popupMenu.addMenu(ui->menuSelect);
263     action = popupMenu.addAction(tr("Display time as time of day"), this, SLOT(switchTimeOfDay()));
264     action->setCheckable(true);
265     action->setChecked(call_infos_model_->timeOfDay());
266     action->setEnabled(!file_closed_);
267     popupMenu.addSeparator();
268     action = popupMenu.addAction(tr("Copy as CSV"), this, SLOT(copyAsCSV()));
269     action->setToolTip(tr("Copy stream list as CSV."));
270     action = popupMenu.addAction(tr("Copy as YAML"), this, SLOT(copyAsYAML()));
271     action->setToolTip(tr("Copy stream list as YAML."));
272     popupMenu.addSeparator();
273     popupMenu.addAction(ui->actionSelectRtpStreams);
274     popupMenu.addAction(ui->actionDeselectRtpStreams);
275 
276     popupMenu.exec(event->globalPos());
277 }
278 
279 void VoipCallsDialog::changeEvent(QEvent *event)
280 {
281     if (0 != event)
282     {
283         switch (event->type())
284         {
285         case QEvent::LanguageChange:
286             ui->retranslateUi(this);
287             break;
288         default:
289             break;
290         }
291     }
292     QDialog::changeEvent(event);
293 }
294 
295 void VoipCallsDialog::captureEvent(CaptureEvent e)
296 {
297     if (e.captureContext() == CaptureEvent::Retap)
298     {
299         switch (e.eventType())
300         {
301         case CaptureEvent::Started:
302             ui->displayFilterCheckBox->setEnabled(false);
303             break;
304         case CaptureEvent::Finished:
305             ui->displayFilterCheckBox->setEnabled(true);
306             break;
307         default:
308             break;
309         }
310     }
311 
312 }
313 
314 void VoipCallsDialog::tapReset(void *tapinfo_ptr)
315 {
316     voip_calls_tapinfo_t *tapinfo = static_cast<voip_calls_tapinfo_t *>(tapinfo_ptr);
317     VoipCallsDialog *voip_calls_dialog = static_cast<VoipCallsDialog *>(tapinfo->tap_data);
318 
319     // Create new callsinfos queue in tapinfo. Current callsinfos are
320     // in shown_callsinfos_.
321     voip_calls_dialog->tapinfo_.callsinfos = g_queue_new();
322     voip_calls_reset_all_taps(tapinfo);
323 
324     // Leave old graph_analysis as is and allocate new one
325     voip_calls_dialog->sequence_info_->unref();
326     voip_calls_dialog->tapinfo_.graph_analysis = sequence_analysis_info_new();
327     voip_calls_dialog->tapinfo_.graph_analysis->name = "voip";
328     voip_calls_dialog->sequence_info_ = new SequenceInfo(voip_calls_dialog->tapinfo_.graph_analysis);
329 }
330 
331 tap_packet_status VoipCallsDialog::tapPacket(void *, packet_info *, epan_dissect_t *, const void *)
332 {
333 #ifdef QT_MULTIMEDIA_LIB
334 //    voip_calls_tapinfo_t *tapinfo = (voip_calls_tapinfo_t *) tapinfo_ptr;
335     // add_rtp_packet for voip player.
336 //    return TAP_PACKET_REDRAW;
337 #endif
338     return TAP_PACKET_DONT_REDRAW;
339 }
340 
341 void VoipCallsDialog::tapDraw(void *tapinfo_ptr)
342 {
343     voip_calls_tapinfo_t *tapinfo = static_cast<voip_calls_tapinfo_t *>(tapinfo_ptr);
344 
345     if (!tapinfo || !tapinfo->redraw) {
346         return;
347     }
348 
349     GList *graph_item = g_queue_peek_nth_link(tapinfo->graph_analysis->items, 0);
350     for (; graph_item; graph_item = gxx_list_next(graph_item)) {
351         for (GList *rsi_entry = g_list_first(tapinfo->rtpstream_list); rsi_entry; rsi_entry = gxx_list_next(rsi_entry)) {
352             seq_analysis_item_t * sai = gxx_list_data(seq_analysis_item_t *, graph_item);
353             rtpstream_info_t *rsi = gxx_list_data(rtpstream_info_t *, rsi_entry);
354 
355             if (rsi->start_fd->num == sai->frame_number) {
356                 rsi->call_num = sai->conv_num;
357                 // VOIP_CALLS_DEBUG("setting conv num %u for frame %u", sai->conv_num, sai->frame_number);
358             }
359         }
360     }
361 
362     VoipCallsDialog *voip_calls_dialog = static_cast<VoipCallsDialog *>(tapinfo->tap_data);
363     if (voip_calls_dialog) {
364         voip_calls_dialog->updateCalls();
365     }
366 }
367 
368 gint VoipCallsDialog::compareCallNums(gconstpointer a, gconstpointer b)
369 {
370     const voip_calls_info_t *call_a = (const voip_calls_info_t *)a;
371     const voip_calls_info_t *call_b = (const voip_calls_info_t *)b;
372 
373     return (call_a->call_num != call_b->call_num);
374 }
375 
376 void VoipCallsDialog::updateCalls()
377 {
378     voip_calls_info_t *new_callsinfo;
379     voip_calls_info_t *old_callsinfo;
380     GList *found;
381 
382     ui->callTreeView->setSortingEnabled(false);
383 
384     // Merge new callsinfos with old ones
385     // It keeps list of calls visible including selected items
386     GList *list = g_queue_peek_nth_link(tapinfo_.callsinfos, 0);
387     while (list) {
388         // Find new callsinfo
389         new_callsinfo = gxx_list_data(voip_calls_info_t*, list);
390         found = g_queue_find_custom(shown_callsinfos_, new_callsinfo, VoipCallsDialog::compareCallNums);
391         if (!found) {
392             // New call, add it to list for show
393             g_queue_push_tail(shown_callsinfos_, new_callsinfo);
394         } else {
395             // Existing call
396             old_callsinfo = (voip_calls_info_t *)found->data;
397             if (new_callsinfo != old_callsinfo) {
398                 // Replace it
399                 voip_calls_free_callsinfo(old_callsinfo);
400                 found->data = new_callsinfo;
401             }
402         }
403 
404         list = gxx_list_next(list);
405     }
406 
407     // Update model
408     call_infos_model_->updateCalls(shown_callsinfos_);
409 
410     // Resize columns
411     for (int i = 0; i < call_infos_model_->columnCount(); i++) {
412         ui->callTreeView->resizeColumnToContents(i);
413     }
414 
415     ui->callTreeView->setSortingEnabled(true);
416 
417     updateWidgets();
418 }
419 
420 void VoipCallsDialog::updateWidgets()
421 {
422     bool selected = ui->callTreeView->selectionModel()->hasSelection();
423     bool have_ga_items = false;
424 
425     if (tapinfo_.graph_analysis && tapinfo_.graph_analysis->items) {
426         have_ga_items = true;
427     }
428 
429     bool enable = selected && have_ga_items && !file_closed_;
430 
431     prepare_button_->setEnabled(enable);
432     sequence_button_->setEnabled(enable);
433     ui->actionSelectRtpStreams->setEnabled(enable);
434     ui->actionDeselectRtpStreams->setEnabled(enable);
435 #if defined(QT_MULTIMEDIA_LIB)
436     player_button_->setEnabled(enable);
437 #endif
438 
439     WiresharkDialog::updateWidgets();
440 }
441 
442 void VoipCallsDialog::prepareFilter()
443 {
444     if (!ui->callTreeView->selectionModel()->hasSelection() || !tapinfo_.graph_analysis) {
445         return;
446     }
447 
448     QString filter_str;
449     QSet<guint16> selected_calls;
450     QString frame_numbers;
451     QList<int> rows;
452 
453     /* Build a new filter based on frame numbers */
454     foreach (QModelIndex index, ui->callTreeView->selectionModel()->selectedIndexes()) {
455         if (index.isValid() && ! rows.contains(index.row()))
456         {
457             voip_calls_info_t *call_info = VoipCallsInfoModel::indexToCallInfo(index);
458             if (!call_info) {
459                 return;
460             }
461 
462             selected_calls << call_info->call_num;
463             rows << index.row();
464         }
465     }
466 
467     GList *cur_ga_item = g_queue_peek_nth_link(tapinfo_.graph_analysis->items, 0);
468     while (cur_ga_item && cur_ga_item->data) {
469         seq_analysis_item_t *ga_item = gxx_list_data(seq_analysis_item_t*, cur_ga_item);
470         if (selected_calls.contains(ga_item->conv_num)) {
471             frame_numbers += QString("%1,").arg(ga_item->frame_number);
472         }
473         cur_ga_item = gxx_list_next(cur_ga_item);
474     }
475 
476     if (!frame_numbers.isEmpty()) {
477         frame_numbers.chop(1);
478         filter_str = QString("frame.number in {%1} or rtp.setup-frame in {%1}").arg(frame_numbers);
479     }
480 
481 #if 0
482     // XXX The GTK+ UI falls back to building a filter based on protocols if the filter
483     // length is too long. Leaving this here for the time being in case we need to do
484     // the same in the Qt UI.
485     const sip_calls_info_t *sipinfo;
486     const isup_calls_info_t *isupinfo;
487     const h323_calls_info_t *h323info;
488     const h245_address_t *h245_add = NULL;
489     const gcp_ctx_t* ctx;
490     char *guid_str;
491 
492     if (filter_length < max_filter_length) {
493         gtk_editable_insert_text(GTK_EDITABLE(main_display_filter_widget), filter_string_fwd->str, -1, &pos);
494     } else {
495         g_string_free(filter_string_fwd, TRUE);
496         filter_string_fwd = g_string_new(filter_prepend);
497 
498         g_string_append_printf(filter_string_fwd, "(");
499         is_first = TRUE;
500         /* Build a new filter based on protocol fields */
501         lista = g_queue_peek_nth_link(voip_calls_get_info()->callsinfos, 0);
502         while (lista) {
503             listinfo = gxx_list_data(voip_calls_info_t *, lista);
504             if (listinfo->selected) {
505                 if (!is_first)
506                     g_string_append_printf(filter_string_fwd, " or ");
507                 switch (listinfo->protocol) {
508                 case VOIP_SIP:
509                     sipinfo = (sip_calls_info_t *)listinfo->prot_info;
510                     g_string_append_printf(filter_string_fwd,
511                         "(sip.Call-ID == \"%s\")",
512                         sipinfo->call_identifier
513                     );
514                     break;
515                 case VOIP_ISUP:
516                     isupinfo = (isup_calls_info_t *)listinfo->prot_info;
517                     g_string_append_printf(filter_string_fwd,
518                         "(isup.cic == %i and frame.number >= %i and frame.number <= %i and mtp3.network_indicator == %i and ((mtp3.dpc == %i) and (mtp3.opc == %i)) or ((mtp3.dpc == %i) and (mtp3.opc == %i)))",
519                         isupinfo->cic, listinfo->start_fd->num,
520                         listinfo->stop_fd->num,
521                         isupinfo->ni, isupinfo->dpc, isupinfo->opc,
522                         isupinfo->opc, isupinfo->dpc
523                     );
524                     break;
525                 case VOIP_H323:
526                 {
527                     h323info = (h323_calls_info_t *)listinfo->prot_info;
528 					guid_str = guid_to_str(NULL, &h323info->guid[0]);
529                     g_string_append_printf(filter_string_fwd,
530                         "((h225.guid == %s || q931.call_ref == %x:%x || q931.call_ref == %x:%x)",
531                         guid_str,
532                         (guint8) (h323info->q931_crv & 0x00ff),
533                         (guint8)((h323info->q931_crv & 0xff00)>>8),
534                         (guint8) (h323info->q931_crv2 & 0x00ff),
535                         (guint8)((h323info->q931_crv2 & 0xff00)>>8));
536                     listb = g_list_first(h323info->h245_list);
537 					wmem_free(NULL, guid_str);
538                     while (listb) {
539                         h245_add = gxx_list_data(h245_address_t *, listb);
540                         g_string_append_printf(filter_string_fwd,
541                             " || (ip.addr == %s && tcp.port == %d && h245)",
542                             address_to_qstring(&h245_add->h245_address), h245_add->h245_port);
543                         listb = gxx_list_next(listb);
544                     }
545                     g_string_append_printf(filter_string_fwd, ")");
546                 }
547                     break;
548                 case TEL_H248:
549                     ctx = (gcp_ctx_t *)listinfo->prot_info;
550                     g_string_append_printf(filter_string_fwd,
551                         "(h248.ctx == 0x%x)", ctx->id);
552                     break;
553                 default:
554                     /* placeholder to assure valid display filter expression */
555                     g_string_append_printf(filter_string_fwd,
556                         "(frame)");
557                     break;
558                 }
559                 is_first = FALSE;
560             }
561             lista = gxx_list_next(lista);
562         }
563 
564         g_string_append_printf(filter_string_fwd, ")");
565         gtk_editable_insert_text(GTK_EDITABLE(main_display_filter_widget), filter_string_fwd->str, -1, &pos);
566     }
567 #endif
568 
569     emit updateFilter(filter_str);
570 }
571 
572 void VoipCallsDialog::showSequence()
573 {
574     if (file_closed_) return;
575 
576     QSet<guint16> selected_calls;
577     foreach (QModelIndex index, ui->callTreeView->selectionModel()->selectedIndexes()) {
578         voip_calls_info_t *call_info = VoipCallsInfoModel::indexToCallInfo(index);
579         if (!call_info) {
580             return;
581         }
582         selected_calls << call_info->call_num;
583     }
584 
585     sequence_analysis_list_sort(tapinfo_.graph_analysis);
586     GList *cur_ga_item = g_queue_peek_nth_link(tapinfo_.graph_analysis->items, 0);
587     while (cur_ga_item && cur_ga_item->data) {
588         seq_analysis_item_t *ga_item = gxx_list_data(seq_analysis_item_t*, cur_ga_item);
589         ga_item->display = selected_calls.contains(ga_item->conv_num);
590         cur_ga_item = gxx_list_next(cur_ga_item);
591     }
592 
593     SequenceDialog *sequence_dialog = new SequenceDialog(parent_, cap_file_, sequence_info_);
594     // Bypass this dialog and forward signals to parent
595     connect(sequence_dialog, SIGNAL(rtpStreamsDialogSelectRtpStreams(QVector<rtpstream_id_t *>)), &parent_, SLOT(rtpStreamsDialogSelectRtpStreams(QVector<rtpstream_id_t *>)));
596     connect(sequence_dialog, SIGNAL(rtpStreamsDialogDeselectRtpStreams(QVector<rtpstream_id_t *>)), &parent_, SLOT(rtpStreamsDialogDeselectRtpStreams(QVector<rtpstream_id_t *>)));
597     connect(sequence_dialog, SIGNAL(rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)), &parent_, SLOT(rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)));
598     connect(sequence_dialog, SIGNAL(rtpPlayerDialogAddRtpStreams(QVector<rtpstream_id_t *>)), &parent_, SLOT(rtpPlayerDialogAddRtpStreams(QVector<rtpstream_id_t *>)));
599     connect(sequence_dialog, SIGNAL(rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)), &parent_, SLOT(rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)));
600 
601     sequence_dialog->setAttribute(Qt::WA_DeleteOnClose);
602     sequence_dialog->enableVoIPFeatures();
603     sequence_dialog->show();
604 }
605 
606 QVector<rtpstream_id_t *>VoipCallsDialog::getSelectedRtpIds()
607 {
608     QVector<rtpstream_id_t *> stream_ids;
609     foreach (QModelIndex index, ui->callTreeView->selectionModel()->selectedIndexes()) {
610         voip_calls_info_t *vci = VoipCallsInfoModel::indexToCallInfo(index);
611         if (!vci) continue;
612 
613         for (GList *rsi_entry = g_list_first(tapinfo_.rtpstream_list); rsi_entry; rsi_entry = gxx_list_next(rsi_entry)) {
614             rtpstream_info_t *rsi = gxx_list_data(rtpstream_info_t *, rsi_entry);
615             if (!rsi) continue;
616 
617             //VOIP_CALLS_DEBUG("checking call %u, start frame %u == stream call %u, start frame %u, setup frame %u",
618             //                vci->call_num, vci->start_fd->num,
619             //                rsi->call_num, rsi->start_fd->num, rsi->setup_frame_number);
620             if (vci->call_num == static_cast<guint>(rsi->call_num)) {
621                 //VOIP_CALLS_DEBUG("adding call number %u", vci->call_num);
622                 if (-1 == stream_ids.indexOf(&(rsi->id))) {
623                     // Add only new stream
624                     stream_ids << &(rsi->id);
625                 }
626             }
627         }
628     }
629 
630     return stream_ids;
631 }
632 
633 void VoipCallsDialog::rtpPlayerReplace()
634 {
635     if (ui->callTreeView->selectionModel()->selectedIndexes().count() < 1) return;
636 
637     emit rtpPlayerDialogReplaceRtpStreams(getSelectedRtpIds());
638 }
639 
640 void VoipCallsDialog::rtpPlayerAdd()
641 {
642     if (ui->callTreeView->selectionModel()->selectedIndexes().count() < 1) return;
643 
644     emit rtpPlayerDialogAddRtpStreams(getSelectedRtpIds());
645 }
646 
647 void VoipCallsDialog::rtpPlayerRemove()
648 {
649     if (ui->callTreeView->selectionModel()->selectedIndexes().count() < 1) return;
650 
651     emit rtpPlayerDialogRemoveRtpStreams(getSelectedRtpIds());
652 }
653 
654 QList<QVariant> VoipCallsDialog::streamRowData(int row) const
655 {
656     QList<QVariant> row_data;
657 
658     if (row >= sorted_model_->rowCount()) {
659         return row_data;
660     }
661 
662     for (int col = 0; col < sorted_model_->columnCount(); col++) {
663         if (row < 0) {
664             row_data << sorted_model_->headerData(col, Qt::Horizontal);
665         } else {
666             row_data << sorted_model_->index(row, col).data();
667         }
668     }
669     return row_data;
670 }
671 
672 void VoipCallsDialog::on_callTreeView_activated(const QModelIndex &index)
673 {
674     voip_calls_info_t *call_info = VoipCallsInfoModel::indexToCallInfo(index);
675     if (!call_info) {
676         return;
677     }
678     emit goToPacket(call_info->start_fd->num);
679 }
680 
681 void VoipCallsDialog::selectAll()
682 {
683     ui->callTreeView->selectAll();
684 }
685 
686 void VoipCallsDialog::selectNone()
687 {
688     ui->callTreeView->clearSelection();
689 }
690 
691 void VoipCallsDialog::copyAsCSV()
692 {
693     QString csv;
694     QTextStream stream(&csv, QIODevice::Text);
695     for (int row = -1; row < sorted_model_->rowCount(); row++) {
696         QStringList rdsl;
697         foreach (QVariant v, streamRowData(row)) {
698             QString strval = v.toString();
699             // XXX should quotes (") in strval be stripped/sanitized?
700             rdsl << QString("\"%1\"").arg(strval);
701         }
702         stream << rdsl.join(",") << '\n';
703     }
704     wsApp->clipboard()->setText(stream.readAll());
705 }
706 
707 void VoipCallsDialog::copyAsYAML()
708 {
709     QString yaml;
710     QTextStream stream(&yaml, QIODevice::Text);
711     stream << "---" << '\n';
712     for (int row = -1; row < sorted_model_->rowCount(); row++) {
713         stream << "-" << '\n';
714         foreach (QVariant v, streamRowData(row)) {
715             stream << " - " << v.toString() << '\n';
716         }
717     }
718     wsApp->clipboard()->setText(stream.readAll());
719 }
720 
721 void VoipCallsDialog::on_buttonBox_clicked(QAbstractButton *button)
722 {
723     if (button == prepare_button_) {
724         prepareFilter();
725     } else if (button == sequence_button_) {
726         showSequence();
727     }
728 }
729 
730 void VoipCallsDialog::removeAllCalls()
731 {
732     voip_calls_info_t *callsinfo;
733     GList *list = NULL;
734 
735     call_infos_model_->removeAllCalls();
736 
737     /* Free shown callsinfos */
738     list = g_queue_peek_nth_link(shown_callsinfos_, 0);
739     while (list)
740     {
741         callsinfo = (voip_calls_info_t *)list->data;
742         voip_calls_free_callsinfo(callsinfo);
743         list = g_list_next(list);
744     }
745     g_queue_clear(shown_callsinfos_);
746 }
747 
748 void VoipCallsDialog::on_displayFilterCheckBox_toggled(bool checked)
749 {
750     if (!cap_file_.isValid()) {
751         return;
752     }
753 
754     tapinfo_.apply_display_filter = checked;
755     removeAllCalls();
756 
757     cap_file_.retapPackets();
758 }
759 
760 void VoipCallsDialog::on_buttonBox_helpRequested()
761 {
762     wsApp->helpTopicAction(HELP_TELEPHONY_VOIP_CALLS_DIALOG);
763 }
764 
765 void VoipCallsDialog::switchTimeOfDay()
766 {
767     bool checked = ! call_infos_model_->timeOfDay();
768 
769     ui->todCheckBox->setChecked(checked);
770     call_infos_model_->setTimeOfDay(checked);
771     ui->callTreeView->resizeColumnToContents(VoipCallsInfoModel::StartTime);
772     ui->callTreeView->resizeColumnToContents(VoipCallsInfoModel::StopTime);
773 }
774 
775 void VoipCallsDialog::displayFilterSuccess(bool success)
776 {
777     if (success && ui->displayFilterCheckBox->isChecked()) {
778         removeAllCalls();
779         cap_file_.retapPackets();
780     }
781 }
782 
783 void VoipCallsDialog::invertSelection()
784 {
785     QModelIndex rootIndex = ui->callTreeView->rootIndex();
786     QModelIndex first = sorted_model_->index(0, 0, QModelIndex());
787     int numOfItems = sorted_model_->rowCount(rootIndex);
788     int numOfCols = sorted_model_->columnCount(rootIndex);
789     QModelIndex last = sorted_model_->index(numOfItems - 1, numOfCols - 1, QModelIndex());
790 
791     QItemSelection selection(first, last);
792     ui->callTreeView->selectionModel()->select(selection, QItemSelectionModel::Toggle);
793 }
794 
795 void VoipCallsDialog::on_actionSelectAll_triggered()
796 {
797     ui->callTreeView->selectAll();
798 }
799 
800 void VoipCallsDialog::on_actionSelectInvert_triggered()
801 {
802     invertSelection();
803 }
804 
805 void VoipCallsDialog::on_actionSelectNone_triggered()
806 {
807     ui->callTreeView->clearSelection();
808 }
809 
810 void VoipCallsDialog::on_actionSelectRtpStreams_triggered()
811 {
812     QVector<rtpstream_id_t *>stream_ids = qvector_rtpstream_ids_copy(getSelectedRtpIds());
813 
814     emit rtpStreamsDialogSelectRtpStreams(stream_ids);
815 
816     qvector_rtpstream_ids_free(stream_ids);
817     raise();
818 }
819 
820 void VoipCallsDialog::on_actionDeselectRtpStreams_triggered()
821 {
822     QVector<rtpstream_id_t *>stream_ids = qvector_rtpstream_ids_copy(getSelectedRtpIds());
823 
824     emit rtpStreamsDialogDeselectRtpStreams(stream_ids);
825 
826     qvector_rtpstream_ids_free(stream_ids);
827     raise();
828 }
829 
830