OSDN Git Service

fix comment at hexview.cpp
[pheasant/hexedit.git] / src / control / standard / hexview.cpp
1
2 #include <QtGui>
3 #include <algorithm>
4 #include <vector>
5 #include "hexview.h"
6 #include "scursor.h"
7 #include "../document.h"
8 #include "../highlight.h"
9
10 using namespace std;
11
12 namespace Standard {
13
14 ////////////////////////////////////////
15 // Config
16 HexConfig::HexConfig()
17         : Margin(2, 2, 3, 3)
18         , ByteMargin(3, 0, 2, 0)
19         , Font("Monaco", 17)
20         , EnableCaret(true)
21         , CaretBlinkTime(500)
22         , FontMetrics(Font)
23 {
24         // Coloring
25         Colors[Color::Background] = QColor(0xEF,0xEF,0xEF);
26         Colors[Color::Text] = QColor(0,0,0);
27         Colors[Color::SelBackground] = QColor(0xA0,0xA0,0xFF);
28         Colors[Color::SelText] = QColor(0,0,0);
29         Colors[Color::CaretBackground] = QColor(0xFF, 0, 0);
30         Colors[Color::CaretText] = QColor(0xFF,0xFF,0xFF);
31
32         // Font
33         Font.setFixedPitch(true);
34
35         update();
36 }
37
38 void HexConfig::update()
39 {
40         // TODO: set ByteMargin value(left=charWidth/2, right=charWidth/2)
41
42         // Pos
43         x_begin[0] = Margin.left() + ByteMargin.left();
44         for (int i = 1; i < Num; i++) {
45                 x_begin[i] = x_begin[i-1] + byteWidth();
46         }
47
48         // Pos of end
49         for (int i = 0; i < Num; i++) {
50                 x_end[i] = x_begin[i] + charWidth(2) + ByteMargin.right();
51         }
52
53         // Area
54         x_area[0] = Margin.left() + ByteMargin.left();
55         for (int i = 1; i < Num; i++) {
56                 x_area[i] = x_area[i-1] + byteWidth();
57         }
58         x_area[Num] = x_area[Num-1] + byteWidth();
59 }
60
61 int HexConfig::drawableLines(int height) const
62 {
63         const int y = top() + byteMargin().top();
64         return (height - y + byteHeight()) / byteHeight();
65 }
66
67 int HexConfig::XToPos(int x) const
68 {
69         if (x < Margin.left()) {
70                 return -1;
71         }
72
73         return (int)distance(x_area, lower_bound(x_area, x_area + Num + 1, x)) - 1;
74 }
75
76 int HexConfig::YToLine(int y) const
77 {
78         if (y < top()) {
79                 return -1;
80         }
81         return (y - top()) / byteHeight();
82 }
83
84 ////////////////////////////////////////
85 // View
86
87 HexView::HexView(QWidget *parent, Document *doc, Highlight *hi)
88         : ::View(parent, doc, hi)
89         , cursor_(new Cursor(doc, this))
90 {
91         // Enable keyboard input
92         setFocusPolicy(Qt::WheelFocus);
93 }
94
95 void HexView::resizeEvent(QResizeEvent *rs)
96 {
97         QSize size(qMin(rs->size().width(), config_.maxWidth()), rs->size().height());
98         QResizeEvent resize(size, rs->oldSize());
99         View::resizeEvent(&resize);
100         pix_.fill(config_.Colors[Color::Background]);
101         drawView();
102 }
103
104 void HexView::drawView(DrawMode mode, int line_start, int end)
105 {
106         //qDebug("refresh event mode:%d line:%d end:%d", mode, line_start, end);
107         //qDebug(" pos:%llu, anchor:%llu", cursor_->Position, cursor_->PositionAnchor);
108
109         // FIXME: refactoring refresh event
110         QPainter painter;
111         painter.begin(&pix_);
112         painter.setFont(config_.Font);
113
114         if (!document_->length()) {
115                 // TODO: draw Empty Background only
116                 QBrush brush(config_.Colors[Color::Background]);
117                 painter.fillRect(0, 0, width(), height(), brush);
118                 painter.end();
119                 // Update screen buffer
120                 update(0, 0, width(), height());
121                 return;
122         }
123
124         Q_ASSERT(0 <= line_start);
125         Q_ASSERT(static_cast<uint>(line_start) <= document_->length() / HexConfig::Num + 1);
126         Q_ASSERT(0 <= end);
127         Q_ASSERT(static_cast<uint>(end) <= document_->length() / HexConfig::Num + 1);
128
129         // Get draw range
130         int y_top = config_.top();
131         int y = config_.top() + config_.byteMargin().top();
132         int count_draw_line, max_y;
133
134         // Get minumum drawing area
135         switch (mode) {
136         case DRAW_ALL:
137                 count_draw_line = config_.drawableLines(height());
138                 break;
139         case DRAW_LINE:
140                 y_top += config_.byteHeight() * line_start;
141                 y     += config_.byteHeight() * line_start;
142                 count_draw_line = 1;
143                 break;
144         case DRAW_AFTER:
145                 y_top += config_.byteHeight() * line_start;
146                 y     += config_.byteHeight() * line_start;
147                 max_y = qMax(y + config_.byteHeight(), height());
148                 count_draw_line = config_.drawableLines(max_y - y);
149                 break;
150         case DRAW_RANGE:
151                 y_top += config_.byteHeight() * line_start;
152                 y     += config_.byteHeight() * line_start;
153                 max_y = qMin(y + config_.byteHeight() * end, height());
154                 count_draw_line = config_.drawableLines(max_y - y);
155                 break;
156         }
157
158         // Get top position of view
159         const quint64 top = (cursor_->Top + line_start) * HexConfig::Num;
160         const uint size = qMin(document_->length() - top, (quint64)HexConfig::Num * count_draw_line);
161         if (size == 0) {
162                 return;
163         }
164
165         // Draw empty area(after end line)
166         if (mode == DRAW_ALL || mode == DRAW_AFTER) {
167                 //qDebug("draw empty area DRAW_ALL or DRAW_AFTER");
168                 QBrush brush(config_.Colors[Color::Background]);
169                 const int y_start = y_top + qMax(0, count_draw_line - 1) * config_.byteHeight();
170                 painter.fillRect(0, y_start, width(), height(), brush);
171         }
172
173         // Copy from document
174         if (buff_.capacity() < size) {
175                 buff_.resize(size);
176         }
177         document_->get(top, &buff_[0], size);
178
179         // Get selectead area
180         bool selected = false;
181         quint64 sel_begin = 0, sel_end = 0;
182         isSelected(selected, sel_begin, sel_end, top, count_draw_line, size);
183
184         // TODO: Adding cache class for color highligh data
185         ::DrawInfo di(y, top, sel_begin, sel_end, size, selected);
186         getDrawColors(di, dcolors_);
187
188         // Draw lines
189         //qDebug("x:%d", (width() - config_.Margin.left()) / config_.byteWidth());
190         const int x_count_max = (width() - config_.Margin.left()) / config_.byteWidth() + 1;
191         drawLines(painter, dcolors_, y_top, 0, x_count_max);
192
193         // Update screen buffer
194         const int draw_width  = qMin(width(), config_.maxWidth());
195         const int draw_height = count_draw_line * config_.byteHeight();
196         painter.end();
197         update(0, y_top, draw_width, draw_height);
198 }
199
200 inline void HexView::isSelected(bool &selected, quint64 &sel_begin, quint64 &sel_end, quint64 top, int count_draw_line, uint size)
201 {
202         if (!cursor_->hasSelection()) {
203                 return;
204         }
205
206         sel_begin = qMin(cursor_->Position, cursor_->PositionAnchor);
207         sel_end   = qMax(cursor_->Position, cursor_->PositionAnchor);
208
209         if (top <= sel_end && sel_begin <= qMax(top + (HexConfig::Num * count_draw_line), top + size)) {
210                 selected = true;
211         } else {
212                 selected = false;
213         }
214 }
215
216 inline bool HexView::isSelected(quint64 pos)
217 {
218         const quint64 sel_begin = qMin(cursor_->Position, cursor_->PositionAnchor);
219         const quint64 sel_end   = qMax(cursor_->Position, cursor_->PositionAnchor);
220         return sel_begin <= pos && pos <  sel_end;
221 }
222
223 void HexView::drawLines(QPainter &painter, DCIList &dcolors, int y, int x_begin, int x_end)
224 {
225         int index_data = 0, x = 0;
226         bool reset_color = true;
227         QBrush brush;
228         QString hex;
229         hex.resize(2);
230
231         for (DCIList::iterator itr_color = dcolors.begin(); itr_color != dcolors.end(); ) {
232                 // Setup/Update color settings
233                 if (reset_color) {
234                         // Create brush for background
235                         brush = QBrush(config_.Colors[itr_color->BackgroundColor]);
236                         // Set color
237                         painter.setBackground(brush);
238                         painter.setPen(config_.Colors[itr_color->TextColor]);
239                         reset_color = false;
240                 }
241
242                 // Skip
243                 if (x < x_begin || x_end <= x) {
244                         goto COUNTUP;
245                 }
246
247                 // Draw background
248                 painter.fillRect(config_.x(x), y, config_.byteWidth(), config_.byteHeight(), brush);
249
250                 // Draw text
251                 byteToHex(buff_[index_data], hex);
252                 drawText(painter, hex, config_.x(x) + config_.ByteMargin.left(), y + config_.ByteMargin.top());
253
254 COUNTUP:// Count up
255                 index_data++;
256                 x = (x + 1) % HexConfig::Num;
257
258                 // Iterate color
259                 Q_ASSERT(0 <= itr_color->Length);
260                 if (--itr_color->Length <= 0) {
261                         // Move next color
262                         ++itr_color;
263                         // Change color
264                         reset_color = true;
265                 }
266
267                 // Move next line
268                 if (x == 0) {
269                         y += config_.byteHeight();
270                 }
271         }
272
273         // Draw empty area(after end line)
274         if (0 < x && x < x_end && x < HexConfig::Num) {
275                 //qDebug("empty: %d", x);
276                 QBrush brush(config_.Colors[Color::Background]);
277                 painter.fillRect(config_.x(x), y, width(), config_.byteHeight(), brush);
278         }
279 }
280
281 inline void HexView::drawText(QPainter &painter, const QString &hex, int x, int y)
282 {
283         painter.drawText(x, y, config_.charWidth(2), config_.charHeight(), Qt::AlignCenter, hex);
284 }
285
286 void HexView::drawCaret(bool visible)
287 {
288         drawCaret(visible, cursor_->Position);
289 }
290
291 void HexView::drawCaret(bool visible, quint64 pos)
292 {
293         // Check out of range
294         if (!(config_.top() + config_.byteHeight() < height())) {
295                 return;
296         }
297
298         // Redraw line
299         const quint64 line = cursor_->Position / HexConfig::Num;
300         if (cursor_->Top <= line && line - cursor_->Top < static_cast<unsigned int>(config_.drawableLines(height()))) {
301                 drawView(DRAW_LINE, line - cursor_->Top);
302         }
303
304         // Shape
305         const CaretShape shape = visible ? cursor_->CaretVisibleShape : cursor_->CaretInvisibleShape;
306         if (shape == CARET_NONE) {
307                 return;
308         }
309
310         // Begin paint
311         QPainter painter;
312         painter.begin(&pix_);
313         painter.setFont(config_.Font);
314
315         // Get caret coordinates
316         const int x = pos % HexConfig::Num;
317         const int y = config_.top() + config_.byteHeight() * (pos / HexConfig::Num - cursor_->Top);
318
319         // Draw shape
320         drawCaretShape(CaretDrawInfo(painter, shape, pos, x, y, pos < document_->length()));
321
322         // Finish paint and update screen buffer
323         painter.end();
324         update(config_.x(x), y, config_.byteWidth(), config_.charHeight());
325 }
326
327 void HexView::drawCaretShape(CaretDrawInfo info)
328 {
329         if (info.caret_middle) {
330                 // Copy from document
331                 uchar data;
332                 document_->get(info.pos, &data, 1);
333
334                 info.hex.resize(2);
335                 byteToHex(data, info.hex);
336         }
337
338         switch (info.shape) {
339         case CARET_LINE:
340                 drawCaretLine(info);
341                 break;
342         case CARET_BLOCK:
343                 drawCaretBlock(info);
344                 break;
345         case CARET_FRAME:
346                 drawCaretFrame(info);
347                 break;
348         case CARET_UNDERBAR:
349                 drawCaretUnderbar(info);
350                 break;
351         default:
352                 ;
353         }
354 }
355
356 void HexView::drawCaretLine(const CaretDrawInfo &info)
357 {
358         int x;
359         if (cursor_->HighNibble || !info.caret_middle) {
360                 x = config_.x(info.x);
361         } else {
362                 x = config_.x(info.x) + config_.ByteMargin.left() + config_.charWidth();
363         }
364         QBrush brush(config_.Colors[Color::CaretBackground]);
365         info.painter.fillRect(x, info.y, 2, config_.byteHeight(), brush);
366 }
367
368 void HexView::drawCaretBlock(const CaretDrawInfo &info)
369 {
370         if (info.caret_middle) {
371                 if (cursor_->HighNibble || cursor_->hasSelection()) {
372                         // Draw block byte
373                         QBrush brush(config_.Colors[Color::CaretBackground]);
374                         info.painter.setBackground(brush);
375                         info.painter.setPen(config_.Colors[Color::CaretText]);
376                         info.painter.fillRect(config_.x(info.x), info.y, config_.byteWidth(), config_.byteHeight(), brush);
377                         info.painter.drawText(config_.x(info.x) + config_.ByteMargin.left(), info.y + config_.ByteMargin.top(), config_.charWidth(2), config_.charHeight(), Qt::AlignCenter, info.hex);
378                 } else {
379                         // Draw block lowwer nibble
380                         QBrush brush(config_.Colors[Color::CaretBackground]);
381                         info.painter.setBackground(brush);
382                         info.painter.setPen(config_.Colors[Color::CaretText]);
383                         info.painter.fillRect(config_.x(info.x) + config_.ByteMargin.left() + config_.charWidth(), info.y, config_.charWidth() + config_.ByteMargin.right(), config_.byteHeight(), brush);
384                         QString low(info.hex[1]);
385                         info.painter.drawText(config_.x(info.x) + config_.ByteMargin.left() + config_.charWidth(), info.y + config_.ByteMargin.top(), config_.charWidth(2), config_.charHeight(), Qt::AlignLeft, low);
386                 }
387         } else {
388                 // Draw block without data
389                 QBrush brush(config_.Colors[Color::CaretBackground]);
390                 info.painter.fillRect(config_.x(info.x), info.y, config_.byteWidth(), config_.byteHeight(), brush);
391         }
392 }
393
394 void HexView::drawCaretFrame(const CaretDrawInfo &info)
395 {
396         int width, x;
397         if (cursor_->HighNibble || !info.caret_middle) {
398                 width = config_.byteWidth() - 1;
399                 x = config_.x(info.x);
400         } else {
401                 width = config_.charWidth() + config_.ByteMargin.right() - 1;
402                 x = config_.x(info.x) + config_.charWidth() + config_.ByteMargin.left();
403         }
404         info.painter.setPen(config_.Colors[Color::CaretBackground]);
405         info.painter.drawRect(x, info.y, width, config_.byteHeight() - 1);
406 }
407
408 void HexView::drawCaretUnderbar(const CaretDrawInfo &info)
409 {
410         int width, x;
411         if (cursor_->HighNibble || !info.caret_middle) {
412                 width = config_.byteWidth() - 1;
413                 x = config_.x(info.x);
414         } else {
415                 width = config_.charWidth() + config_.ByteMargin.right() - 1;
416                 x = config_.x(info.x) + config_.ByteMargin.left() + config_.charWidth();
417         }
418
419         QBrush brush(config_.Colors[Color::CaretBackground]);
420         info.painter.fillRect(x, info.y + config_.byteHeight() - 2, width, 2, brush);
421 }
422
423 void HexView::byteToHex(uchar c, QString &h)
424 {
425         const uchar H = (c >> 4) & 0xF;
426         if (H <= 9) {
427                 h[0] = QChar('0' + H);
428         } else {
429                 h[0] = QChar('A' + H - 10);
430         }
431         const uchar L = c & 0xF;
432         if (L <= 9) {
433                 h[1] = QChar('0' + L);
434         } else {
435                 h[1] = QChar('A' + L - 10);
436         }
437 }
438
439 void HexView::mousePressEvent(QMouseEvent *ev)
440 {
441         if (ev->button() == Qt::LeftButton) {
442
443                 cursor_->HighNibble = true;
444                 cursor_->movePosition(posAt(ev->pos()), false, false);
445
446                 // Start mouse capture
447                 grabMouse();
448         }
449 }
450
451 void HexView::mouseMoveEvent(QMouseEvent *ev)
452 {
453         if (ev->button() == Qt::LeftButton) {
454                 // FIXME: move down automatically
455                 if (height() < ev->pos().y()) {
456                         return;
457                 }
458                 cursor_->movePosition(posAt(ev->pos()), true, false);
459         }
460 }
461
462 void HexView::mouseReleaseEvent(QMouseEvent *)
463 {
464         //qDebug("mouse release");
465
466         // End mouse capture
467         releaseMouse();
468 }
469
470 quint64 HexView::posAt(const QPoint &pos)
471 {
472         int x = config_.XToPos(pos.x());
473         int y = config_.YToLine(pos.y());
474
475         if (x < 0) {
476                 x = 0;
477         }
478         if (y < 0) {
479                 x = y = 0;
480         }
481
482         return qMin((cursor_->Top + y) * HexConfig::Num + x, document_->length());
483 }
484
485 // Enable caret blink
486 void HexView::setCaretBlink(bool enable)
487 {
488         if (!config_.EnableCaret || !config_.CaretBlinkTime) {
489                 return;
490         }
491         if (enable) {
492                 if (cursor_->CaretTimerId == 0) {
493                         cursor_->CaretTimerId = startTimer(config_.CaretBlinkTime);
494                 }
495         } else {
496                 if (cursor_->CaretTimerId != 0) {
497                         killTimer(cursor_->CaretTimerId);
498                         cursor_->CaretTimerId = 0;
499                 }
500         }
501 }
502
503 void HexView::timerEvent(QTimerEvent *ev)
504 {
505         if (cursor_->CaretTimerId == ev->timerId()) {
506                 // Caret blink
507                 drawCaret(cursor_->HexCaretVisible);
508                 cursor_->turnHexCaretVisible();
509         }
510 }
511
512 void HexView::keyPressEvent(QKeyEvent *ev)
513 {
514         if (ev == QKeySequence::SelectAll) {
515                 //ev->accept();
516                 //all
517                 return;
518         } else if (ev == QKeySequence::Undo) {
519                 return;
520         } else if (ev == QKeySequence::Redo) {
521                 return;
522         }
523
524
525         // TODO: support keyboard remap
526
527         bool keepAnchor = ev->modifiers() & Qt::SHIFT ? true : false;
528         switch (ev->key()) {
529         case Qt::Key_Home:
530                 cursor_->HighNibble = true;
531                 cursor_->movePosition(0, keepAnchor, false);
532                 break;
533         case Qt::Key_End:
534                 cursor_->HighNibble = true;
535                 cursor_->movePosition(document_->length(), keepAnchor, false);
536                 break;
537         case Qt::Key_Left:
538                 cursor_->HighNibble = true;
539                 cursor_->moveRelativePosition(-1, keepAnchor, false);
540                 break;
541         case Qt::Key_Right:
542                 cursor_->HighNibble = true;
543                 cursor_->moveRelativePosition(1, keepAnchor, false);
544                 break;
545         case Qt::Key_Up:
546                 cursor_->HighNibble = true;
547                 cursor_->moveRelativePosition(-16, keepAnchor, false);
548                 break;
549         case Qt::Key_Down:
550                 cursor_->HighNibble = true;
551                 cursor_->moveRelativePosition(16, keepAnchor, false);
552                 break;
553         case Qt::Key_PageUp:
554                 cursor_->HighNibble = true;
555                 cursor_->moveRelativePosition(-16 * 15, keepAnchor, true);
556                 break;
557         case Qt::Key_PageDown:
558                 cursor_->HighNibble = true;
559                 cursor_->moveRelativePosition(16 * 15, keepAnchor, true);
560                 break;
561         case Qt::Key_Backspace:
562                 qDebug("key backspace");
563                 break;
564         case Qt::Key_Insert:
565                 qDebug("key insert");
566                 cursor_->Insert = !cursor_->Insert;
567                 break;
568         case Qt::Key_Delete:
569                 qDebug("key delete");
570                 break;
571         default:
572                 {
573                         // copy from QtCreator
574                         QString text = ev->text();
575                         for (int i = 0; i < text.length(); i++) {
576                                 QChar c = text.at(i).toLower();
577                                 int nibble = -1;
578                                 if (c.unicode() >= 'a' && c.unicode() <= 'f') {
579                                         nibble = c.unicode() - 'a' + 10;
580                                 } else if (c.unicode() >= '0' && c.unicode() <= '9') {
581                                         nibble = c.unicode() - '0';
582                                 }
583                                 if (nibble < 0) {
584                                         continue;
585                                 }
586                                 if (cursor_->HighNibble) {
587                                         // TODO: insert/rewrite document
588                                         //changeData(m_cursorPosition, (nibble << 4) + (m_data[m_cursorPosition] & 0x0f), true);
589                                         cursor_->HighNibble = false;
590                                         // Clear and redraw caret
591                                         drawView(DRAW_LINE, cursor_->Position / HexConfig::Num - cursor_->Top);
592                                         drawCaret();
593                                 } else {
594                                         // TODO: insert/rewrite document
595                                         //changeData(m_cursorPosition, nibble + (m_data[m_cursorPosition] & 0xf0));
596                                         cursor_->HighNibble = true;
597                                         cursor_->moveRelativePosition(1, false, false);
598                                 }
599                         }
600                 }
601                 return;
602         }
603 }
604
605 }       // namespace