Kerberos: add kerberos_inject_longterm_key() helper function
[wireshark-sm.git] / ui / qt / sequence_dialog.cpp
bloba740feefd8b0d85a3ef75feef5096847d098b2b7
1 /* sequence_dialog.cpp
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
8 */
10 #include "sequence_dialog.h"
11 #include <ui_sequence_dialog.h>
13 #include "epan/addr_resolv.h"
15 #include "file.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"
33 #include <QDir>
34 #include <QFontMetrics>
35 #include <QPoint>
37 // To do:
38 // - Resize the Time and Comment axis as well?
39 // - For general flows, let the user show columns other than COL_INFO.
40 // (#12549)
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.)
46 // - Hide nodes
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;
56 typedef struct {
57 int curr_index;
58 QComboBox *flow;
59 SequenceInfo *info;
60 } sequence_items_t;
62 SequenceDialog::SequenceDialog(QWidget &parent, CaptureFile &cf, SequenceInfo *info, bool voipFeatures) :
63 WiresharkDialog(parent, cf),
64 ui(new Ui::SequenceDialog),
65 info_(info),
66 num_items_(0),
67 packet_num_(0),
68 sequence_w_(1),
69 axis_pressed_(false),
70 current_rtp_sai_selected_(nullptr),
71 current_rtp_sai_hovered_(nullptr),
72 voipFeaturesEnabled(voipFeatures)
74 QAction *action;
76 ui->setupUi(this);
77 ui->hintLabel->setSmallText();
79 QCustomPlot *sp = ui->sequencePlot;
80 setWindowSubtitle(info_ ? tr("Call Flow") : tr("Flow"));
82 if (!info_) {
83 info_ = new SequenceInfo(sequence_analysis_info_new());
84 info_->sainfo()->name = "any";
85 info_->sainfo()->any_addr = true;
86 } else {
87 info_->ref();
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);
188 #else
189 connect(ui->addressComboBox, &QComboBox::currentIndexChanged, this, &SequenceDialog::addressChanged);
190 #endif
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);
213 if (close_bt) {
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
221 // visible.
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()
245 info_->unref();
246 delete ui;
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);
269 } else {
270 QToolTip::hideText();
271 event->ignore();
274 return true;
276 return QWidget::event(event);
279 void SequenceDialog::showEvent(QShowEvent *)
281 QTimer::singleShot(0, this, SLOT(fillDiagram()));
284 void SequenceDialog::resizeEvent(QResizeEvent *)
286 if (!info_) return;
288 resetAxes(true);
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()) {
297 case Qt::Key_Minus:
298 case Qt::Key_Underscore: // Shifted minus on U.S. keyboards
299 on_actionZoomOut_triggered();
300 break;
301 case Qt::Key_Plus:
302 case Qt::Key_Equal: // Unshifted plus on U.S. keyboards
303 on_actionZoomIn_triggered();
304 break;
306 case Qt::Key_Right:
307 case Qt::Key_L:
308 panAxes(pan_pixels, 0);
309 break;
310 case Qt::Key_Left:
311 case Qt::Key_H:
312 panAxes(-1 * pan_pixels, 0);
313 break;
314 case Qt::Key_Up:
315 case Qt::Key_K:
316 panAxes(0, pan_pixels);
317 break;
318 case Qt::Key_Down:
319 case Qt::Key_J:
320 panAxes(0, -1 * pan_pixels);
321 break;
323 case Qt::Key_PageDown:
324 case Qt::Key_Space:
325 ui->verticalScrollBar->setValue(ui->verticalScrollBar->value() + ui->verticalScrollBar->pageStep());
326 break;
327 case Qt::Key_PageUp:
328 ui->verticalScrollBar->setValue(ui->verticalScrollBar->value() - ui->verticalScrollBar->pageStep());
329 break;
331 case Qt::Key_0:
332 case Qt::Key_ParenRight: // Shifted 0 on U.S. keyboards
333 case Qt::Key_R:
334 case Qt::Key_Home:
335 resetAxes();
336 break;
338 case Qt::Key_G:
339 on_actionGoToPacket_triggered();
340 break;
341 case Qt::Key_N:
342 on_actionGoToNextPacket_triggered();
343 break;
344 case Qt::Key_P:
345 on_actionGoToPreviousPacket_triggered();
346 break;
347 case Qt::Key_S:
348 if (voipFeaturesEnabled) {
349 on_actionSelectRtpStreams_triggered();
351 break;
352 case Qt::Key_D:
353 if (voipFeaturesEnabled) {
354 on_actionDeselectRtpStreams_triggered();
356 break;
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;
398 if (event) {
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);
409 if (sai) {
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()) {
420 case Qt::LeftButton:
421 on_actionGoToPacket_triggered();
422 break;
423 default:
424 break;
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;
452 packet_num_ = 0;
453 QString hint;
454 Qt::CursorShape shape = Qt::ArrowCursor;
455 if (event) {
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)
459 if (axis_pressed_) {
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);
466 } else {
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());
472 if (sai) {
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");
490 } else {
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();
503 if (scroll_x) {
504 ui->horizontalScrollBar->setValue(ui->horizontalScrollBar->value() + scroll_x);
507 int scroll_y = event->angleDelta().ry() * -1 / 8;
508 scroll_y *= ui->verticalScrollBar->singleStep();
509 if (scroll_y) {
510 ui->verticalScrollBar->setValue(ui->verticalScrollBar->value() + scroll_y);
513 event->accept();
516 void SequenceDialog::on_buttonBox_clicked(QAbstractButton *button)
518 if (button == reset_button_) {
519 resetView();
520 } else if (button == export_button_) {
521 exportDiagram();
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")
537 .arg(pdf_filter)
538 .arg(png_filter)
539 .arg(bmp_filter)
540 .arg(jpeg_filter);
541 if (!file_closed_) {
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);
603 save_ok = true;
604 fclose(outfile);
605 } else {
606 save_ok = false;
609 sp->yAxis->setRange(old_yrange);
610 sp->xAxis2->setRange(old_xrange);
611 // else error dialog?
612 if (save_ok) {
613 mainApp->setLastOpenDirFromFilename(file_name);
614 } else {
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());
628 } else {
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);
642 if (error_string) {
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
657 mouseMoved(NULL);
658 resetAxes();
660 // XXX QCustomPlot doesn't seem to draw any sort of focus indicator.
661 sp->setFocus();
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;
670 double h_pan = 0.0;
671 double v_pan = 0.0;
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.)
677 if (h_pan < 0) {
678 h_pan = qMax(h_pan, min_left_ - sp->xAxis2->range().lower);
679 } else {
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();
689 if (v_pan < 0) {
690 v_pan = qMax(v_pan, min_top_ - sp->yAxis->range().lower);
691 } else {
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_;
713 if (keep_lower) {
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
739 // following:
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()
765 resetAxes();
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) {
785 if (new_key >= 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
792 if (old_key >= 0) {
793 range_offset = new_key - old_key;
796 if (new_key < sp->yAxis->range().lower) {
797 // Out of range, top
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);
802 } else if (next) {
803 // In range, next
804 if (new_key + scroll_margin < sp->yAxis->range().upper) {
805 range_offset = 0.0;
807 } else {
808 // In range, previous
809 if (new_key - scroll_margin > sp->yAxis->range().lower) {
810 range_offset = 0.0;
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)
829 fillDiagram();
832 void SequenceDialog::on_flowComboBox_activated(int index)
834 if (!info_->sainfo() || (strcmp(info_->sainfo()->name, "voip") == 0) || index < 0)
835 return;
837 register_analysis_t* analysis = VariantPointer<register_analysis_t>::asPtr(ui->flowComboBox->itemData(index));
838 info_->sainfo()->name = sequence_analysis_get_name(analysis);
840 fillDiagram();
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();
850 fillDiagram();
854 void SequenceDialog::on_actionMoveRight10_triggered()
856 panAxes(10, 0);
859 void SequenceDialog::on_actionMoveLeft10_triggered()
861 panAxes(-10, 0);
864 void SequenceDialog::on_actionMoveUp10_triggered()
866 panAxes(0, 10);
869 void SequenceDialog::on_actionMoveDown10_triggered()
871 panAxes(0, -10);
874 void SequenceDialog::on_actionMoveRight1_triggered()
876 panAxes(1, 0);
879 void SequenceDialog::on_actionMoveLeft1_triggered()
881 panAxes(-1, 0);
884 void SequenceDialog::on_actionMoveUp1_triggered()
886 panAxes(0, 1);
889 void SequenceDialog::on_actionMoveDown1_triggered()
891 panAxes(0, -1);
894 void SequenceDialog::on_actionZoomIn_triggered()
896 zoomXAxis(true);
899 void SequenceDialog::on_actionZoomOut_triggered()
901 zoomXAxis(false);
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;
920 if (select) {
921 emit rtpStreamsDialogSelectRtpStreams(stream_ids);
922 } else {
923 emit rtpStreamsDialogDeselectRtpStreams(stream_ids);
925 raise();
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);
944 if (!in) {
945 h_factor = pow(h_factor, -1);
948 sp->xAxis2->scaleRange(h_factor, sp->xAxis->range().lower);
949 sp->replot();
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)
961 return false;
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++;
970 return false;
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;
981 return stream_ids;
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) :
1005 sainfo_(sainfo),
1006 count_(1)
1010 SequenceInfo::~SequenceInfo()
1012 sequence_analysis_info_free(sainfo_);