wireshark/ui/qt/packet_diagram.cpp
Gerald Combs eda588d1a6 Revert "Qt: fix memory leaks found by Visual Leak Detector"
This reverts commit c2edb44a9a.

While we should definitely avoid leaking memory, this runs up against
the Qt code's assumption that it will always have valid epan data.

Fixes #17590.
Fixes #17719.
2021-11-10 11:48:26 -08:00

823 lines
26 KiB
C++

/* packet_diagram.cpp
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "packet_diagram.h"
#include "math.h"
#include "epan/epan.h"
#include "epan/epan_dissect.h"
#include "wsutil/utf8_entities.h"
#include "wireshark_application.h"
#include "ui/qt/main_window.h"
#include "ui/qt/utils/proto_node.h"
#include "ui/qt/utils/variant_pointer.h"
#include "ui/recent.h"
#include <QContextMenuEvent>
#include <QGraphicsItem>
#include <QMenu>
#if defined(QT_SVG_LIB) && 0
#include <QBuffer>
#include <QMimeData>
#include <QSvgGenerator>
#endif
// Item offsets and lengths
//#define DEBUG_PACKET_DIAGRAM 1
#ifdef DEBUG_PACKET_DIAGRAM
#include <QDebug>
#endif
// "rems" are root em widths, aka the regular font height, similar to rems in CSS.
class DiagramLayout {
public:
DiagramLayout() :
bits_per_row_(32),
small_font_rems_(0.75),
bit_width_rems_(1.0),
padding_rems_(0.5),
span_mark_offset_rems_(0.2)
{
setFont(wsApp->font());
}
void setFont(QFont font) {
regular_font_ = font;
small_font_ = font;
small_font_.setPointSize(regular_font_.pointSize() * small_font_rems_);
QFontMetrics fm(regular_font_);
root_em_ = fm.height();
}
void setShowFields(bool show_fields = false) { recent.gui_packet_diagram_field_values = show_fields; }
int bitsPerRow() const { return bits_per_row_; }
const QFont regularFont() const { return regular_font_; }
const QFont smallFont() const { return small_font_; }
int bitWidth() const { return root_em_ * bit_width_rems_; }
int lineHeight() const { return root_em_; }
int hPadding() const { return root_em_ * padding_rems_; }
int vPadding() const { return root_em_ * padding_rems_; }
int spanMarkOffset() const { return root_em_ * span_mark_offset_rems_; }
int rowHeight() const {
int rows = recent.gui_packet_diagram_field_values ? 2 : 1;
return ((lineHeight() * rows) + (vPadding() * 2));
}
bool showFields() const { return recent.gui_packet_diagram_field_values; }
private:
int bits_per_row_;
double small_font_rems_;
double bit_width_rems_;
double padding_rems_;
double span_mark_offset_rems_; // XXX Make this padding_rems_ / 2 instead?
QFont regular_font_;
QFont small_font_;
int root_em_;
};
class FieldInformationGraphicsItem : public QGraphicsPolygonItem
{
public:
FieldInformationGraphicsItem(field_info *fi, int start_bit, int fi_length, const DiagramLayout *layout, QGraphicsItem *parent = nullptr) :
QGraphicsPolygonItem(QPolygonF(), parent),
finfo_(new FieldInformation(fi)),
representation_("Unknown"),
start_bit_(start_bit),
layout_(layout),
collapsed_len_(fi_length),
collapsed_row_(-1)
{
Q_ASSERT(layout_);
for (int idx = 0; idx < NumSpanMarks; idx++) {
span_marks_[idx] = new QGraphicsLineItem(this);
span_marks_[idx]->hide();
}
int bits_per_row = layout_->bitsPerRow();
int row1_start = start_bit_ % bits_per_row;
int bits_remain = fi_length;
int row1_bits = bits_remain;
if (bits_remain + row1_start > bits_per_row) {
row1_bits = bits_per_row - row1_start;
bits_remain -= row1_bits;
if (row1_start == 0 && bits_remain >= bits_per_row) {
// Collapse first row
bits_remain %= bits_per_row;
collapsed_row_ = 0;
}
} else {
bits_remain = 0;
}
int row2_bits = bits_remain;
if (bits_remain > bits_per_row) {
row2_bits = bits_per_row;
bits_remain -= bits_per_row;
if (bits_remain > bits_per_row) {
// Collapse second row
bits_remain %= bits_per_row;
collapsed_row_ = 1;
}
} else {
bits_remain = 0;
}
int row3_bits = bits_remain;
collapsed_len_ = row1_bits + row2_bits + row3_bits;
QRectF rr1, rr2, rr3;
QRectF row_rect = QRectF(row1_start, 0, row1_bits, 1);
unit_shape_ = QPolygonF(row_rect);
rr1 = row_rect;
unit_tr_ = row_rect;
if (row2_bits > 0) {
row_rect = QRectF(0, 1, row2_bits, 1);
unit_shape_ = unit_shape_.united(QPolygonF(row_rect));
rr2 = row_rect;
if (row2_bits > row1_bits) {
unit_tr_ = row_rect;
}
if (row3_bits > 0) {
row_rect = QRectF(0, 2, row3_bits, 1);
unit_shape_ = unit_shape_.united(QPolygonF(row_rect));
rr3 = row_rect;
}
QPainterPath pp;
pp.addPolygon(unit_shape_);
unit_shape_ = pp.simplified().toFillPolygon();
}
updateLayout();
if (finfo_->isValid()) {
setToolTip(QString("%1 (%2) = %3")
.arg(finfo_->headerInfo().name)
.arg(finfo_->headerInfo().abbreviation)
.arg(finfo_->toString()));
setData(Qt::UserRole, VariantPointer<field_info>::asQVariant(finfo_->fieldInfo()));
representation_ = fi->rep->representation;
} else {
setToolTip(QObject::tr("Gap in dissection"));
}
}
~FieldInformationGraphicsItem()
{
delete finfo_;
}
int collapsedLength() { return collapsed_len_; }
void setPos(qreal x, qreal y) {
QGraphicsPolygonItem::setPos(x, y);
updateLayout();
}
int maxLeftY() {
qreal rel_len = (start_bit_ % layout_->bitsPerRow()) + collapsed_len_;
QPointF pt = mapToParent(QPointF(0, ceil(rel_len / layout_->bitsPerRow()) * layout_->rowHeight()));
return pt.y();
}
int maxRightY() {
qreal rel_len = (start_bit_ % layout_->bitsPerRow()) + collapsed_len_;
QPointF pt = mapToParent(QPointF(0, floor(rel_len / layout_->bitsPerRow()) * layout_->rowHeight()));
return pt.y();
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) {
painter->setPen(Qt::NoPen);
painter->save();
if (!finfo_->isValid()) {
QBrush brush = QBrush(option->palette.text().color(), Qt::BDiagPattern);
painter->setBrush(brush);
} else if (isSelected()) {
painter->setBrush(option->palette.highlight().color());
}
painter->drawPolygon(polygon());
painter->restore();
// Lower and inner right borders
painter->setPen(option->palette.text().color());
QPolygonF shape = polygon();
for (int idx = 1; idx < unit_shape_.size(); idx++) {
QPointF u_start = unit_shape_[idx - 1];
QPointF u_end = unit_shape_[idx];
QPointF start, end;
bool draw_line = false;
if (u_start.y() > 0 && u_start.y() == u_end.y()) {
draw_line = true;
} else if (u_start.x() > 0 && u_start.x() < layout_->bitsPerRow() && u_start.x() == u_end.x()) {
draw_line = true;
}
if (draw_line) {
start = shape[idx - 1];
end = shape[idx];
painter->drawLine(start, end);
}
}
if (!finfo_->isValid()) {
return;
}
// Field label(s)
QString label;
if (finfo_->headerInfo().type == FT_NONE) {
label = representation_;
} else {
label = finfo_->headerInfo().name;
}
paintLabel(painter, label, scaled_tr_);
if (layout_->showFields()) {
label = finfo_->toString();
paintLabel(painter, label, scaled_tr_.adjusted(0, scaled_tr_.height(), 0, scaled_tr_.height()));
}
}
private:
enum SpanMark {
TopLeft,
BottomLeft,
TopRight,
BottomRight,
NumSpanMarks
};
FieldInformation *finfo_;
QString representation_;
int start_bit_;
const DiagramLayout *layout_;
int collapsed_len_;
int collapsed_row_;
QPolygonF unit_shape_;
QRectF unit_tr_;
QRectF scaled_tr_;
QGraphicsLineItem *span_marks_[NumSpanMarks];
void updateLayout() {
QTransform xform;
xform.scale(layout_->bitWidth(), layout_->rowHeight());
setPolygon(xform.map(unit_shape_));
scaled_tr_ = xform.mapRect(unit_tr_);
scaled_tr_.adjust(layout_->hPadding(), layout_->vPadding(), -layout_->hPadding(), -layout_->vPadding());
scaled_tr_.setHeight(layout_->lineHeight());
// Collapsed / span marks
for (int idx = 0; idx < NumSpanMarks; idx++) {
span_marks_[idx]->hide();
}
if (collapsed_row_ >= 0) {
QRectF bounding_rect = polygon().boundingRect();
qreal center_y = bounding_rect.top() + (layout_->rowHeight() * collapsed_row_) + (layout_->rowHeight() / 2);
qreal mark_w = layout_->bitWidth() / 3; // Each mark side to center
QLineF span_l = QLineF(-mark_w, mark_w / 2, mark_w, -mark_w / 2);
for (int idx = 0; idx < NumSpanMarks; idx++) {
QPointF center;
switch (idx) {
case TopLeft:
center = QPointF(bounding_rect.left(), center_y - layout_->spanMarkOffset());
break;
case BottomLeft:
center = QPointF(bounding_rect.left(), center_y + layout_->spanMarkOffset());
break;
case TopRight:
center = QPointF(bounding_rect.right(), center_y - layout_->spanMarkOffset());
break;
case BottomRight:
center = QPointF(bounding_rect.right(), center_y + layout_->spanMarkOffset());
break;
}
span_marks_[idx]->setLine(span_l.translated(center));
span_marks_[idx]->setZValue(zValue() - 0.1);
span_marks_[idx]->show();
}
}
}
void paintLabel(QPainter *painter, QString label, QRectF label_rect) {
QFontMetrics fm = QFontMetrics(layout_->regularFont());
painter->setFont(layout_->regularFont());
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
int label_w = fm.horizontalAdvance(label);
#else
int label_w = fm.width(label);
#endif
if (label_w > label_rect.width()) {
painter->setFont(layout_->smallFont());
fm = QFontMetrics(layout_->smallFont());
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
label_w = fm.horizontalAdvance(label);
#else
label_w = fm.width(label);
#endif
if (label_w > label_rect.width()) {
// XXX Use parent+ItemClipsChildrenToShape or setScale instead?
label = fm.elidedText(label, Qt::ElideRight, label_rect.width());
}
}
painter->drawText(label_rect, Qt::AlignCenter, label);
}
};
PacketDiagram::PacketDiagram(QWidget *parent) :
QGraphicsView(parent),
layout_(new DiagramLayout),
cap_file_(nullptr),
root_node_(nullptr),
selected_field_(nullptr),
y_pos_(0)
{
setAccessibleName(tr("Packet diagram"));
setRenderHint(QPainter::Antialiasing);
// XXX Move to setMonospaceFont similar to ProtoTree
layout_->setFont(font());
connect(wsApp, &WiresharkApplication::appInitialized, this, &PacketDiagram::connectToMainWindow);
connect(wsApp, &WiresharkApplication::zoomRegularFont, this, &PacketDiagram::setFont);
resetScene();
}
PacketDiagram::~PacketDiagram()
{
delete layout_;
}
void PacketDiagram::setRootNode(proto_node *root_node)
{
// As https://doc.qt.io/qt-5/qgraphicsscene.html#clear says, this
// "Removes and deletes all items from the scene, but otherwise leaves
// the state of the scene unchanged."
// This means that the scene rect grows but doesn't shrink, which is
// useful in our case because it gives us a cheap way to retain our
// scroll position between packets.
scene()->clear();
selected_field_ = nullptr;
y_pos_ = 0;
root_node_ = root_node;
if (!isVisible() || !root_node) {
return;
}
ProtoNode parent_node(root_node_);
if (!parent_node.isValid()) {
return;
}
ProtoNode::ChildIterator kids = parent_node.children();
while (kids.element().isValid())
{
proto_node *tl_node = kids.element().protoNode();
kids.next();
// Exclude all ("Frame") and nothing
if (tl_node->finfo->start == 0 && tl_node->finfo->length == (int) tvb_captured_length(cap_file_->edt->tvb)) {
continue;
}
if (tl_node->finfo->length < 1) {
continue;
}
addDiagram(tl_node);
}
}
void PacketDiagram::clear()
{
setRootNode(nullptr);
}
void PacketDiagram::setCaptureFile(capture_file *cf)
{
// For use by the main view, set the capture file which will later have a
// dissection (EDT) ready.
// The packet dialog sets a fixed EDT context and MUST NOT use this.
cap_file_ = cf;
if (!cf) {
resetScene();
}
}
void PacketDiagram::setFont(const QFont &font)
{
layout_->setFont(font);
resetScene(false);
}
void PacketDiagram::selectedFieldChanged(FieldInformation *finfo)
{
setSelectedField(finfo ? finfo->fieldInfo() : nullptr);
}
void PacketDiagram::selectedFrameChanged(QList<int> frames)
{
if (frames.count() == 1 && cap_file_ && cap_file_->edt && cap_file_->edt->tree) {
setRootNode(cap_file_->edt->tree);
} else {
// Clear the proto tree contents as they have become invalid.
setRootNode(nullptr);
}
}
bool PacketDiagram::event(QEvent *event)
{
switch (event->type()) {
case QEvent::ApplicationPaletteChange:
resetScene(false);
break;
default:
break;
}
return QGraphicsView::event(event);
}
void PacketDiagram::contextMenuEvent(QContextMenuEvent *event)
{
if (!event) {
return;
}
QAction *action;
QMenu ctx_menu(this);
action = ctx_menu.addAction(tr("Show Field Values"));
action->setCheckable(true);
action->setChecked(layout_->showFields());
connect(action, &QAction::toggled, this, &PacketDiagram::showFieldsToggled);
ctx_menu.addSeparator();
action = ctx_menu.addAction(tr("Save Diagram As…"));
connect(action, &QAction::triggered, this, &PacketDiagram::saveAsTriggered);
action = ctx_menu.addAction(tr("Copy as Raster Image"));
connect(action, &QAction::triggered, this, &PacketDiagram::copyAsRasterTriggered);
#if defined(QT_SVG_LIB) && !defined(Q_OS_MAC)
action = ctx_menu.addAction(tr("…as SVG"));
connect(action, &QAction::triggered, this, &PacketDiagram::copyAsSvgTriggered);
#endif
ctx_menu.exec(event->globalPos());
}
void PacketDiagram::connectToMainWindow()
{
MainWindow *main_window = qobject_cast<MainWindow *>(wsApp->mainWindow());
if (!main_window) {
return;
}
connect(main_window, &MainWindow::setCaptureFile, this, &PacketDiagram::setCaptureFile);
connect(main_window, &MainWindow::fieldSelected, this, &PacketDiagram::selectedFieldChanged);
connect(main_window, &MainWindow::framesSelected, this, &PacketDiagram::selectedFrameChanged);
connect(this, &PacketDiagram::fieldSelected, main_window, &MainWindow::fieldSelected);
}
void PacketDiagram::sceneSelectionChanged()
{
field_info *sel_fi = nullptr;
if (! scene()->selectedItems().isEmpty()) {
sel_fi = VariantPointer<field_info>::asPtr(scene()->selectedItems().first()->data(Qt::UserRole));
}
if (sel_fi) {
FieldInformation finfo(sel_fi, this);
emit fieldSelected(&finfo);
} else {
emit fieldSelected(nullptr);
}
}
void PacketDiagram::resetScene(bool reset_root)
{
// As noted in setRootNode, scene()->clear() doesn't clear everything.
// Do a "hard" clear, which resets our various rects and scroll position.
if (scene()) {
delete scene();
}
QGraphicsScene *new_scene = new QGraphicsScene();
setScene(new_scene);
connect(new_scene, &QGraphicsScene::selectionChanged, this, &PacketDiagram::sceneSelectionChanged);
setRootNode(reset_root ? nullptr : root_node_);
}
struct DiagramItemSpan {
field_info *finfo;
int start_bit;
int length;
};
void PacketDiagram::addDiagram(proto_node *tl_node)
{
QGraphicsItem *item;
QGraphicsSimpleTextItem *t_item;
int bits_per_row = layout_->bitsPerRow();
int bit_width = layout_->bitWidth();
int diag_w = bit_width * layout_->bitsPerRow();
qreal x = layout_->hPadding();
// Title
t_item = scene()->addSimpleText(tl_node->finfo->hfinfo->name);
t_item->setFont(layout_->regularFont());
t_item->setPos(0, y_pos_);
y_pos_ += layout_->lineHeight() + (bit_width / 4);
int border_top = y_pos_;
// Bit scale + tick marks
QList<int> tick_nums;
for (int tn = 0 ; tn < layout_->bitsPerRow(); tn += 16) {
tick_nums << tn << tn + 15;
}
qreal y_bottom = y_pos_ + bit_width;
QGraphicsItem *tl_item = scene()->addLine(x, y_bottom, x + diag_w, y_bottom);
QFontMetrics sfm = QFontMetrics(layout_->smallFont());
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
int space_w = sfm.horizontalAdvance(' ');
#else
int space_w = sfm.width(' ');
#endif
#ifdef Q_OS_WIN
// t_item->boundingRect() has a pixel of space on the left on my (gcc)
// Windows VM.
int tl_adjust = 1;
#else
int tl_adjust = 0;
#endif
for (int tick_n = 0; tick_n < bits_per_row; tick_n++) {
x = layout_->hPadding() + (tick_n * bit_width);
qreal y_top = y_pos_ + (tick_n % 8 == 0 ? 0 : bit_width / 2);
if (tick_n > 0) {
scene()->addLine(x, y_top, x, y_bottom);
}
if (tick_nums.contains(tick_n)) {
t_item = scene()->addSimpleText(QString::number(tick_n));
t_item->setFont(layout_->smallFont());
if (tick_n % 2 == 0) {
t_item->setPos(x + space_w - tl_adjust, y_pos_);
} else {
t_item->setPos(x + bit_width - space_w - t_item->boundingRect().width() - tl_adjust, y_pos_);
}
// Does the placement above look funny on your system? Try
// uncommenting the lines below.
// QGraphicsRectItem *br_item = scene()->addRect(t_item->boundingRect(), QPen(palette().highlight().color()));
// br_item->setPos(t_item->pos());
}
}
y_pos_ = y_bottom;
x = layout_->hPadding();
// Collect our top-level fields
int last_start_bit = -1;
int max_l_y = y_bottom;
QList<DiagramItemSpan>item_spans;
for (proto_item *cur_item = tl_node->first_child; cur_item; cur_item = cur_item->next) {
if (proto_item_is_generated(cur_item) || proto_item_is_hidden(cur_item)) {
continue;
}
field_info *fi = cur_item->finfo;
int start_bit = ((fi->start - tl_node->finfo->start) * 8) + FI_GET_BITS_OFFSET(fi);
int length = FI_GET_BITS_SIZE(fi) ? FI_GET_BITS_SIZE(fi) : fi->length * 8;
if (start_bit <= last_start_bit || length <= 0) {
#ifdef DEBUG_PACKET_DIAGRAM
qDebug() << "Skipping item" << fi->hfinfo->abbrev << start_bit << last_start_bit << length;
#endif
continue;
}
last_start_bit = start_bit;
if (item_spans.size() > 0) {
DiagramItemSpan prev_span = item_spans.last();
// Get rid of overlaps.
if (prev_span.start_bit + prev_span.length > start_bit) {
#ifdef DEBUG_PACKET_DIAGRAM
qDebug() << "Resized prev" << prev_span.finfo->hfinfo->abbrev << prev_span.start_bit << prev_span.length << "->" << start_bit - prev_span.start_bit;
#endif
prev_span.length = start_bit - prev_span.start_bit;
}
if (prev_span.length < 1) {
#ifdef DEBUG_PACKET_DIAGRAM
qDebug() << "Removed prev" << prev_span.finfo->hfinfo->abbrev << prev_span.start_bit << prev_span.length;
item_spans.removeLast();
if (item_spans.size() < 1) {
continue;
}
prev_span = item_spans.last();
#endif
}
// Fill in gaps.
if (prev_span.start_bit + prev_span.length < start_bit) {
#ifdef DEBUG_PACKET_DIAGRAM
qDebug() << "Adding gap" << prev_span.finfo->hfinfo->abbrev << prev_span.start_bit << prev_span.length << start_bit;
#endif
int gap_start = prev_span.start_bit + prev_span.length;
DiagramItemSpan gap_span = { nullptr, gap_start, start_bit - gap_start };
item_spans << gap_span;
}
}
DiagramItemSpan item_span = { cur_item->finfo, start_bit, length };
item_spans << item_span;
}
qreal z_value = tl_item->zValue();
int start_bit = 0;
for (int idx = 0; idx < item_spans.size(); idx++) {
DiagramItemSpan *item_span = &item_spans[idx];
int y_off = (start_bit / bits_per_row) * layout_->rowHeight();
// Stack each item behind the previous one.
z_value -= .01;
FieldInformationGraphicsItem *fi_item = new FieldInformationGraphicsItem(item_span->finfo, start_bit, item_span->length, layout_);
start_bit += fi_item->collapsedLength();
fi_item->setPos(x, y_bottom + y_off);
fi_item->setFlag(QGraphicsItem::ItemIsSelectable);
fi_item->setAcceptedMouseButtons(Qt::LeftButton);
fi_item->setZValue(z_value);
scene()->addItem(fi_item);
y_pos_ = fi_item->maxRightY();
max_l_y = fi_item->maxLeftY();
}
// Left & right borders
scene()->addLine(x, border_top, x, max_l_y);
scene()->addLine(x + diag_w, border_top, x + diag_w, y_pos_);
// Inter-diagram margin
y_pos_ = max_l_y + bit_width;
// Set the proper color. Needed for dark mode on macOS + Qt 5.15.0 at least, possibly other cases.
foreach (item, scene()->items()) {
QGraphicsSimpleTextItem *t_item = qgraphicsitem_cast<QGraphicsSimpleTextItem *>(item);
if (t_item) {
t_item->setBrush(palette().text().color());
}
QGraphicsLineItem *l_item = qgraphicsitem_cast<QGraphicsLineItem *>(item);
if (l_item) {
l_item->setPen(palette().text().color());
}
}
}
void PacketDiagram::setSelectedField(field_info *fi)
{
QSignalBlocker blocker(this);
FieldInformationGraphicsItem *fi_item;
foreach (QGraphicsItem *item, scene()->items()) {
if (item->isSelected()) {
item->setSelected(false);
}
if (fi && VariantPointer<field_info>::asPtr(item->data(Qt::UserRole)) == fi) {
fi_item = qgraphicsitem_cast<FieldInformationGraphicsItem *>(item);
if (fi_item) {
fi_item->setSelected(true);
}
}
}
}
QImage PacketDiagram::exportToImage()
{
// Create a hi-res 2x scaled image.
int scale = 2;
QRect rr = QRect(0, 0, sceneRect().size().width() * scale, sceneRect().size().height() * scale);
QImage raster_diagram = QImage(rr.size(), QImage::Format_ARGB32);
QPainter raster_painter(&raster_diagram);
raster_painter.setRenderHint(QPainter::Antialiasing);
raster_painter.fillRect(rr, palette().base().color());
scene()->render(&raster_painter);
raster_painter.end();
return raster_diagram;
}
#if defined(QT_SVG_LIB) && 0
QByteArray PacketDiagram::exportToSvg()
{
QRect sr = QRect(0, 0, sceneRect().size().width(), sceneRect().size().height());
QBuffer svg_buf;
QSvgGenerator svg_diagram;
svg_diagram.setSize(sr.size());
svg_diagram.setViewBox(sr);
svg_diagram.setOutputDevice(&svg_buf);
QPainter svg_painter(&svg_diagram);
svg_painter.fillRect(sr, palette().base().color());
scene()->render(&svg_painter);
svg_painter.end();
return svg_buf.buffer();
}
#endif
void PacketDiagram::showFieldsToggled(bool checked)
{
layout_->setShowFields(checked);
setRootNode(root_node_);
}
// XXX - We have similar code in tcp_stream_dialog and io_graph_dialog. Should this be a common routine?
void PacketDiagram::saveAsTriggered()
{
QString file_name, extension;
QDir path(wsApp->lastOpenDir());
QString png_filter = tr("Portable Network Graphics (*.png)");
QString bmp_filter = tr("Windows Bitmap (*.bmp)");
// Gaze upon my beautiful graph with lossy artifacts!
QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
QStringList fl = QStringList() << png_filter << bmp_filter << jpeg_filter;
#if defined(QT_SVG_LIB) && 0
QString svg_filter = tr("Scalable Vector Graphics (*.svg)");
fl << svg_filter;
#endif
QString filter = fl.join(";;");
file_name = WiresharkFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Graph As…")),
path.canonicalPath(), filter, &extension);
if (file_name.length() > 0) {
bool save_ok = false;
if (extension.compare(png_filter) == 0) {
QImage raster_diagram = exportToImage();
save_ok = raster_diagram.save(file_name, "PNG");
} else if (extension.compare(bmp_filter) == 0) {
QImage raster_diagram = exportToImage();
save_ok = raster_diagram.save(file_name, "BMP");
} else if (extension.compare(jpeg_filter) == 0) {
QImage raster_diagram = exportToImage();
save_ok = raster_diagram.save(file_name, "JPG");
}
#if defined(QT_SVG_LIB) && 0
else if (extension.compare(svg_filter) == 0) {
QByteArray svg_diagram = exportToSvg();
QFile file(file_name);
if (file.open(QIODevice::WriteOnly)) {
save_ok = file.write(svg_diagram) > 0;
file.close();
}
}
#endif
// else error dialog?
if (save_ok) {
wsApp->setLastOpenDirFromFilename(file_name);
}
}
}
void PacketDiagram::copyAsRasterTriggered()
{
QImage raster_diagram = exportToImage();
wsApp->clipboard()->setImage(raster_diagram);
}
#if defined(QT_SVG_LIB) && !defined(Q_OS_MAC) && 0
void PacketDiagram::copyAsSvgTriggered()
{
QByteArray svg_ba = exportToSvg();
// XXX It looks like we have to use/subclass QMacPasteboardMime in
// order for this to work on macOS.
// It might be easier to just do "Save As" instead.
QMimeData *md = new QMimeData();
md->setData("image/svg+xml", svg_buf);
wsApp->clipboard()->setMimeData(md);
}
#endif