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