Put the structure of a capture_file back in cfile.h.
[gd/wireshark/.git] / ui / qt / iax2_analysis_dialog.cpp
1 /* iax2_analysis_dialog.cpp
2  *
3  * Wireshark - Network traffic analyzer
4  * By Gerald Combs <gerald@wireshark.org>
5  * Copyright 1998 Gerald Combs
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "iax2_analysis_dialog.h"
23 #include <ui_iax2_analysis_dialog.h>
24
25 #include "file.h"
26 #include "frame_tvbuff.h"
27
28 #include <epan/epan_dissect.h>
29 #include <epan/rtp_pt.h>
30
31 #include <epan/dfilter/dfilter.h>
32
33 #include <epan/dissectors/packet-iax2.h>
34
35 #include "ui/help_url.h"
36 #ifdef IAX2_RTP_STREAM_CHECK
37 #include "ui/rtp_stream.h"
38 #endif
39 #include <wsutil/utf8_entities.h>
40
41 #include <wsutil/g711.h>
42 #include <wsutil/pint.h>
43
44 #include <QFileDialog>
45 #include <QMessageBox>
46 #include <QPushButton>
47 #include <QTemporaryFile>
48
49 #include <ui/qt/utils/color_utils.h>
50 #include <ui/qt/utils/qt_ui_utils.h>
51 #include <ui/qt/utils/stock_icon.h>
52 #include "wireshark_application.h"
53
54 /*
55  * @file RTP stream analysis dialog
56  *
57  * Displays forward and reverse RTP streams and graphs each stream
58  */
59
60 // To do:
61 // - Progress bar for tapping and saving.
62 // - Add a refresh button and/or action.
63 // - Fixup output file names.
64 // - Add a graph title and legend when saving?
65
66 enum {
67     packet_col_,
68     delta_col_,
69     jitter_col_,
70     bandwidth_col_,
71     status_col_,
72     length_col_
73 };
74
75 static const QRgb color_rtp_warn_ = 0xffdbbf;
76
77 enum { iax2_analysis_type_ = 1000 };
78 class Iax2AnalysisTreeWidgetItem : public QTreeWidgetItem
79 {
80 public:
81     Iax2AnalysisTreeWidgetItem(QTreeWidget *tree, tap_iax2_stat_t *statinfo, packet_info *pinfo) :
82         QTreeWidgetItem(tree, iax2_analysis_type_)
83     {
84         frame_num_ = pinfo->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         } else {
91             delta_ = statinfo->delta;
92             jitter_ = statinfo->jitter;
93         }
94         bandwidth_ = statinfo->bandwidth;
95         ok_ = false;
96
97         QColor bg_color = QColor();
98         QString status;
99
100         if (statinfo->flags & STAT_FLAG_WRONG_SEQ) {
101             status = QObject::tr("Wrong sequence number");
102             bg_color = ColorUtils::expert_color_error;
103         } else if (statinfo->flags & STAT_FLAG_REG_PT_CHANGE) {
104             status = QObject::tr("Payload changed to PT=%1").arg(statinfo->pt);
105             bg_color = color_rtp_warn_;
106         } else if (statinfo->flags & STAT_FLAG_WRONG_TIMESTAMP) {
107             status = QObject::tr("Incorrect timestamp");
108             /* color = COLOR_WARNING; */
109             bg_color = color_rtp_warn_;
110         } else if ((statinfo->flags & STAT_FLAG_PT_CHANGE)
111             &&  !(statinfo->flags & STAT_FLAG_FIRST)
112             &&  !(statinfo->flags & STAT_FLAG_PT_CN)
113             &&  (statinfo->flags & STAT_FLAG_FOLLOW_PT_CN)
114             &&  !(statinfo->flags & STAT_FLAG_MARKER)) {
115             status = QObject::tr("Marker missing?");
116             bg_color = color_rtp_warn_;
117         } else {
118             if (statinfo->flags & STAT_FLAG_MARKER) {
119                 bg_color = color_rtp_warn_;
120             }
121         }
122
123         if (status.isEmpty()) {
124             ok_ = true;
125             status = UTF8_CHECK_MARK;
126         }
127
128         setText(packet_col_, QString::number(frame_num_));
129         setText(delta_col_, QString::number(delta_, 'f', 2));
130         setText(jitter_col_, QString::number(jitter_, 'f', 2));
131         setText(bandwidth_col_, QString::number(bandwidth_, 'f', 2));
132         setText(status_col_, status);
133         setText(length_col_, QString::number(pkt_len_));
134
135         setTextAlignment(packet_col_, Qt::AlignRight);
136         setTextAlignment(delta_col_, Qt::AlignRight);
137         setTextAlignment(jitter_col_, Qt::AlignRight);
138         setTextAlignment(bandwidth_col_, Qt::AlignRight);
139         setTextAlignment(length_col_, Qt::AlignRight);
140
141         if (bg_color.isValid()) {
142             for (int col = 0; col < columnCount(); col++) {
143                 setBackground(col, bg_color);
144                 setForeground(col, ColorUtils::expert_color_foreground);
145             }
146         }
147     }
148
149     guint32 frameNum() { return frame_num_; }
150     bool frameStatus() { return ok_; }
151
152     QList<QVariant> rowData() {
153         QString status_str = ok_ ? "OK" : text(status_col_);
154
155         return QList<QVariant>()
156                 << frame_num_ << delta_ << jitter_ << bandwidth_
157                 << status_str << pkt_len_;
158     }
159
160     bool operator< (const QTreeWidgetItem &other) const
161     {
162         if (other.type() != iax2_analysis_type_) return QTreeWidgetItem::operator< (other);
163         const Iax2AnalysisTreeWidgetItem *other_row = static_cast<const Iax2AnalysisTreeWidgetItem *>(&other);
164
165         switch (treeWidget()->sortColumn()) {
166         case (packet_col_):
167             return frame_num_ < other_row->frame_num_;
168             break;
169         case (delta_col_):
170             return delta_ < other_row->delta_;
171             break;
172         case (jitter_col_):
173             return jitter_ < other_row->jitter_;
174             break;
175         case (bandwidth_col_):
176             return bandwidth_ < other_row->bandwidth_;
177             break;
178         case (length_col_):
179             return pkt_len_ < other_row->pkt_len_;
180             break;
181         default:
182             break;
183         }
184
185         // Fall back to string comparison
186         return QTreeWidgetItem::operator <(other);
187     }
188 private:
189     guint32 frame_num_;
190     guint32 pkt_len_;
191     guint32 flags_;
192     double delta_;
193     double jitter_;
194     double bandwidth_;
195     bool ok_;
196 };
197
198 enum {
199     fwd_jitter_graph_,
200     fwd_diff_graph_,
201     rev_jitter_graph_,
202     rev_diff_graph_,
203     num_graphs_
204 };
205
206 Iax2AnalysisDialog::Iax2AnalysisDialog(QWidget &parent, CaptureFile &cf) :
207     WiresharkDialog(parent, cf),
208     ui(new Ui::Iax2AnalysisDialog),
209     port_src_fwd_(0),
210     port_dst_fwd_(0),
211     port_src_rev_(0),
212     port_dst_rev_(0),
213     save_payload_error_(TAP_IAX2_NO_ERROR)
214 {
215     ui->setupUi(this);
216     loadGeometry(parent.width() * 4 / 5, parent.height() * 4 / 5);
217     setWindowSubtitle(tr("IAX2 Stream Analysis"));
218
219     ui->progressFrame->hide();
220
221     stream_ctx_menu_.addAction(ui->actionGoToPacket);
222     stream_ctx_menu_.addAction(ui->actionNextProblem);
223     stream_ctx_menu_.addSeparator();
224     stream_ctx_menu_.addAction(ui->actionSaveAudio);
225     stream_ctx_menu_.addAction(ui->actionSaveForwardAudio);
226     stream_ctx_menu_.addAction(ui->actionSaveReverseAudio);
227     stream_ctx_menu_.addSeparator();
228     stream_ctx_menu_.addAction(ui->actionSaveCsv);
229     stream_ctx_menu_.addAction(ui->actionSaveForwardCsv);
230     stream_ctx_menu_.addAction(ui->actionSaveReverseCsv);
231     stream_ctx_menu_.addSeparator();
232     stream_ctx_menu_.addAction(ui->actionSaveGraph);
233     ui->forwardTreeWidget->installEventFilter(this);
234     ui->forwardTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
235     connect(ui->forwardTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
236                 SLOT(showStreamMenu(QPoint)));
237     ui->reverseTreeWidget->installEventFilter(this);
238     ui->reverseTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
239     connect(ui->reverseTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
240                 SLOT(showStreamMenu(QPoint)));
241     connect(ui->streamGraph, SIGNAL(mousePress(QMouseEvent*)),
242             this, SLOT(graphClicked(QMouseEvent*)));
243
244     graph_ctx_menu_.addAction(ui->actionSaveGraph);
245
246     QStringList header_labels;
247     for (int i = 0; i < ui->forwardTreeWidget->columnCount(); i++) {
248         header_labels << ui->forwardTreeWidget->headerItem()->text(i);
249     }
250     ui->reverseTreeWidget->setHeaderLabels(header_labels);
251
252     memset(&src_fwd_, 0, sizeof(address));
253     memset(&dst_fwd_, 0, sizeof(address));
254     memset(&src_rev_, 0, sizeof(address));
255     memset(&dst_rev_, 0, sizeof(address));
256
257     QList<QCheckBox *> graph_cbs = QList<QCheckBox *>()
258             << ui->fJitterCheckBox << ui->fDiffCheckBox
259             << ui->rJitterCheckBox << ui->rDiffCheckBox;
260
261     for (int i = 0; i < num_graphs_; i++) {
262         QCPGraph *graph = ui->streamGraph->addGraph();
263         graph->setPen(QPen(ColorUtils::graphColor(i)));
264         graph->setName(graph_cbs[i]->text());
265         graphs_ << graph;
266         graph_cbs[i]->setChecked(true);
267         graph_cbs[i]->setIcon(StockIcon::colorIcon(ColorUtils::graphColor(i), QPalette::Text));
268     }
269     ui->streamGraph->xAxis->setLabel("Arrival Time");
270     ui->streamGraph->yAxis->setLabel("Value (ms)");
271
272     // We keep our temp files open for the lifetime of the dialog. The GTK+
273     // UI opens and closes at various points.
274     QString tempname = QString("%1/wireshark_iax2_f").arg(QDir::tempPath());
275     fwd_tempfile_ = new QTemporaryFile(tempname, this);
276     fwd_tempfile_->open();
277     tempname = QString("%1/wireshark_iax2_r").arg(QDir::tempPath());
278     rev_tempfile_ = new QTemporaryFile(tempname, this);
279     rev_tempfile_->open();
280
281     if (fwd_tempfile_->error() != QFile::NoError || rev_tempfile_->error() != QFile::NoError) {
282         err_str_ = tr("Unable to save RTP data.");
283         ui->actionSaveAudio->setEnabled(false);
284         ui->actionSaveForwardAudio->setEnabled(false);
285         ui->actionSaveReverseAudio->setEnabled(false);
286     }
287
288     QMenu *save_menu = new QMenu();
289     save_menu->addAction(ui->actionSaveAudio);
290     save_menu->addAction(ui->actionSaveForwardAudio);
291     save_menu->addAction(ui->actionSaveReverseAudio);
292     save_menu->addSeparator();
293     save_menu->addAction(ui->actionSaveCsv);
294     save_menu->addAction(ui->actionSaveForwardCsv);
295     save_menu->addAction(ui->actionSaveReverseCsv);
296     save_menu->addSeparator();
297     save_menu->addAction(ui->actionSaveGraph);
298     ui->buttonBox->button(QDialogButtonBox::Save)->setMenu(save_menu);
299
300     ui->buttonBox->button(QDialogButtonBox::Close)->setDefault(true);
301
302     resetStatistics();
303     updateStatistics(); // Initialize stats if an error occurs
304
305 #if 0
306     /* Only accept Voice or MiniPacket packets */
307     const gchar filter_text[] = "iax2.call && (ip || ipv6)";
308 #else
309     const gchar filter_text[] = "iax2 && (ip || ipv6)";
310 #endif
311     dfilter_t *sfcode;
312     gchar *err_msg;
313
314     /* Try to compile the filter. */
315     if (!dfilter_compile(filter_text, &sfcode, &err_msg)) {
316         err_str_ = QString(err_msg);
317         g_free(err_msg);
318         updateWidgets();
319         return;
320     }
321
322     if (!cap_file_.capFile() || !cap_file_.capFile()->current_frame) {
323         err_str_ = tr("Please select an IAX2 packet.");
324         save_payload_error_ = TAP_IAX2_NO_PACKET_SELECTED;
325         updateWidgets();
326         return;
327     }
328
329     frame_data *fdata = cap_file_.capFile()->current_frame;
330
331     if (!cf_read_record(cap_file_.capFile(), fdata)) close();
332
333     epan_dissect_t edt;
334
335     epan_dissect_init(&edt, cap_file_.capFile()->epan, TRUE, FALSE);
336     epan_dissect_prime_with_dfilter(&edt, sfcode);
337     epan_dissect_run(&edt, cap_file_.capFile()->cd_t, &cap_file_.capFile()->phdr,
338                      frame_tvbuff_new_buffer(fdata, &cap_file_.capFile()->buf), fdata, NULL);
339
340     // This shouldn't happen (the menu item should be disabled) but check anyway
341     if (!dfilter_apply_edt(sfcode, &edt)) {
342         epan_dissect_cleanup(&edt);
343         dfilter_free(sfcode);
344         err_str_ = tr("Please select an IAX2 packet.");
345         save_payload_error_ = TAP_IAX2_NO_PACKET_SELECTED;
346         updateWidgets();
347         return;
348     }
349
350     dfilter_free(sfcode);
351
352     /* ok, it is a IAX2 frame, so let's get the ip and port values */
353     copy_address(&(src_fwd_), &(edt.pi.src));
354     copy_address(&(dst_fwd_), &(edt.pi.dst));
355     port_src_fwd_ = edt.pi.srcport;
356     port_dst_fwd_ = edt.pi.destport;
357
358     /* assume the inverse ip/port combination for the reverse direction */
359     copy_address(&(src_rev_), &(edt.pi.dst));
360     copy_address(&(dst_rev_), &(edt.pi.src));
361     port_src_rev_ = edt.pi.destport;
362     port_dst_rev_ = edt.pi.srcport;
363
364 #ifdef IAX2_RTP_STREAM_CHECK
365     rtpstream_tapinfot tapinfo;
366
367     /* Register the tap listener */
368     memset(&tapinfo, 0, sizeof(rtpstream_tapinfot));
369     tapinfo.tap_data = this;
370     tapinfo.mode = TAP_ANALYSE;
371
372 //    register_tap_listener_rtp_stream(&tapinfo, NULL);
373     /* Scan for RTP streams (redissect all packets) */
374     rtpstream_scan(&tapinfo, cap_file_.capFile(), NULL);
375
376     int num_streams = 0;
377     GList *filtered_list = NULL;
378     for (GList *strinfo_list = g_list_first(tapinfo.strinfo_list); strinfo_list; strinfo_list = g_list_next(strinfo_list)) {
379         rtp_stream_info_t * strinfo = (rtp_stream_info_t*)(strinfo_list->data);
380                  << address_to_qstring(&strinfo->dest_addr) << address_to_qstring(&src_rev_) << address_to_qstring(&dst_rev_);
381         if (addresses_equal(&(strinfo->src_addr), &(src_fwd_))
382             && (strinfo->src_port == port_src_fwd_)
383             && (addresses_equal(&(strinfo->dest_addr), &(dst_fwd_)))
384             && (strinfo->dest_port == port_dst_fwd_))
385         {
386             ++num_streams;
387             filtered_list = g_list_prepend(filtered_list, strinfo);
388         }
389
390         if (addresses_equal(&(strinfo->src_addr), &(src_rev_))
391             && (strinfo->src_port == port_src_rev_)
392             && (addresses_equal(&(strinfo->dest_addr), &(dst_rev_)))
393             && (strinfo->dest_port == port_dst_rev_))
394         {
395             ++num_streams;
396             filtered_list = g_list_append(filtered_list, strinfo);
397         }
398     }
399
400     if (num_streams > 1) {
401         // Open the RTP streams dialog.
402     }
403 #endif
404
405     connect(ui->tabWidget, SIGNAL(currentChanged(int)),
406             this, SLOT(updateWidgets()));
407     connect(ui->forwardTreeWidget, SIGNAL(itemSelectionChanged()),
408             this, SLOT(updateWidgets()));
409     connect(ui->reverseTreeWidget, SIGNAL(itemSelectionChanged()),
410             this, SLOT(updateWidgets()));
411     connect(&cap_file_, SIGNAL(captureFileClosing()),
412             this, SLOT(updateWidgets()));
413     updateWidgets();
414
415     registerTapListener("IAX2", this, NULL, 0, tapReset, tapPacket, tapDraw);
416     cap_file_.retapPackets();
417     removeTapListeners();
418
419     updateStatistics();
420 }
421
422 Iax2AnalysisDialog::~Iax2AnalysisDialog()
423 {
424     delete ui;
425 //    remove_tap_listener_rtp_stream(&tapinfo);
426     delete fwd_tempfile_;
427     delete rev_tempfile_;
428 }
429
430 void Iax2AnalysisDialog::updateWidgets()
431 {
432     bool enable_tab = false;
433     QString hint = err_str_;
434
435     if (hint.isEmpty() || save_payload_error_ != TAP_IAX2_NO_ERROR) {
436         /* We cannot save the payload but can still display the widget
437            or save CSV data */
438         enable_tab = true;
439     }
440
441     bool enable_nav = false;
442     if (!file_closed_
443             && ((ui->tabWidget->currentWidget() == ui->forwardTreeWidget
444                  && ui->forwardTreeWidget->selectedItems().length() > 0)
445                 || (ui->tabWidget->currentWidget() == ui->reverseTreeWidget
446                     && ui->reverseTreeWidget->selectedItems().length() > 0))) {
447         enable_nav = true;
448     }
449     ui->actionGoToPacket->setEnabled(enable_nav);
450     ui->actionNextProblem->setEnabled(enable_nav);
451
452     if (enable_nav) {
453         hint.append(tr(" G: Go to packet, N: Next problem packet"));
454     }
455
456     bool enable_save_fwd_audio = fwd_tempfile_->isOpen() && (save_payload_error_ == TAP_IAX2_NO_ERROR);
457     bool enable_save_rev_audio = rev_tempfile_->isOpen() && (save_payload_error_ == TAP_IAX2_NO_ERROR);
458     ui->actionSaveAudio->setEnabled(enable_save_fwd_audio && enable_save_rev_audio);
459     ui->actionSaveForwardAudio->setEnabled(enable_save_fwd_audio);
460     ui->actionSaveReverseAudio->setEnabled(enable_save_rev_audio);
461
462     bool enable_save_fwd_csv = ui->forwardTreeWidget->topLevelItemCount() > 0;
463     bool enable_save_rev_csv = ui->reverseTreeWidget->topLevelItemCount() > 0;
464     ui->actionSaveCsv->setEnabled(enable_save_fwd_csv && enable_save_rev_csv);
465     ui->actionSaveForwardCsv->setEnabled(enable_save_fwd_csv);
466     ui->actionSaveReverseCsv->setEnabled(enable_save_rev_csv);
467
468     ui->tabWidget->setEnabled(enable_tab);
469     hint.prepend("<small><i>");
470     hint.append("</i></small>");
471     ui->hintLabel->setText(hint);
472
473     WiresharkDialog::updateWidgets();
474 }
475
476 void Iax2AnalysisDialog::on_actionGoToPacket_triggered()
477 {
478     if (file_closed_) return;
479     QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
480     if (!cur_tree || cur_tree->selectedItems().length() < 1) return;
481
482     QTreeWidgetItem *ti = cur_tree->selectedItems()[0];
483     if (ti->type() != iax2_analysis_type_) return;
484
485     Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)ti);
486     emit goToPacket(ra_ti->frameNum());
487 }
488
489 void Iax2AnalysisDialog::on_actionNextProblem_triggered()
490 {
491     QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
492     if (!cur_tree || cur_tree->topLevelItemCount() < 2) return;
493
494     // Choose convenience over correctness.
495     if (cur_tree->selectedItems().length() < 1) {
496         cur_tree->setCurrentItem(cur_tree->topLevelItem(0));
497     }
498
499     QTreeWidgetItem *sel_ti = cur_tree->selectedItems()[0];
500     if (sel_ti->type() != iax2_analysis_type_) return;
501     QTreeWidgetItem *test_ti = cur_tree->itemBelow(sel_ti);
502     while (test_ti != sel_ti) {
503         if (!test_ti) test_ti = cur_tree->topLevelItem(0);
504         Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)test_ti);
505         if (!ra_ti->frameStatus()) {
506             cur_tree->setCurrentItem(ra_ti);
507             break;
508         }
509
510         test_ti = cur_tree->itemBelow(test_ti);
511     }
512 }
513
514 void Iax2AnalysisDialog::on_fJitterCheckBox_toggled(bool checked)
515 {
516     ui->streamGraph->graph(fwd_jitter_graph_)->setVisible(checked);
517     updateGraph();
518 }
519
520 void Iax2AnalysisDialog::on_fDiffCheckBox_toggled(bool checked)
521 {
522     ui->streamGraph->graph(fwd_diff_graph_)->setVisible(checked);
523     updateGraph();
524 }
525
526 void Iax2AnalysisDialog::on_rJitterCheckBox_toggled(bool checked)
527 {
528     ui->streamGraph->graph(rev_jitter_graph_)->setVisible(checked);
529     updateGraph();
530 }
531
532 void Iax2AnalysisDialog::on_rDiffCheckBox_toggled(bool checked)
533 {
534     ui->streamGraph->graph(rev_diff_graph_)->setVisible(checked);
535     updateGraph();
536 }
537
538 void Iax2AnalysisDialog::on_actionSaveAudio_triggered()
539 {
540     saveAudio(dir_both_);
541 }
542
543 void Iax2AnalysisDialog::on_actionSaveForwardAudio_triggered()
544 {
545     saveAudio(dir_forward_);
546 }
547
548 void Iax2AnalysisDialog::on_actionSaveReverseAudio_triggered()
549 {
550     saveAudio(dir_reverse_);
551 }
552
553 void Iax2AnalysisDialog::on_actionSaveCsv_triggered()
554 {
555     saveCsv(dir_both_);
556 }
557
558 void Iax2AnalysisDialog::on_actionSaveForwardCsv_triggered()
559 {
560     saveCsv(dir_forward_);
561 }
562
563 void Iax2AnalysisDialog::on_actionSaveReverseCsv_triggered()
564 {
565     saveCsv(dir_reverse_);
566 }
567
568 void Iax2AnalysisDialog::on_actionSaveGraph_triggered()
569 {
570     ui->tabWidget->setCurrentWidget(ui->graphTab);
571
572     QString file_name, extension;
573     QDir path(wsApp->lastOpenDir());
574     QString pdf_filter = tr("Portable Document Format (*.pdf)");
575     QString png_filter = tr("Portable Network Graphics (*.png)");
576     QString bmp_filter = tr("Windows Bitmap (*.bmp)");
577     // Gaze upon my beautiful graph with lossy artifacts!
578     QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
579     QString filter = QString("%1;;%2;;%3;;%4")
580             .arg(pdf_filter)
581             .arg(png_filter)
582             .arg(bmp_filter)
583             .arg(jpeg_filter);
584
585     QString save_file = path.canonicalPath();
586     if (!file_closed_) {
587         save_file += QString("/%1").arg(cap_file_.fileTitle());
588     }
589     file_name = QFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Graph As" UTF8_HORIZONTAL_ELLIPSIS)),
590                                              save_file, filter, &extension);
591
592     if (!file_name.isEmpty()) {
593         bool save_ok = false;
594         // http://www.qcustomplot.com/index.php/support/forum/63
595 //        ui->streamGraph->legend->setVisible(true);
596         if (extension.compare(pdf_filter) == 0) {
597             save_ok = ui->streamGraph->savePdf(file_name);
598         } else if (extension.compare(png_filter) == 0) {
599             save_ok = ui->streamGraph->savePng(file_name);
600         } else if (extension.compare(bmp_filter) == 0) {
601             save_ok = ui->streamGraph->saveBmp(file_name);
602         } else if (extension.compare(jpeg_filter) == 0) {
603             save_ok = ui->streamGraph->saveJpg(file_name);
604         }
605 //        ui->streamGraph->legend->setVisible(false);
606         // else error dialog?
607         if (save_ok) {
608             path = QDir(file_name);
609             wsApp->setLastOpenDir(path.canonicalPath().toUtf8().constData());
610         }
611     }
612 }
613
614 void Iax2AnalysisDialog::on_buttonBox_helpRequested()
615 {
616     wsApp->helpTopicAction(HELP_IAX2_ANALYSIS_DIALOG);
617 }
618
619 void Iax2AnalysisDialog::tapReset(void *tapinfoptr)
620 {
621     Iax2AnalysisDialog *iax2_analysis_dialog = dynamic_cast<Iax2AnalysisDialog *>((Iax2AnalysisDialog*)tapinfoptr);
622     if (!iax2_analysis_dialog) return;
623
624     iax2_analysis_dialog->resetStatistics();
625 }
626
627 gboolean Iax2AnalysisDialog::tapPacket(void *tapinfoptr, packet_info *pinfo, struct epan_dissect *, const void *iax2info_ptr)
628 {
629     Iax2AnalysisDialog *iax2_analysis_dialog = dynamic_cast<Iax2AnalysisDialog *>((Iax2AnalysisDialog*)tapinfoptr);
630     if (!iax2_analysis_dialog) return FALSE;
631
632     const iax2_info_t *iax2info = (const iax2_info_t *)iax2info_ptr;
633     if (!iax2info) return FALSE;
634
635     /* we ignore packets that are not displayed */
636     if (pinfo->fd->flags.passed_dfilter == 0)
637         return FALSE;
638
639     /* we ignore packets that carry no data */
640     if (iax2info->payload_len < 1)
641         return FALSE;
642
643     /* is it the forward direction?  */
644     else if ((cmp_address(&(iax2_analysis_dialog->src_fwd_), &(pinfo->src)) == 0)
645          && (iax2_analysis_dialog->port_src_fwd_ == pinfo->srcport)
646          && (cmp_address(&(iax2_analysis_dialog->dst_fwd_), &(pinfo->dst)) == 0)
647          && (iax2_analysis_dialog->port_dst_fwd_ == pinfo->destport))  {
648
649         iax2_analysis_dialog->addPacket(true, pinfo, iax2info);
650     }
651     /* is it the reversed direction? */
652     else if ((cmp_address(&(iax2_analysis_dialog->src_rev_), &(pinfo->src)) == 0)
653          && (iax2_analysis_dialog->port_src_rev_ == pinfo->srcport)
654          && (cmp_address(&(iax2_analysis_dialog->dst_rev_), &(pinfo->dst)) == 0)
655          && (iax2_analysis_dialog->port_dst_rev_ == pinfo->destport))  {
656
657         iax2_analysis_dialog->addPacket(false, pinfo, iax2info);
658     }
659     return FALSE;
660 }
661
662 void Iax2AnalysisDialog::tapDraw(void *tapinfoptr)
663 {
664     Iax2AnalysisDialog *iax2_analysis_dialog = dynamic_cast<Iax2AnalysisDialog *>((Iax2AnalysisDialog*)tapinfoptr);
665     if (!iax2_analysis_dialog) return;
666     iax2_analysis_dialog->updateStatistics();
667 }
668
669 void Iax2AnalysisDialog::resetStatistics()
670 {
671     memset(&fwd_statinfo_, 0, sizeof(fwd_statinfo_));
672     memset(&rev_statinfo_, 0, sizeof(rev_statinfo_));
673
674     fwd_statinfo_.first_packet = TRUE;
675     rev_statinfo_.first_packet = TRUE;
676     fwd_statinfo_.reg_pt = PT_UNDEFINED;
677     rev_statinfo_.reg_pt = PT_UNDEFINED;
678
679     ui->forwardTreeWidget->clear();
680     ui->reverseTreeWidget->clear();
681
682     for (int i = 0; i < ui->streamGraph->graphCount(); i++) {
683         ui->streamGraph->graph(i)->clearData();
684     }
685
686     fwd_time_vals_.clear();
687     fwd_jitter_vals_.clear();
688     fwd_diff_vals_.clear();
689     rev_time_vals_.clear();
690     rev_jitter_vals_.clear();
691     rev_diff_vals_.clear();
692
693     fwd_tempfile_->resize(0);
694     rev_tempfile_->resize(0);
695 }
696
697 void Iax2AnalysisDialog::addPacket(bool forward, packet_info *pinfo, const struct _iax2_info_t *iax2info)
698 {
699     /* add this RTP for future listening using the RTP Player*/
700 //    add_rtp_packet(rtpinfo, pinfo);
701
702     if (forward) {
703         iax2_packet_analyse(&fwd_statinfo_, pinfo, iax2info);
704         new Iax2AnalysisTreeWidgetItem(ui->forwardTreeWidget, &fwd_statinfo_, pinfo);
705
706         fwd_time_vals_.append((fwd_statinfo_.time));
707         fwd_jitter_vals_.append(fwd_statinfo_.jitter * 1000);
708         fwd_diff_vals_.append(fwd_statinfo_.diff * 1000);
709
710         savePayload(fwd_tempfile_, pinfo, iax2info);
711     } else {
712         iax2_packet_analyse(&rev_statinfo_, pinfo, iax2info);
713         new Iax2AnalysisTreeWidgetItem(ui->reverseTreeWidget, &rev_statinfo_, pinfo);
714
715         rev_time_vals_.append((rev_statinfo_.time));
716         rev_jitter_vals_.append(rev_statinfo_.jitter * 1000);
717         rev_diff_vals_.append(rev_statinfo_.diff * 1000);
718
719         savePayload(rev_tempfile_, pinfo, iax2info);
720     }
721
722 }
723
724 // iax2_analysis.c:rtp_packet_save_payload
725 const guint8 silence_pcmu_ = 0xff;
726 const guint8 silence_pcma_ = 0x55;
727 void Iax2AnalysisDialog::savePayload(QTemporaryFile *tmpfile, packet_info *pinfo, const struct _iax2_info_t *iax2info)
728 {
729     /* Is this the first packet we got in this direction? */
730 //    if (statinfo->flags & STAT_FLAG_FIRST) {
731 //        if (saveinfo->fp == NULL) {
732 //            saveinfo->saved = FALSE;
733 //            saveinfo->error_type = TAP_RTP_FILE_OPEN_ERROR;
734 //        } else {
735 //            saveinfo->saved = TRUE;
736 //        }
737 //    }
738
739     /* Save the voice information */
740
741     /* If there was already an error, we quit */
742     if (!tmpfile->isOpen() || tmpfile->error() != QFile::NoError) return;
743
744     /* Quit if the captured length and packet length aren't equal.
745      */
746     if (pinfo->fd->pkt_len != pinfo->fd->cap_len) {
747         tmpfile->close();
748         err_str_ = tr("Can't save in a file: Wrong length of captured packets.");
749         save_payload_error_ = TAP_IAX2_WRONG_LENGTH;
750         return;
751     }
752
753     if (iax2info->payload_len > 0) {
754         const char *data = (const char *) iax2info->payload_data;
755         size_t nchars;
756
757         nchars = tmpfile->write(data, iax2info->payload_len);
758         if (nchars != (iax2info->payload_len)) {
759             /* Write error or short write */
760             err_str_ = tr("Can't save in a file: File I/O problem.");
761             save_payload_error_ = TAP_IAX2_FILE_IO_ERROR;
762             tmpfile->close();
763             return;
764         }
765         return;
766     }
767     return;
768 }
769
770 void Iax2AnalysisDialog::updateStatistics()
771 {
772     double f_duration = fwd_statinfo_.time - fwd_statinfo_.start_time; // s
773     double r_duration = rev_statinfo_.time - rev_statinfo_.start_time;
774 #if 0 // Marked as "TODO" in tap-iax2-analysis.c:128
775     unsigned int f_expected = fwd_statinfo_.stop_seq_nr - fwd_statinfo_.start_seq_nr + 1;
776     unsigned int r_expected = rev_statinfo_.stop_seq_nr - rev_statinfo_.start_seq_nr + 1;
777     int f_lost = f_expected - fwd_statinfo_.total_nr;
778     int r_lost = r_expected - rev_statinfo_.total_nr;
779     double f_perc, r_perc;
780
781     if (f_expected) {
782         f_perc = (double)(f_lost*100)/(double)f_expected;
783     } else {
784         f_perc = 0;
785     }
786     if (r_expected) {
787         r_perc = (double)(r_lost*100)/(double)r_expected;
788     } else {
789         r_perc = 0;
790     }
791 #endif
792
793     QString stats_tables = "<html><head></head><body>\n";
794     stats_tables += QString("<p>%1:%2 " UTF8_LEFT_RIGHT_ARROW)
795             .arg(address_to_qstring(&src_fwd_, true))
796             .arg(port_src_fwd_);
797     stats_tables += QString("<br>%1:%2</p>\n")
798             .arg(address_to_qstring(&dst_fwd_, true))
799             .arg(port_dst_fwd_);
800     stats_tables += "<h4>Forward</h4>\n";
801     stats_tables += "<p><table>\n";
802     stats_tables += QString("<tr><th align=\"left\">Max Delta</th><td>%1 ms @ %2</td></tr>")
803             .arg(fwd_statinfo_.max_delta, 0, 'f', 2)
804             .arg(fwd_statinfo_.max_nr);
805     stats_tables += QString("<tr><th align=\"left\">Max Jitter</th><td>%1 ms</tr>")
806             .arg(fwd_statinfo_.max_jitter, 0, 'f', 2);
807     stats_tables += QString("<tr><th align=\"left\">Mean Jitter</th><td>%1 ms</tr>")
808             .arg(fwd_statinfo_.mean_jitter, 0, 'f', 2);
809     stats_tables += QString("<tr><th align=\"left\">IAX2 Packets</th><td>%1</tr>")
810             .arg(fwd_statinfo_.total_nr);
811 #if 0
812     stats_tables += QString("<tr><th align=\"left\">Expected</th><td>%1</tr>")
813             .arg(f_expected);
814     stats_tables += QString("<tr><th align=\"left\">Lost</th><td>%1 (%2 %)</tr>")
815             .arg(f_lost).arg(f_perc, 0, 'f', 2);
816     stats_tables += QString("<tr><th align=\"left\">Seq Errs</th><td>%1</tr>")
817             .arg(fwd_statinfo_.sequence);
818 #endif
819     stats_tables += QString("<tr><th align=\"left\">Duration</th><td>%1 s</tr>")
820             .arg(f_duration, 0, 'f', 2);
821     stats_tables += "</table></p>\n";
822
823     stats_tables += "<h4>Reverse</h4>\n";
824     stats_tables += "<p><table>\n";
825     stats_tables += QString("<tr><th align=\"left\">Max Delta</th><td>%1 ms @ %2</td></tr>")
826             .arg(rev_statinfo_.max_delta, 0, 'f', 2)
827             .arg(rev_statinfo_.max_nr);
828     stats_tables += QString("<tr><th align=\"left\">Max Jitter</th><td>%1 ms</tr>")
829             .arg(rev_statinfo_.max_jitter, 0, 'f', 2);
830     stats_tables += QString("<tr><th align=\"left\">Mean Jitter</th><td>%1 ms</tr>")
831             .arg(rev_statinfo_.mean_jitter, 0, 'f', 2);
832     stats_tables += QString("<tr><th align=\"left\">IAX2 Packets</th><td>%1</tr>")
833             .arg(rev_statinfo_.total_nr);
834 #if 0
835     stats_tables += QString("<tr><th align=\"left\">Expected</th><td>%1</tr>")
836             .arg(r_expected);
837     stats_tables += QString("<tr><th align=\"left\">Lost</th><td>%1 (%2 %)</tr>")
838             .arg(r_lost).arg(r_perc, 0, 'f', 2);
839     stats_tables += QString("<tr><th align=\"left\">Seq Errs</th><td>%1</tr>")
840             .arg(rev_statinfo_.sequence);
841 #endif
842     stats_tables += QString("<tr><th align=\"left\">Duration</th><td>%1 s</tr>")
843             .arg(r_duration, 0, 'f', 2);
844     stats_tables += "</table></p></body>\n";
845
846     ui->statisticsLabel->setText(stats_tables);
847
848     for (int col = 0; col < ui->forwardTreeWidget->columnCount() - 1; col++) {
849         ui->forwardTreeWidget->resizeColumnToContents(col);
850         ui->reverseTreeWidget->resizeColumnToContents(col);
851     }
852
853     graphs_[fwd_jitter_graph_]->setData(fwd_time_vals_, fwd_jitter_vals_);
854     graphs_[fwd_diff_graph_]->setData(fwd_time_vals_, fwd_diff_vals_);
855     graphs_[rev_jitter_graph_]->setData(rev_time_vals_, rev_jitter_vals_);
856     graphs_[rev_diff_graph_]->setData(rev_time_vals_, rev_diff_vals_);
857
858     updateGraph();
859
860     updateWidgets();
861 }
862
863 void Iax2AnalysisDialog::updateGraph()
864 {
865     for (int i = 0; i < ui->streamGraph->graphCount(); i++) {
866         if (ui->streamGraph->graph(i)->visible()) {
867             ui->streamGraph->graph(i)->rescaleAxes(i > 0);
868         }
869     }
870     ui->streamGraph->replot();
871 }
872
873 // iax2_analysis.c:copy_file
874 enum { save_audio_none_, save_audio_au_, save_audio_raw_ };
875 void Iax2AnalysisDialog::saveAudio(Iax2AnalysisDialog::StreamDirection direction)
876 {
877     if (!fwd_tempfile_->isOpen() || !rev_tempfile_->isOpen()) return;
878
879     QString caption;
880
881     switch (direction) {
882     case dir_forward_:
883         caption = tr("Save forward stream audio");
884         break;
885     case dir_reverse_:
886         caption = tr("Save reverse stream audio");
887         break;
888     case dir_both_:
889     default:
890         caption = tr("Save audio");
891         break;
892     }
893
894     QString ext_filter = tr("Sun Audio (*.au)");
895     if (direction != dir_both_) {
896         ext_filter.append(tr(";;Raw (*.raw)"));
897     }
898     QString sel_filter;
899     QString file_path = QFileDialog::getSaveFileName(
900                 this, caption, wsApp->lastOpenDir().absoluteFilePath("Saved RTP Audio.au"),
901                 ext_filter, &sel_filter);
902
903     if (file_path.isEmpty()) return;
904
905     int save_format = save_audio_none_;
906     if (file_path.endsWith(".au")) {
907         save_format = save_audio_au_;
908     } else if (file_path.endsWith(".raw")) {
909         save_format = save_audio_raw_;
910     }
911
912     if (save_format == save_audio_none_) {
913         QMessageBox::warning(this, tr("Warning"), tr("Unable to save in that format"));
914         return;
915     }
916
917     QFile      save_file(file_path);
918     gint16     sample;
919     gchar      pd[4];
920     gboolean   stop_flag = FALSE;
921     size_t     nchars;
922
923     save_file.open(QIODevice::WriteOnly);
924     fwd_tempfile_->seek(0);
925     rev_tempfile_->seek(0);
926
927     if (save_file.error() != QFile::NoError) {
928         QMessageBox::warning(this, tr("Warning"), tr("Unable to save %1").arg(save_file.fileName()));
929         return;
930     }
931
932     ui->hintLabel->setText(tr("Saving %1" UTF8_HORIZONTAL_ELLIPSIS).arg(save_file.fileName()));
933     ui->progressFrame->showProgress(true, true, &stop_flag);
934
935     if  (save_format == save_audio_au_) { /* au format */
936         /* First we write the .au header. XXX Hope this is endian independent */
937         /* the magic word 0x2e736e64 == .snd */
938         phton32(pd, 0x2e736e64);
939         nchars = save_file.write((const char *)pd, 4);
940         if (nchars != 4)
941             goto copy_file_err;
942         /* header offset == 24 bytes */
943         phton32(pd, 24);
944         nchars = save_file.write((const char *)pd, 4);
945         if (nchars != 4)
946             goto copy_file_err;
947         /* total length; it is permitted to set this to 0xffffffff */
948         phton32(pd, 0xffffffff);
949         nchars = save_file.write((const char *)pd, 4);
950         if (nchars != 4)
951             goto copy_file_err;
952         /* encoding format == 16-bit linear PCM */
953         phton32(pd, 3);
954         nchars = save_file.write((const char *)pd, 4);
955         if (nchars != 4)
956             goto copy_file_err;
957         /* sample rate == 8000 Hz */
958         phton32(pd, 8000);
959         nchars = save_file.write((const char *)pd, 4);
960         if (nchars != 4)
961             goto copy_file_err;
962         /* channels == 1 */
963         phton32(pd, 1);
964         nchars = save_file.write((const char *)pd, 4);
965         if (nchars != 4)
966             goto copy_file_err;
967
968         switch (direction) {
969         /* Only forward direction */
970         case dir_forward_:
971         {
972             char f_rawvalue;
973             while (fwd_tempfile_->getChar(&f_rawvalue)) {
974                 if (stop_flag) {
975                     break;
976                 }
977                 ui->progressFrame->setValue(int(fwd_tempfile_->pos() * 100 / fwd_tempfile_->size()));
978
979                 if (fwd_statinfo_.pt == PT_PCMU) {
980                     sample = ulaw2linear((unsigned char)f_rawvalue);
981                     phton16(pd, sample);
982                 } else if (fwd_statinfo_.pt == PT_PCMA) {
983                     sample = alaw2linear((unsigned char)f_rawvalue);
984                     phton16(pd, sample);
985                 } else {
986                     goto copy_file_err;
987                 }
988
989                 nchars = save_file.write((const char *)pd, 2);
990                 if (nchars < 2) {
991                     goto copy_file_err;
992                 }
993             }
994             break;
995         }
996             /* Only reverse direction */
997         case dir_reverse_:
998         {
999             char r_rawvalue;
1000             while (rev_tempfile_->getChar(&r_rawvalue)) {
1001                 if (stop_flag) {
1002                     break;
1003                 }
1004                 ui->progressFrame->setValue(int(rev_tempfile_->pos() * 100 / rev_tempfile_->size()));
1005
1006                 if (rev_statinfo_.pt == PT_PCMU) {
1007                     sample = ulaw2linear((unsigned char)r_rawvalue);
1008                     phton16(pd, sample);
1009                 } else if (rev_statinfo_.pt == PT_PCMA) {
1010                     sample = alaw2linear((unsigned char)r_rawvalue);
1011                     phton16(pd, sample);
1012                 } else {
1013                     goto copy_file_err;
1014                 }
1015
1016                 nchars = save_file.write((const char *)pd, 2);
1017                 if (nchars < 2) {
1018                     goto copy_file_err;
1019                 }
1020             }
1021             break;
1022         }
1023             /* Both directions */
1024         case dir_both_:
1025         {
1026             char f_rawvalue, r_rawvalue;
1027             guint32 f_write_silence = 0;
1028             guint32 r_write_silence = 0;
1029             /* since conversation in one way can start later than in the other one,
1030                  * we have to write some silence information for one channel */
1031             if (fwd_statinfo_.start_time > rev_statinfo_.start_time) {
1032                 f_write_silence = (guint32)
1033                         ((fwd_statinfo_.start_time - rev_statinfo_.start_time)
1034                          * (8000/1000));
1035             } else if (fwd_statinfo_.start_time < rev_statinfo_.start_time) {
1036                 r_write_silence = (guint32)
1037                         ((rev_statinfo_.start_time - fwd_statinfo_.start_time)
1038                          * (8000/1000));
1039             }
1040             for (;;) {
1041                 if (stop_flag) {
1042                     break;
1043                 }
1044                 int fwd_pct = int(fwd_tempfile_->pos() * 100 / fwd_tempfile_->size());
1045                 int rev_pct = int(rev_tempfile_->pos() * 100 / rev_tempfile_->size());
1046                 ui->progressFrame->setValue(qMin(fwd_pct, rev_pct));
1047
1048                 if (f_write_silence > 0) {
1049                     rev_tempfile_->getChar(&r_rawvalue);
1050                     switch (fwd_statinfo_.reg_pt) {
1051                     case PT_PCMU:
1052                         f_rawvalue = silence_pcmu_;
1053                         break;
1054                     case PT_PCMA:
1055                         f_rawvalue = silence_pcma_;
1056                         break;
1057                     default:
1058                         f_rawvalue = 0;
1059                         break;
1060                     }
1061                     f_write_silence--;
1062                 } else if (r_write_silence > 0) {
1063                     fwd_tempfile_->getChar(&f_rawvalue);
1064                     switch (rev_statinfo_.reg_pt) {
1065                     case PT_PCMU:
1066                         r_rawvalue = silence_pcmu_;
1067                         break;
1068                     case PT_PCMA:
1069                         r_rawvalue = silence_pcma_;
1070                         break;
1071                     default:
1072                         r_rawvalue = 0;
1073                         break;
1074                     }
1075                     r_write_silence--;
1076                 } else {
1077                     fwd_tempfile_->getChar(&f_rawvalue);
1078                     rev_tempfile_->getChar(&r_rawvalue);
1079                 }
1080                 if (fwd_tempfile_->atEnd() && rev_tempfile_->atEnd())
1081                     break;
1082                 if ((fwd_statinfo_.pt == PT_PCMU)
1083                         && (rev_statinfo_.pt == PT_PCMU)) {
1084                     sample = (ulaw2linear((unsigned char)r_rawvalue)
1085                               + ulaw2linear((unsigned char)f_rawvalue)) / 2;
1086                     phton16(pd, sample);
1087                 }
1088                 else if ((fwd_statinfo_.pt == PT_PCMA)
1089                          && (rev_statinfo_.pt == PT_PCMA)) {
1090                     sample = (alaw2linear((unsigned char)r_rawvalue)
1091                               + alaw2linear((unsigned char)f_rawvalue)) / 2;
1092                     phton16(pd, sample);
1093                 } else {
1094                     goto copy_file_err;
1095                 }
1096
1097                 nchars = save_file.write((const char *)pd, 2);
1098                 if (nchars < 2) {
1099                     goto copy_file_err;
1100                 }
1101             }
1102         }
1103         }
1104     } else if (save_format == save_audio_raw_) { /* raw format */
1105         QFile *tempfile;
1106         int progress_pct;
1107
1108         switch (direction) {
1109         /* Only forward direction */
1110         case dir_forward_: {
1111             progress_pct = int(fwd_tempfile_->pos() * 100 / fwd_tempfile_->size());
1112             tempfile = fwd_tempfile_;
1113             break;
1114         }
1115             /* only reversed direction */
1116         case dir_reverse_: {
1117             progress_pct = int(rev_tempfile_->pos() * 100 / rev_tempfile_->size());
1118             tempfile = rev_tempfile_;
1119             break;
1120         }
1121         default: {
1122             goto copy_file_err;
1123         }
1124         }
1125
1126         int chunk_size = 65536;
1127         /* XXX how do you just copy the file? */
1128         while (chunk_size > 0) {
1129             if (stop_flag)
1130                 break;
1131             QByteArray bytes = tempfile->read(chunk_size);
1132             ui->progressFrame->setValue(progress_pct);
1133
1134             if (!save_file.write(bytes)) {
1135                 goto copy_file_err;
1136             }
1137             chunk_size = bytes.length();
1138         }
1139     }
1140
1141 copy_file_err:
1142     ui->progressFrame->hide();
1143     updateWidgets();
1144     return;
1145 }
1146
1147 // XXX The GTK+ UI saves the length and timestamp.
1148 void Iax2AnalysisDialog::saveCsv(Iax2AnalysisDialog::StreamDirection direction)
1149 {
1150     QString caption;
1151
1152     switch (direction) {
1153     case dir_forward_:
1154         caption = tr("Save forward stream CSV");
1155         break;
1156     case dir_reverse_:
1157         caption = tr("Save reverse stream CSV");
1158         break;
1159     case dir_both_:
1160     default:
1161         caption = tr("Save CSV");
1162         break;
1163     }
1164
1165     QString file_path = QFileDialog::getSaveFileName(
1166                 this, caption, wsApp->lastOpenDir().absoluteFilePath("RTP Packet Data.csv"),
1167                 tr("Comma-separated values (*.csv)"));
1168
1169     if (file_path.isEmpty()) return;
1170
1171     QFile save_file(file_path);
1172     save_file.open(QFile::WriteOnly);
1173
1174     if (direction == dir_forward_ || direction == dir_both_) {
1175         save_file.write("Forward\n");
1176
1177         for (int row = 0; row < ui->forwardTreeWidget->topLevelItemCount(); row++) {
1178             QTreeWidgetItem *ti = ui->forwardTreeWidget->topLevelItem(row);
1179             if (ti->type() != iax2_analysis_type_) continue;
1180             Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)ti);
1181             QStringList values;
1182             foreach (QVariant v, ra_ti->rowData()) {
1183                 if (!v.isValid()) {
1184                     values << "\"\"";
1185                 } else if ((int) v.type() == (int) QMetaType::QString) {
1186                     values << QString("\"%1\"").arg(v.toString());
1187                 } else {
1188                     values << v.toString();
1189                 }
1190             }
1191             save_file.write(values.join(",").toUtf8());
1192             save_file.write("\n");
1193         }
1194     }
1195     if (direction == dir_both_) {
1196         save_file.write("\n");
1197     }
1198     if (direction == dir_reverse_ || direction == dir_both_) {
1199         save_file.write("Reverse\n");
1200
1201         for (int row = 0; row < ui->reverseTreeWidget->topLevelItemCount(); row++) {
1202             QTreeWidgetItem *ti = ui->reverseTreeWidget->topLevelItem(row);
1203             if (ti->type() != iax2_analysis_type_) continue;
1204             Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)ti);
1205             QStringList values;
1206             foreach (QVariant v, ra_ti->rowData()) {
1207                 if (!v.isValid()) {
1208                     values << "\"\"";
1209                 } else if ((int) v.type() == (int) QMetaType::QString) {
1210                     values << QString("\"%1\"").arg(v.toString());
1211                 } else {
1212                     values << v.toString();
1213                 }
1214             }
1215             save_file.write(values.join(",").toUtf8());
1216             save_file.write("\n");
1217         }
1218     }
1219 }
1220
1221 bool Iax2AnalysisDialog::eventFilter(QObject *, QEvent *event)
1222 {
1223     if (event->type() != QEvent::KeyPress) return false;
1224
1225     QKeyEvent *kevt = static_cast<QKeyEvent *>(event);
1226
1227     switch(kevt->key()) {
1228     case Qt::Key_G:
1229         on_actionGoToPacket_triggered();
1230         return true;
1231     case Qt::Key_N:
1232         on_actionNextProblem_triggered();
1233         return true;
1234     default:
1235         break;
1236     }
1237     return false;
1238 }
1239
1240 void Iax2AnalysisDialog::graphClicked(QMouseEvent *event)
1241 {
1242     updateWidgets();
1243     if (event->button() == Qt::RightButton) {
1244         graph_ctx_menu_.exec(event->globalPos());
1245     }
1246 }
1247
1248 void Iax2AnalysisDialog::showStreamMenu(QPoint pos)
1249 {
1250     QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
1251     if (!cur_tree) return;
1252
1253     updateWidgets();
1254     stream_ctx_menu_.popup(cur_tree->viewport()->mapToGlobal(pos));
1255 }
1256
1257 /*
1258  * Editor modelines
1259  *
1260  * Local Variables:
1261  * c-basic-offset: 4
1262  * tab-width: 8
1263  * indent-tabs-mode: nil
1264  * End:
1265  *
1266  * ex: set shiftwidth=4 tabstop=8 expandtab:
1267  * :indentSize=4:tabSize=8:noTabs=true:
1268  */