1 /* lte_rlc_graph_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 "lte_rlc_graph_dialog.h"
11 #include <ui_lte_rlc_graph_dialog.h>
12
13 #include <epan/epan.h>
14 #include <epan/epan_dissect.h>
15 #include <epan/tap.h>
16 #include <epan/stat_tap_ui.h>
17
18 #include <epan/tvbuff-int.h>
19 #include <epan/tvbuff.h>
20 #include <frame_tvbuff.h>
21
22 #include <ui/qt/utils/tango_colors.h>
23
24 #include <QMenu>
25 #include <QRubberBand>
26
27 #include <wsutil/utf8_entities.h>
28 #include <ui/qt/utils/qt_ui_utils.h>
29 #include "wireshark_application.h"
30 #include "simple_dialog.h"
31 #include "ui/qt/widgets/wireshark_file_dialog.h"
32
33 #include <epan/dissectors/packet-rlc-lte.h>
34
35 #include <ui/tap-rlc-graph.h>
36
37
38 const QRgb graph_color_ack = tango_sky_blue_4; // Blue for ACK lines
39 const QRgb graph_color_nack = tango_scarlet_red_3; // Red for NACKs
40
41 // Size of selectable packet points in the base graph
42 const double pkt_point_size_ = 3.0;
43
44
45 // Constructor.
LteRlcGraphDialog(QWidget & parent,CaptureFile & cf,bool channelKnown)46 LteRlcGraphDialog::LteRlcGraphDialog(QWidget &parent, CaptureFile &cf, bool channelKnown) :
47 WiresharkDialog(parent, cf),
48 ui(new Ui::LteRlcGraphDialog),
49 mouse_drags_(true),
50 rubber_band_(NULL),
51 base_graph_(NULL),
52 reseg_graph_(NULL),
53 acks_graph_(NULL),
54 nacks_graph_(NULL),
55 tracer_(NULL),
56 packet_num_(0)
57 {
58 ui->setupUi(this);
59 loadGeometry(parent.width() * 4 / 5, parent.height() * 3 / 4);
60
61 QCustomPlot *rp = ui->rlcPlot;
62 rp->xAxis->setLabel(tr("Time"));
63 rp->yAxis->setLabel(tr("Sequence Number"));
64
65 // TODO: couldn't work out how to tell rp->xAxis not to label fractions of a SN...
66
67 ui->dragRadioButton->setChecked(mouse_drags_);
68
69 ctx_menu_ = new QMenu(this);
70 ctx_menu_->addAction(ui->actionZoomIn);
71 ctx_menu_->addAction(ui->actionZoomInX);
72 ctx_menu_->addAction(ui->actionZoomInY);
73 ctx_menu_->addAction(ui->actionZoomOut);
74 ctx_menu_->addAction(ui->actionZoomOutX);
75 ctx_menu_->addAction(ui->actionZoomOutY);
76 ctx_menu_->addAction(ui->actionReset);
77 ctx_menu_->addSeparator();
78 ctx_menu_->addAction(ui->actionMoveRight10);
79 ctx_menu_->addAction(ui->actionMoveLeft10);
80 ctx_menu_->addAction(ui->actionMoveUp10);
81 ctx_menu_->addAction(ui->actionMoveUp100);
82 ctx_menu_->addAction(ui->actionMoveDown10);
83 ctx_menu_->addAction(ui->actionMoveDown100);
84 ctx_menu_->addAction(ui->actionMoveRight1);
85 ctx_menu_->addAction(ui->actionMoveLeft1);
86 ctx_menu_->addAction(ui->actionMoveUp1);
87 ctx_menu_->addAction(ui->actionMoveDown1);
88 ctx_menu_->addSeparator();
89 ctx_menu_->addAction(ui->actionGoToPacket);
90 ctx_menu_->addSeparator();
91 ctx_menu_->addAction(ui->actionDragZoom);
92 // ctx_menu_->addAction(ui->actionToggleTimeOrigin);
93 ctx_menu_->addAction(ui->actionCrosshairs);
94 ctx_menu_->addSeparator();
95 ctx_menu_->addAction(ui->actionSwitchDirection);
96 set_action_shortcuts_visible_in_context_menu(ctx_menu_->actions());
97
98 // Zero out this struct.
99 memset(&graph_, 0, sizeof(graph_));
100
101 // If channel is known, details will be supplied by setChannelInfo().
102 if (!channelKnown) {
103 completeGraph();
104 }
105 }
106
107 // Destructor
~LteRlcGraphDialog()108 LteRlcGraphDialog::~LteRlcGraphDialog()
109 {
110 delete ui;
111 }
112
113 // Set the channel information that this graph should show.
setChannelInfo(guint16 ueid,guint8 rlcMode,guint16 channelType,guint16 channelId,guint8 direction,bool maybe_empty)114 void LteRlcGraphDialog::setChannelInfo(guint16 ueid, guint8 rlcMode,
115 guint16 channelType, guint16 channelId, guint8 direction,
116 bool maybe_empty)
117 {
118 graph_.ueid = ueid;
119 graph_.rlcMode = rlcMode;
120 graph_.channelType = channelType;
121 graph_.channelId = channelId;
122 graph_.channelSet = TRUE;
123 graph_.direction = direction;
124
125 completeGraph(maybe_empty);
126 }
127
128 // Once channel details are known, complete the graph with details that depend upon the channel.
completeGraph(bool may_be_empty)129 void LteRlcGraphDialog::completeGraph(bool may_be_empty)
130 {
131 QCustomPlot *rp = ui->rlcPlot;
132
133 // If no channel chosen already, try to use currently selected frame.
134 findChannel(may_be_empty);
135
136 // Set window title here.
137 if (graph_.channelSet) {
138 QString dlg_title = tr("LTE RLC Graph (UE=%1 chan=%2%3 %4 - %5)")
139 .arg(graph_.ueid)
140 .arg((graph_.channelType == CHANNEL_TYPE_SRB) ? "SRB" : "DRB")
141 .arg(graph_.channelId)
142 .arg((graph_.direction == DIRECTION_UPLINK) ? "UL" : "DL")
143 .arg((graph_.rlcMode == RLC_UM_MODE) ? "UM" : "AM");
144 setWindowTitle(dlg_title);
145 }
146 else {
147 setWindowTitle(tr("LTE RLC Graph - no channel selected"));
148 }
149
150 // Set colours/styles for each of the traces on the graph.
151 QCustomPlot *sp = ui->rlcPlot;
152 base_graph_ = sp->addGraph(); // All: Selectable segments
153 base_graph_->setPen(QPen(QBrush(Qt::black), 0.25));
154
155 reseg_graph_ = sp->addGraph();
156 reseg_graph_->setPen(QPen(QBrush(Qt::lightGray), 0.25));
157
158 acks_graph_ = sp->addGraph();
159 acks_graph_->setPen(QPen(QBrush(graph_color_ack), 1.0));
160
161 nacks_graph_ = sp->addGraph();
162 nacks_graph_->setPen(QPen(QBrush(graph_color_nack), 0.25));
163
164 // Create tracer
165 tracer_ = new QCPItemTracer(sp);
166 tracer_->setVisible(false);
167 toggleTracerStyle(true);
168
169 // Change label on save/export button.
170 QPushButton *save_bt = ui->buttonBox->button(QDialogButtonBox::Save);
171 save_bt->setText(tr("Save As…"));
172
173 // Don't want to connect again after first time. - causes mouse handlers to get called
174 // multiple times.
175 if (!may_be_empty) {
176 connect(rp, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(graphClicked(QMouseEvent*)));
177 connect(rp, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*)));
178 connect(rp, SIGNAL(mouseRelease(QMouseEvent*)), this, SLOT(mouseReleased(QMouseEvent*)));
179 }
180 disconnect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
181 this->setResult(QDialog::Accepted);
182
183 // Extract the data that the graph can use.
184 fillGraph();
185 }
186
187 // See if the given segment matches the channel this graph is plotting.
compareHeaders(rlc_segment * seg)188 bool LteRlcGraphDialog::compareHeaders(rlc_segment *seg)
189 {
190 return compare_rlc_headers(graph_.ueid, graph_.channelType,
191 graph_.channelId, graph_.rlcMode, graph_.direction,
192 seg->ueid, seg->channelType,
193 seg->channelId, seg->rlcMode, seg->direction,
194 seg->isControlPDU);
195 }
196
197 // Look for channel to plot based upon currently selected frame.
findChannel(bool may_fail)198 void LteRlcGraphDialog::findChannel(bool may_fail)
199 {
200 // Temporarily disconnect mouse move signals.
201 QCustomPlot *rp = ui->rlcPlot;
202 disconnect(rp, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*)));
203
204 char *err_string = NULL;
205
206 // Rescan for channel data.
207 rlc_graph_segment_list_free(&graph_);
208 if (!rlc_graph_segment_list_get(cap_file_.capFile(), &graph_, graph_.channelSet,
209 &err_string)) {
210 if (may_fail) {
211 g_free(err_string);
212 } else {
213 // Pop up an error box to report error.
214 simple_error_message_box("%s", err_string);
215 g_free(err_string);
216 return;
217 }
218 }
219
220 // Reconnect mouse move signal.
221 connect(rp, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*)));
222 }
223
224 // Fill in graph data based upon what was read into the rlc_graph struct.
fillGraph()225 void LteRlcGraphDialog::fillGraph()
226 {
227 QCustomPlot *sp = ui->rlcPlot;
228
229 // We should always have 4 graphs, but cover case if no channel was chosen.
230 if (sp->graphCount() < 1) {
231 return;
232 }
233
234 tracer_->setGraph(NULL);
235
236 base_graph_->setLineStyle(QCPGraph::lsNone); // dot
237 reseg_graph_->setLineStyle(QCPGraph::lsNone); // dot
238 acks_graph_->setLineStyle(QCPGraph::lsStepLeft); // to get step effect...
239 nacks_graph_->setLineStyle(QCPGraph::lsNone); // dot, but bigger.
240
241 // Will show all graphs with data we find.
242 for (int i = 0; i < sp->graphCount(); i++) {
243 sp->graph(i)->data()->clear();
244 sp->graph(i)->setVisible(true);
245 }
246
247 // N.B. ssDisc is really too slow. TODO: work out how to turn off aliasing, or experiment
248 // with ssCustom. Other styles tried didn't look right.
249 // GTK version was speeded up noticibly by turning down aliasing level...
250 base_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, pkt_point_size_));
251 reseg_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, pkt_point_size_));
252 acks_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, pkt_point_size_));
253 // NACKs are shown bigger than others.
254 nacks_graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, pkt_point_size_*2));
255
256 // Map timestamps -> segments in first pass.
257 time_stamp_map_.clear();
258 for (struct rlc_segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
259 if (!compareHeaders(seg)) {
260 continue;
261 }
262 double ts = seg->rel_secs + seg->rel_usecs / 1000000.0;
263
264 time_stamp_map_.insert(ts, seg);
265 }
266
267 // Now sequence numbers.
268 QVector<double> seq_time, seq,
269 reseg_seq_time, reseg_seq,
270 acks_time, acks,
271 nacks_time, nacks;
272 for (struct rlc_segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
273 double ts = seg->rel_secs + seg->rel_usecs / 1000000.0;
274 if (compareHeaders(seg)) {
275 if (!seg->isControlPDU) {
276 // Data
277 if (seg->isResegmented) {
278 reseg_seq_time.append(ts);
279 reseg_seq.append(seg->SN);
280 }
281 else {
282 seq_time.append(ts);
283 seq.append(seg->SN);
284 }
285 }
286 else {
287 // Status (ACKs/NACKs)
288 acks_time.append(ts);
289 acks.append(seg->ACKNo-1);
290 for (int n=0; n < seg->noOfNACKs; n++) {
291 nacks_time.append(ts);
292 nacks.append(seg->NACKs[n]);
293 }
294 }
295 }
296 }
297
298 // Add the data from the graphs.
299 base_graph_->setData(seq_time, seq);
300 reseg_graph_->setData(reseg_seq_time, reseg_seq);
301 acks_graph_->setData(acks_time, acks);
302 nacks_graph_->setData(nacks_time, nacks);
303
304 sp->setEnabled(true);
305
306 // Auto-size...
307 mouseMoved(NULL);
308 resetAxes();
309
310 // This is why, in mouseMoved(), we only match the entries
311 // corresponding to data segments (base_graph_)...
312 tracer_->setGraph(base_graph_);
313
314 // XXX QCustomPlot doesn't seem to draw any sort of focus indicator.
315 sp->setFocus();
316 }
317
318 // Copied from TCP graphs, seems like a kludge to get the graph resized immediately after it is built...
showEvent(QShowEvent *)319 void LteRlcGraphDialog::showEvent(QShowEvent *)
320 {
321 resetAxes();
322 }
323
324 // Respond to a key press.
keyPressEvent(QKeyEvent * event)325 void LteRlcGraphDialog::keyPressEvent(QKeyEvent *event)
326 {
327 int pan_pixels = (event->modifiers() & Qt::ShiftModifier) ? 1 : 10;
328
329 switch(event->key()) {
330 case Qt::Key_Minus:
331 case Qt::Key_Underscore: // Shifted minus on U.S. keyboards
332 case Qt::Key_O: // GTK+
333 zoomAxes(false);
334 break;
335 case Qt::Key_Plus:
336 case Qt::Key_Equal: // Unshifted plus on U.S. keyboards
337 case Qt::Key_I: // GTK+
338 zoomAxes(true);
339 break;
340
341 case Qt::Key_X: // Zoom X axis only
342 if (event->modifiers() & Qt::ShiftModifier) {
343 zoomXAxis(false); // upper case X -> Zoom out
344 } else {
345 zoomXAxis(true); // lower case x -> Zoom in
346 }
347 break;
348 case Qt::Key_Y: // Zoom Y axis only
349 if (event->modifiers() & Qt::ShiftModifier) {
350 zoomYAxis(false); // upper case Y -> Zoom out
351 } else {
352 zoomYAxis(true); // lower case y -> Zoom in
353 }
354 break;
355
356 case Qt::Key_Right:
357 case Qt::Key_L:
358 panAxes(pan_pixels, 0);
359 break;
360 case Qt::Key_Left:
361 case Qt::Key_H:
362 panAxes(-1 * pan_pixels, 0);
363 break;
364 case Qt::Key_Up:
365 case Qt::Key_K:
366 panAxes(0, pan_pixels);
367 break;
368 case Qt::Key_Down:
369 case Qt::Key_J:
370 panAxes(0, -1 * pan_pixels);
371 break;
372
373 case Qt::Key_PageUp:
374 panAxes(0, 20 * pan_pixels);
375 break;
376 case Qt::Key_PageDown:
377 panAxes(0, -20 * pan_pixels);
378 break;
379
380 case Qt::Key_Space:
381 toggleTracerStyle(false);
382 break;
383
384 case Qt::Key_0:
385 case Qt::Key_ParenRight: // Shifted 0 on U.S. keyboards
386 case Qt::Key_Home:
387 case Qt::Key_R:
388 resetAxes();
389 break;
390
391 case Qt::Key_G:
392 on_actionGoToPacket_triggered();
393 break;
394 case Qt::Key_T:
395 // on_actionToggleTimeOrigin_triggered();
396 break;
397 case Qt::Key_Z:
398 on_actionDragZoom_triggered();
399 break;
400
401 case Qt::Key_D:
402 on_actionSwitchDirection_triggered();
403 break;
404
405 }
406
407 WiresharkDialog::keyPressEvent(event);
408 }
409
zoomAxes(bool in)410 void LteRlcGraphDialog::zoomAxes(bool in)
411 {
412 QCustomPlot *rp = ui->rlcPlot;
413 double h_factor = rp->axisRect()->rangeZoomFactor(Qt::Horizontal);
414 double v_factor = rp->axisRect()->rangeZoomFactor(Qt::Vertical);
415
416 if (!in) {
417 h_factor = pow(h_factor, -1);
418 v_factor = pow(v_factor, -1);
419 }
420
421 if (in) {
422 // Don't want to zoom in *too* far on y axis.
423 if (rp->yAxis->range().size() < 10) {
424 return;
425 }
426 }
427 else {
428 // Don't want to zoom out *too* far on y axis.
429 if (rp->yAxis->range().size() > (65536+10)) {
430 return;
431 }
432 }
433
434 rp->xAxis->scaleRange(h_factor, rp->xAxis->range().center());
435 rp->yAxis->scaleRange(v_factor, rp->yAxis->range().center());
436 rp->replot(QCustomPlot::rpQueuedReplot);
437 }
438
zoomXAxis(bool in)439 void LteRlcGraphDialog::zoomXAxis(bool in)
440 {
441 QCustomPlot *rp = ui->rlcPlot;
442 double h_factor = rp->axisRect()->rangeZoomFactor(Qt::Horizontal);
443
444 if (!in) {
445 h_factor = pow(h_factor, -1);
446 }
447
448 rp->xAxis->scaleRange(h_factor, rp->xAxis->range().center());
449 rp->replot(QCustomPlot::rpQueuedReplot);
450 }
451
zoomYAxis(bool in)452 void LteRlcGraphDialog::zoomYAxis(bool in)
453 {
454 QCustomPlot *rp = ui->rlcPlot;
455 double v_factor = rp->axisRect()->rangeZoomFactor(Qt::Vertical);
456
457 if (in) {
458 // Don't want to zoom in *too* far on y axis.
459 if (rp->yAxis->range().size() < 10) {
460 return;
461 }
462 }
463 else {
464 // Don't want to zoom out *too* far on y axis.
465 if (rp->yAxis->range().size() > (65536+10)) {
466 return;
467 }
468 }
469
470 if (!in) {
471 v_factor = pow(v_factor, -1);
472 }
473
474 rp->yAxis->scaleRange(v_factor, rp->yAxis->range().center());
475 rp->replot(QCustomPlot::rpQueuedReplot);
476 }
477
panAxes(int x_pixels,int y_pixels)478 void LteRlcGraphDialog::panAxes(int x_pixels, int y_pixels)
479 {
480 QCustomPlot *rp = ui->rlcPlot;
481 double h_pan = 0.0;
482 double v_pan = 0.0;
483
484 // Don't scroll up beyond max range, or below 0
485 if (((y_pixels > 0) && (rp->yAxis->range().upper > 65536)) ||
486 ((y_pixels < 0) && (rp->yAxis->range().lower < 0))) {
487 return;
488 }
489 // Don't scroll left beyond 0. Arguably should be time of first segment.
490 if ((x_pixels < 0) && (rp->xAxis->range().lower < 0)) {
491 return;
492 }
493
494 h_pan = rp->xAxis->range().size() * x_pixels / rp->xAxis->axisRect()->width();
495 v_pan = rp->yAxis->range().size() * y_pixels / rp->yAxis->axisRect()->height();
496
497 // The GTK+ version won't pan unless we're zoomed. Should we do the same here?
498 if (h_pan) {
499 rp->xAxis->moveRange(h_pan);
500 rp->replot(QCustomPlot::rpQueuedReplot);
501 }
502 if (v_pan) {
503 rp->yAxis->moveRange(v_pan);
504 rp->replot(QCustomPlot::rpQueuedReplot);
505 }
506 }
507
508 // Given a selected rect in pixels, work out what this should be in graph units.
509 // Don't accidentally zoom into a 1x1 rect if you happen to click on the graph
510 // in zoom mode.
511 const int min_zoom_pixels_ = 20;
getZoomRanges(QRect zoom_rect)512 QRectF LteRlcGraphDialog::getZoomRanges(QRect zoom_rect)
513 {
514 QRectF zoom_ranges = QRectF();
515
516 if (zoom_rect.width() < min_zoom_pixels_ && zoom_rect.height() < min_zoom_pixels_) {
517 return zoom_ranges;
518 }
519
520 QCustomPlot *rp = ui->rlcPlot;
521 QRect zr = zoom_rect.normalized();
522 QRect ar = rp->axisRect()->rect();
523 if (ar.intersects(zr)) {
524 QRect zsr = ar.intersected(zr);
525 zoom_ranges.setX(rp->xAxis->range().lower
526 + rp->xAxis->range().size() * (zsr.left() - ar.left()) / ar.width());
527 zoom_ranges.setWidth(rp->xAxis->range().size() * zsr.width() / ar.width());
528
529 // QRects grow down
530 zoom_ranges.setY(rp->yAxis->range().lower
531 + rp->yAxis->range().size() * (ar.bottom() - zsr.bottom()) / ar.height());
532 zoom_ranges.setHeight(rp->yAxis->range().size() * zsr.height() / ar.height());
533 }
534 return zoom_ranges;
535 }
536
graphClicked(QMouseEvent * event)537 void LteRlcGraphDialog::graphClicked(QMouseEvent *event)
538 {
539 QCustomPlot *rp = ui->rlcPlot;
540
541 if (event->button() == Qt::RightButton) {
542 // XXX We should find some way to get rlcPlot to handle a
543 // contextMenuEvent instead.
544 ctx_menu_->exec(event->globalPos());
545 } else if (mouse_drags_) {
546 if (rp->axisRect()->rect().contains(event->pos())) {
547 rp->setCursor(QCursor(Qt::ClosedHandCursor));
548 }
549 on_actionGoToPacket_triggered();
550 } else {
551 if (!rubber_band_) {
552 rubber_band_ = new QRubberBand(QRubberBand::Rectangle, rp);
553 }
554 rb_origin_ = event->pos();
555 rubber_band_->setGeometry(QRect(rb_origin_, QSize()));
556 rubber_band_->show();
557 }
558 rp->setFocus();
559 }
560
mouseMoved(QMouseEvent * event)561 void LteRlcGraphDialog::mouseMoved(QMouseEvent *event)
562 {
563 QCustomPlot *rp = ui->rlcPlot;
564 Qt::CursorShape shape = Qt::ArrowCursor;
565
566 // Set the cursor shape.
567 if (event) {
568 if (event->buttons().testFlag(Qt::LeftButton)) {
569 if (mouse_drags_) {
570 shape = Qt::ClosedHandCursor;
571 } else {
572 shape = Qt::CrossCursor;
573 }
574 } else if (rp->axisRect()->rect().contains(event->pos())) {
575 if (mouse_drags_) {
576 shape = Qt::OpenHandCursor;
577 } else {
578 shape = Qt::CrossCursor;
579 }
580 }
581 rp->setCursor(QCursor(shape));
582 }
583
584 // Trying to let 'hint' grow efficiently. Still pretty slow for a dense graph...
585 QString hint;
586 hint.reserve(128);
587 hint = "<small><i>";
588
589 if (mouse_drags_) {
590 double tr_key = tracer_->position->key();
591 struct rlc_segment *packet_seg = NULL;
592 packet_num_ = 0;
593
594 // XXX If we have multiple packets with the same timestamp tr_key
595 // may not return the packet we want. It might be possible to fudge
596 // unique keys using nextafter().
597 if (event && tracer_->graph() && tracer_->position->axisRect()->rect().contains(event->pos())) {
598 packet_seg = time_stamp_map_.value(tr_key, NULL);
599 }
600
601 if (!packet_seg) {
602 tracer_->setVisible(false);
603 hint += "Hover over the graph for details. </i></small>";
604 ui->hintLabel->setText(hint);
605 ui->rlcPlot->replot(QCustomPlot::rpQueuedReplot);
606 return;
607 }
608
609 tracer_->setVisible(true);
610 packet_num_ = packet_seg->num;
611 // N.B. because tracer only looks up entries in base_graph_,
612 // we know that packet_seg will be a data segment, so no need to check
613 // iscontrolPDU or isResegmented fields.
614 hint += tr("%1 %2 (%3s seq %4 len %5)")
615 .arg(cap_file_.capFile() ? tr("Click to select packet") : tr("Packet"))
616 .arg(packet_num_)
617 .arg(QString::number(packet_seg->rel_secs + packet_seg->rel_usecs / 1000000.0, 'g', 4))
618 .arg(packet_seg->SN)
619 .arg(packet_seg->pduLength);
620 tracer_->setGraphKey(ui->rlcPlot->xAxis->pixelToCoord(event->pos().x()));
621 // Redrawing the whole graph is making the update *very* slow!
622 // TODO: Is there a way just to draw the parts that may have changed?
623 // In the GTK version, we displayed the stored pixbuf and draw temporary items on top...
624 rp->replot(QCustomPlot::rpQueuedReplot);
625
626 } else {
627 if (event && rubber_band_ && rubber_band_->isVisible()) {
628 // Work out zoom based upon selected region (in pixels).
629 rubber_band_->setGeometry(QRect(rb_origin_, event->pos()).normalized());
630 QRectF zoom_ranges = getZoomRanges(QRect(rb_origin_, event->pos()));
631 if (zoom_ranges.width() > 0.0 && zoom_ranges.height() > 0.0) {
632 hint += tr("Release to zoom, x = %1 to %2, y = %3 to %4")
633 .arg(zoom_ranges.x())
634 .arg(zoom_ranges.x() + zoom_ranges.width())
635 .arg(zoom_ranges.y())
636 .arg(zoom_ranges.y() + zoom_ranges.height());
637 } else {
638 hint += tr("Unable to select range.");
639 }
640 } else {
641 hint += tr("Click to select a portion of the graph.");
642 }
643 }
644
645 hint.append("</i></small>");
646 ui->hintLabel->setText(hint);
647 }
648
mouseReleased(QMouseEvent * event)649 void LteRlcGraphDialog::mouseReleased(QMouseEvent *event)
650 {
651 QCustomPlot *rp = ui->rlcPlot;
652 if (rubber_band_) {
653 rubber_band_->hide();
654 if (!mouse_drags_) {
655 // N.B. work out range to zoom to *before* resetting axes.
656 QRectF zoom_ranges = getZoomRanges(QRect(rb_origin_, event->pos()));
657 resetAxes();
658 if (zoom_ranges.width() > 0.0 && zoom_ranges.height() > 0.0) {
659 rp->xAxis->setRangeLower(zoom_ranges.x());
660 rp->xAxis->setRangeUpper(zoom_ranges.x() + zoom_ranges.width());
661 rp->yAxis->setRangeLower(zoom_ranges.y());
662 rp->yAxis->setRangeUpper(zoom_ranges.y() + zoom_ranges.height());
663 rp->replot();
664 }
665 }
666 } else if (rp->cursor().shape() == Qt::ClosedHandCursor) {
667 rp->setCursor(QCursor(Qt::OpenHandCursor));
668 }
669 }
670
resetAxes()671 void LteRlcGraphDialog::resetAxes()
672 {
673 QCustomPlot *rp = ui->rlcPlot;
674
675 QCPRange x_range = rp->xAxis->scaleType() == QCPAxis::stLogarithmic ?
676 rp->xAxis->range().sanitizedForLogScale() : rp->xAxis->range();
677
678 double pixel_pad = 10.0; // per side
679
680 rp->rescaleAxes(true);
681 base_graph_->rescaleValueAxis(false, true);
682
683 double axis_pixels = rp->xAxis->axisRect()->width();
684 rp->xAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, x_range.center());
685
686 axis_pixels = rp->yAxis->axisRect()->height();
687 rp->yAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, rp->yAxis->range().center());
688
689 rp->replot(QCustomPlot::rpQueuedReplot);
690 }
691
on_actionGoToPacket_triggered()692 void LteRlcGraphDialog::on_actionGoToPacket_triggered()
693 {
694 if (tracer_->visible() && cap_file_.capFile() && (packet_num_ > 0)) {
695 // Signal to the packetlist which frame we want to show.
696 emit goToPacket(packet_num_);
697 }
698 }
699
on_actionCrosshairs_triggered()700 void LteRlcGraphDialog::on_actionCrosshairs_triggered()
701 {
702 toggleTracerStyle(false);
703 }
704
toggleTracerStyle(bool force_default)705 void LteRlcGraphDialog::toggleTracerStyle(bool force_default)
706 {
707 if (!tracer_->visible() && !force_default) {
708 return;
709 }
710
711 QPen sp_pen = ui->rlcPlot->graph(0)->pen();
712 QCPItemTracer::TracerStyle tstyle = QCPItemTracer::tsCrosshair;
713 QPen tr_pen = QPen(tracer_->pen());
714 QColor tr_color = sp_pen.color();
715
716 if (force_default || tracer_->style() != QCPItemTracer::tsCircle) {
717 tstyle = QCPItemTracer::tsCircle;
718 tr_color.setAlphaF(1.0);
719 tr_pen.setWidthF(1.5);
720 } else {
721 tr_color.setAlphaF(0.5);
722 tr_pen.setWidthF(1.0);
723 }
724
725 tracer_->setStyle(tstyle);
726 tr_pen.setColor(tr_color);
727 tracer_->setPen(tr_pen);
728 ui->rlcPlot->replot();
729 }
730
on_actionReset_triggered()731 void LteRlcGraphDialog::on_actionReset_triggered()
732 {
733 resetAxes();
734 }
735
on_actionZoomIn_triggered()736 void LteRlcGraphDialog::on_actionZoomIn_triggered()
737 {
738 zoomAxes(true);
739 }
740
on_actionZoomOut_triggered()741 void LteRlcGraphDialog::on_actionZoomOut_triggered()
742 {
743 zoomAxes(false);
744 }
745
on_actionMoveUp10_triggered()746 void LteRlcGraphDialog::on_actionMoveUp10_triggered()
747 {
748 panAxes(0, 10);
749 }
750
on_actionMoveUp100_triggered()751 void LteRlcGraphDialog::on_actionMoveUp100_triggered()
752 {
753 panAxes(0, 100);
754 }
755
on_actionMoveLeft10_triggered()756 void LteRlcGraphDialog::on_actionMoveLeft10_triggered()
757 {
758 panAxes(-10, 0);
759 }
760
on_actionMoveRight10_triggered()761 void LteRlcGraphDialog::on_actionMoveRight10_triggered()
762 {
763 panAxes(10, 0);
764 }
765
on_actionMoveDown10_triggered()766 void LteRlcGraphDialog::on_actionMoveDown10_triggered()
767 {
768 panAxes(0, -10);
769 }
770
on_actionMoveDown100_triggered()771 void LteRlcGraphDialog::on_actionMoveDown100_triggered()
772 {
773 panAxes(0, -100);
774 }
775
on_actionMoveUp1_triggered()776 void LteRlcGraphDialog::on_actionMoveUp1_triggered()
777 {
778 panAxes(0, 1);
779 }
780
on_actionMoveLeft1_triggered()781 void LteRlcGraphDialog::on_actionMoveLeft1_triggered()
782 {
783 panAxes(-1, 0);
784 }
785
on_actionMoveRight1_triggered()786 void LteRlcGraphDialog::on_actionMoveRight1_triggered()
787 {
788 panAxes(1, 0);
789 }
790
on_actionMoveDown1_triggered()791 void LteRlcGraphDialog::on_actionMoveDown1_triggered()
792 {
793 panAxes(0, -1);
794 }
795
on_actionSwitchDirection_triggered()796 void LteRlcGraphDialog::on_actionSwitchDirection_triggered()
797 {
798 // Channel settings exactly the same, except change direction.
799 // N.B. do not fail and close if there are no packets in opposite direction.
800 setChannelInfo(graph_.ueid,
801 graph_.rlcMode,
802 graph_.channelType,
803 graph_.channelId,
804 !graph_.direction,
805 true /* maybe_empty */);
806 }
807
808
809
810 // Switch between zoom/drag.
on_actionDragZoom_triggered()811 void LteRlcGraphDialog::on_actionDragZoom_triggered()
812 {
813 if (mouse_drags_) {
814 ui->zoomRadioButton->toggle();
815 } else {
816 ui->dragRadioButton->toggle();
817 }
818 }
819
on_dragRadioButton_toggled(bool checked)820 void LteRlcGraphDialog::on_dragRadioButton_toggled(bool checked)
821 {
822 if (checked) {
823 mouse_drags_ = true;
824 }
825 ui->rlcPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
826 }
827
on_zoomRadioButton_toggled(bool checked)828 void LteRlcGraphDialog::on_zoomRadioButton_toggled(bool checked)
829 {
830 if (checked) mouse_drags_ = false;
831 ui->rlcPlot->setInteractions(QCP::Interactions());
832 }
833
on_resetButton_clicked()834 void LteRlcGraphDialog::on_resetButton_clicked()
835 {
836 resetAxes();
837 }
838
on_otherDirectionButton_clicked()839 void LteRlcGraphDialog::on_otherDirectionButton_clicked()
840 {
841 on_actionSwitchDirection_triggered();
842 }
843
844 // Prompt for filename/format to save graph to.
845 // N.B. Copied from tcp_stream_dialog.cpp
on_buttonBox_accepted()846 void LteRlcGraphDialog::on_buttonBox_accepted()
847 {
848 QString file_name, extension;
849 QDir path(wsApp->lastOpenDir());
850 QString pdf_filter = tr("Portable Document Format (*.pdf)");
851 QString png_filter = tr("Portable Network Graphics (*.png)");
852 QString bmp_filter = tr("Windows Bitmap (*.bmp)");
853 // Gaze upon my beautiful graph with lossy artifacts!
854 QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
855 QString filter = QString("%1;;%2;;%3;;%4")
856 .arg(pdf_filter)
857 .arg(png_filter)
858 .arg(bmp_filter)
859 .arg(jpeg_filter);
860
861 file_name = WiresharkFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Graph As…")),
862 path.canonicalPath(), filter, &extension);
863
864 if (file_name.length() > 0) {
865 bool save_ok = false;
866 if (extension.compare(pdf_filter) == 0) {
867 save_ok = ui->rlcPlot->savePdf(file_name);
868 } else if (extension.compare(png_filter) == 0) {
869 save_ok = ui->rlcPlot->savePng(file_name);
870 } else if (extension.compare(bmp_filter) == 0) {
871 save_ok = ui->rlcPlot->saveBmp(file_name);
872 } else if (extension.compare(jpeg_filter) == 0) {
873 save_ok = ui->rlcPlot->saveJpg(file_name);
874 }
875 // else error dialog?
876 if (save_ok) {
877 wsApp->setLastOpenDirFromFilename(file_name);
878 }
879 }
880 }
881
882
883 // No need to register tap listeners here. This is done
884 // in calls to the common functions in ui/tap-rlc-graph.c
885