]> sigrok.org Git - pulseview.git/blame - pv/views/decoder_output/QHexView.cpp
Add QHexView
[pulseview.git] / pv / views / decoder_output / QHexView.cpp
CommitLineData
c7b76823
SA
1/*
2 * This file is part of the PulseView project.
3 *
4 * Copyright (C) 2015 Victor Anjin <virinext@gmail.com>
5 * Copyright (C) 2019 Soeren Apel <soeren@apelpie.net>
6 *
7 * The MIT License (MIT)
8 *
9 * Copyright (c) 2015
10 *
11 * Permission is hereby granted, free of charge, to any person obtaining a copy
12 * of this software and associated documentation files (the "Software"), to deal
13 * in the Software without restriction, including without limitation the rights
14 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 * copies of the Software, and to permit persons to whom the Software is
16 * furnished to do so, subject to the following conditions:
17 *
18 * The above copyright notice and this permission notice shall be included in all
19 * copies or substantial portions of the Software.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 * SOFTWARE.
28 */
29
30#include <QApplication>
31#include <QClipboard>
32#include <QFont>
33#include <QKeyEvent>
34#include <QScrollBar>
35#include <QSize>
36#include <QPainter>
37#include <QPaintEvent>
38
39#include "QHexView.hpp"
40
41using std::size_t;
42
43const unsigned int HEXCHARS_IN_LINE = 47;
44const unsigned int GAP_ADR_HEX = 10;
45const unsigned int GAP_HEX_ASCII = 16;
46const unsigned int BYTES_PER_LINE = 16;
47
48
49DataStorageArray::DataStorageArray(const QByteArray &arr)
50{
51 data_ = arr;
52}
53
54QByteArray DataStorageArray::getData(size_t position, size_t length)
55{
56 return data_.mid(position, length);
57}
58
59size_t DataStorageArray::size()
60{
61 return data_.count();
62}
63
64
65
66QHexView::QHexView(QWidget *parent):
67 QAbstractScrollArea(parent),
68 pdata_(nullptr)
69{
70 setFont(QFont("Courier", 10));
71
72 charWidth_ = fontMetrics().width(QLatin1Char('9'));
73 charHeight_ = fontMetrics().height();
74
75 posAddr_ = 0;
76 posHex_ = 10 * charWidth_ + GAP_ADR_HEX;
77 posAscii_ = posHex_ + HEXCHARS_IN_LINE * charWidth_ + GAP_HEX_ASCII;
78
79 setMinimumWidth(posAscii_ + (BYTES_PER_LINE * charWidth_));
80
81 setFocusPolicy(Qt::StrongFocus);
82}
83
84QHexView::~QHexView()
85{
86 if (pdata_)
87 delete pdata_;
88}
89
90void QHexView::setData(DataStorage *pData)
91{
92 verticalScrollBar()->setValue(0);
93
94 if (pdata_)
95 delete pdata_;
96
97 pdata_ = pData;
98 cursorPos_ = 0;
99 resetSelection(0);
100}
101
102void QHexView::showFromOffset(size_t offset)
103{
104 if (pdata_ && (offset < pdata_->size())) {
105 setCursorPos(offset * 2);
106
107 int cursorY = cursorPos_ / (2 * BYTES_PER_LINE);
108 verticalScrollBar() -> setValue(cursorY);
109 }
110}
111
112void QHexView::clear()
113{
114 verticalScrollBar()->setValue(0);
115}
116
117QSize QHexView::fullSize() const
118{
119 if (!pdata_)
120 return QSize(0, 0);
121
122 size_t width = posAscii_ + (BYTES_PER_LINE * charWidth_);
123 size_t height = pdata_->size() / BYTES_PER_LINE;
124 if (pdata_->size() % BYTES_PER_LINE)
125 height++;
126
127 height *= charHeight_;
128
129 return QSize(width, height);
130}
131
132void QHexView::paintEvent(QPaintEvent *event)
133{
134 if (!pdata_)
135 return;
136
137 QPainter painter(viewport());
138
139 QSize areaSize = viewport()->size();
140 QSize widgetSize = fullSize();
141 verticalScrollBar()->setPageStep(areaSize.height() / charHeight_);
142 verticalScrollBar()->setRange(0, (widgetSize.height() - areaSize.height()) / charHeight_ + 1);
143
144 size_t firstLineIdx = verticalScrollBar()->value();
145
146 size_t lastLineIdx = firstLineIdx + areaSize.height() / charHeight_;
147 if (lastLineIdx > (pdata_->size() / BYTES_PER_LINE)) {
148 lastLineIdx = pdata_->size() / BYTES_PER_LINE;
149 if (pdata_->size() % BYTES_PER_LINE)
150 lastLineIdx++;
151 }
152
153 painter.fillRect(event->rect(), this->palette().color(QPalette::Base));
154
155 QColor addressAreaColor = QColor(0xd4, 0xd4, 0xd4, 0xff);
156 painter.fillRect(QRect(posAddr_, event->rect().top(),
157 posHex_ - GAP_ADR_HEX + 2, height()), addressAreaColor);
158
159 int linePos = posAscii_ - (GAP_HEX_ASCII / 2);
160 painter.setPen(Qt::gray);
161
162 painter.drawLine(linePos, event->rect().top(), linePos, height());
163
164 painter.setPen(Qt::black);
165
166 int yPosStart = charHeight_;
167
168 QBrush def = painter.brush();
169 QBrush selected = QBrush(QColor(0x6d, 0x9e, 0xff, 0xff));
170 QByteArray data = pdata_->getData(firstLineIdx * BYTES_PER_LINE, (lastLineIdx - firstLineIdx) * BYTES_PER_LINE);
171
172 for (size_t lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx++) {
173 QString address = QString("%1").arg(lineIdx * 16, 10, 16, QChar('0'));
174 painter.drawText(posAddr_, yPos, address);
175
176 int xPos = posHex_;
177 for (size_t i = 0; i < BYTES_PER_LINE && ((lineIdx - firstLineIdx) * BYTES_PER_LINE + i) < (size_t)data.size(); i++) {
178 size_t pos = (lineIdx * BYTES_PER_LINE + i) * 2;
179 if ((pos >= selectBegin_) && (pos < selectEnd_)) {
180 painter.setBackground(selected);
181 painter.setBackgroundMode(Qt::OpaqueMode);
182 }
183
184 QString val = QString::number((data.at((lineIdx - firstLineIdx) * BYTES_PER_LINE + i) & 0xF0) >> 4, 16);
185 painter.drawText(xPos, yPos, val);
186
187 if (((pos+1) >= selectBegin_) && ((pos+1) < selectEnd_)) {
188 painter.setBackground(selected);
189 painter.setBackgroundMode(Qt::OpaqueMode);
190 } else {
191 painter.setBackground(def);
192 painter.setBackgroundMode(Qt::OpaqueMode);
193 }
194
195 val = QString::number((data.at((lineIdx - firstLineIdx) * BYTES_PER_LINE + i) & 0xF), 16);
196 painter.drawText(xPos + charWidth_, yPos, val);
197
198 painter.setBackground(def);
199 painter.setBackgroundMode(Qt::OpaqueMode);
200
201 xPos += 3 * charWidth_;
202 }
203
204 int xPosAscii = posAscii_;
205 for (size_t i = 0; ((lineIdx - firstLineIdx) * BYTES_PER_LINE + i) < (size_t)data.size() && (i < BYTES_PER_LINE); i++) {
206 char ch = data[(unsigned int)((lineIdx - firstLineIdx) * BYTES_PER_LINE + i)];
207
208 if ((ch < 0x20) || (ch > 0x7e))
209 ch = '.';
210
211 painter.drawText(xPosAscii, yPos, QString(ch));
212 xPosAscii += charWidth_;
213 }
214
215 yPos += charHeight_;
216 }
217
218 if (hasFocus()) {
219 int x = (cursorPos_ % (2 * BYTES_PER_LINE));
220 int y = cursorPos_ / (2 * BYTES_PER_LINE);
221 y -= firstLineIdx;
222 int cursorX = (((x / 2) * 3) + (x % 2)) * charWidth_ + posHex_;
223 int cursorY = y * charHeight_ + 4;
224 painter.fillRect(cursorX, cursorY, 2, charHeight_, this->palette().color(QPalette::WindowText));
225 }
226}
227
228void QHexView::keyPressEvent(QKeyEvent *event)
229{
230 bool setVisible = false;
231
232 /*****************************************************************************/
233 /* Cursor movements */
234 /*****************************************************************************/
235 if (event->matches(QKeySequence::MoveToNextChar)) {
236 setCursorPos(cursorPos_ + 1);
237 resetSelection(cursorPos_);
238 setVisible = true;
239 }
240 if (event->matches(QKeySequence::MoveToPreviousChar)) {
241 setCursorPos(cursorPos_ - 1);
242 resetSelection(cursorPos_);
243 setVisible = true;
244 }
245
246 if (event->matches(QKeySequence::MoveToEndOfLine)) {
247 setCursorPos(cursorPos_ | ((BYTES_PER_LINE * 2) - 1));
248 resetSelection(cursorPos_);
249 setVisible = true;
250 }
251 if (event->matches(QKeySequence::MoveToStartOfLine)) {
252 setCursorPos(cursorPos_ | (cursorPos_ % (BYTES_PER_LINE * 2)));
253 resetSelection(cursorPos_);
254 setVisible = true;
255 }
256 if (event->matches(QKeySequence::MoveToPreviousLine)) {
257 setCursorPos(cursorPos_ - BYTES_PER_LINE * 2);
258 resetSelection(cursorPos_);
259 setVisible = true;
260 }
261 if (event->matches(QKeySequence::MoveToNextLine)) {
262 setCursorPos(cursorPos_ + BYTES_PER_LINE * 2);
263 resetSelection(cursorPos_);
264 setVisible = true;
265 }
266
267 if (event->matches(QKeySequence::MoveToNextPage)) {
268 setCursorPos(cursorPos_ + (viewport()->height() / charHeight_ - 1) * 2 * BYTES_PER_LINE);
269 resetSelection(cursorPos_);
270 setVisible = true;
271 }
272 if (event->matches(QKeySequence::MoveToPreviousPage)) {
273 setCursorPos(cursorPos_ - (viewport()->height() / charHeight_ - 1) * 2 * BYTES_PER_LINE);
274 resetSelection(cursorPos_);
275 setVisible = true;
276 }
277 if (event->matches(QKeySequence::MoveToEndOfDocument)) {
278 if (pdata_)
279 setCursorPos(pdata_->size() * 2);
280 resetSelection(cursorPos_);
281 setVisible = true;
282 }
283 if (event->matches(QKeySequence::MoveToStartOfDocument)) {
284 setCursorPos(0);
285 resetSelection(cursorPos_);
286 setVisible = true;
287 }
288
289 /*****************************************************************************/
290 /* Select commands */
291 /*****************************************************************************/
292 if (event->matches(QKeySequence::SelectAll)) {
293 resetSelection(0);
294 if (pdata_)
295 setSelection(2 * pdata_->size() + 1);
296 setVisible = true;
297 }
298 if (event->matches(QKeySequence::SelectNextChar)) {
299 int pos = cursorPos_ + 1;
300 setCursorPos(pos);
301 setSelection(pos);
302 setVisible = true;
303 }
304 if (event->matches(QKeySequence::SelectPreviousChar)) {
305 int pos = cursorPos_ - 1;
306 setSelection(pos);
307 setCursorPos(pos);
308 setVisible = true;
309 }
310 if (event->matches(QKeySequence::SelectEndOfLine)) {
311 int pos = cursorPos_ - (cursorPos_ % (2 * BYTES_PER_LINE)) + (2 * BYTES_PER_LINE);
312 setCursorPos(pos);
313 setSelection(pos);
314 setVisible = true;
315 }
316 if (event->matches(QKeySequence::SelectStartOfLine)) {
317 int pos = cursorPos_ - (cursorPos_ % (2 * BYTES_PER_LINE));
318 setCursorPos(pos);
319 setSelection(pos);
320 setVisible = true;
321 }
322 if (event->matches(QKeySequence::SelectPreviousLine)) {
323 int pos = cursorPos_ - (2 * BYTES_PER_LINE);
324 setCursorPos(pos);
325 setSelection(pos);
326 setVisible = true;
327 }
328 if (event->matches(QKeySequence::SelectNextLine)) {
329 int pos = cursorPos_ + (2 * BYTES_PER_LINE);
330 setCursorPos(pos);
331 setSelection(pos);
332 setVisible = true;
333 }
334
335 if (event->matches(QKeySequence::SelectNextPage)) {
336 int pos = cursorPos_ + (((viewport()->height() / charHeight_) - 1) * 2 * BYTES_PER_LINE);
337 setCursorPos(pos);
338 setSelection(pos);
339 setVisible = true;
340 }
341 if (event->matches(QKeySequence::SelectPreviousPage)) {
342 int pos = cursorPos_ - (((viewport()->height() / charHeight_) - 1) * 2 * BYTES_PER_LINE);
343 setCursorPos(pos);
344 setSelection(pos);
345 setVisible = true;
346 }
347 if (event->matches(QKeySequence::SelectEndOfDocument)) {
348 int pos = 0;
349 if (pdata_)
350 pos = pdata_->size() * 2;
351 setCursorPos(pos);
352 setSelection(pos);
353 setVisible = true;
354 }
355 if (event->matches(QKeySequence::SelectStartOfDocument)) {
356 int pos = 0;
357 setCursorPos(pos);
358 setSelection(pos);
359 setVisible = true;
360 }
361
362 if (event->matches(QKeySequence::Copy) && (pdata_)) {
363 QString text;
364 int idx = 0;
365 int copyOffset = 0;
366
367 QByteArray data = pdata_->getData(selectBegin_ / 2, (selectEnd_ - selectBegin_) / 2 + 1);
368 if (selectBegin_ % 2) {
369 text += QString::number((data.at((idx+1) / 2) & 0xF), 16);
370 text += " ";
371 idx++;
372 copyOffset = 1;
373 }
374
375 int selectedSize = selectEnd_ - selectBegin_;
376 while (idx < selectedSize) {
377 QString val = QString::number((data.at((copyOffset + idx) / 2) & 0xF0) >> 4, 16);
378 if ((idx + 1) < selectedSize) {
379 val += QString::number((data.at((copyOffset + idx) / 2) & 0xF), 16);
380 val += " ";
381 }
382 text += val;
383
384 if ((idx/2) % BYTES_PER_LINE == (BYTES_PER_LINE - 1))
385 text += "\n";
386
387 idx += 2;
388 }
389
390 QClipboard *clipboard = QApplication::clipboard();
391 clipboard->setText(text);
392 if (clipboard->supportsSelection())
393 clipboard->setText(text);
394 }
395
396 if (setVisible)
397 ensureVisible();
398
399 viewport()->update();
400}
401
402void QHexView::mouseMoveEvent(QMouseEvent *event)
403{
404 int actPos = cursorPos(event->pos());
405 setCursorPos(actPos);
406 setSelection(actPos);
407
408 viewport()->update();
409}
410
411void QHexView::mousePressEvent(QMouseEvent *event)
412{
413 int cPos = cursorPos(event->pos());
414
415 if ((QApplication::keyboardModifiers() & Qt::ShiftModifier) && (event->button() == Qt::LeftButton))
416 setSelection(cPos);
417 else
418 resetSelection(cPos);
419
420 setCursorPos(cPos);
421
422 viewport()->update();
423}
424
425size_t QHexView::cursorPos(const QPoint &position)
426{
427 int pos = -1;
428
429 if (((size_t)position.x() >= posHex_) &&
430 ((size_t)position.x() < (posHex_ + HEXCHARS_IN_LINE * charWidth_))) {
431
432 int x = (position.x() - posHex_) / charWidth_;
433
434 if ((x % 3) == 0)
435 x = (x / 3) * 2;
436 else
437 x = ((x / 3) * 2) + 1;
438
439 int firstLineIdx = verticalScrollBar()->value();
440 int y = (position.y() / charHeight_) * 2 * BYTES_PER_LINE;
441 pos = x + y + firstLineIdx * BYTES_PER_LINE * 2;
442 }
443
444 return pos;
445}
446
447void QHexView::resetSelection()
448{
449 selectBegin_ = selectInit_;
450 selectEnd_ = selectInit_;
451}
452
453void QHexView::resetSelection(int pos)
454{
455 if (pos < 0)
456 pos = 0;
457
458 selectInit_ = pos;
459 selectBegin_ = pos;
460 selectEnd_ = pos;
461}
462
463void QHexView::setSelection(int pos)
464{
465 if (pos < 0)
466 pos = 0;
467
468 if ((size_t)pos >= selectInit_) {
469 selectEnd_ = pos;
470 selectBegin_ = selectInit_;
471 } else {
472 selectBegin_ = pos;
473 selectEnd_ = selectInit_;
474 }
475}
476
477void QHexView::setCursorPos(int position)
478{
479 if (position < 0)
480 position = 0;
481
482 int maxPos = 0;
483 if (pdata_) {
484 maxPos = pdata_->size() * 2;
485 if (pdata_->size() % BYTES_PER_LINE)
486 maxPos++;
487 }
488
489 if (position > maxPos)
490 position = maxPos;
491
492 cursorPos_ = position;
493}
494
495void QHexView::ensureVisible()
496{
497 QSize areaSize = viewport()->size();
498
499 int firstLineIdx = verticalScrollBar() -> value();
500 int lastLineIdx = firstLineIdx + areaSize.height() / charHeight_;
501
502 int cursorY = cursorPos_ / (2 * BYTES_PER_LINE);
503
504 if (cursorY < firstLineIdx)
505 verticalScrollBar() -> setValue(cursorY);
506 else if(cursorY >= lastLineIdx)
507 verticalScrollBar() -> setValue(cursorY - areaSize.height() / charHeight_ + 1);
508}