]> sigrok.org Git - pulseview.git/blob - pv/views/decoder_binary/QHexView.cpp
Segment: Include <memory> so we don't get error at compile time
[pulseview.git] / pv / views / decoder_binary / 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 <limits>
31
32 #include <QApplication>
33 #include <QClipboard>
34 #include <QDebug>
35 #include <QFont>
36 #include <QKeyEvent>
37 #include <QScrollBar>
38 #include <QSize>
39 #include <QString>
40 #include <QPainter>
41 #include <QPaintEvent>
42
43 #include "QHexView.hpp"
44
45 using std::make_pair;
46
47 const unsigned int BYTES_PER_LINE   = 16;
48 const unsigned int HEXCHARS_IN_LINE = BYTES_PER_LINE * 3 - 1;
49 const unsigned int GAP_ADR_HEX      = 10;
50 const unsigned int GAP_HEX_ASCII    = 10;
51 const unsigned int GAP_ASCII_SLIDER = 5;
52
53
54 QHexView::QHexView(QWidget *parent):
55         QAbstractScrollArea(parent),
56         mode_(ChunkedDataMode),
57         data_(nullptr),
58         selectBegin_(0),
59         selectEnd_(0),
60         cursorPos_(0),
61         visible_range_(0, 0),
62         highlighted_sample_(std::numeric_limits<uint64_t>::max())
63 {
64         setFont(QFont("Courier", 10));
65
66         charWidth_ = fontMetrics().boundingRect('X').width();
67         charHeight_ = fontMetrics().height();
68
69         setFocusPolicy(Qt::StrongFocus);
70
71         if (palette().color(QPalette::ButtonText).toHsv().value() > 127) {
72                 // Color is bright
73                 chunk_colors_.emplace_back(100, 149, 237); // QColorConstants::Svg::cornflowerblue
74                 chunk_colors_.emplace_back(60, 179, 113);  // QColorConstants::Svg::mediumseagreen
75                 chunk_colors_.emplace_back(210, 180, 140); // QColorConstants::Svg::tan
76                 visible_range_color_ = QColor("#fff5ee");  // QColorConstants::Svg::seashell
77         } else {
78                 // Color is dark
79                 chunk_colors_.emplace_back(0, 0, 139);    // QColorConstants::Svg::darkblue
80                 chunk_colors_.emplace_back(34, 139, 34);  // QColorConstants::Svg::forestgreen
81                 chunk_colors_.emplace_back(160, 82, 45);  // QColorConstants::Svg::sienna
82                 visible_range_color_ = QColor("#fff5ee"); // QColorConstants::Svg::seashell
83         }
84 }
85
86 void QHexView::set_mode(Mode m)
87 {
88         mode_ = m;
89
90         // This is not expected to be set when data is showing,
91         // so we don't update the viewport here
92 }
93
94 void QHexView::set_data(const DecodeBinaryClass* data)
95 {
96         data_ = data;
97
98         size_t size = 0;
99         if (data) {
100                 size_t chunks = data_->chunks.size();
101                 for (size_t i = 0; i < chunks; i++)
102                         size += data_->chunks[i].data.size();
103         }
104         data_size_ = size;
105
106         address_digits_ = (uint8_t)QString::number(data_size_, 16).length();
107
108         // Calculate X coordinates of the three sub-areas
109         posAddr_  = 0;
110         posHex_   = address_digits_ * charWidth_ + GAP_ADR_HEX;
111         posAscii_ = posHex_ + HEXCHARS_IN_LINE * charWidth_ + GAP_HEX_ASCII;
112
113         viewport()->update();
114 }
115
116 void QHexView::set_visible_sample_range(uint64_t start, uint64_t end)
117 {
118         visible_range_ = make_pair(start, end);
119
120         viewport()->update();
121 }
122
123 void QHexView::set_highlighted_data_sample(uint64_t sample)
124 {
125         highlighted_sample_ = sample;
126
127         viewport()->update();
128 }
129
130 unsigned int QHexView::get_bytes_per_line() const
131 {
132         return BYTES_PER_LINE;
133 }
134
135 void QHexView::clear()
136 {
137         verticalScrollBar()->setValue(0);
138         data_ = nullptr;
139         data_size_ = 0;
140
141         highlighted_sample_ = std::numeric_limits<uint64_t>::max();
142
143         viewport()->update();
144 }
145
146 void QHexView::showFromOffset(size_t offset)
147 {
148         if (data_ && (offset < data_size_)) {
149                 setCursorPos(offset * 2);
150
151                 int cursorY = cursorPos_ / (2 * BYTES_PER_LINE);
152                 verticalScrollBar() -> setValue(cursorY);
153         }
154
155         viewport()->update();
156 }
157
158 QSizePolicy QHexView::sizePolicy() const
159 {
160         return QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
161 }
162
163 pair<size_t, size_t> QHexView::get_selection() const
164 {
165         size_t start = selectBegin_ / 2;
166         size_t end = selectEnd_ / 2;
167
168         if (start == end) {
169                 // Nothing is currently selected
170                 start = 0;
171                 end = data_size_;
172         } if (end < data_size_)
173                 end++;
174
175         return std::make_pair(start, end);
176 }
177
178 size_t QHexView::create_hex_line(size_t start, size_t end, QString* dest,
179         bool with_offset, bool with_ascii)
180 {
181         dest->clear();
182
183         // Determine start address for the row
184         uint64_t row = start / BYTES_PER_LINE;
185         uint64_t offset = row * BYTES_PER_LINE;
186         end = std::min((uint64_t)end, offset + BYTES_PER_LINE);
187
188         if (with_offset)
189                 dest->append(QString("%1 ").arg(row * BYTES_PER_LINE, address_digits_, 16, QChar('0')).toUpper());
190
191         initialize_byte_iterator(offset);
192         for (size_t i = offset; i < offset + BYTES_PER_LINE; i++) {
193                 uint8_t value = 0;
194
195                 if (i < end)
196                         value = get_next_byte();
197
198                 if ((i < start) || (i >= end))
199                         dest->append("   ");
200                 else
201                         dest->append(QString("%1 ").arg(value, 2, 16, QChar('0')).toUpper());
202         }
203
204         if (with_ascii) {
205                 initialize_byte_iterator(offset);
206                 for (size_t i = offset; i < end; i++) {
207                         uint8_t value = get_next_byte();
208
209                         if ((value < 0x20) || (value > 0x7E))
210                                 value = '.';
211
212                         if (i < start)
213                                 dest->append(' ');
214                         else
215                                 dest->append((char)value);
216                 }
217         }
218
219         return end;
220 }
221
222 void QHexView::initialize_byte_iterator(size_t offset)
223 {
224         current_chunk_id_ = 0;
225         current_chunk_offset_ = 0;
226         current_offset_ = offset;
227
228         size_t chunks = data_->chunks.size();
229         for (size_t i = 0; i < chunks; i++) {
230                 size_t size = data_->chunks[i].data.size();
231
232                 if (offset >= size) {
233                         current_chunk_id_++;
234                         offset -= size;
235                 } else {
236                         current_chunk_offset_ = offset;
237                         break;
238                 }
239         }
240
241         if (current_chunk_id_ < data_->chunks.size())
242                 current_chunk_ = data_->chunks[current_chunk_id_];
243
244         current_chunk_sample_ = current_chunk_.sample;
245
246         // Obtain sample of next chunk if there is one
247         if ((current_chunk_id_ + 1) < data_->chunks.size())
248                 next_chunk_sample_ = data_->chunks[current_chunk_id_ + 1].sample;
249         else
250                 next_chunk_sample_ = std::numeric_limits<uint64_t>::max();
251 }
252
253 uint8_t QHexView::get_next_byte(bool* is_new_chunk)
254 {
255         if (is_new_chunk != nullptr)
256                 *is_new_chunk = (current_chunk_offset_ == 0);
257
258         uint8_t v = 0;
259         if (current_chunk_offset_ < current_chunk_.data.size())
260                 v = current_chunk_.data[current_chunk_offset_];
261
262         current_chunk_sample_ = current_chunk_.sample;
263
264         if (is_new_chunk) {
265                 // Obtain sample of next chunk if there is one
266                 if ((current_chunk_id_ + 1) < data_->chunks.size())
267                         next_chunk_sample_ = data_->chunks[current_chunk_id_ + 1].sample;
268                 else
269                         next_chunk_sample_ = std::numeric_limits<uint64_t>::max();
270         }
271
272         current_offset_++;
273         current_chunk_offset_++;
274
275         if (current_offset_ > data_size_) {
276                 qWarning() << "QHexView::get_next_byte() overran binary data boundary:" <<
277                         current_offset_ << "of" << data_size_ << "bytes";
278                 return 0xEE;
279         }
280
281         if ((current_chunk_offset_ == current_chunk_.data.size()) && (current_offset_ < data_size_)) {
282                 current_chunk_id_++;
283                 current_chunk_offset_ = 0;
284                 current_chunk_ = data_->chunks[current_chunk_id_];
285         }
286
287         return v;
288 }
289
290 QSize QHexView::getFullSize() const
291 {
292         size_t width = posAscii_ + (BYTES_PER_LINE * charWidth_);
293
294         if (verticalScrollBar()->isEnabled())
295                 width += GAP_ASCII_SLIDER + verticalScrollBar()->width();
296
297         if (!data_ || (data_size_ == 0))
298                 return QSize(width, 0);
299
300         size_t height = data_size_ / BYTES_PER_LINE;
301
302         if (data_size_ % BYTES_PER_LINE)
303                 height++;
304
305         height *= charHeight_;
306
307         return QSize(width, height);
308 }
309
310 void QHexView::paintEvent(QPaintEvent *event)
311 {
312         QPainter painter(viewport());
313
314         QFont normal_font = painter.font();
315         QFont bold_font = painter.font();
316         bold_font.setWeight(QFont::Bold);
317
318         bool bold_font_was_used = false;
319
320         // Calculate and update the widget and paint area sizes
321         QSize widgetSize = getFullSize();
322         setMinimumWidth(widgetSize.width());
323         setMaximumWidth(widgetSize.width());
324         QSize areaSize = viewport()->size() - QSize(0, charHeight_);
325
326         // Only show scrollbar if the content goes beyond the visible area
327         if (widgetSize.height() > areaSize.height()) {
328                 verticalScrollBar()->setEnabled(true);
329                 verticalScrollBar()->setPageStep(areaSize.height() / charHeight_);
330                 verticalScrollBar()->setRange(0, ((widgetSize.height() - areaSize.height())) / charHeight_ + 1);
331         } else
332                 verticalScrollBar()->setEnabled(false);
333
334         // Fill widget background
335         painter.fillRect(event->rect(), palette().color(QPalette::Base));
336
337         if (!data_ || (data_size_ == 0) || (data_->chunks.empty())) {
338                 painter.setPen(palette().color(QPalette::Text));
339                 QString s = tr("No data available");
340                 int x = (areaSize.width() - fontMetrics().boundingRect(s).width()) / 2;
341                 int y = areaSize.height() / 2;
342                 painter.drawText(x, y, s);
343                 return;
344         }
345
346         // Determine first/last line indices
347         size_t firstLineIdx = verticalScrollBar()->value();
348
349         size_t lastLineIdx = firstLineIdx + (areaSize.height() / charHeight_);
350         if (lastLineIdx > (data_size_ / BYTES_PER_LINE)) {
351                 lastLineIdx = data_size_ / BYTES_PER_LINE;
352                 if (data_size_ % BYTES_PER_LINE)
353                         lastLineIdx++;
354         }
355
356         // Paint divider line between hex and ASCII areas
357         int line_x = posAscii_ - (GAP_HEX_ASCII / 2);
358         painter.setPen(palette().color(QPalette::Midlight));
359         painter.drawLine(line_x, event->rect().top(), line_x, height());
360
361         // Fill address area background
362         painter.fillRect(QRect(posAddr_, event->rect().top(),
363                 posHex_ - (GAP_ADR_HEX / 2), height()), palette().color(QPalette::Window));
364         painter.fillRect(QRect(posAddr_, event->rect().top(),
365                 posAscii_ - (GAP_HEX_ASCII / 2), charHeight_ + 2), palette().color(QPalette::Window));
366
367         // Paint address area
368         painter.setPen(palette().color(QPalette::ButtonText));
369
370         int yStart = 2 * charHeight_;
371         for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) {
372
373                 QString address = QString("%1").arg(lineIdx * 16, address_digits_, 16, QChar('0')).toUpper();
374                 painter.drawText(posAddr_, y, address);
375                 y += charHeight_;
376         }
377
378         // Paint top row with hex offsets
379         painter.setPen(palette().color(QPalette::ButtonText));
380         for (int offset = 0; offset <= 0xF; offset++)
381                 painter.drawText(posHex_ + (1 + offset * 3) * charWidth_,
382                         charHeight_ - 3, QString::number(offset, 16).toUpper());
383
384         // Paint hex values
385         QBrush regular_brush = palette().buttonText();
386         QBrush selected_brush = palette().highlight();
387         QBrush visible_range_brush = QBrush(visible_range_color_);
388
389         bool multiple_chunks = (data_->chunks.size() > 1);
390         unsigned int chunk_color = 0;
391
392         initialize_byte_iterator(firstLineIdx * BYTES_PER_LINE);
393
394         yStart = 2 * charHeight_;
395         for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) {
396
397                 int x = posHex_;
398                 for (size_t i = 0; (i < BYTES_PER_LINE) && (current_offset_ < data_size_); i++) {
399                         size_t pos = (lineIdx * BYTES_PER_LINE + i) * 2;
400
401                         // Fetch byte
402                         bool is_new_chunk;
403                         uint8_t byte_value = get_next_byte(&is_new_chunk);
404
405                         if (is_new_chunk) {
406                                 chunk_color++;
407                                 if (chunk_color == chunk_colors_.size())
408                                         chunk_color = 0;
409
410                                 // New chunk means also new chunk sample, so check for required changes
411                                 if (bold_font_was_used)
412                                         painter.setFont(normal_font);
413                                 if ((highlighted_sample_ >= current_chunk_sample_) && (highlighted_sample_ < next_chunk_sample_)) {
414                                         painter.setFont(bold_font);
415                                         bold_font_was_used = true;
416                                 }
417                         }
418
419                         // Restore default paint style
420                         painter.setBackground(regular_brush);
421                         painter.setBackgroundMode(Qt::TransparentMode);
422                         if (!multiple_chunks)
423                                 painter.setPen(palette().color(QPalette::Text));
424                         else
425                                 painter.setPen(chunk_colors_[chunk_color]);
426
427                         // Highlight needed because it's the range visible in main view?
428                         if ((current_chunk_sample_ >= visible_range_.first) && (current_chunk_sample_ < visible_range_.second)) {
429                                 painter.setBackgroundMode(Qt::OpaqueMode);
430                                 painter.setBackground(visible_range_brush);
431                         }
432
433                         // Highlight for selection range needed? (takes priority over visible range highlight)
434                         if ((pos >= selectBegin_) && (pos < selectEnd_)) {
435                                 painter.setBackgroundMode(Qt::OpaqueMode);
436                                 painter.setBackground(selected_brush);
437                                 painter.setPen(palette().color(QPalette::HighlightedText));
438                         }
439
440                         // First nibble
441                         QString val = QString::number((byte_value & 0xF0) >> 4, 16).toUpper();
442                         painter.drawText(x, y, val);
443
444                         // Second nibble
445                         val = QString::number((byte_value & 0xF), 16).toUpper();
446                         painter.drawText(x + charWidth_, y, val);
447
448                         if ((i < BYTES_PER_LINE - 1) && (current_offset_ < data_size_))
449                                 painter.drawText(x + 2 * charWidth_, y, QString(' '));
450
451                         x += 3 * charWidth_;
452                 }
453
454                 y += charHeight_;
455         }
456
457         // Paint ASCII characters
458         initialize_byte_iterator(firstLineIdx * BYTES_PER_LINE);
459         yStart = 2 * charHeight_;
460         for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) {
461
462                 int x = posAscii_;
463                 for (size_t i = 0; (i < BYTES_PER_LINE) && (current_offset_ < data_size_); i++) {
464                         // Fetch byte
465                         bool is_new_chunk;
466                         uint8_t ch = get_next_byte(&is_new_chunk);
467
468                         if (is_new_chunk) {
469                                 // New chunk means also new chunk sample, so check for required changes
470                                 if (bold_font_was_used)
471                                         painter.setFont(normal_font);
472                                 if ((highlighted_sample_ >= current_chunk_sample_) && (highlighted_sample_ < next_chunk_sample_)) {
473                                         painter.setFont(bold_font);
474                                         bold_font_was_used = true;
475                                 }
476                         }
477
478                         if ((ch < 0x20) || (ch > 0x7E))
479                                 ch = '.';
480
481                         // Restore default paint style
482                         painter.setBackgroundMode(Qt::TransparentMode);
483                         painter.setBackground(regular_brush);
484                         painter.setPen(palette().color(QPalette::Text));
485
486                         // Highlight needed because it's the range visible in main view?
487                         if ((current_chunk_sample_ >= visible_range_.first) && (current_chunk_sample_ < visible_range_.second)) {
488                                 painter.setBackgroundMode(Qt::OpaqueMode);
489                                 painter.setBackground(visible_range_brush);
490                         }
491
492                         // Highlight for selection range needed? (takes priority over visible range highlight)
493                         size_t pos = (lineIdx * BYTES_PER_LINE + i) * 2;
494                         if ((pos >= selectBegin_) && (pos < selectEnd_)) {
495                                 painter.setBackgroundMode(Qt::OpaqueMode);
496                                 painter.setBackground(selected_brush);
497                                 painter.setPen(palette().color(QPalette::HighlightedText));
498                         }
499
500                         painter.drawText(x, y, QString(ch));
501                         x += charWidth_;
502                 }
503
504                 y += charHeight_;
505         }
506
507         // Restore painter defaults
508         painter.setBackgroundMode(Qt::TransparentMode);
509         painter.setBackground(regular_brush);
510
511         // Paint cursor
512         if (hasFocus()) {
513                 int x = (cursorPos_ % (2 * BYTES_PER_LINE));
514                 int y = cursorPos_ / (2 * BYTES_PER_LINE);
515                 y -= firstLineIdx;
516                 int cursorX = (((x / 2) * 3) + (x % 2)) * charWidth_ + posHex_;
517                 int cursorY = charHeight_ + y * charHeight_ + 4;
518                 painter.fillRect(cursorX, cursorY, 2, charHeight_, palette().color(QPalette::WindowText));
519         }
520 }
521
522 void QHexView::keyPressEvent(QKeyEvent *event)
523 {
524         bool setVisible = false;
525
526         // Cursor movements
527         if (event->matches(QKeySequence::MoveToNextChar)) {
528                 setCursorPos(cursorPos_ + 1);
529                 resetSelection(cursorPos_);
530                 setVisible = true;
531         }
532         if (event->matches(QKeySequence::MoveToPreviousChar)) {
533                 setCursorPos(cursorPos_ - 1);
534                 resetSelection(cursorPos_);
535                 setVisible = true;
536         }
537
538         if (event->matches(QKeySequence::MoveToEndOfLine)) {
539                 setCursorPos(cursorPos_ | ((BYTES_PER_LINE * 2) - 1));
540                 resetSelection(cursorPos_);
541                 setVisible = true;
542         }
543         if (event->matches(QKeySequence::MoveToStartOfLine)) {
544                 setCursorPos(cursorPos_ | (cursorPos_ % (BYTES_PER_LINE * 2)));
545                 resetSelection(cursorPos_);
546                 setVisible = true;
547         }
548         if (event->matches(QKeySequence::MoveToPreviousLine)) {
549                 setCursorPos(cursorPos_ - BYTES_PER_LINE * 2);
550                 resetSelection(cursorPos_);
551                 setVisible = true;
552         }
553         if (event->matches(QKeySequence::MoveToNextLine)) {
554                 setCursorPos(cursorPos_ + BYTES_PER_LINE * 2);
555                 resetSelection(cursorPos_);
556                 setVisible = true;
557         }
558
559         if (event->matches(QKeySequence::MoveToNextPage)) {
560                 setCursorPos(cursorPos_ + (viewport()->height() / charHeight_ - 1) * 2 * BYTES_PER_LINE);
561                 resetSelection(cursorPos_);
562                 setVisible = true;
563         }
564         if (event->matches(QKeySequence::MoveToPreviousPage)) {
565                 setCursorPos(cursorPos_ - (viewport()->height() / charHeight_ - 1) * 2 * BYTES_PER_LINE);
566                 resetSelection(cursorPos_);
567                 setVisible = true;
568         }
569         if (event->matches(QKeySequence::MoveToEndOfDocument)) {
570                 setCursorPos(data_size_ * 2);
571                 resetSelection(cursorPos_);
572                 setVisible = true;
573         }
574         if (event->matches(QKeySequence::MoveToStartOfDocument)) {
575                 setCursorPos(0);
576                 resetSelection(cursorPos_);
577                 setVisible = true;
578         }
579
580         // Select commands
581         if (event->matches(QKeySequence::SelectAll)) {
582                 resetSelection(0);
583                 setSelection(2 * data_size_);
584                 setVisible = true;
585         }
586         if (event->matches(QKeySequence::SelectNextChar)) {
587                 int pos = cursorPos_ + 1;
588                 setCursorPos(pos);
589                 setSelection(pos);
590                 setVisible = true;
591         }
592         if (event->matches(QKeySequence::SelectPreviousChar)) {
593                 int pos = cursorPos_ - 1;
594                 setSelection(pos);
595                 setCursorPos(pos);
596                 setVisible = true;
597         }
598         if (event->matches(QKeySequence::SelectEndOfLine)) {
599                 int pos = cursorPos_ - (cursorPos_ % (2 * BYTES_PER_LINE)) + (2 * BYTES_PER_LINE);
600                 setCursorPos(pos);
601                 setSelection(pos);
602                 setVisible = true;
603         }
604         if (event->matches(QKeySequence::SelectStartOfLine)) {
605                 int pos = cursorPos_ - (cursorPos_ % (2 * BYTES_PER_LINE));
606                 setCursorPos(pos);
607                 setSelection(pos);
608                 setVisible = true;
609         }
610         if (event->matches(QKeySequence::SelectPreviousLine)) {
611                 int pos = cursorPos_ - (2 * BYTES_PER_LINE);
612                 setCursorPos(pos);
613                 setSelection(pos);
614                 setVisible = true;
615         }
616         if (event->matches(QKeySequence::SelectNextLine)) {
617                 int pos = cursorPos_ + (2 * BYTES_PER_LINE);
618                 setCursorPos(pos);
619                 setSelection(pos);
620                 setVisible = true;
621         }
622
623         if (event->matches(QKeySequence::SelectNextPage)) {
624                 int pos = cursorPos_ + (((viewport()->height() / charHeight_) - 1) * 2 * BYTES_PER_LINE);
625                 setCursorPos(pos);
626                 setSelection(pos);
627                 setVisible = true;
628         }
629         if (event->matches(QKeySequence::SelectPreviousPage)) {
630                 int pos = cursorPos_ - (((viewport()->height() / charHeight_) - 1) * 2 * BYTES_PER_LINE);
631                 setCursorPos(pos);
632                 setSelection(pos);
633                 setVisible = true;
634         }
635         if (event->matches(QKeySequence::SelectEndOfDocument)) {
636                 int pos = data_size_ * 2;
637                 setCursorPos(pos);
638                 setSelection(pos);
639                 setVisible = true;
640         }
641         if (event->matches(QKeySequence::SelectStartOfDocument)) {
642                 setCursorPos(0);
643                 setSelection(0);
644                 setVisible = true;
645         }
646
647         if (event->matches(QKeySequence::Copy) && (data_)) {
648                 QString text;
649
650                 initialize_byte_iterator(selectBegin_ / 2);
651
652                 size_t selectedSize = (selectEnd_ - selectBegin_ + 1) / 2;
653                 for (size_t i = 0; i < selectedSize; i++) {
654                         uint8_t byte_value = get_next_byte();
655
656                         QString s = QString::number((byte_value & 0xF0) >> 4, 16).toUpper() +
657                                 QString::number((byte_value & 0xF), 16).toUpper() + " ";
658                         text += s;
659
660                         if (i % BYTES_PER_LINE == (BYTES_PER_LINE - 1))
661                                 text += "\n";
662                 }
663
664                 QClipboard *clipboard = QApplication::clipboard();
665                 clipboard->setText(text, QClipboard::Clipboard);
666                 if (clipboard->supportsSelection())
667                         clipboard->setText(text, QClipboard::Selection);
668         }
669
670         if (setVisible)
671                 ensureVisible();
672
673         viewport()->update();
674 }
675
676 void QHexView::mouseMoveEvent(QMouseEvent *event)
677 {
678         int actPos = cursorPosFromMousePos(event->pos());
679         setCursorPos(actPos);
680         setSelection(actPos);
681
682         viewport()->update();
683 }
684
685 void QHexView::mousePressEvent(QMouseEvent *event)
686 {
687         int cPos = cursorPosFromMousePos(event->pos());
688
689         if ((QApplication::keyboardModifiers() & Qt::ShiftModifier) && (event->button() == Qt::LeftButton))
690                 setSelection(cPos);
691         else
692                 resetSelection(cPos);
693
694         setCursorPos(cPos);
695
696         viewport()->update();
697 }
698
699 size_t QHexView::cursorPosFromMousePos(const QPoint &position)
700 {
701         size_t pos = -1;
702
703         if (((size_t)position.x() >= posHex_) &&
704                 ((size_t)position.x() < (posHex_ + HEXCHARS_IN_LINE * charWidth_))) {
705
706                 // Note: We add 1.5 character widths so that selection across
707                 // byte gaps is smoother
708                 size_t x = (position.x() + (1.5 * charWidth_ / 2) - posHex_) / charWidth_;
709
710                 // Note: We allow only full bytes to be selected, not nibbles,
711                 // so we round to the nearest byte gap
712                 x = (2 * x + 1) / 3;
713
714                 size_t firstLineIdx = verticalScrollBar()->value();
715                 size_t y = ((position.y() / charHeight_) - 1) * 2 * BYTES_PER_LINE;
716                 pos = x + y + firstLineIdx * BYTES_PER_LINE * 2;
717         }
718
719         size_t max_pos = data_size_ * 2;
720
721         return std::min(pos, max_pos);
722 }
723
724 void QHexView::resetSelection()
725 {
726         selectBegin_ = selectInit_;
727         selectEnd_ = selectInit_;
728 }
729
730 void QHexView::resetSelection(int pos)
731 {
732         if (pos < 0)
733                 pos = 0;
734
735         selectInit_ = pos;
736         selectBegin_ = pos;
737         selectEnd_ = pos;
738 }
739
740 void QHexView::setSelection(int pos)
741 {
742         if (pos < 0)
743                 pos = 0;
744
745         if ((size_t)pos >= selectInit_) {
746                 selectEnd_ = pos;
747                 selectBegin_ = selectInit_;
748         } else {
749                 selectBegin_ = pos;
750                 selectEnd_ = selectInit_;
751         }
752 }
753
754 void QHexView::setCursorPos(int position)
755 {
756         if (position < 0)
757                 position = 0;
758
759         int max_pos = data_size_ * 2;
760
761         if (position > max_pos)
762                 position = max_pos;
763
764         cursorPos_ = position;
765 }
766
767 void QHexView::ensureVisible()
768 {
769         QSize areaSize = viewport()->size();
770
771         int firstLineIdx = verticalScrollBar()->value();
772         int lastLineIdx = firstLineIdx + areaSize.height() / charHeight_;
773
774         int cursorY = cursorPos_ / (2 * BYTES_PER_LINE);
775
776         if (cursorY < firstLineIdx)
777                 verticalScrollBar()->setValue(cursorY);
778         else
779                 if(cursorY >= lastLineIdx)
780                         verticalScrollBar()->setValue(cursorY - areaSize.height() / charHeight_ + 1);
781 }