1 /* packet_diagram.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 "packet_diagram.h"
11
12 #include "math.h"
13
14 #include "epan/epan.h"
15 #include "epan/epan_dissect.h"
16
17 #include "wsutil/utf8_entities.h"
18
19 #include "wireshark_application.h"
20
21 #include "ui/qt/main_window.h"
22 #include "ui/qt/utils/proto_node.h"
23 #include "ui/qt/utils/variant_pointer.h"
24 #include "ui/recent.h"
25
26
27 #include <QContextMenuEvent>
28 #include <QGraphicsItem>
29 #include <QMenu>
30
31 #if defined(QT_SVG_LIB) && 0
32 #include <QBuffer>
33 #include <QMimeData>
34 #include <QSvgGenerator>
35 #endif
36
37 // Item offsets and lengths
38 //#define DEBUG_PACKET_DIAGRAM 1
39
40 #ifdef DEBUG_PACKET_DIAGRAM
41 #include <QDebug>
42 #endif
43
44 // "rems" are root em widths, aka the regular font height, similar to rems in CSS.
45 class DiagramLayout {
46 public:
DiagramLayout()47 DiagramLayout() :
48 bits_per_row_(32),
49 small_font_rems_(0.75),
50 bit_width_rems_(1.0),
51 padding_rems_(0.5),
52 span_mark_offset_rems_(0.2)
53 {
54 setFont(wsApp->font());
55 }
56
setFont(QFont font)57 void setFont(QFont font) {
58 regular_font_ = font;
59 small_font_ = font;
60 small_font_.setPointSize(regular_font_.pointSize() * small_font_rems_);
61
62 QFontMetrics fm(regular_font_);
63 root_em_ = fm.height();
64 }
setShowFields(bool show_fields=false)65 void setShowFields(bool show_fields = false) { recent.gui_packet_diagram_field_values = show_fields; }
66
bitsPerRow() const67 int bitsPerRow() const { return bits_per_row_; }
regularFont() const68 const QFont regularFont() const { return regular_font_; }
smallFont() const69 const QFont smallFont() const { return small_font_; }
bitWidth() const70 int bitWidth() const { return root_em_ * bit_width_rems_; }
lineHeight() const71 int lineHeight() const { return root_em_; }
hPadding() const72 int hPadding() const { return root_em_ * padding_rems_; }
vPadding() const73 int vPadding() const { return root_em_ * padding_rems_; }
spanMarkOffset() const74 int spanMarkOffset() const { return root_em_ * span_mark_offset_rems_; }
rowHeight() const75 int rowHeight() const {
76 int rows = recent.gui_packet_diagram_field_values ? 2 : 1;
77 return ((lineHeight() * rows) + (vPadding() * 2));
78 }
showFields() const79 bool showFields() const { return recent.gui_packet_diagram_field_values; }
80 private:
81 int bits_per_row_;
82 double small_font_rems_;
83 double bit_width_rems_;
84 double padding_rems_;
85 double span_mark_offset_rems_; // XXX Make this padding_rems_ / 2 instead?
86 QFont regular_font_;
87 QFont small_font_;
88 int root_em_;
89 };
90
91 class FieldInformationGraphicsItem : public QGraphicsPolygonItem
92 {
93 public:
FieldInformationGraphicsItem(field_info * fi,int start_bit,int fi_length,const DiagramLayout * layout,QGraphicsItem * parent=nullptr)94 FieldInformationGraphicsItem(field_info *fi, int start_bit, int fi_length, const DiagramLayout *layout, QGraphicsItem *parent = nullptr) :
95 QGraphicsPolygonItem(QPolygonF(), parent),
96 finfo_(new FieldInformation(fi)),
97 representation_("Unknown"),
98 start_bit_(start_bit),
99 layout_(layout),
100 collapsed_len_(fi_length),
101 collapsed_row_(-1)
102 {
103 Q_ASSERT(layout_);
104
105 for (int idx = 0; idx < NumSpanMarks; idx++) {
106 span_marks_[idx] = new QGraphicsLineItem(this);
107 span_marks_[idx]->hide();
108 }
109
110 int bits_per_row = layout_->bitsPerRow();
111 int row1_start = start_bit_ % bits_per_row;
112 int bits_remain = fi_length;
113
114 int row1_bits = bits_remain;
115 if (bits_remain + row1_start > bits_per_row) {
116 row1_bits = bits_per_row - row1_start;
117 bits_remain -= row1_bits;
118 if (row1_start == 0 && bits_remain >= bits_per_row) {
119 // Collapse first row
120 bits_remain %= bits_per_row;
121 collapsed_row_ = 0;
122 }
123 } else {
124 bits_remain = 0;
125 }
126
127 int row2_bits = bits_remain;
128 if (bits_remain > bits_per_row) {
129 row2_bits = bits_per_row;
130 bits_remain -= bits_per_row;
131 if (bits_remain > bits_per_row) {
132 // Collapse second row
133 bits_remain %= bits_per_row;
134 collapsed_row_ = 1;
135 }
136 } else {
137 bits_remain = 0;
138 }
139 int row3_bits = bits_remain;
140
141 collapsed_len_ = row1_bits + row2_bits + row3_bits;
142
143 QRectF rr1, rr2, rr3;
144 QRectF row_rect = QRectF(row1_start, 0, row1_bits, 1);
145 unit_shape_ = QPolygonF(row_rect);
146 rr1 = row_rect;
147 unit_tr_ = row_rect;
148
149 if (row2_bits > 0) {
150 row_rect = QRectF(0, 1, row2_bits, 1);
151 unit_shape_ = unit_shape_.united(QPolygonF(row_rect));
152 rr2 = row_rect;
153 if (row2_bits > row1_bits) {
154 unit_tr_ = row_rect;
155 }
156
157 if (row3_bits > 0) {
158 row_rect = QRectF(0, 2, row3_bits, 1);
159 unit_shape_ = unit_shape_.united(QPolygonF(row_rect));
160 rr3 = row_rect;
161 }
162 QPainterPath pp;
163 pp.addPolygon(unit_shape_);
164 unit_shape_ = pp.simplified().toFillPolygon();
165 }
166
167 updateLayout();
168
169 if (finfo_->isValid()) {
170 setToolTip(QString("%1 (%2) = %3")
171 .arg(finfo_->headerInfo().name)
172 .arg(finfo_->headerInfo().abbreviation)
173 .arg(finfo_->toString()));
174 setData(Qt::UserRole, VariantPointer<field_info>::asQVariant(finfo_->fieldInfo()));
175 representation_ = fi->rep->representation;
176 } else {
177 setToolTip(QObject::tr("Gap in dissection"));
178 }
179 }
180
~FieldInformationGraphicsItem()181 ~FieldInformationGraphicsItem()
182 {
183 delete finfo_;
184 }
185
collapsedLength()186 int collapsedLength() { return collapsed_len_; }
187
setPos(qreal x,qreal y)188 void setPos(qreal x, qreal y) {
189 QGraphicsPolygonItem::setPos(x, y);
190 updateLayout();
191 }
192
maxLeftY()193 int maxLeftY() {
194 qreal rel_len = (start_bit_ % layout_->bitsPerRow()) + collapsed_len_;
195 QPointF pt = mapToParent(QPointF(0, ceil(rel_len / layout_->bitsPerRow()) * layout_->rowHeight()));
196 return pt.y();
197 }
198
maxRightY()199 int maxRightY() {
200 qreal rel_len = (start_bit_ % layout_->bitsPerRow()) + collapsed_len_;
201 QPointF pt = mapToParent(QPointF(0, floor(rel_len / layout_->bitsPerRow()) * layout_->rowHeight()));
202 return pt.y();
203 }
204
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget *)205 void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) {
206
207 painter->setPen(Qt::NoPen);
208 painter->save();
209 if (!finfo_->isValid()) {
210 QBrush brush = QBrush(option->palette.text().color(), Qt::BDiagPattern);
211 painter->setBrush(brush);
212 } else if (isSelected()) {
213 painter->setBrush(option->palette.highlight().color());
214 }
215 painter->drawPolygon(polygon());
216 painter->restore();
217
218 // Lower and inner right borders
219 painter->setPen(option->palette.text().color());
220 QPolygonF shape = polygon();
221 for (int idx = 1; idx < unit_shape_.size(); idx++) {
222 QPointF u_start = unit_shape_[idx - 1];
223 QPointF u_end = unit_shape_[idx];
224 QPointF start, end;
225 bool draw_line = false;
226
227 if (u_start.y() > 0 && u_start.y() == u_end.y()) {
228 draw_line = true;
229 } else if (u_start.x() > 0 && u_start.x() < layout_->bitsPerRow() && u_start.x() == u_end.x()) {
230 draw_line = true;
231 }
232 if (draw_line) {
233 start = shape[idx - 1];
234 end = shape[idx];
235 painter->drawLine(start, end);
236 }
237 }
238
239 if (!finfo_->isValid()) {
240 return;
241 }
242
243 // Field label(s)
244 QString label;
245 if (finfo_->headerInfo().type == FT_NONE) {
246 label = representation_;
247 } else {
248 label = finfo_->headerInfo().name;
249 }
250 paintLabel(painter, label, scaled_tr_);
251
252 if (layout_->showFields()) {
253 label = finfo_->toString();
254 paintLabel(painter, label, scaled_tr_.adjusted(0, scaled_tr_.height(), 0, scaled_tr_.height()));
255 }
256 }
257
258 private:
259 enum SpanMark {
260 TopLeft,
261 BottomLeft,
262 TopRight,
263 BottomRight,
264 NumSpanMarks
265 };
266 FieldInformation *finfo_;
267 QString representation_;
268 int start_bit_;
269 const DiagramLayout *layout_;
270 int collapsed_len_;
271 int collapsed_row_;
272 QPolygonF unit_shape_;
273 QRectF unit_tr_;
274 QRectF scaled_tr_;
275 QGraphicsLineItem *span_marks_[NumSpanMarks];
276
updateLayout()277 void updateLayout() {
278 QTransform xform;
279
280 xform.scale(layout_->bitWidth(), layout_->rowHeight());
281 setPolygon(xform.map(unit_shape_));
282 scaled_tr_ = xform.mapRect(unit_tr_);
283 scaled_tr_.adjust(layout_->hPadding(), layout_->vPadding(), -layout_->hPadding(), -layout_->vPadding());
284 scaled_tr_.setHeight(layout_->lineHeight());
285
286 // Collapsed / span marks
287 for (int idx = 0; idx < NumSpanMarks; idx++) {
288 span_marks_[idx]->hide();
289 }
290 if (collapsed_row_ >= 0) {
291 QRectF bounding_rect = polygon().boundingRect();
292 qreal center_y = bounding_rect.top() + (layout_->rowHeight() * collapsed_row_) + (layout_->rowHeight() / 2);
293 qreal mark_w = layout_->bitWidth() / 3; // Each mark side to center
294 QLineF span_l = QLineF(-mark_w, mark_w / 2, mark_w, -mark_w / 2);
295 for (int idx = 0; idx < NumSpanMarks; idx++) {
296 QPointF center;
297 switch (idx) {
298 case TopLeft:
299 center = QPointF(bounding_rect.left(), center_y - layout_->spanMarkOffset());
300 break;
301 case BottomLeft:
302 center = QPointF(bounding_rect.left(), center_y + layout_->spanMarkOffset());
303 break;
304 case TopRight:
305 center = QPointF(bounding_rect.right(), center_y - layout_->spanMarkOffset());
306 break;
307 case BottomRight:
308 center = QPointF(bounding_rect.right(), center_y + layout_->spanMarkOffset());
309 break;
310 }
311
312 span_marks_[idx]->setLine(span_l.translated(center));
313 span_marks_[idx]->setZValue(zValue() - 0.1);
314 span_marks_[idx]->show();
315 }
316 }
317 }
318
paintLabel(QPainter * painter,QString label,QRectF label_rect)319 void paintLabel(QPainter *painter, QString label, QRectF label_rect) {
320 QFontMetrics fm = QFontMetrics(layout_->regularFont());
321
322 painter->setFont(layout_->regularFont());
323 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
324 int label_w = fm.horizontalAdvance(label);
325 #else
326 int label_w = fm.width(label);
327 #endif
328 if (label_w > label_rect.width()) {
329 painter->setFont(layout_->smallFont());
330 fm = QFontMetrics(layout_->smallFont());
331 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
332 label_w = fm.horizontalAdvance(label);
333 #else
334 label_w = fm.width(label);
335 #endif
336 if (label_w > label_rect.width()) {
337 // XXX Use parent+ItemClipsChildrenToShape or setScale instead?
338 label = fm.elidedText(label, Qt::ElideRight, label_rect.width());
339 }
340 }
341 painter->drawText(label_rect, Qt::AlignCenter, label);
342 }
343 };
344
PacketDiagram(QWidget * parent)345 PacketDiagram::PacketDiagram(QWidget *parent) :
346 QGraphicsView(parent),
347 layout_(new DiagramLayout),
348 cap_file_(nullptr),
349 root_node_(nullptr),
350 selected_field_(nullptr),
351 y_pos_(0)
352 {
353 setAccessibleName(tr("Packet diagram"));
354
355 setRenderHint(QPainter::Antialiasing);
356
357 // XXX Move to setMonospaceFont similar to ProtoTree
358 layout_->setFont(font());
359
360 connect(wsApp, &WiresharkApplication::appInitialized, this, &PacketDiagram::connectToMainWindow);
361 connect(wsApp, &WiresharkApplication::zoomRegularFont, this, &PacketDiagram::setFont);
362
363 resetScene();
364 }
365
~PacketDiagram()366 PacketDiagram::~PacketDiagram()
367 {
368 delete layout_;
369 }
370
setRootNode(proto_node * root_node)371 void PacketDiagram::setRootNode(proto_node *root_node)
372 {
373 // As https://doc.qt.io/qt-5/qgraphicsscene.html#clear says, this
374 // "Removes and deletes all items from the scene, but otherwise leaves
375 // the state of the scene unchanged."
376 // This means that the scene rect grows but doesn't shrink, which is
377 // useful in our case because it gives us a cheap way to retain our
378 // scroll position between packets.
379 scene()->clear();
380 selected_field_ = nullptr;
381 y_pos_ = 0;
382
383 root_node_ = root_node;
384 if (!isVisible() || !root_node) {
385 return;
386 }
387
388 ProtoNode parent_node(root_node_);
389 if (!parent_node.isValid()) {
390 return;
391 }
392
393 ProtoNode::ChildIterator kids = parent_node.children();
394 while (kids.element().isValid())
395 {
396 proto_node *tl_node = kids.element().protoNode();
397 kids.next();
398
399 // Exclude all ("Frame") and nothing
400 if (tl_node->finfo->start == 0 && tl_node->finfo->length == (int) tvb_captured_length(cap_file_->edt->tvb)) {
401 continue;
402 }
403 if (tl_node->finfo->length < 1) {
404 continue;
405 }
406 addDiagram(tl_node);
407 }
408 }
409
clear()410 void PacketDiagram::clear()
411 {
412 setRootNode(nullptr);
413 }
414
setCaptureFile(capture_file * cf)415 void PacketDiagram::setCaptureFile(capture_file *cf)
416 {
417 // For use by the main view, set the capture file which will later have a
418 // dissection (EDT) ready.
419 // The packet dialog sets a fixed EDT context and MUST NOT use this.
420 cap_file_ = cf;
421
422 if (!cf) {
423 resetScene();
424 }
425 }
426
setFont(const QFont & font)427 void PacketDiagram::setFont(const QFont &font)
428 {
429 layout_->setFont(font);
430 resetScene(false);
431 }
432
selectedFieldChanged(FieldInformation * finfo)433 void PacketDiagram::selectedFieldChanged(FieldInformation *finfo)
434 {
435 setSelectedField(finfo ? finfo->fieldInfo() : nullptr);
436 }
437
selectedFrameChanged(QList<int> frames)438 void PacketDiagram::selectedFrameChanged(QList<int> frames)
439 {
440 if (frames.count() == 1 && cap_file_ && cap_file_->edt && cap_file_->edt->tree) {
441 setRootNode(cap_file_->edt->tree);
442 } else {
443 // Clear the proto tree contents as they have become invalid.
444 setRootNode(nullptr);
445 }
446 }
447
event(QEvent * event)448 bool PacketDiagram::event(QEvent *event)
449 {
450 switch (event->type()) {
451 case QEvent::ApplicationPaletteChange:
452 resetScene(false);
453 break;
454 default:
455 break;
456
457 }
458 return QGraphicsView::event(event);
459 }
460
contextMenuEvent(QContextMenuEvent * event)461 void PacketDiagram::contextMenuEvent(QContextMenuEvent *event)
462 {
463 if (!event) {
464 return;
465 }
466
467 QAction *action;
468 QMenu ctx_menu(this);
469
470 action = ctx_menu.addAction(tr("Show Field Values"));
471 action->setCheckable(true);
472 action->setChecked(layout_->showFields());
473 connect(action, &QAction::toggled, this, &PacketDiagram::showFieldsToggled);
474
475 ctx_menu.addSeparator();
476
477 action = ctx_menu.addAction(tr("Save Diagram As…"));
478 connect(action, &QAction::triggered, this, &PacketDiagram::saveAsTriggered);
479
480 action = ctx_menu.addAction(tr("Copy as Raster Image"));
481 connect(action, &QAction::triggered, this, &PacketDiagram::copyAsRasterTriggered);
482
483 #if defined(QT_SVG_LIB) && !defined(Q_OS_MAC)
484 action = ctx_menu.addAction(tr("…as SVG"));
485 connect(action, &QAction::triggered, this, &PacketDiagram::copyAsSvgTriggered);
486 #endif
487
488 ctx_menu.exec(event->globalPos());
489 }
490
connectToMainWindow()491 void PacketDiagram::connectToMainWindow()
492 {
493 MainWindow *main_window = qobject_cast<MainWindow *>(wsApp->mainWindow());
494 if (!main_window) {
495 return;
496 }
497 connect(main_window, &MainWindow::setCaptureFile, this, &PacketDiagram::setCaptureFile);
498 connect(main_window, &MainWindow::fieldSelected, this, &PacketDiagram::selectedFieldChanged);
499 connect(main_window, &MainWindow::framesSelected, this, &PacketDiagram::selectedFrameChanged);
500
501 connect(this, &PacketDiagram::fieldSelected, main_window, &MainWindow::fieldSelected);
502 }
503
sceneSelectionChanged()504 void PacketDiagram::sceneSelectionChanged()
505 {
506 field_info *sel_fi = nullptr;
507 if (! scene()->selectedItems().isEmpty()) {
508 sel_fi = VariantPointer<field_info>::asPtr(scene()->selectedItems().first()->data(Qt::UserRole));
509 }
510
511 if (sel_fi) {
512 FieldInformation finfo(sel_fi, this);
513 emit fieldSelected(&finfo);
514 } else {
515 emit fieldSelected(nullptr);
516 }
517 }
518
resetScene(bool reset_root)519 void PacketDiagram::resetScene(bool reset_root)
520 {
521 // As noted in setRootNode, scene()->clear() doesn't clear everything.
522 // Do a "hard" clear, which resets our various rects and scroll position.
523 if (scene()) {
524 delete scene();
525 }
526 QGraphicsScene *new_scene = new QGraphicsScene();
527 setScene(new_scene);
528 connect(new_scene, &QGraphicsScene::selectionChanged, this, &PacketDiagram::sceneSelectionChanged);
529 setRootNode(reset_root ? nullptr : root_node_);
530 }
531
532 struct DiagramItemSpan {
533 field_info *finfo;
534 int start_bit;
535 int length;
536 };
537
addDiagram(proto_node * tl_node)538 void PacketDiagram::addDiagram(proto_node *tl_node)
539 {
540 QGraphicsItem *item;
541 QGraphicsSimpleTextItem *t_item;
542 int bits_per_row = layout_->bitsPerRow();
543 int bit_width = layout_->bitWidth();
544 int diag_w = bit_width * layout_->bitsPerRow();
545 qreal x = layout_->hPadding();
546
547 // Title
548 t_item = scene()->addSimpleText(tl_node->finfo->hfinfo->name);
549 t_item->setFont(layout_->regularFont());
550 t_item->setPos(0, y_pos_);
551 y_pos_ += layout_->lineHeight() + (bit_width / 4);
552
553 int border_top = y_pos_;
554
555 // Bit scale + tick marks
556 QList<int> tick_nums;
557 for (int tn = 0 ; tn < layout_->bitsPerRow(); tn += 16) {
558 tick_nums << tn << tn + 15;
559 }
560 qreal y_bottom = y_pos_ + bit_width;
561 QGraphicsItem *tl_item = scene()->addLine(x, y_bottom, x + diag_w, y_bottom);
562 QFontMetrics sfm = QFontMetrics(layout_->smallFont());
563 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
564 int space_w = sfm.horizontalAdvance(' ');
565 #else
566 int space_w = sfm.width(' ');
567 #endif
568 #ifdef Q_OS_WIN
569 // t_item->boundingRect() has a pixel of space on the left on my (gcc)
570 // Windows VM.
571 int tl_adjust = 1;
572 #else
573 int tl_adjust = 0;
574 #endif
575
576 for (int tick_n = 0; tick_n < bits_per_row; tick_n++) {
577 x = layout_->hPadding() + (tick_n * bit_width);
578 qreal y_top = y_pos_ + (tick_n % 8 == 0 ? 0 : bit_width / 2);
579 if (tick_n > 0) {
580 scene()->addLine(x, y_top, x, y_bottom);
581 }
582
583 if (tick_nums.contains(tick_n)) {
584 t_item = scene()->addSimpleText(QString::number(tick_n));
585 t_item->setFont(layout_->smallFont());
586 if (tick_n % 2 == 0) {
587 t_item->setPos(x + space_w - tl_adjust, y_pos_);
588 } else {
589 t_item->setPos(x + bit_width - space_w - t_item->boundingRect().width() - tl_adjust, y_pos_);
590 }
591 // Does the placement above look funny on your system? Try
592 // uncommenting the lines below.
593 // QGraphicsRectItem *br_item = scene()->addRect(t_item->boundingRect(), QPen(palette().highlight().color()));
594 // br_item->setPos(t_item->pos());
595 }
596 }
597 y_pos_ = y_bottom;
598 x = layout_->hPadding();
599
600 // Collect our top-level fields
601 int last_start_bit = -1;
602 int max_l_y = y_bottom;
603 QList<DiagramItemSpan>item_spans;
604 for (proto_item *cur_item = tl_node->first_child; cur_item; cur_item = cur_item->next) {
605 if (proto_item_is_generated(cur_item) || proto_item_is_hidden(cur_item)) {
606 continue;
607 }
608
609 field_info *fi = cur_item->finfo;
610 int start_bit = ((fi->start - tl_node->finfo->start) * 8) + FI_GET_BITS_OFFSET(fi);
611 int length = FI_GET_BITS_SIZE(fi) ? FI_GET_BITS_SIZE(fi) : fi->length * 8;
612
613 if (start_bit <= last_start_bit || length <= 0) {
614 #ifdef DEBUG_PACKET_DIAGRAM
615 qDebug() << "Skipping item" << fi->hfinfo->abbrev << start_bit << last_start_bit << length;
616 #endif
617 continue;
618 }
619 last_start_bit = start_bit;
620
621 if (item_spans.size() > 0) {
622 DiagramItemSpan prev_span = item_spans.last();
623 // Get rid of overlaps.
624 if (prev_span.start_bit + prev_span.length > start_bit) {
625 #ifdef DEBUG_PACKET_DIAGRAM
626 qDebug() << "Resized prev" << prev_span.finfo->hfinfo->abbrev << prev_span.start_bit << prev_span.length << "->" << start_bit - prev_span.start_bit;
627 #endif
628 prev_span.length = start_bit - prev_span.start_bit;
629 }
630 if (prev_span.length < 1) {
631 #ifdef DEBUG_PACKET_DIAGRAM
632 qDebug() << "Removed prev" << prev_span.finfo->hfinfo->abbrev << prev_span.start_bit << prev_span.length;
633 item_spans.removeLast();
634 if (item_spans.size() < 1) {
635 continue;
636 }
637 prev_span = item_spans.last();
638 #endif
639 }
640 // Fill in gaps.
641 if (prev_span.start_bit + prev_span.length < start_bit) {
642 #ifdef DEBUG_PACKET_DIAGRAM
643 qDebug() << "Adding gap" << prev_span.finfo->hfinfo->abbrev << prev_span.start_bit << prev_span.length << start_bit;
644 #endif
645 int gap_start = prev_span.start_bit + prev_span.length;
646 DiagramItemSpan gap_span = { nullptr, gap_start, start_bit - gap_start };
647 item_spans << gap_span;
648 }
649 }
650
651 DiagramItemSpan item_span = { cur_item->finfo, start_bit, length };
652 item_spans << item_span;
653 }
654
655 qreal z_value = tl_item->zValue();
656 int start_bit = 0;
657 for (int idx = 0; idx < item_spans.size(); idx++) {
658 DiagramItemSpan *item_span = &item_spans[idx];
659
660 int y_off = (start_bit / bits_per_row) * layout_->rowHeight();
661 // Stack each item behind the previous one.
662 z_value -= .01;
663 FieldInformationGraphicsItem *fi_item = new FieldInformationGraphicsItem(item_span->finfo, start_bit, item_span->length, layout_);
664 start_bit += fi_item->collapsedLength();
665 fi_item->setPos(x, y_bottom + y_off);
666 fi_item->setFlag(QGraphicsItem::ItemIsSelectable);
667 fi_item->setAcceptedMouseButtons(Qt::LeftButton);
668 fi_item->setZValue(z_value);
669 scene()->addItem(fi_item);
670
671 y_pos_ = fi_item->maxRightY();
672 max_l_y = fi_item->maxLeftY();
673 }
674
675 // Left & right borders
676 scene()->addLine(x, border_top, x, max_l_y);
677 scene()->addLine(x + diag_w, border_top, x + diag_w, y_pos_);
678
679 // Inter-diagram margin
680 y_pos_ = max_l_y + bit_width;
681
682 // Set the proper color. Needed for dark mode on macOS + Qt 5.15.0 at least, possibly other cases.
683 foreach (item, scene()->items()) {
684 QGraphicsSimpleTextItem *t_item = qgraphicsitem_cast<QGraphicsSimpleTextItem *>(item);
685 if (t_item) {
686 t_item->setBrush(palette().text().color());
687 }
688 QGraphicsLineItem *l_item = qgraphicsitem_cast<QGraphicsLineItem *>(item);
689 if (l_item) {
690 l_item->setPen(palette().text().color());
691 }
692 }
693 }
694
setSelectedField(field_info * fi)695 void PacketDiagram::setSelectedField(field_info *fi)
696 {
697 QSignalBlocker blocker(this);
698 FieldInformationGraphicsItem *fi_item;
699
700 foreach (QGraphicsItem *item, scene()->items()) {
701 if (item->isSelected()) {
702 item->setSelected(false);
703 }
704 if (fi && VariantPointer<field_info>::asPtr(item->data(Qt::UserRole)) == fi) {
705 fi_item = qgraphicsitem_cast<FieldInformationGraphicsItem *>(item);
706 if (fi_item) {
707 fi_item->setSelected(true);
708 }
709 }
710 }
711 }
712
exportToImage()713 QImage PacketDiagram::exportToImage()
714 {
715 // Create a hi-res 2x scaled image.
716 int scale = 2;
717 QRect rr = QRect(0, 0, sceneRect().size().width() * scale, sceneRect().size().height() * scale);
718 QImage raster_diagram = QImage(rr.size(), QImage::Format_ARGB32);
719 QPainter raster_painter(&raster_diagram);
720
721 raster_painter.setRenderHint(QPainter::Antialiasing);
722 raster_painter.fillRect(rr, palette().base().color());
723 scene()->render(&raster_painter);
724
725 raster_painter.end();
726
727 return raster_diagram;
728 }
729
730 #if defined(QT_SVG_LIB) && 0
exportToSvg()731 QByteArray PacketDiagram::exportToSvg()
732 {
733 QRect sr = QRect(0, 0, sceneRect().size().width(), sceneRect().size().height());
734 QBuffer svg_buf;
735 QSvgGenerator svg_diagram;
736 svg_diagram.setSize(sr.size());
737 svg_diagram.setViewBox(sr);
738 svg_diagram.setOutputDevice(&svg_buf);
739
740 QPainter svg_painter(&svg_diagram);
741 svg_painter.fillRect(sr, palette().base().color());
742 scene()->render(&svg_painter);
743
744 svg_painter.end();
745
746 return svg_buf.buffer();
747 }
748 #endif
749
showFieldsToggled(bool checked)750 void PacketDiagram::showFieldsToggled(bool checked)
751 {
752 layout_->setShowFields(checked);
753 setRootNode(root_node_);
754 }
755
756 // XXX - We have similar code in tcp_stream_dialog and io_graph_dialog. Should this be a common routine?
saveAsTriggered()757 void PacketDiagram::saveAsTriggered()
758 {
759 QString file_name, extension;
760 QDir path(wsApp->lastOpenDir());
761 QString png_filter = tr("Portable Network Graphics (*.png)");
762 QString bmp_filter = tr("Windows Bitmap (*.bmp)");
763 // Gaze upon my beautiful graph with lossy artifacts!
764 QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
765 QStringList fl = QStringList() << png_filter << bmp_filter << jpeg_filter;
766 #if defined(QT_SVG_LIB) && 0
767 QString svg_filter = tr("Scalable Vector Graphics (*.svg)");
768 fl << svg_filter;
769 #endif
770 QString filter = fl.join(";;");
771
772 file_name = WiresharkFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Graph As…")),
773 path.canonicalPath(), filter, &extension);
774
775 if (file_name.length() > 0) {
776 bool save_ok = false;
777 if (extension.compare(png_filter) == 0) {
778 QImage raster_diagram = exportToImage();
779 save_ok = raster_diagram.save(file_name, "PNG");
780 } else if (extension.compare(bmp_filter) == 0) {
781 QImage raster_diagram = exportToImage();
782 save_ok = raster_diagram.save(file_name, "BMP");
783 } else if (extension.compare(jpeg_filter) == 0) {
784 QImage raster_diagram = exportToImage();
785 save_ok = raster_diagram.save(file_name, "JPG");
786 }
787 #if defined(QT_SVG_LIB) && 0
788 else if (extension.compare(svg_filter) == 0) {
789 QByteArray svg_diagram = exportToSvg();
790 QFile file(file_name);
791 if (file.open(QIODevice::WriteOnly)) {
792 save_ok = file.write(svg_diagram) > 0;
793 file.close();
794 }
795 }
796 #endif
797 // else error dialog?
798 if (save_ok) {
799 wsApp->setLastOpenDirFromFilename(file_name);
800 }
801 }
802 }
803
copyAsRasterTriggered()804 void PacketDiagram::copyAsRasterTriggered()
805 {
806 QImage raster_diagram = exportToImage();
807 wsApp->clipboard()->setImage(raster_diagram);
808 }
809
810 #if defined(QT_SVG_LIB) && !defined(Q_OS_MAC) && 0
copyAsSvgTriggered()811 void PacketDiagram::copyAsSvgTriggered()
812 {
813 QByteArray svg_ba = exportToSvg();
814
815 // XXX It looks like we have to use/subclass QMacPasteboardMime in
816 // order for this to work on macOS.
817 // It might be easier to just do "Save As" instead.
818 QMimeData *md = new QMimeData();
819 md->setData("image/svg+xml", svg_buf);
820 wsApp->clipboard()->setMimeData(md);
821 }
822 #endif
823