]> sigrok.org Git - pulseview.git/blob - pv/views/decoder_output/QHexView.cpp
b379026c1b16a74cf2169867e2852ac4ddfc31e9
[pulseview.git] / pv / views / decoder_output / QHexView.cpp
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
41 using std::size_t;
42
43 const unsigned int BYTES_PER_LINE   = 16;
44 const unsigned int HEXCHARS_IN_LINE = BYTES_PER_LINE * 3 - 1;
45 const unsigned int GAP_ADR_HEX      = 10;
46 const unsigned int GAP_HEX_ASCII    = 10;
47 const unsigned int GAP_ASCII_SLIDER = 5;
48
49 DataStorageArray::DataStorageArray(const QByteArray &arr)
50 {
51         data_ = arr;
52 }
53
54 QByteArray DataStorageArray::getData(size_t position, size_t length)
55 {
56         return data_.mid(position, length);
57 }
58
59 size_t DataStorageArray::size()
60 {
61         return data_.count();
62 }
63
64
65
66 QHexView::QHexView(QWidget *parent):
67         QAbstractScrollArea(parent),
68         pdata_(nullptr)
69 {
70         setFont(QFont("Courier", 10));
71
72         charWidth_ = fontMetrics().boundingRect('X').width();
73         charHeight_ = fontMetrics().height();
74
75         // Determine X coordinates of the three sub-areas
76         posAddr_  = 0;
77         posHex_   = 10 * charWidth_ + GAP_ADR_HEX;
78         posAscii_ = posHex_ + HEXCHARS_IN_LINE * charWidth_ + GAP_HEX_ASCII;
79
80         setFocusPolicy(Qt::StrongFocus);
81 }
82
83 QHexView::~QHexView()
84 {
85         if (pdata_)
86                 delete pdata_;
87 }
88
89 void QHexView::setData(DataStorage *pData)
90 {
91         verticalScrollBar()->setValue(0);
92
93         if (pdata_)
94                 delete pdata_;
95
96         pdata_ = pData;
97         cursorPos_ = 0;
98         resetSelection(0);
99 }
100
101 void QHexView::showFromOffset(size_t offset)
102 {
103         if (pdata_ && (offset < pdata_->size())) {
104                 setCursorPos(offset * 2);
105
106                 int cursorY = cursorPos_ / (2 * BYTES_PER_LINE);
107                 verticalScrollBar() -> setValue(cursorY);
108         }
109 }
110
111 void QHexView::clear()
112 {
113         verticalScrollBar()->setValue(0);
114 }
115
116 QSize QHexView::getFullSize() const
117 {
118         if (!pdata_)
119                 return QSize(0, 0);
120
121         size_t width = posAscii_ + (BYTES_PER_LINE * charWidth_) +
122                 GAP_ASCII_SLIDER + verticalScrollBar()->width();
123
124         size_t height = pdata_->size() / BYTES_PER_LINE;
125
126         if (pdata_->size() % BYTES_PER_LINE)
127                 height++;
128
129         height *= charHeight_;
130
131         return QSize(width, height);
132 }
133
134 void QHexView::paintEvent(QPaintEvent *event)
135 {
136         if (!pdata_)
137                 return;
138
139         QPainter painter(viewport());
140
141         // Calculate and update the widget and paint area sizes
142         QSize areaSize = viewport()->size();
143         QSize widgetSize = getFullSize();
144         setMinimumWidth(widgetSize.width());
145         setMaximumWidth(widgetSize.width());
146
147         verticalScrollBar()->setPageStep(areaSize.height() / charHeight_);
148         verticalScrollBar()->setRange(0, (widgetSize.height() - areaSize.height()) / charHeight_ + 1);
149
150         size_t firstLineIdx = verticalScrollBar()->value();
151
152         size_t lastLineIdx = firstLineIdx + areaSize.height() / charHeight_;
153         if (lastLineIdx > (pdata_->size() / BYTES_PER_LINE)) {
154                 lastLineIdx = pdata_->size() / BYTES_PER_LINE;
155                 if (pdata_->size() % BYTES_PER_LINE)
156                         lastLineIdx++;
157         }
158
159         // Fill widget and address area backgrounds
160         painter.fillRect(event->rect(), palette().color(QPalette::Base));
161         painter.fillRect(QRect(posAddr_, event->rect().top(),
162                 posHex_ - (GAP_ADR_HEX / 2), height()), palette().color(QPalette::Window));
163
164         // Paint divider line between hex and ASCII areas
165         int line_x = posAscii_ - (GAP_HEX_ASCII / 2);
166         painter.setPen(palette().color(QPalette::Midlight));
167         painter.drawLine(line_x, event->rect().top(), line_x, height());
168
169         // Paint address area
170         painter.setPen(palette().color(QPalette::ButtonText));
171
172         int yStart = charHeight_;
173         for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) {
174
175                 QString address = QString("%1").arg(lineIdx * 16, 10, 16, QChar('0')).toUpper();
176                 painter.drawText(posAddr_, y, address);
177                 y += charHeight_;
178         }
179
180         // Paint hex values
181         QBrush regular = painter.brush();
182         QBrush selected = QBrush(palette().color(QPalette::Highlight));
183         QByteArray data = pdata_->getData(firstLineIdx * BYTES_PER_LINE, (lastLineIdx - firstLineIdx) * BYTES_PER_LINE);
184
185         yStart = charHeight_;
186         for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) {
187
188                 painter.setBackgroundMode(Qt::OpaqueMode);
189
190                 int x = posHex_;
191                 for (size_t i = 0; i < BYTES_PER_LINE && ((lineIdx - firstLineIdx) * BYTES_PER_LINE + i) < (size_t)data.size(); i++) {
192                         size_t pos = (lineIdx * BYTES_PER_LINE + i) * 2;
193
194                         if ((pos >= selectBegin_) && (pos < selectEnd_)) {
195                                 painter.setBackground(selected);
196                                 painter.setPen(palette().color(QPalette::HighlightedText));
197                         } else {
198                                 painter.setBackground(regular);
199                                 painter.setPen(palette().color(QPalette::Text));
200                         }
201
202                         // First nibble
203                         QString val = QString::number((data.at((lineIdx - firstLineIdx) * BYTES_PER_LINE + i) & 0xF0) >> 4, 16).toUpper();
204                         painter.drawText(x, y, val);
205
206                         // Second nibble
207                         val = QString::number((data.at((lineIdx - firstLineIdx) * BYTES_PER_LINE + i) & 0xF), 16).toUpper();
208                         painter.drawText(x + charWidth_, y, val);
209
210                         x += 3 * charWidth_;
211                 }
212
213                 y += charHeight_;
214         }
215
216         // Paint ASCII characters
217         yStart = charHeight_;
218         for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) {
219
220                 int x = posAscii_;
221                 for (size_t i = 0; ((lineIdx - firstLineIdx) * BYTES_PER_LINE + i) < (size_t)data.size() && (i < BYTES_PER_LINE); i++) {
222                         char ch = data[(unsigned int)((lineIdx - firstLineIdx) * BYTES_PER_LINE + i)];
223
224                         if ((ch < 0x20) || (ch > 0x7E))
225                                 ch = '.';
226
227                         size_t pos = (lineIdx * BYTES_PER_LINE + i) * 2;
228                         if ((pos >= selectBegin_) && (pos < selectEnd_)) {
229                                 painter.setBackground(selected);
230                                 painter.setPen(palette().color(QPalette::HighlightedText));
231                         } else {
232                                 painter.setBackground(regular);
233                                 painter.setPen(palette().color(QPalette::Text));
234                         }
235
236                         painter.drawText(x, y, QString(ch));
237                         x += charWidth_;
238                 }
239
240                 y += charHeight_;
241         }
242
243         // Paint cursor
244         if (hasFocus()) {
245                 int x = (cursorPos_ % (2 * BYTES_PER_LINE));
246                 int y = cursorPos_ / (2 * BYTES_PER_LINE);
247                 y -= firstLineIdx;
248                 int cursorX = (((x / 2) * 3) + (x % 2)) * charWidth_ + posHex_;
249                 int cursorY = y * charHeight_ + 4;
250                 painter.fillRect(cursorX, cursorY, 2, charHeight_, palette().color(QPalette::WindowText));
251         }
252 }
253
254 void QHexView::keyPressEvent(QKeyEvent *event)
255 {
256         bool setVisible = false;
257
258         // Cursor movements
259         if (event->matches(QKeySequence::MoveToNextChar)) {
260                 setCursorPos(cursorPos_ + 1);
261                 resetSelection(cursorPos_);
262                 setVisible = true;
263         }
264         if (event->matches(QKeySequence::MoveToPreviousChar)) {
265                 setCursorPos(cursorPos_ - 1);
266                 resetSelection(cursorPos_);
267                 setVisible = true;
268         }
269
270         if (event->matches(QKeySequence::MoveToEndOfLine)) {
271                 setCursorPos(cursorPos_ | ((BYTES_PER_LINE * 2) - 1));
272                 resetSelection(cursorPos_);
273                 setVisible = true;
274         }
275         if (event->matches(QKeySequence::MoveToStartOfLine)) {
276                 setCursorPos(cursorPos_ | (cursorPos_ % (BYTES_PER_LINE * 2)));
277                 resetSelection(cursorPos_);
278                 setVisible = true;
279         }
280         if (event->matches(QKeySequence::MoveToPreviousLine)) {
281                 setCursorPos(cursorPos_ - BYTES_PER_LINE * 2);
282                 resetSelection(cursorPos_);
283                 setVisible = true;
284         }
285         if (event->matches(QKeySequence::MoveToNextLine)) {
286                 setCursorPos(cursorPos_ + BYTES_PER_LINE * 2);
287                 resetSelection(cursorPos_);
288                 setVisible = true;
289         }
290
291         if (event->matches(QKeySequence::MoveToNextPage)) {
292                 setCursorPos(cursorPos_ + (viewport()->height() / charHeight_ - 1) * 2 * BYTES_PER_LINE);
293                 resetSelection(cursorPos_);
294                 setVisible = true;
295         }
296         if (event->matches(QKeySequence::MoveToPreviousPage)) {
297                 setCursorPos(cursorPos_ - (viewport()->height() / charHeight_ - 1) * 2 * BYTES_PER_LINE);
298                 resetSelection(cursorPos_);
299                 setVisible = true;
300         }
301         if (event->matches(QKeySequence::MoveToEndOfDocument)) {
302                 if (pdata_)
303                         setCursorPos(pdata_->size() * 2);
304                 resetSelection(cursorPos_);
305                 setVisible = true;
306         }
307         if (event->matches(QKeySequence::MoveToStartOfDocument)) {
308                 setCursorPos(0);
309                 resetSelection(cursorPos_);
310                 setVisible = true;
311         }
312
313         // Select commands
314         if (event->matches(QKeySequence::SelectAll)) {
315                 resetSelection(0);
316                 if (pdata_)
317                         setSelection(2 * pdata_->size() + 1);
318                 setVisible = true;
319         }
320         if (event->matches(QKeySequence::SelectNextChar)) {
321                 int pos = cursorPos_ + 1;
322                 setCursorPos(pos);
323                 setSelection(pos);
324                 setVisible = true;
325         }
326         if (event->matches(QKeySequence::SelectPreviousChar)) {
327                 int pos = cursorPos_ - 1;
328                 setSelection(pos);
329                 setCursorPos(pos);
330                 setVisible = true;
331         }
332         if (event->matches(QKeySequence::SelectEndOfLine)) {
333                 int pos = cursorPos_ - (cursorPos_ % (2 * BYTES_PER_LINE)) + (2 * BYTES_PER_LINE);
334                 setCursorPos(pos);
335                 setSelection(pos);
336                 setVisible = true;
337         }
338         if (event->matches(QKeySequence::SelectStartOfLine)) {
339                 int pos = cursorPos_ - (cursorPos_ % (2 * BYTES_PER_LINE));
340                 setCursorPos(pos);
341                 setSelection(pos);
342                 setVisible = true;
343         }
344         if (event->matches(QKeySequence::SelectPreviousLine)) {
345                 int pos = cursorPos_ - (2 * BYTES_PER_LINE);
346                 setCursorPos(pos);
347                 setSelection(pos);
348                 setVisible = true;
349         }
350         if (event->matches(QKeySequence::SelectNextLine)) {
351                 int pos = cursorPos_ + (2 * BYTES_PER_LINE);
352                 setCursorPos(pos);
353                 setSelection(pos);
354                 setVisible = true;
355         }
356
357         if (event->matches(QKeySequence::SelectNextPage)) {
358                 int pos = cursorPos_ + (((viewport()->height() / charHeight_) - 1) * 2 * BYTES_PER_LINE);
359                 setCursorPos(pos);
360                 setSelection(pos);
361                 setVisible = true;
362         }
363         if (event->matches(QKeySequence::SelectPreviousPage)) {
364                 int pos = cursorPos_ - (((viewport()->height() / charHeight_) - 1) * 2 * BYTES_PER_LINE);
365                 setCursorPos(pos);
366                 setSelection(pos);
367                 setVisible = true;
368         }
369         if (event->matches(QKeySequence::SelectEndOfDocument)) {
370                 int pos = 0;
371                 if (pdata_)
372                         pos = pdata_->size() * 2;
373                 setCursorPos(pos);
374                 setSelection(pos);
375                 setVisible = true;
376         }
377         if (event->matches(QKeySequence::SelectStartOfDocument)) {
378                 int pos = 0;
379                 setCursorPos(pos);
380                 setSelection(pos);
381                 setVisible = true;
382         }
383
384         if (event->matches(QKeySequence::Copy) && (pdata_)) {
385                 QString text;
386                 int idx = 0;
387                 int copyOffset = 0;
388
389                 QByteArray data = pdata_->getData(selectBegin_ / 2, (selectEnd_ - selectBegin_) / 2 + 1);
390                 if (selectBegin_ % 2) {
391                         text += QString::number((data.at((idx+1) / 2) & 0xF), 16);
392                         text += " ";
393                         idx++;
394                         copyOffset = 1;
395                 }
396
397                 int selectedSize = selectEnd_ - selectBegin_;
398                 while (idx < selectedSize) {
399                         QString val = QString::number((data.at((copyOffset + idx) / 2) & 0xF0) >> 4, 16);
400                         if ((idx + 1) < selectedSize) {
401                                 val += QString::number((data.at((copyOffset + idx) / 2) & 0xF), 16);
402                                 val += " ";
403                         }
404                         text += val;
405
406                         if ((idx/2) % BYTES_PER_LINE == (BYTES_PER_LINE - 1))
407                                 text += "\n";
408
409                         idx += 2;
410                 }
411
412                 QClipboard *clipboard = QApplication::clipboard();
413                 clipboard->setText(text);
414                 if (clipboard->supportsSelection())
415                         clipboard->setText(text);
416         }
417
418         if (setVisible)
419                 ensureVisible();
420
421         viewport()->update();
422 }
423
424 void QHexView::mouseMoveEvent(QMouseEvent *event)
425 {
426         int actPos = cursorPosFromMousePos(event->pos());
427         setCursorPos(actPos);
428         setSelection(actPos);
429
430         viewport()->update();
431 }
432
433 void QHexView::mousePressEvent(QMouseEvent *event)
434 {
435         int cPos = cursorPosFromMousePos(event->pos());
436
437         if ((QApplication::keyboardModifiers() & Qt::ShiftModifier) && (event->button() == Qt::LeftButton))
438                 setSelection(cPos);
439         else
440                 resetSelection(cPos);
441
442         setCursorPos(cPos);
443
444         viewport()->update();
445 }
446
447 size_t QHexView::cursorPosFromMousePos(const QPoint &position)
448 {
449         int pos = -1;
450
451         if (((size_t)position.x() >= posHex_) &&
452                 ((size_t)position.x() < (posHex_ + HEXCHARS_IN_LINE * charWidth_))) {
453
454                 // Note: We add 1.5 character widths so that selection across
455                 // byte gaps is smoother
456                 int x = (position.x() + (1.5 * charWidth_ / 2) - posHex_) / charWidth_;
457
458                 // Note: We allow only full bytes to be selected, not nibbles,
459                 // so we round to the nearest byte gap
460                 x = (2 * x + 1) / 3;
461
462                 int firstLineIdx = verticalScrollBar()->value();
463                 int y = (position.y() / charHeight_) * 2 * BYTES_PER_LINE;
464                 pos = x + y + firstLineIdx * BYTES_PER_LINE * 2;
465         }
466
467         return pos;
468 }
469
470 void QHexView::resetSelection()
471 {
472         selectBegin_ = selectInit_;
473         selectEnd_ = selectInit_;
474 }
475
476 void QHexView::resetSelection(int pos)
477 {
478         if (pos < 0)
479                 pos = 0;
480
481         selectInit_ = pos;
482         selectBegin_ = pos;
483         selectEnd_ = pos;
484 }
485
486 void QHexView::setSelection(int pos)
487 {
488         if (pos < 0)
489                 pos = 0;
490
491         if ((size_t)pos >= selectInit_) {
492                 selectEnd_ = pos;
493                 selectBegin_ = selectInit_;
494         } else {
495                 selectBegin_ = pos;
496                 selectEnd_ = selectInit_;
497         }
498 }
499
500 void QHexView::setCursorPos(int position)
501 {
502         if (position < 0)
503                 position = 0;
504
505         int maxPos = 0;
506         if (pdata_) {
507                 maxPos = pdata_->size() * 2;
508                 if (pdata_->size() % BYTES_PER_LINE)
509                         maxPos++;
510         }
511
512         if (position > maxPos)
513                 position = maxPos;
514
515         cursorPos_ = position;
516 }
517
518 void QHexView::ensureVisible()
519 {
520         QSize areaSize = viewport()->size();
521
522         int firstLineIdx = verticalScrollBar() -> value();
523         int lastLineIdx = firstLineIdx + areaSize.height() / charHeight_;
524
525         int cursorY = cursorPos_ / (2 * BYTES_PER_LINE);
526
527         if (cursorY < firstLineIdx)
528                 verticalScrollBar() -> setValue(cursorY);
529         else if(cursorY >= lastLineIdx)
530                 verticalScrollBar() -> setValue(cursorY - areaSize.height() / charHeight_ + 1);
531 }