wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
DolphinClipboard.cpp
Go to the documentation of this file.
1// Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
2// SPDX-License-Identifier: GPL-3.0-or-later
3
5
6#include <algorithm>
7
8#include <QApplication>
9#include <QClipboard>
10#include <QDataStream>
11#include <QItemSelection>
12#include <QMimeData>
13
15#include "App/Core/Common.h"
17
18namespace {
20constexpr auto kWaveformMimeType = "application/x-bewaveddolphin-waveform";
22constexpr auto kLegacyMimeType = "bdolphin/copydata";
23
25template <typename Extract>
26int minAcrossRanges(int fallback, const QItemSelection &ranges, Extract extract)
27{
28 int result = fallback;
29 for (const auto &range : ranges) {
30 result = (std::min)(result, extract(range));
31 }
32 return result;
33}
34} // namespace
35
37
38int firstColumn(const SignalModel &model, const QItemSelection &ranges)
39{
40 return minAcrossRanges(model.columnCount() - 1, ranges, [](const auto &r) { return r.left(); });
41}
42
43int firstRow(const SignalModel &model, const QItemSelection &ranges)
44{
45 return minAcrossRanges(model.rowCount() - 1, ranges, [](const auto &r) { return r.top(); });
46}
47
48void copy(const SignalModel &model, const QItemSelection &ranges, QDataStream &stream)
49{
50 const int anchorRow = firstRow(model, ranges);
51 const int anchorCol = firstColumn(model, ranges);
52 const auto itemList = ranges.indexes();
53 const qint64 sz = itemList.size();
54 stream << sz;
55
56 for (const auto &item : itemList) {
57 const int value = model.value(item.row(), item.column());
58 // Store offsets relative to the selection origin so paste can re-anchor
59 // the data at any target cell regardless of absolute position
60 stream << static_cast<qint64>(item.row() - anchorRow);
61 stream << static_cast<qint64>(item.column() - anchorCol);
62 stream << static_cast<qint64>(value);
63 }
64}
65
66void paste(SignalModel &model, const QItemSelection &ranges, QDataStream &stream)
67{
68 const int anchorCol = firstColumn(model, ranges);
69 const int anchorRow = firstRow(model, ranges);
70 quint64 itemListSize; stream >> itemListSize;
71
72 // The clipboard is not a trusted source (any process can set MIME data ahead of a
73 // Ctrl+V) — bound the announced item count by what the stream can actually hold
74 // before looping, the same way DolphinSerializer::loadBinary bounds its row count,
75 // so a crafted/corrupt payload can't spin this loop for eons instead of failing fast.
76 constexpr qint64 kBytesPerItem = 3 * static_cast<qint64>(sizeof(quint64));
77 const qint64 available = stream.device() ? stream.device()->bytesAvailable() : 0;
78 const quint64 maxItems = available > 0 ? static_cast<quint64>(available / kBytesPerItem) : 0;
79 if (itemListSize > maxItems) {
80 qCWarning(zero) << "DolphinClipboard: truncating paste from" << itemListSize
81 << "items to" << maxItems << "(insufficient stream data)";
82 itemListSize = maxItems;
83 }
84
85 SignalModel::BulkEditGuard guard(model);
86 for (quint64 i = 0; i < itemListSize; ++i) {
87 quint64 row; stream >> row;
88 quint64 col; stream >> col;
89 quint64 value; stream >> value;
90 // Re-anchor the stored relative offsets to the paste-target cell
91 const int newRow = static_cast<int>(static_cast<quint64>(anchorRow) + row);
92 const int newCol = static_cast<int>(static_cast<quint64>(anchorCol) + col);
93
94 // Silently drop cells that land outside the input rows or past the simulation
95 // length; output rows are never editable
96 if ((newRow < model.inputRows()) && (newCol < model.columnCount())) {
97 model.setValue(newRow, newCol, static_cast<int>(value));
98 }
99 }
100}
101
102void copyToClipboard(const SignalModel &model, const QItemSelection &ranges)
103{
104 // Serialise using a versioned header so paste can verify format compatibility.
105 QByteArray itemData;
106 QDataStream stream(&itemData, QIODevice::WriteOnly);
108 copy(model, ranges, stream);
109
110 auto *mimeData = new QMimeData();
111 mimeData->setData(kWaveformMimeType, itemData);
112 QApplication::clipboard()->setMimeData(mimeData);
113}
114
115bool pasteFromClipboard(SignalModel &model, const QItemSelection &ranges)
116{
117 const auto *mimeData = QApplication::clipboard()->mimeData();
118 QByteArray itemData;
119
120 // Support both the legacy MIME type and the current one, so files or clipboard
121 // data from older versions of the app can still be pasted.
122 if (mimeData->hasFormat(kLegacyMimeType)) {
123 itemData = mimeData->data(kLegacyMimeType);
124 }
125
126 if (mimeData->hasFormat(kWaveformMimeType)) {
127 itemData = mimeData->data(kWaveformMimeType);
128 }
129
130 if (itemData.isEmpty()) {
131 return false;
132 }
133
134 QDataStream stream(&itemData, QIODevice::ReadOnly);
136 paste(model, ranges, stream);
137 return true;
138}
139
140} // namespace DolphinClipboard
Common logging utilities, the Pandaception error type, and helper macros.
DolphinClipboard: copy/paste of waveform cell rectangles to/from a data stream.
Circuit and waveform file serialization/deserialization utilities.
SignalModel for the beWavedDolphin waveform table.
static void readDolphinHeader(QDataStream &stream)
Reads and validates the BeWavedDolphin waveform file header.
static void writeDolphinHeader(QDataStream &stream)
Writes the BeWavedDolphin waveform file header to stream.
QStandardItemModel subclass that makes all cells non-editable.
Definition SignalModel.h:21
void setValue(int row, int col, int value)
Sets the logic value (0/1) of the cell at (row, col).
int value(int row, int col) const
Returns the logic value (0/1) of the cell at (row, col).
int inputRows() const
Returns the number of input rows (the output rows follow them).
Definition SignalModel.h:55
The beWavedDolphin clipboard layer: cell ↔ stream mapping plus the system clipboard transport (MIME w...
int firstRow(const SignalModel &model, const QItemSelection &ranges)
Returns the topmost row index in ranges (clamped to the model's rows).
bool pasteFromClipboard(SignalModel &model, const QItemSelection &ranges)
void copyToClipboard(const SignalModel &model, const QItemSelection &ranges)
int firstColumn(const SignalModel &model, const QItemSelection &ranges)
Returns the leftmost column index in ranges (clamped to the model's columns).
void paste(SignalModel &model, const QItemSelection &ranges, QDataStream &stream)
void copy(const SignalModel &model, const QItemSelection &ranges, QDataStream &stream)