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