3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
10 #include "sequence_dialog.h"
11 #include <ui_sequence_dialog.h>
13 #include "epan/addr_resolv.h"
17 #include "wsutil/nstime.h"
18 #include "wsutil/utf8_entities.h"
19 #include "wsutil/file_util.h"
20 #include <wsutil/report_message.h>
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 "main_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"
34 #include <QFontMetrics>
38 // - Resize the Time and Comment axis as well?
39 // - For general flows, let the user show columns other than COL_INFO.
41 // - Add UTF8 to text dump
42 // - Save to XMI? https://www.spinellis.gr/umlgraph/
43 // - Save to SVG? https://www.qcustomplot.com/index.php/support/forum/1677
44 // - Time: abs vs delta (XXX - This is currently achieved by changing
45 // View->Time Display Format before opening the dialog.)
47 // - Clickable time + comments? (XXX - Clicking on them selects the item for
48 // the row, is there anything else?)
49 // - Incorporate packet comments?
50 // - Change line_style to seq_type (i.e. draw ACKs dashed)
51 // - Create WSGraph subclasses with common behavior.
53 static const double min_top_
= -1.0;
54 static const double min_left_
= -0.5;
62 SequenceDialog::SequenceDialog(QWidget
&parent
, CaptureFile
&cf
, SequenceInfo
*info
, bool voipFeatures
) :
63 WiresharkDialog(parent
, cf
),
64 ui(new Ui::SequenceDialog
),
70 current_rtp_sai_selected_(nullptr),
71 current_rtp_sai_hovered_(nullptr),
72 voipFeaturesEnabled(voipFeatures
)
77 ui
->hintLabel
->setSmallText();
79 QCustomPlot
*sp
= ui
->sequencePlot
;
80 setWindowSubtitle(info_
? tr("Call Flow") : tr("Flow"));
83 info_
= new SequenceInfo(sequence_analysis_info_new());
84 info_
->sainfo()->name
= "any";
85 info_
->sainfo()->any_addr
= true;
88 sequence_analysis_free_nodes(info_
->sainfo());
89 num_items_
= sequence_analysis_get_nodes(info_
->sainfo());
92 seq_diagram_
= new SequenceDiagram(sp
->yAxis
, sp
->xAxis2
, sp
->yAxis2
);
94 // When dragging is enabled it's easy to drag past the lower and upper
95 // bounds of each axis. Disable it for now.
96 //sp->axisRect()->setRangeDragAxes(sp->xAxis2, sp->yAxis);
97 //sp->setInteractions(QCP::iRangeDrag);
99 sp
->setInteraction(QCP::iSelectAxes
, true);
100 sp
->xAxis
->setSelectableParts(QCPAxis::spNone
);
101 sp
->xAxis2
->setSelectableParts(QCPAxis::spNone
);
102 sp
->yAxis
->setSelectableParts(QCPAxis::spNone
);
103 sp
->yAxis2
->setSelectableParts(QCPAxis::spAxis
);
105 sp
->xAxis
->setVisible(false);
106 sp
->xAxis
->setPadding(0);
107 sp
->xAxis
->setLabelPadding(0);
108 sp
->xAxis
->setTickLabelPadding(0);
110 // Light border for the diagram
111 QPen
base_pen(ColorUtils::alphaBlend(palette().text(), palette().base(), 0.25));
112 base_pen
.setWidthF(0.5);
113 sp
->xAxis2
->setBasePen(base_pen
);
114 sp
->yAxis
->setBasePen(base_pen
);
115 sp
->yAxis2
->setBasePen(base_pen
);
116 // Keep the border the same if/when the axis is selected, instead of blue
117 sp
->xAxis2
->setSelectedBasePen(base_pen
);
118 sp
->yAxis
->setSelectedBasePen(base_pen
);
119 sp
->yAxis2
->setSelectedBasePen(base_pen
);
121 /* QCP documentation for setTicks() says "setting show to false does not imply
122 * that tick labels are invisible, too." In practice it seems to make them
123 * invisible, though, so set the length to 0.
125 sp
->yAxis2
->setTickLength(0);
127 sp
->xAxis2
->setVisible(true);
128 sp
->yAxis2
->setVisible(true);
130 key_text_
= new QCPItemText(sp
);
131 key_text_
->setText(tr("Time"));
133 key_text_
->setPositionAlignment(Qt::AlignRight
| Qt::AlignVCenter
);
134 key_text_
->position
->setType(QCPItemPosition::ptAbsolute
);
135 key_text_
->setClipToAxisRect(false);
137 comment_text_
= new QCPItemText(sp
);
138 comment_text_
->setText(tr("Comment"));
140 comment_text_
->setPositionAlignment(Qt::AlignLeft
| Qt::AlignVCenter
);
141 comment_text_
->position
->setType(QCPItemPosition::ptAbsolute
);
142 comment_text_
->setClipToAxisRect(false);
144 one_em_
= QFontMetrics(sp
->yAxis
->labelFont()).height();
145 ui
->horizontalScrollBar
->setSingleStep(100 / one_em_
);
146 ui
->verticalScrollBar
->setSingleStep(100 / one_em_
);
148 ui
->gridLayout
->setSpacing(0);
149 connect(sp
->yAxis
, SIGNAL(rangeChanged(QCPRange
)), sp
->yAxis2
, SLOT(setRange(QCPRange
)));
151 ctx_menu_
.addAction(ui
->actionZoomIn
);
152 ctx_menu_
.addAction(ui
->actionZoomOut
);
153 action
= ctx_menu_
.addAction(tr("Reset Diagram"), this, SLOT(resetView()));
154 action
->setToolTip(tr("Reset the diagram to its initial state."));
155 ctx_menu_
.addSeparator();
156 ctx_menu_
.addAction(ui
->actionMoveRight10
);
157 ctx_menu_
.addAction(ui
->actionMoveLeft10
);
158 ctx_menu_
.addAction(ui
->actionMoveUp10
);
159 ctx_menu_
.addAction(ui
->actionMoveDown10
);
160 ctx_menu_
.addAction(ui
->actionMoveRight1
);
161 ctx_menu_
.addAction(ui
->actionMoveLeft1
);
162 ctx_menu_
.addAction(ui
->actionMoveUp1
);
163 ctx_menu_
.addAction(ui
->actionMoveDown1
);
164 ctx_menu_
.addSeparator();
165 ctx_menu_
.addAction(ui
->actionGoToPacket
);
166 ctx_menu_
.addAction(ui
->actionGoToNextPacket
);
167 ctx_menu_
.addAction(ui
->actionGoToPreviousPacket
);
168 ctx_menu_
.addSeparator();
169 action
= ui
->actionSelectRtpStreams
;
170 ctx_menu_
.addAction(action
);
171 action
->setVisible(false);
172 action
->setEnabled(false);
173 action
= ui
->actionDeselectRtpStreams
;
174 ctx_menu_
.addAction(action
);
175 action
->setVisible(false);
176 action
->setEnabled(false);
177 set_action_shortcuts_visible_in_context_menu(ctx_menu_
.actions());
179 sp
->setContextMenuPolicy(Qt::CustomContextMenu
);
180 connect(sp
, &QCustomPlot::customContextMenuRequested
, this, &SequenceDialog::showContextMenu
);
182 ui
->addressComboBox
->addItem(tr("Any"), QVariant(true));
183 ui
->addressComboBox
->addItem(tr("Network"), QVariant(false));
184 ui
->addressComboBox
->setCurrentIndex(ui
->addressComboBox
->findData(QVariant(true)));
186 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
187 connect(ui
->addressComboBox
, QOverload
<int>::of(&QComboBox::currentIndexChanged
), this, &SequenceDialog::addressChanged
);
189 connect(ui
->addressComboBox
, &QComboBox::currentIndexChanged
, this, &SequenceDialog::addressChanged
);
192 sequence_items_t item_data
;
194 item_data
.curr_index
= 0;
195 item_data
.flow
= ui
->flowComboBox
;
196 item_data
.info
= info_
;
198 //Add all registered analysis to combo box
199 sequence_analysis_table_iterate_tables(addFlowSequenceItem
, &item_data
);
201 if (strcmp(info_
->sainfo()->name
, "voip") == 0) {
202 ui
->flowComboBox
->blockSignals(true);
203 ui
->controlFrame
->hide();
206 reset_button_
= ui
->buttonBox
->addButton(ui
->actionResetDiagram
->text(), QDialogButtonBox::ActionRole
);
207 reset_button_
->setToolTip(ui
->actionResetDiagram
->toolTip());
208 player_button_
= RtpPlayerDialog::addPlayerButton(ui
->buttonBox
, this);
209 export_button_
= ui
->buttonBox
->addButton(ui
->actionExportDiagram
->text(), QDialogButtonBox::ActionRole
);
210 export_button_
->setToolTip(ui
->actionExportDiagram
->toolTip());
212 QPushButton
*close_bt
= ui
->buttonBox
->button(QDialogButtonBox::Close
);
214 close_bt
->setDefault(true);
217 enableVoIPFeatures();
219 // Enable or disable VoIP features before adding the ProgressFrame,
220 // because the layout position depends on whether player_button_ is
222 ProgressFrame::addToButtonBox(ui
->buttonBox
, &parent
);
224 loadGeometry(parent
.width(), parent
.height() * 4 / 5);
226 if (cf
.isValid() && cf
.displayFilter().length() > 0) {
227 ui
->displayFilterCheckBox
->setChecked(true);
230 connect(ui
->displayFilterCheckBox
, &QCheckBox::toggled
, this, &SequenceDialog::displayFilterCheckBoxToggled
);
231 connect(ui
->horizontalScrollBar
, SIGNAL(valueChanged(int)), this, SLOT(hScrollBarChanged(int)));
232 connect(ui
->verticalScrollBar
, SIGNAL(valueChanged(int)), this, SLOT(vScrollBarChanged(int)));
233 connect(sp
->xAxis2
, SIGNAL(rangeChanged(QCPRange
)), this, SLOT(xAxisChanged(QCPRange
)));
234 connect(sp
->yAxis
, SIGNAL(rangeChanged(QCPRange
)), this, SLOT(yAxisChanged(QCPRange
)));
235 connect(sp
, &QCustomPlot::mousePress
, this, &SequenceDialog::diagramClicked
);
236 connect(sp
, &QCustomPlot::mouseRelease
, this, &SequenceDialog::mouseReleased
);
237 connect(sp
, &QCustomPlot::mouseMove
, this, &SequenceDialog::mouseMoved
);
238 connect(sp
, &QCustomPlot::mouseWheel
, this, &SequenceDialog::mouseWheeled
);
239 connect(sp
, &QCustomPlot::axisDoubleClick
, this, &SequenceDialog::axisDoubleClicked
);
240 connect(sp
, &QCustomPlot::afterLayout
, this, &SequenceDialog::layoutAxisLabels
);
243 SequenceDialog::~SequenceDialog()
249 void SequenceDialog::enableVoIPFeatures()
251 player_button_
->setVisible(voipFeaturesEnabled
);
252 ui
->actionSelectRtpStreams
->setVisible(voipFeaturesEnabled
);
253 ui
->actionDeselectRtpStreams
->setVisible(voipFeaturesEnabled
);
254 // Buttons and actions are enabled when valid call selected
257 void SequenceDialog::updateWidgets()
259 WiresharkDialog::updateWidgets();
262 bool SequenceDialog::event(QEvent
*event
)
264 if (event
->type() == QEvent::ToolTip
) {
265 QHelpEvent
*helpEvent
= static_cast<QHelpEvent
*>(event
);
266 seq_analysis_item_t
*sai
= seq_diagram_
->itemForPosY(helpEvent
->pos().y());
267 if (sai
&& seq_diagram_
->inComment(helpEvent
->pos()) && (sai
->comment
!= seq_diagram_
->elidedComment(sai
->comment
))) {
268 QToolTip::showText(helpEvent
->globalPos(), sai
->comment
);
270 QToolTip::hideText();
276 return QWidget::event(event
);
279 void SequenceDialog::showEvent(QShowEvent
*)
281 QTimer::singleShot(0, this, SLOT(fillDiagram()));
284 void SequenceDialog::resizeEvent(QResizeEvent
*)
291 void SequenceDialog::keyPressEvent(QKeyEvent
*event
)
293 int pan_pixels
= event
->modifiers() & Qt::ShiftModifier
? 1 : 10;
295 // XXX - Copy some shortcuts from tcp_stream_dialog.cpp
296 switch(event
->key()) {
298 case Qt::Key_Underscore
: // Shifted minus on U.S. keyboards
299 on_actionZoomOut_triggered();
302 case Qt::Key_Equal
: // Unshifted plus on U.S. keyboards
303 on_actionZoomIn_triggered();
308 panAxes(pan_pixels
, 0);
312 panAxes(-1 * pan_pixels
, 0);
316 panAxes(0, pan_pixels
);
320 panAxes(0, -1 * pan_pixels
);
323 case Qt::Key_PageDown
:
325 ui
->verticalScrollBar
->setValue(ui
->verticalScrollBar
->value() + ui
->verticalScrollBar
->pageStep());
328 ui
->verticalScrollBar
->setValue(ui
->verticalScrollBar
->value() - ui
->verticalScrollBar
->pageStep());
332 case Qt::Key_ParenRight
: // Shifted 0 on U.S. keyboards
339 on_actionGoToPacket_triggered();
342 on_actionGoToNextPacket_triggered();
345 on_actionGoToPreviousPacket_triggered();
348 if (voipFeaturesEnabled
) {
349 on_actionSelectRtpStreams_triggered();
353 if (voipFeaturesEnabled
) {
354 on_actionDeselectRtpStreams_triggered();
359 QDialog::keyPressEvent(event
);
362 void SequenceDialog::hScrollBarChanged(int value
)
364 if (qAbs(ui
->sequencePlot
->xAxis2
->range().center()-value
/100.0) > 0.01) {
365 ui
->sequencePlot
->xAxis2
->setRange(value
/100.0, ui
->sequencePlot
->xAxis2
->range().size(), Qt::AlignCenter
);
366 ui
->sequencePlot
->replot(QCustomPlot::rpQueuedReplot
);
370 void SequenceDialog::vScrollBarChanged(int value
)
372 if (qAbs(ui
->sequencePlot
->yAxis
->range().center()-value
/100.0) > 0.01) {
373 ui
->sequencePlot
->yAxis
->setRange(value
/100.0, ui
->sequencePlot
->yAxis
->range().size(), Qt::AlignCenter
);
374 ui
->sequencePlot
->replot(QCustomPlot::rpQueuedReplot
);
378 void SequenceDialog::xAxisChanged(QCPRange range
)
380 ui
->horizontalScrollBar
->setValue(qRound(qreal(range
.center()*100.0)));
381 ui
->horizontalScrollBar
->setPageStep(qRound(qreal(range
.size()*100.0)));
384 void SequenceDialog::yAxisChanged(QCPRange range
)
386 ui
->verticalScrollBar
->setValue(qRound(qreal(range
.center()*100.0)));
387 ui
->verticalScrollBar
->setPageStep(qRound(qreal(range
.size()*100.0)));
390 void SequenceDialog::showContextMenu(const QPoint
&pos
)
392 ctx_menu_
.popup(ui
->sequencePlot
->mapToGlobal(pos
));
395 void SequenceDialog::diagramClicked(QMouseEvent
*event
)
397 current_rtp_sai_selected_
= NULL
;
399 QCPAxis
*yAxis2
= ui
->sequencePlot
->yAxis2
;
400 if (std::abs(event
->pos().x() - yAxis2
->axisRect()->right()) < 5) {
401 yAxis2
->setSelectedParts(QCPAxis::spAxis
);
402 axis_pressed_
= true;
404 seq_analysis_item_t
*sai
= seq_diagram_
->itemForPosY(event
->pos().y());
405 if (voipFeaturesEnabled
) {
406 ui
->actionSelectRtpStreams
->setEnabled(false);
407 ui
->actionDeselectRtpStreams
->setEnabled(false);
408 player_button_
->setEnabled(false);
410 if (GA_INFO_TYPE_RTP
== sai
->info_type
) {
411 ui
->actionSelectRtpStreams
->setEnabled(true && !file_closed_
);
412 ui
->actionDeselectRtpStreams
->setEnabled(true && !file_closed_
);
413 player_button_
->setEnabled(true && !file_closed_
);
414 current_rtp_sai_selected_
= sai
;
419 switch (event
->button()) {
421 on_actionGoToPacket_triggered();
430 void SequenceDialog::axisDoubleClicked(QCPAxis
*axis
, QCPAxis::SelectablePart
, QMouseEvent
*)
432 if (axis
== ui
->sequencePlot
->yAxis2
) {
433 QCP::MarginSides autoMargins
= axis
->axisRect()->autoMargins();
434 axis
->axisRect()->setAutoMargins(autoMargins
| QCP::msRight
);
435 ui
->sequencePlot
->replot();
436 axis
->axisRect()->setAutoMargins(autoMargins
);
437 ui
->sequencePlot
->replot();
441 void SequenceDialog::mouseReleased(QMouseEvent
*)
443 QCustomPlot
*sp
= ui
->sequencePlot
;
444 sp
->yAxis2
->setSelectedParts(QCPAxis::spNone
);
445 axis_pressed_
= false;
446 sp
->replot(QCustomPlot::rpQueuedReplot
);
449 void SequenceDialog::mouseMoved(QMouseEvent
*event
)
451 current_rtp_sai_hovered_
= NULL
;
454 Qt::CursorShape shape
= Qt::ArrowCursor
;
456 QCPAxis
*yAxis2
= ui
->sequencePlot
->yAxis2
;
457 // For some reason we need this extra bool and can't rely just on
458 // yAxis2->selectedParts().testFlag(QCPAxis::spAxis)
460 int x
= qMax(event
->pos().x(), yAxis2
->axisRect()->left());
461 QMargins margins
= yAxis2
->axisRect()->margins();
462 margins
+= QMargins(0, 0, yAxis2
->axisRect()->right() - x
, 0);
463 yAxis2
->axisRect()->setMargins(margins
);
464 shape
= Qt::SplitHCursor
;
465 ui
->sequencePlot
->replot(QCustomPlot::rpQueuedReplot
);
467 if (std::abs(event
->pos().x() - yAxis2
->axisRect()->right()) < 5) {
468 shape
= Qt::SplitHCursor
;
471 seq_analysis_item_t
*sai
= seq_diagram_
->itemForPosY(event
->pos().y());
473 if (GA_INFO_TYPE_RTP
== sai
->info_type
) {
474 ui
->actionSelectRtpStreams
->setEnabled(true);
475 ui
->actionDeselectRtpStreams
->setEnabled(true);
476 current_rtp_sai_hovered_
= sai
;
478 packet_num_
= sai
->frame_number
;
479 hint
= QStringLiteral("Packet %1: %2").arg(packet_num_
).arg(sai
->comment
);
483 if (ui
->sequencePlot
->cursor().shape() != shape
) {
484 ui
->sequencePlot
->setCursor(QCursor(shape
));
487 if (hint
.isEmpty()) {
488 if (!info_
->sainfo()) {
489 hint
+= tr("No data");
491 hint
+= tr("%Ln node(s)", "", info_
->sainfo()->num_nodes
) + QStringLiteral(", ")
492 + tr("%Ln item(s)", "", num_items_
);
496 ui
->hintLabel
->setText(hint
);
499 void SequenceDialog::mouseWheeled(QWheelEvent
*event
)
501 int scroll_x
= event
->angleDelta().x() * -1 / 8;
502 scroll_x
*= ui
->horizontalScrollBar
->singleStep();
504 ui
->horizontalScrollBar
->setValue(ui
->horizontalScrollBar
->value() + scroll_x
);
507 int scroll_y
= event
->angleDelta().ry() * -1 / 8;
508 scroll_y
*= ui
->verticalScrollBar
->singleStep();
510 ui
->verticalScrollBar
->setValue(ui
->verticalScrollBar
->value() + scroll_y
);
516 void SequenceDialog::on_buttonBox_clicked(QAbstractButton
*button
)
518 if (button
== reset_button_
) {
520 } else if (button
== export_button_
) {
525 void SequenceDialog::exportDiagram()
527 QString file_name
, extension
;
528 QDir
path(mainApp
->openDialogInitialDir());
529 QString pdf_filter
= tr("Portable Document Format (*.pdf)");
530 QString png_filter
= tr("Portable Network Graphics (*.png)");
531 QString bmp_filter
= tr("Windows Bitmap (*.bmp)");
532 // Gaze upon my beautiful graph with lossy artifacts!
533 QString jpeg_filter
= tr("JPEG File Interchange Format (*.jpeg *.jpg)");
534 QString ascii_filter
= tr("ASCII (*.txt)");
536 QString filter
= QStringLiteral("%1;;%2;;%3;;%4")
542 filter
.append(QStringLiteral(";;%5").arg(ascii_filter
));
545 file_name
= WiresharkFileDialog::getSaveFileName(this, mainApp
->windowTitleString(tr("Save Graph As…")),
546 path
.canonicalPath(), filter
, &extension
);
548 if (file_name
.length() > 0) {
549 bool save_ok
= false;
550 // The QCustomPlot save functions take a width and a height, measured
551 // in pixels (for the entire viewport).
552 // In order to display the whole graph, we have to change the axes
553 // and scale up the width and height appropriately so that the text
554 // has the proper spacing. (Using the scale factor in some of the
555 // image writing functions makes the text illegible.)
556 // If we set the axes back to their old value without replotting,
557 // there's no visual effects from doing this.
558 QCustomPlot
*sp
= ui
->sequencePlot
;
559 QCPRange old_yrange
= sp
->yAxis
->range();
560 QCPRange old_xrange
= sp
->xAxis2
->range();
561 // For the horizontal aspect, we'll display all the nodes.
562 // Nodes can excluded by filtering (hiding nodes is in the todo list.)
563 // Use the current width of a node as determined by the user zooming
564 // with Key_Plus and Key_Minus, multiply that by the number of nodes,
565 // and add in the margin from the Time and Comment columns.
566 // MAX_NUM_NODES is 40, which results in a manageable 8802 pixel width
567 // at the default zoom level on my Linux box.
568 // (If the user has zoomed in unreasonably, that's on the user.)
569 int hmargin
= sp
->axisRect()->outerRect().width() - sp
->axisRect()->width();
570 double nodeSize
= (sp
->axisRect()->width()) / old_xrange
.size();
571 // For the vertical aspect, we need to put a bound on the number of
572 // pixels or items we'll put in an image, as it can get far too large.
573 // (JPEG only supports 16 bit aspect sizes, PNG supports 31 bit but
574 // many viewers don't.)
575 int vmargin
= sp
->axisRect()->outerRect().height() - sp
->axisRect()->height();
576 // 1000 items is a little over 27000 pixels in height on my machine.
577 // XXX - Should this pref be pixels instead of items?
578 //int max_pixel = 24576;
579 //double range_span = ((max_pixel - vmargin) / (one_em_ * 1.5));
580 double range_span
= prefs
.flow_graph_max_export_items
;
581 // Start at the current top item, and QCPRange::bounded does what
582 // we want, with margins of 1.0 on top and bottom.
583 QCPRange
new_yrange(old_yrange
.lower
, old_yrange
.lower
+ range_span
);
584 new_yrange
= new_yrange
.bounded(min_top_
, num_items_
);
585 sp
->yAxis
->setRange(new_yrange
);
586 // margins of 0.5 on left and right for port number, etc.
587 sp
->xAxis2
->setRange(min_left_
, info_
->sainfo()->num_nodes
- 0.5);
588 // As seen in resetAxes(), we have an item take ~ 1.5*one_em_ pixels.
589 int ySize
= new_yrange
.size() * (one_em_
* 1.5) + vmargin
;
590 int xSize
= (nodeSize
* info_
->sainfo()->num_nodes
) + hmargin
;
591 if (extension
.compare(pdf_filter
) == 0) {
592 save_ok
= ui
->sequencePlot
->savePdf(file_name
, xSize
, ySize
);
593 } else if (extension
.compare(png_filter
) == 0) {
594 save_ok
= ui
->sequencePlot
->savePng(file_name
, xSize
, ySize
);
595 } else if (extension
.compare(bmp_filter
) == 0) {
596 save_ok
= ui
->sequencePlot
->saveBmp(file_name
, xSize
, ySize
);
597 } else if (extension
.compare(jpeg_filter
) == 0) {
598 save_ok
= ui
->sequencePlot
->saveJpg(file_name
, xSize
, ySize
);
599 } else if (extension
.compare(ascii_filter
) == 0 && !file_closed_
&& info_
->sainfo()) {
600 FILE *outfile
= ws_fopen(file_name
.toUtf8().constData(), "w");
601 if (outfile
!= NULL
) {
602 sequence_analysis_dump_to_file(outfile
, info_
->sainfo(), 0);
609 sp
->yAxis
->setRange(old_yrange
);
610 sp
->xAxis2
->setRange(old_xrange
);
611 // else error dialog?
613 mainApp
->setLastOpenDirFromFilename(file_name
);
615 open_failure_alert_box(file_name
.toUtf8().constData(), errno
, true);
620 void SequenceDialog::fillDiagram()
622 if (!info_
->sainfo() || file_closed_
) return;
624 QCustomPlot
*sp
= ui
->sequencePlot
;
626 if (strcmp(info_
->sainfo()->name
, "voip") == 0) {
627 seq_diagram_
->setData(info_
->sainfo());
629 seq_diagram_
->clearData();
630 sequence_analysis_list_free(info_
->sainfo());
632 register_analysis_t
* analysis
= sequence_analysis_find_by_name(info_
->sainfo()->name
);
633 if (analysis
!= NULL
)
635 GString
*error_string
;
636 const char *filter
= NULL
;
637 if (ui
->displayFilterCheckBox
->checkState() == Qt::Checked
)
638 filter
= cap_file_
.capFile()->dfilter
;
640 error_string
= register_tap_listener(sequence_analysis_get_tap_listener_name(analysis
), info_
->sainfo(), filter
, sequence_analysis_get_tap_flags(analysis
),
641 NULL
, sequence_analysis_get_packet_func(analysis
), NULL
, NULL
);
643 report_failure("Sequence dialog - tap registration failed: %s", error_string
->str
);
644 g_string_free(error_string
, TRUE
);
647 cf_retap_packets(cap_file_
.capFile());
648 remove_tap_listener(info_
->sainfo());
650 num_items_
= sequence_analysis_get_nodes(info_
->sainfo());
651 seq_diagram_
->setData(info_
->sainfo());
655 sequence_w_
= one_em_
* 15; // Arbitrary
660 // XXX QCustomPlot doesn't seem to draw any sort of focus indicator.
664 void SequenceDialog::panAxes(int x_pixels
, int y_pixels
)
666 // We could simplify this quite a bit if we set the scroll bar values instead.
667 if (!info_
->sainfo()) return;
669 QCustomPlot
*sp
= ui
->sequencePlot
;
673 h_pan
= sp
->xAxis2
->range().size() * x_pixels
/ sp
->xAxis2
->axisRect()->width();
674 // The nodes are placed on integer x values from 0 to num_nodes - 1.
675 // We allow 0.5 of margin around a node (also reflected in the
676 // horizontalScrollBar range.)
678 h_pan
= qMax(h_pan
, min_left_
- sp
->xAxis2
->range().lower
);
680 h_pan
= qMin(h_pan
, info_
->sainfo()->num_nodes
- 0.5 - sp
->xAxis2
->range().upper
);
683 if (sp
->yAxis
->rangeReversed()) {
684 // For reversed axes, lower still references the mathematically
685 // smaller number than upper, so reverse the direction.
686 y_pixels
= -y_pixels
;
688 v_pan
= sp
->yAxis
->range().size() * y_pixels
/ sp
->yAxis
->axisRect()->height();
690 v_pan
= qMax(v_pan
, min_top_
- sp
->yAxis
->range().lower
);
692 v_pan
= qMin(v_pan
, num_items_
- sp
->yAxis
->range().upper
);
695 if (h_pan
&& !(sp
->xAxis2
->range().contains(min_left_
) && sp
->xAxis2
->range().contains(info_
->sainfo()->num_nodes
- 0.5))) {
696 sp
->xAxis2
->moveRange(h_pan
);
697 sp
->replot(QCustomPlot::rpQueuedReplot
);
699 if (v_pan
&& !(sp
->yAxis
->range().contains(min_top_
) && sp
->yAxis
->range().contains(num_items_
))) {
700 sp
->yAxis
->moveRange(v_pan
);
701 sp
->replot(QCustomPlot::rpQueuedReplot
);
705 void SequenceDialog::resetAxes(bool keep_lower
)
707 if (!info_
->sainfo()) return;
709 QCustomPlot
*sp
= ui
->sequencePlot
;
711 // Allow space for labels on the top and port numbers on the left.
712 double top_pos
= min_top_
, left_pos
= min_left_
;
714 top_pos
= sp
->yAxis
->range().lower
;
715 left_pos
= sp
->xAxis2
->range().lower
;
718 double range_span
= sp
->viewport().width() / sequence_w_
* sp
->axisRect()->rangeZoomFactor(Qt::Horizontal
);
719 sp
->xAxis2
->setRange(left_pos
, range_span
+ left_pos
);
721 range_span
= sp
->axisRect()->height() / (one_em_
* 1.5);
722 sp
->yAxis
->setRange(top_pos
, range_span
+ top_pos
);
724 double rmin
= sp
->xAxis2
->range().size() / 2;
725 ui
->horizontalScrollBar
->setRange((rmin
+ min_left_
) * 100, (info_
->sainfo()->num_nodes
- 0.5 - rmin
) * 100);
726 xAxisChanged(sp
->xAxis2
->range());
727 ui
->horizontalScrollBar
->setValue(ui
->horizontalScrollBar
->minimum()); // Shouldn't be needed.
729 rmin
= (sp
->yAxis
->range().size() / 2);
730 ui
->verticalScrollBar
->setRange((rmin
+ min_top_
) * 100, (num_items_
- 0.5 - rmin
) * 100);
731 yAxisChanged(sp
->yAxis
->range());
733 sp
->replot(QCustomPlot::rpQueuedReplot
);
736 void SequenceDialog::layoutAxisLabels()
738 // It would be exceedingly handy if we could do one or both of the
740 // - Position an axis label above its axis inline with the tick labels.
741 // - Anchor a QCPItemText to one of the corners of a QCPAxis.
742 // Neither of those appear to be possible, so we place our labels using
743 // absolute positioning immediately after the layout size and positions
744 // are set, and right before the replot (or print) draw step occurs,
745 // using the new QCustomPlot 2.1.0 QCustomPlot::afterLayout signal.
747 QCustomPlot
*sp
= ui
->sequencePlot
;
749 QRect axis_rect
= sp
->axisRect()->rect();
751 key_text_
->position
->setCoords(axis_rect
.left()
752 - sp
->yAxis
->padding()
753 - sp
->yAxis
->tickLabelPadding()
754 - sp
->yAxis
->offset(),
755 axis_rect
.top() / 2);
756 comment_text_
->position
->setCoords(axis_rect
.right()
757 + sp
->yAxis2
->padding()
758 + sp
->yAxis2
->tickLabelPadding()
759 + sp
->yAxis2
->offset(),
760 axis_rect
.top() / 2);
763 void SequenceDialog::resetView()
768 void SequenceDialog::on_actionGoToPacket_triggered()
770 if (!file_closed_
&& packet_num_
> 0) {
771 cf_goto_frame(cap_file_
.capFile(), packet_num_
, false);
772 seq_diagram_
->setSelectedPacket(packet_num_
);
776 void SequenceDialog::goToAdjacentPacket(bool next
)
778 if (file_closed_
) return;
780 int old_key
= seq_diagram_
->selectedKey();
781 int adjacent_packet
= seq_diagram_
->adjacentPacket(next
);
782 int new_key
= seq_diagram_
->selectedKey();
784 if (adjacent_packet
> 0) {
786 QCustomPlot
*sp
= ui
->sequencePlot
;
787 double range_offset
= 0.0;
788 // Scroll if we're at our scroll margin and we haven't reached
789 // the end of our range.
790 double scroll_margin
= 3.0; // Lines
793 range_offset
= new_key
- old_key
;
796 if (new_key
< sp
->yAxis
->range().lower
) {
798 range_offset
= qRound(new_key
- sp
->yAxis
->range().lower
- scroll_margin
- 0.5);
799 } else if (new_key
> sp
->yAxis
->range().upper
) {
800 // Out of range, bottom
801 range_offset
= qRound(new_key
- sp
->yAxis
->range().upper
+ scroll_margin
+ 0.5);
804 if (new_key
+ scroll_margin
< sp
->yAxis
->range().upper
) {
808 // In range, previous
809 if (new_key
- scroll_margin
> sp
->yAxis
->range().lower
) {
814 // Clamp to our upper & lower bounds.
815 if (range_offset
> 0) {
816 range_offset
= qMin(range_offset
, num_items_
- sp
->yAxis
->range().upper
);
817 } else if (range_offset
< 0) {
818 range_offset
= qMax(range_offset
, min_top_
- sp
->yAxis
->range().lower
);
820 sp
->yAxis
->moveRange(range_offset
);
822 cf_goto_frame(cap_file_
.capFile(), adjacent_packet
, false);
823 seq_diagram_
->setSelectedPacket(adjacent_packet
);
827 void SequenceDialog::displayFilterCheckBoxToggled(bool)
832 void SequenceDialog::on_flowComboBox_activated(int index
)
834 if (!info_
->sainfo() || (strcmp(info_
->sainfo()->name
, "voip") == 0) || index
< 0)
837 register_analysis_t
* analysis
= VariantPointer
<register_analysis_t
>::asPtr(ui
->flowComboBox
->itemData(index
));
838 info_
->sainfo()->name
= sequence_analysis_get_name(analysis
);
843 void SequenceDialog::addressChanged(int)
845 if (!info_
->sainfo()) return;
847 QVariant data
= ui
->addressComboBox
->currentData();
848 if (data
.isValid()) {
849 info_
->sainfo()->any_addr
= data
.toBool();
854 void SequenceDialog::on_actionMoveRight10_triggered()
859 void SequenceDialog::on_actionMoveLeft10_triggered()
864 void SequenceDialog::on_actionMoveUp10_triggered()
869 void SequenceDialog::on_actionMoveDown10_triggered()
874 void SequenceDialog::on_actionMoveRight1_triggered()
879 void SequenceDialog::on_actionMoveLeft1_triggered()
884 void SequenceDialog::on_actionMoveUp1_triggered()
889 void SequenceDialog::on_actionMoveDown1_triggered()
894 void SequenceDialog::on_actionZoomIn_triggered()
899 void SequenceDialog::on_actionZoomOut_triggered()
904 void SequenceDialog::processRtpStream(bool select
)
906 seq_analysis_item_t
*current_rtp_sai
= NULL
;
908 // If RTP sai is below mouse, use it. If not, try selected RTP sai
909 if (current_rtp_sai_hovered_
&& GA_INFO_TYPE_RTP
== current_rtp_sai_hovered_
->info_type
) {
910 current_rtp_sai
= current_rtp_sai_hovered_
;
911 } else if (current_rtp_sai_selected_
&& GA_INFO_TYPE_RTP
== current_rtp_sai_selected_
->info_type
) {
912 current_rtp_sai
= current_rtp_sai_selected_
;
915 if (current_rtp_sai
) {
916 QVector
<rtpstream_id_t
*> stream_ids
;
918 // We don't need copy it as it is not cleared during retap
919 stream_ids
<< &((rtpstream_info_t
*)current_rtp_sai
->info_ptr
)->id
;
921 emit
rtpStreamsDialogSelectRtpStreams(stream_ids
);
923 emit
rtpStreamsDialogDeselectRtpStreams(stream_ids
);
929 void SequenceDialog::on_actionSelectRtpStreams_triggered()
931 processRtpStream(true);
934 void SequenceDialog::on_actionDeselectRtpStreams_triggered()
936 processRtpStream(false);
939 void SequenceDialog::zoomXAxis(bool in
)
941 QCustomPlot
*sp
= ui
->sequencePlot
;
942 double h_factor
= sp
->axisRect()->rangeZoomFactor(Qt::Horizontal
);
945 h_factor
= pow(h_factor
, -1);
948 sp
->xAxis2
->scaleRange(h_factor
, sp
->xAxis
->range().lower
);
952 bool SequenceDialog::addFlowSequenceItem(const void* key
, void *value
, void *userdata
)
954 const char* name
= (const char*)key
;
955 register_analysis_t
* analysis
= (register_analysis_t
*)value
;
956 sequence_items_t
* item_data
= (sequence_items_t
*)userdata
;
958 /* XXX - Although "voip" isn't a registered name yet, it appears to have special
959 handling that will be done outside of registered data */
960 if (strcmp(name
, "voip") == 0)
963 item_data
->flow
->addItem(sequence_analysis_get_ui_name(analysis
), VariantPointer
<register_analysis_t
>::asQVariant(analysis
));
965 if (item_data
->flow
->itemData(item_data
->curr_index
).toString().compare(item_data
->info
->sainfo()->name
) == 0)
966 item_data
->flow
->setCurrentIndex(item_data
->curr_index
);
968 item_data
->curr_index
++;
973 QVector
<rtpstream_id_t
*>SequenceDialog::getSelectedRtpIds()
975 QVector
<rtpstream_id_t
*> stream_ids
;
977 if (current_rtp_sai_selected_
&& GA_INFO_TYPE_RTP
== current_rtp_sai_selected_
->info_type
) {
978 stream_ids
<< &((rtpstream_info_t
*)current_rtp_sai_selected_
->info_ptr
)->id
;
984 void SequenceDialog::rtpPlayerReplace()
986 emit
rtpPlayerDialogReplaceRtpStreams(getSelectedRtpIds());
989 void SequenceDialog::rtpPlayerAdd()
991 emit
rtpPlayerDialogAddRtpStreams(getSelectedRtpIds());
994 void SequenceDialog::rtpPlayerRemove()
996 emit
rtpPlayerDialogRemoveRtpStreams(getSelectedRtpIds());
999 void SequenceDialog::on_buttonBox_helpRequested()
1001 mainApp
->helpTopicAction(HELP_STAT_FLOW_GRAPH
);
1004 SequenceInfo::SequenceInfo(seq_analysis_info_t
*sainfo
) :
1010 SequenceInfo::~SequenceInfo()
1012 sequence_analysis_info_free(sainfo_
);