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