1 /* sequence_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  */
9 
10 #include "sequence_dialog.h"
11 #include <ui_sequence_dialog.h>
12 
13 #include "epan/addr_resolv.h"
14 
15 #include "file.h"
16 
17 #include "wsutil/nstime.h"
18 #include "wsutil/utf8_entities.h"
19 #include "wsutil/file_util.h"
20 #include <wsutil/report_message.h>
21 
22 #include <ui/qt/utils/color_utils.h>
23 #include "progress_frame.h"
24 #include <ui/qt/utils/qt_ui_utils.h>
25 #include "sequence_diagram.h"
26 #include "wireshark_application.h"
27 #include <ui/qt/utils/variant_pointer.h>
28 #include <ui/alert_box.h>
29 #include "ui/qt/widgets/wireshark_file_dialog.h"
30 #include <ui/voip_calls.h>
31 #include "rtp_stream_dialog.h"
32 
33 #include <QDir>
34 #include <QFontMetrics>
35 #include <QPoint>
36 
37 // To do:
38 // - Resize or show + hide the Time and Comment axes, possibly via one of
39 //   the following:
40 //   - Split the time, diagram, and comment sections into three separate
41 //     widgets inside a QSplitter. This would resemble the GTK+ UI, but we'd
42 //     have to coordinate between the three and we'd lose time and comment
43 //     values in PDF and PNG exports.
44 //   - Add separate controls for the width and/or visibility of the Time and
45 //     Comment columns.
46 //   - Fake a splitter widget by catching mouse events in the plot area.
47 //     Drawing a QCPItemLine or QCPItemPixmap over each Y axis might make
48 //     this easier.
49 // - For general flows, let the user show columns other than COL_INFO.
50 // - Add UTF8 to text dump
51 // - Save to XMI? https://www.spinellis.gr/umlgraph/
52 // - Time: abs vs delta
53 // - Hide nodes
54 // - Clickable time + comments?
55 // - Incorporate packet comments?
56 // - Change line_style to seq_type (i.e. draw ACKs dashed)
57 // - Create WSGraph subclasses with common behavior.
58 // - Help button and text
59 
60 static const double min_top_ = -1.0;
61 static const double min_left_ = -0.5;
62 
63 typedef struct {
64     int curr_index;
65     QComboBox *flow;
66     SequenceInfo *info;
67 } sequence_items_t;
68 
SequenceDialog(QWidget & parent,CaptureFile & cf,SequenceInfo * info)69 SequenceDialog::SequenceDialog(QWidget &parent, CaptureFile &cf, SequenceInfo *info) :
70     WiresharkDialog(parent, cf),
71     ui(new Ui::SequenceDialog),
72     info_(info),
73     num_items_(0),
74     packet_num_(0),
75     sequence_w_(1),
76     voipFeaturesEnabled(false)
77 {
78     QAction *action;
79 
80     ui->setupUi(this);
81 
82     QCustomPlot *sp = ui->sequencePlot;
83     setWindowSubtitle(info_ ? tr("Call Flow") : tr("Flow"));
84 
85     if (!info_) {
86         info_ = new SequenceInfo(sequence_analysis_info_new());
87         info_->sainfo()->name = "any";
88     } else {
89         info_->ref();
90         sequence_analysis_free_nodes(info_->sainfo());
91         num_items_ = sequence_analysis_get_nodes(info_->sainfo());
92     }
93 
94     seq_diagram_ = new SequenceDiagram(sp->yAxis, sp->xAxis2, sp->yAxis2);
95 
96     // When dragging is enabled it's easy to drag past the lower and upper
97     // bounds of each axis. Disable it for now.
98     //sp->axisRect()->setRangeDragAxes(sp->xAxis2, sp->yAxis);
99     //sp->setInteractions(QCP::iRangeDrag);
100 
101     sp->xAxis->setVisible(false);
102     sp->xAxis->setPadding(0);
103     sp->xAxis->setLabelPadding(0);
104     sp->xAxis->setTickLabelPadding(0);
105 
106     QPen base_pen(ColorUtils::alphaBlend(palette().text(), palette().base(), 0.25));
107     base_pen.setWidthF(0.5);
108     sp->xAxis2->setBasePen(base_pen);
109     sp->yAxis->setBasePen(base_pen);
110     sp->yAxis2->setBasePen(base_pen);
111 
112     sp->xAxis2->setVisible(true);
113     sp->yAxis2->setVisible(true);
114 
115     key_text_ = new QCPItemText(sp);
116     key_text_->setText(tr("Time"));
117 
118     key_text_->setPositionAlignment(Qt::AlignRight | Qt::AlignVCenter);
119     key_text_->position->setType(QCPItemPosition::ptAbsolute);
120     key_text_->setClipToAxisRect(false);
121 
122     comment_text_ = new QCPItemText(sp);
123     comment_text_->setText(tr("Comment"));
124 
125     comment_text_->setPositionAlignment(Qt::AlignLeft | Qt::AlignVCenter);
126     comment_text_->position->setType(QCPItemPosition::ptAbsolute);
127     comment_text_->setClipToAxisRect(false);
128 
129     one_em_ = QFontMetrics(sp->yAxis->labelFont()).height();
130     ui->horizontalScrollBar->setSingleStep(100 / one_em_);
131     ui->verticalScrollBar->setSingleStep(100 / one_em_);
132 
133     ui->gridLayout->setSpacing(0);
134     connect(sp->yAxis, SIGNAL(rangeChanged(QCPRange)), sp->yAxis2, SLOT(setRange(QCPRange)));
135 
136     ctx_menu_.addAction(ui->actionZoomIn);
137     ctx_menu_.addAction(ui->actionZoomOut);
138     action = ctx_menu_.addAction(tr("Reset Diagram"), this, SLOT(resetView()));
139     action->setToolTip(tr("Reset the diagram to its initial state."));
140     ctx_menu_.addSeparator();
141     ctx_menu_.addAction(ui->actionMoveRight10);
142     ctx_menu_.addAction(ui->actionMoveLeft10);
143     ctx_menu_.addAction(ui->actionMoveUp10);
144     ctx_menu_.addAction(ui->actionMoveDown10);
145     ctx_menu_.addAction(ui->actionMoveRight1);
146     ctx_menu_.addAction(ui->actionMoveLeft1);
147     ctx_menu_.addAction(ui->actionMoveUp1);
148     ctx_menu_.addAction(ui->actionMoveDown1);
149     ctx_menu_.addSeparator();
150     ctx_menu_.addAction(ui->actionGoToPacket);
151     ctx_menu_.addAction(ui->actionGoToNextPacket);
152     ctx_menu_.addAction(ui->actionGoToPreviousPacket);
153     ctx_menu_.addSeparator();
154     action = ui->actionSelectRtpStreams;
155     ctx_menu_.addAction(action);
156     action->setVisible(false);
157     action->setEnabled(false);
158     action = ui->actionDeselectRtpStreams;
159     ctx_menu_.addAction(action);
160     action->setVisible(false);
161     action->setEnabled(false);
162     set_action_shortcuts_visible_in_context_menu(ctx_menu_.actions());
163 
164     ui->addressComboBox->setCurrentIndex(0);
165 
166     sequence_items_t item_data;
167 
168     item_data.curr_index = 0;
169     item_data.flow = ui->flowComboBox;
170     item_data.info = info_;
171 
172     //Add all registered analysis to combo box
173     sequence_analysis_table_iterate_tables(addFlowSequenceItem, &item_data);
174 
175     if (strcmp(info_->sainfo()->name, "voip") == 0) {
176         ui->flowComboBox->blockSignals(true);
177         ui->controlFrame->hide();
178     }
179 
180     reset_button_ = ui->buttonBox->addButton(ui->actionResetDiagram->text(), QDialogButtonBox::ActionRole);
181     reset_button_->setToolTip(ui->actionResetDiagram->toolTip());
182     player_button_ = RtpPlayerDialog::addPlayerButton(ui->buttonBox, this);
183     export_button_ = ui->buttonBox->addButton(ui->actionExportDiagram->text(), QDialogButtonBox::ActionRole);
184     export_button_->setToolTip(ui->actionExportDiagram->toolTip());
185 
186     QPushButton *close_bt = ui->buttonBox->button(QDialogButtonBox::Close);
187     if (close_bt) {
188         close_bt->setDefault(true);
189     }
190 
191     ProgressFrame::addToButtonBox(ui->buttonBox, &parent);
192 
193     loadGeometry(parent.width(), parent.height() * 4 / 5);
194 
195     connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(hScrollBarChanged(int)));
196     connect(ui->verticalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(vScrollBarChanged(int)));
197     connect(sp->xAxis2, SIGNAL(rangeChanged(QCPRange)), this, SLOT(xAxisChanged(QCPRange)));
198     connect(sp->yAxis, SIGNAL(rangeChanged(QCPRange)), this, SLOT(yAxisChanged(QCPRange)));
199     connect(sp, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(diagramClicked(QMouseEvent*)));
200     connect(sp, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*)));
201     connect(sp, SIGNAL(mouseWheel(QWheelEvent*)), this, SLOT(mouseWheeled(QWheelEvent*)));
202 
203     disconnect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
204 
205     // Button must be enabled by VoIP dialogs
206     player_button_->setVisible(false);
207     player_button_->setEnabled(false);
208 }
209 
~SequenceDialog()210 SequenceDialog::~SequenceDialog()
211 {
212     info_->unref();
213     delete ui;
214 }
215 
enableVoIPFeatures()216 void SequenceDialog::enableVoIPFeatures()
217 {
218     voipFeaturesEnabled = true;
219     player_button_->setVisible(true);
220     ui->actionSelectRtpStreams->setVisible(true);
221     ui->actionDeselectRtpStreams->setVisible(true);
222 }
223 
updateWidgets()224 void SequenceDialog::updateWidgets()
225 {
226     WiresharkDialog::updateWidgets();
227 }
228 
showEvent(QShowEvent *)229 void SequenceDialog::showEvent(QShowEvent *)
230 {
231     QTimer::singleShot(0, this, SLOT(fillDiagram()));
232 }
233 
resizeEvent(QResizeEvent *)234 void SequenceDialog::resizeEvent(QResizeEvent *)
235 {
236     if (!info_) return;
237 
238     resetAxes(true);
239 }
240 
keyPressEvent(QKeyEvent * event)241 void SequenceDialog::keyPressEvent(QKeyEvent *event)
242 {
243     int pan_pixels = event->modifiers() & Qt::ShiftModifier ? 1 : 10;
244 
245     // XXX - Copy some shortcuts from tcp_stream_dialog.cpp
246     switch(event->key()) {
247     case Qt::Key_Minus:
248     case Qt::Key_Underscore:    // Shifted minus on U.S. keyboards
249         on_actionZoomOut_triggered();
250         break;
251     case Qt::Key_Plus:
252     case Qt::Key_Equal:         // Unshifted plus on U.S. keyboards
253         on_actionZoomIn_triggered();
254         break;
255 
256     case Qt::Key_Right:
257     case Qt::Key_L:
258         panAxes(pan_pixels, 0);
259         break;
260     case Qt::Key_Left:
261     case Qt::Key_H:
262         panAxes(-1 * pan_pixels, 0);
263         break;
264     case Qt::Key_Up:
265     case Qt::Key_K:
266         panAxes(0, pan_pixels);
267         break;
268     case Qt::Key_Down:
269     case Qt::Key_J:
270         panAxes(0, -1 * pan_pixels);
271         break;
272 
273     case Qt::Key_PageDown:
274     case Qt::Key_Space:
275         ui->verticalScrollBar->setValue(ui->verticalScrollBar->value() + ui->verticalScrollBar->pageStep());
276         break;
277     case Qt::Key_PageUp:
278         ui->verticalScrollBar->setValue(ui->verticalScrollBar->value() - ui->verticalScrollBar->pageStep());
279         break;
280 
281     case Qt::Key_0:
282     case Qt::Key_ParenRight:    // Shifted 0 on U.S. keyboards
283     case Qt::Key_R:
284     case Qt::Key_Home:
285         resetAxes();
286         break;
287 
288     case Qt::Key_G:
289         on_actionGoToPacket_triggered();
290         break;
291     case Qt::Key_N:
292         on_actionGoToNextPacket_triggered();
293         break;
294     case Qt::Key_P:
295         on_actionGoToPreviousPacket_triggered();
296         break;
297     case Qt::Key_S:
298         if (voipFeaturesEnabled) {
299             on_actionSelectRtpStreams_triggered();
300         }
301         break;
302     case Qt::Key_D:
303         if (voipFeaturesEnabled) {
304             on_actionDeselectRtpStreams_triggered();
305         }
306         break;
307     }
308 
309     QDialog::keyPressEvent(event);
310 }
311 
hScrollBarChanged(int value)312 void SequenceDialog::hScrollBarChanged(int value)
313 {
314     if (qAbs(ui->sequencePlot->xAxis2->range().center()-value/100.0) > 0.01) {
315       ui->sequencePlot->xAxis2->setRange(value/100.0, ui->sequencePlot->xAxis2->range().size(), Qt::AlignCenter);
316       ui->sequencePlot->replot();
317     }
318 }
319 
vScrollBarChanged(int value)320 void SequenceDialog::vScrollBarChanged(int value)
321 {
322     if (qAbs(ui->sequencePlot->yAxis->range().center()-value/100.0) > 0.01) {
323       ui->sequencePlot->yAxis->setRange(value/100.0, ui->sequencePlot->yAxis->range().size(), Qt::AlignCenter);
324       ui->sequencePlot->replot();
325     }
326 }
327 
xAxisChanged(QCPRange range)328 void SequenceDialog::xAxisChanged(QCPRange range)
329 {
330     ui->horizontalScrollBar->setValue(qRound(qreal(range.center()*100.0)));
331     ui->horizontalScrollBar->setPageStep(qRound(qreal(range.size()*100.0)));
332 }
333 
yAxisChanged(QCPRange range)334 void SequenceDialog::yAxisChanged(QCPRange range)
335 {
336     ui->verticalScrollBar->setValue(qRound(qreal(range.center()*100.0)));
337     ui->verticalScrollBar->setPageStep(qRound(qreal(range.size()*100.0)));
338 }
339 
diagramClicked(QMouseEvent * event)340 void SequenceDialog::diagramClicked(QMouseEvent *event)
341 {
342     current_rtp_sai_selected_ = NULL;
343     if (event) {
344         seq_analysis_item_t *sai = seq_diagram_->itemForPosY(event->pos().y());
345         if (voipFeaturesEnabled) {
346             ui->actionSelectRtpStreams->setEnabled(false);
347             ui->actionDeselectRtpStreams->setEnabled(false);
348             player_button_->setEnabled(false);
349             if (sai) {
350                 if (GA_INFO_TYPE_RTP == sai->info_type) {
351                     ui->actionSelectRtpStreams->setEnabled(true && !file_closed_);
352                     ui->actionDeselectRtpStreams->setEnabled(true && !file_closed_);
353                     player_button_->setEnabled(true && !file_closed_);
354                     current_rtp_sai_selected_ = sai;
355                 }
356             }
357         }
358 
359         switch (event->button()) {
360         case Qt::LeftButton:
361             on_actionGoToPacket_triggered();
362             break;
363         case Qt::RightButton:
364             ctx_menu_.exec(event->globalPos());
365             break;
366         default:
367             break;
368         }
369     }
370 
371 }
372 
mouseMoved(QMouseEvent * event)373 void SequenceDialog::mouseMoved(QMouseEvent *event)
374 {
375     current_rtp_sai_hovered_ = NULL;
376     packet_num_ = 0;
377     QString hint;
378     if (event) {
379         seq_analysis_item_t *sai = seq_diagram_->itemForPosY(event->pos().y());
380         if (sai) {
381             if (GA_INFO_TYPE_RTP == sai->info_type) {
382                 ui->actionSelectRtpStreams->setEnabled(true);
383                 ui->actionDeselectRtpStreams->setEnabled(true);
384                 current_rtp_sai_hovered_ = sai;
385             }
386             packet_num_ = sai->frame_number;
387             QString raw_comment = html_escape(sai->comment);
388             hint = QString("Packet %1: %2").arg(packet_num_).arg(raw_comment);
389         }
390     }
391 
392     if (hint.isEmpty()) {
393         if (!info_->sainfo()) {
394             hint += tr("No data");
395         } else {
396             hint += tr("%Ln node(s)", "", info_->sainfo()->num_nodes) + QString(", ")
397                     + tr("%Ln item(s)", "", num_items_);
398         }
399     }
400 
401     hint.prepend("<small><i>");
402     hint.append("</i></small>");
403     ui->hintLabel->setText(hint);
404 }
405 
mouseWheeled(QWheelEvent * event)406 void SequenceDialog::mouseWheeled(QWheelEvent *event)
407 {
408     int scroll_x = event->angleDelta().x() * -1 / 8;
409     scroll_x *= ui->horizontalScrollBar->singleStep();
410     if (scroll_x) {
411         ui->horizontalScrollBar->setValue(ui->horizontalScrollBar->value() + scroll_x);
412     }
413 
414     int scroll_y = event->angleDelta().ry() * -1 / 8;
415     scroll_y *= ui->verticalScrollBar->singleStep();
416     if (scroll_y) {
417         ui->verticalScrollBar->setValue(ui->verticalScrollBar->value() + scroll_y);
418     }
419 
420     event->accept();
421 }
422 
on_buttonBox_clicked(QAbstractButton * button)423 void SequenceDialog::on_buttonBox_clicked(QAbstractButton *button)
424 {
425     if (button == reset_button_) {
426         resetView();
427     } else if (button == export_button_) {
428         exportDiagram();
429     }
430 }
431 
exportDiagram()432 void SequenceDialog::exportDiagram()
433 {
434     QString file_name, extension;
435     QDir path(wsApp->lastOpenDir());
436     QString pdf_filter = tr("Portable Document Format (*.pdf)");
437     QString png_filter = tr("Portable Network Graphics (*.png)");
438     QString bmp_filter = tr("Windows Bitmap (*.bmp)");
439     // Gaze upon my beautiful graph with lossy artifacts!
440     QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
441     QString ascii_filter = tr("ASCII (*.txt)");
442 
443     QString filter = QString("%1;;%2;;%3;;%4")
444             .arg(pdf_filter)
445             .arg(png_filter)
446             .arg(bmp_filter)
447             .arg(jpeg_filter);
448     if (!file_closed_) {
449         filter.append(QString(";;%5").arg(ascii_filter));
450     }
451 
452     file_name = WiresharkFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Graph As…")),
453                                              path.canonicalPath(), filter, &extension);
454 
455     if (file_name.length() > 0) {
456         bool save_ok = false;
457         if (extension.compare(pdf_filter) == 0) {
458             save_ok = ui->sequencePlot->savePdf(file_name);
459         } else if (extension.compare(png_filter) == 0) {
460             save_ok = ui->sequencePlot->savePng(file_name);
461         } else if (extension.compare(bmp_filter) == 0) {
462             save_ok = ui->sequencePlot->saveBmp(file_name);
463         } else if (extension.compare(jpeg_filter) == 0) {
464             save_ok = ui->sequencePlot->saveJpg(file_name);
465         } else if (extension.compare(ascii_filter) == 0 && !file_closed_ && info_->sainfo()) {
466             FILE  *outfile = ws_fopen(file_name.toUtf8().constData(), "w");
467             if (outfile != NULL) {
468                 sequence_analysis_dump_to_file(outfile, info_->sainfo(), 0);
469                 save_ok = true;
470                 fclose(outfile);
471             } else {
472                 save_ok = false;
473             }
474         }
475         // else error dialog?
476         if (save_ok) {
477             wsApp->setLastOpenDirFromFilename(file_name);
478         } else {
479             open_failure_alert_box(file_name.toUtf8().constData(), errno, TRUE);
480         }
481     }
482 }
483 
fillDiagram()484 void SequenceDialog::fillDiagram()
485 {
486     if (!info_->sainfo() || file_closed_) return;
487 
488     QCustomPlot *sp = ui->sequencePlot;
489 
490     if (strcmp(info_->sainfo()->name, "voip") == 0) {
491         seq_diagram_->setData(info_->sainfo());
492     } else {
493         seq_diagram_->clearData();
494         sequence_analysis_list_free(info_->sainfo());
495 
496         register_analysis_t* analysis = sequence_analysis_find_by_name(info_->sainfo()->name);
497         if (analysis != NULL)
498         {
499             GString *error_string;
500             const char *filter = NULL;
501             if (ui->displayFilterCheckBox->checkState() == Qt::Checked)
502                 filter = cap_file_.capFile()->dfilter;
503 
504             error_string = register_tap_listener(sequence_analysis_get_tap_listener_name(analysis), info_->sainfo(), filter, sequence_analysis_get_tap_flags(analysis),
505                                        NULL, sequence_analysis_get_packet_func(analysis), NULL, NULL);
506             if (error_string) {
507                 report_failure("Sequence dialog - tap registration failed: %s", error_string->str);
508                 g_string_free(error_string, TRUE);
509             }
510 
511             cf_retap_packets(cap_file_.capFile());
512             remove_tap_listener(info_->sainfo());
513 
514             num_items_ = sequence_analysis_get_nodes(info_->sainfo());
515             seq_diagram_->setData(info_->sainfo());
516         }
517     }
518 
519     sequence_w_ = one_em_ * 15; // Arbitrary
520 
521     mouseMoved(NULL);
522     resetAxes();
523 
524     // XXX QCustomPlot doesn't seem to draw any sort of focus indicator.
525     sp->setFocus();
526 }
527 
panAxes(int x_pixels,int y_pixels)528 void SequenceDialog::panAxes(int x_pixels, int y_pixels)
529 {
530     // We could simplify this quite a bit if we set the scroll bar values instead.
531     if (!info_->sainfo()) return;
532 
533     QCustomPlot *sp = ui->sequencePlot;
534     double h_pan = 0.0;
535     double v_pan = 0.0;
536 
537     h_pan = sp->xAxis2->range().size() * x_pixels / sp->xAxis2->axisRect()->width();
538     if (h_pan < 0) {
539         h_pan = qMax(h_pan, min_left_ - sp->xAxis2->range().lower);
540     } else {
541         h_pan = qMin(h_pan, info_->sainfo()->num_nodes - sp->xAxis2->range().upper);
542     }
543 
544     v_pan = sp->yAxis->range().size() * y_pixels / sp->yAxis->axisRect()->height();
545     if (v_pan < 0) {
546         v_pan = qMax(v_pan, min_top_ - sp->yAxis->range().lower);
547     } else {
548         v_pan = qMin(v_pan, num_items_ - sp->yAxis->range().upper);
549     }
550 
551     if (h_pan && !(sp->xAxis2->range().contains(min_left_) && sp->xAxis2->range().contains(info_->sainfo()->num_nodes))) {
552         sp->xAxis2->moveRange(h_pan);
553         sp->replot();
554     }
555     if (v_pan && !(sp->yAxis->range().contains(min_top_) && sp->yAxis->range().contains(num_items_))) {
556         sp->yAxis->moveRange(v_pan);
557         sp->replot();
558     }
559 }
560 
resetAxes(bool keep_lower)561 void SequenceDialog::resetAxes(bool keep_lower)
562 {
563     if (!info_->sainfo()) return;
564 
565     QCustomPlot *sp = ui->sequencePlot;
566 
567     // Allow space for labels on the top and port numbers on the left.
568     double top_pos = min_top_, left_pos = min_left_;
569     if (keep_lower) {
570         top_pos = sp->yAxis->range().lower;
571         left_pos = sp->xAxis2->range().lower;
572     }
573 
574     double range_span = sp->viewport().width() / sequence_w_ * sp->axisRect()->rangeZoomFactor(Qt::Horizontal);
575     sp->xAxis2->setRange(left_pos, range_span + left_pos);
576 
577     range_span = sp->axisRect()->height() / (one_em_ * 1.5);
578     sp->yAxis->setRange(top_pos, range_span + top_pos);
579 
580     double rmin = sp->xAxis2->range().size() / 2;
581     ui->horizontalScrollBar->setRange((rmin - 0.5) * 100, (info_->sainfo()->num_nodes - 0.5 - rmin) * 100);
582     xAxisChanged(sp->xAxis2->range());
583     ui->horizontalScrollBar->setValue(ui->horizontalScrollBar->minimum()); // Shouldn't be needed.
584 
585     rmin = (sp->yAxis->range().size() / 2);
586     ui->verticalScrollBar->setRange((rmin - 1.0) * 100, (num_items_ - 0.5 - rmin) * 100);
587     yAxisChanged(sp->yAxis->range());
588 
589     // It would be exceedingly handy if we could do one or both of the
590     // following:
591     // - Position an axis label above its axis inline with the tick labels.
592     // - Anchor a QCPItemText to one of the corners of a QCPAxis.
593     // Neither of those appear to be possible, so we first call replot in
594     // order to lay out our X axes, place our labels, the call replot again.
595     sp->replot(QCustomPlot::rpQueuedReplot);
596 
597     QRect axis_rect = sp->axisRect()->rect();
598 
599     key_text_->position->setCoords(axis_rect.left()
600                                    - sp->yAxis->padding()
601                                    - sp->yAxis->tickLabelPadding()
602                                    - sp->yAxis->offset(),
603                                    axis_rect.top() / 2);
604     comment_text_->position->setCoords(axis_rect.right()
605                                        + sp->yAxis2->padding()
606                                        + sp->yAxis2->tickLabelPadding()
607                                        + sp->yAxis2->offset(),
608                                        axis_rect.top()  / 2);
609 
610     sp->replot(QCustomPlot::rpRefreshHint);
611 }
612 
resetView()613 void SequenceDialog::resetView()
614 {
615     resetAxes();
616 }
617 
on_actionGoToPacket_triggered()618 void SequenceDialog::on_actionGoToPacket_triggered()
619 {
620     if (!file_closed_ && packet_num_ > 0) {
621         cf_goto_frame(cap_file_.capFile(), packet_num_);
622         seq_diagram_->setSelectedPacket(packet_num_);
623     }
624 }
625 
goToAdjacentPacket(bool next)626 void SequenceDialog::goToAdjacentPacket(bool next)
627 {
628     if (file_closed_) return;
629 
630     int old_key = seq_diagram_->selectedKey();
631     int adjacent_packet = seq_diagram_->adjacentPacket(next);
632     int new_key = seq_diagram_->selectedKey();
633 
634     if (adjacent_packet > 0) {
635         if (new_key >= 0) {
636             QCustomPlot *sp = ui->sequencePlot;
637             double range_offset = 0.0;
638             // Scroll if we're at our scroll margin and we haven't reached
639             // the end of our range.
640             double scroll_margin = 3.0; // Lines
641 
642             if (old_key >= 0) {
643                 range_offset = new_key - old_key;
644             }
645 
646             if (new_key < sp->yAxis->range().lower) {
647                 // Out of range, top
648                 range_offset = qRound(new_key - sp->yAxis->range().lower - scroll_margin - 0.5);
649             } else if (new_key > sp->yAxis->range().upper) {
650                 // Out of range, bottom
651                 range_offset = qRound(new_key - sp->yAxis->range().upper + scroll_margin + 0.5);
652             } else if (next) {
653                 // In range, next
654                 if (new_key + scroll_margin < sp->yAxis->range().upper) {
655                     range_offset = 0.0;
656                 }
657             } else {
658                 // In range, previous
659                 if (new_key - scroll_margin > sp->yAxis->range().lower) {
660                     range_offset = 0.0;
661                 }
662             }
663 
664             // Clamp to our upper & lower bounds.
665             if (range_offset > 0) {
666                 range_offset = qMin(range_offset, num_items_ - sp->yAxis->range().upper);
667             } else if (range_offset < 0) {
668                 range_offset = qMax(range_offset, min_top_ - sp->yAxis->range().lower);
669             }
670             sp->yAxis->moveRange(range_offset);
671         }
672         cf_goto_frame(cap_file_.capFile(), adjacent_packet);
673         seq_diagram_->setSelectedPacket(adjacent_packet);
674     }
675 }
676 
on_displayFilterCheckBox_toggled(bool)677 void SequenceDialog::on_displayFilterCheckBox_toggled(bool)
678 {
679     fillDiagram();
680 }
681 
on_flowComboBox_activated(int index)682 void SequenceDialog::on_flowComboBox_activated(int index)
683 {
684     if (!info_->sainfo() || (strcmp(info_->sainfo()->name, "voip") == 0) || index < 0)
685         return;
686 
687     register_analysis_t* analysis = VariantPointer<register_analysis_t>::asPtr(ui->flowComboBox->itemData(index));
688     info_->sainfo()->name = sequence_analysis_get_name(analysis);
689 
690     fillDiagram();
691 }
692 
on_addressComboBox_activated(int index)693 void SequenceDialog::on_addressComboBox_activated(int index)
694 {
695     if (!info_->sainfo()) return;
696 
697     if (index == 0) {
698         info_->sainfo()->any_addr = TRUE;
699     } else {
700         info_->sainfo()->any_addr = FALSE;
701     }
702     fillDiagram();
703 }
704 
on_actionMoveRight10_triggered()705 void SequenceDialog::on_actionMoveRight10_triggered()
706 {
707     panAxes(10, 0);
708 }
709 
on_actionMoveLeft10_triggered()710 void SequenceDialog::on_actionMoveLeft10_triggered()
711 {
712     panAxes(-10, 0);
713 }
714 
on_actionMoveUp10_triggered()715 void SequenceDialog::on_actionMoveUp10_triggered()
716 {
717     panAxes(0, 10);
718 }
719 
on_actionMoveDown10_triggered()720 void SequenceDialog::on_actionMoveDown10_triggered()
721 {
722     panAxes(0, -10);
723 }
724 
on_actionMoveRight1_triggered()725 void SequenceDialog::on_actionMoveRight1_triggered()
726 {
727     panAxes(1, 0);
728 }
729 
on_actionMoveLeft1_triggered()730 void SequenceDialog::on_actionMoveLeft1_triggered()
731 {
732     panAxes(-1, 0);
733 }
734 
on_actionMoveUp1_triggered()735 void SequenceDialog::on_actionMoveUp1_triggered()
736 {
737     panAxes(0, 1);
738 }
739 
on_actionMoveDown1_triggered()740 void SequenceDialog::on_actionMoveDown1_triggered()
741 {
742     panAxes(0, -1);
743 }
744 
on_actionZoomIn_triggered()745 void SequenceDialog::on_actionZoomIn_triggered()
746 {
747     zoomXAxis(true);
748 }
749 
on_actionZoomOut_triggered()750 void SequenceDialog::on_actionZoomOut_triggered()
751 {
752     zoomXAxis(false);
753 }
754 
processRtpStream(bool select)755 void SequenceDialog::processRtpStream(bool select)
756 {
757     seq_analysis_item_t *current_rtp_sai = NULL;
758 
759     // If RTP sai is below mouse, use it. If not, try selected RTP sai
760     if (current_rtp_sai_hovered_ && GA_INFO_TYPE_RTP == current_rtp_sai_hovered_->info_type) {
761         current_rtp_sai = current_rtp_sai_hovered_;
762     } else if (current_rtp_sai_selected_ && GA_INFO_TYPE_RTP == current_rtp_sai_selected_->info_type) {
763         current_rtp_sai = current_rtp_sai_selected_;
764     }
765 
766     if (current_rtp_sai) {
767         QVector<rtpstream_id_t *> stream_ids;
768 
769         // We don't need copy it as it is not cleared during retap
770         stream_ids << &((rtpstream_info_t *)current_rtp_sai->info_ptr)->id;
771         if (select) {
772             emit rtpStreamsDialogSelectRtpStreams(stream_ids);
773         } else {
774             emit rtpStreamsDialogDeselectRtpStreams(stream_ids);
775         }
776         raise();
777     }
778 }
779 
on_actionSelectRtpStreams_triggered()780 void SequenceDialog::on_actionSelectRtpStreams_triggered()
781 {
782     processRtpStream(true);
783 }
784 
on_actionDeselectRtpStreams_triggered()785 void SequenceDialog::on_actionDeselectRtpStreams_triggered()
786 {
787     processRtpStream(false);
788 }
789 
zoomXAxis(bool in)790 void SequenceDialog::zoomXAxis(bool in)
791 {
792     QCustomPlot *sp = ui->sequencePlot;
793     double h_factor = sp->axisRect()->rangeZoomFactor(Qt::Horizontal);
794 
795     if (!in) {
796         h_factor = pow(h_factor, -1);
797     }
798 
799     sp->xAxis2->scaleRange(h_factor, sp->xAxis->range().lower);
800     sp->replot();
801 }
802 
addFlowSequenceItem(const void * key,void * value,void * userdata)803 gboolean SequenceDialog::addFlowSequenceItem(const void* key, void *value, void *userdata)
804 {
805     const char* name = (const char*)key;
806     register_analysis_t* analysis = (register_analysis_t*)value;
807     sequence_items_t* item_data = (sequence_items_t*)userdata;
808 
809     /* XXX - Although "voip" isn't a registered name yet, it appears to have special
810        handling that will be done outside of registered data */
811     if (strcmp(name, "voip") == 0)
812         return FALSE;
813 
814     item_data->flow->addItem(sequence_analysis_get_ui_name(analysis), VariantPointer<register_analysis_t>::asQVariant(analysis));
815 
816     if (item_data->flow->itemData(item_data->curr_index).toString().compare(item_data->info->sainfo()->name) == 0)
817         item_data->flow->setCurrentIndex(item_data->curr_index);
818 
819     item_data->curr_index++;
820 
821     return FALSE;
822 }
823 
getSelectedRtpIds()824 QVector<rtpstream_id_t *>SequenceDialog::getSelectedRtpIds()
825 {
826     QVector<rtpstream_id_t *> stream_ids;
827 
828     if (current_rtp_sai_selected_ && GA_INFO_TYPE_RTP == current_rtp_sai_selected_->info_type) {
829         stream_ids << &((rtpstream_info_t *)current_rtp_sai_selected_->info_ptr)->id;
830     }
831 
832     return stream_ids;
833 }
834 
rtpPlayerReplace()835 void SequenceDialog::rtpPlayerReplace()
836 {
837     emit rtpPlayerDialogReplaceRtpStreams(getSelectedRtpIds());
838 }
839 
rtpPlayerAdd()840 void SequenceDialog::rtpPlayerAdd()
841 {
842     emit rtpPlayerDialogAddRtpStreams(getSelectedRtpIds());
843 }
844 
rtpPlayerRemove()845 void SequenceDialog::rtpPlayerRemove()
846 {
847     emit rtpPlayerDialogRemoveRtpStreams(getSelectedRtpIds());
848 }
849 
on_buttonBox_helpRequested()850 void SequenceDialog::on_buttonBox_helpRequested()
851 {
852     wsApp->helpTopicAction(HELP_STAT_FLOW_GRAPH);
853 }
854 
SequenceInfo(seq_analysis_info_t * sainfo)855 SequenceInfo::SequenceInfo(seq_analysis_info_t *sainfo) :
856     sainfo_(sainfo),
857     count_(1)
858 {
859 }
860 
~SequenceInfo()861 SequenceInfo::~SequenceInfo()
862 {
863     sequence_analysis_info_free(sainfo_);
864 }
865