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