OSDN Git Service

fix comment
[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(2, 0, 2, 0)
19         , Font("Monaco", 13)
20         , EnableCaret(true)
21         , CaretBlinkTime(500)
22         , FontMetrics(Font)
23 {
24         // Coloring
25         Colors[Color::Background] = QColor(0xCE,0xFF,0xCE);
26         Colors[Color::Text] = QColor(0,0,0);
27         Colors[Color::SelBackground] = QColor(0xCC,0xCC,0xFF);
28         Colors[Color::SelText] = QColor(0,0x40,0x40);
29         Colors[Color::CaretBackground] = QColor(0xFF, 0, 0);
30         Colors[Color::CaretText] = QColor(0xFA,0xFA,0x20);
31
32         // Font
33         Font.setFixedPitch(true);
34
35         calculate();
36 }
37
38 void HexConfig::calculate()
39 {
40         // Pos
41         x_[0] = Margin.left() + ByteMargin.left();
42         for (int i = 1; i < Num; i++) {
43                 x_[i] = x_[i-1] + byteWidth();
44         }
45
46         // Pos of end
47         for (int i = 0; i < Num; i++) {
48                 X_[i] = x_[i] + charWidth(2) + ByteMargin.right();
49         }
50
51         // Area
52         xarea_[0] = Margin.left() + ByteMargin.left();
53         for (int i = 1; i < Num; i++) {
54                 xarea_[i] = xarea_[i-1] + byteWidth();
55         }
56         xarea_[Num] = xarea_[Num-1] + byteWidth();
57
58         top_ = Margin.top();
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(xarea_, lower_bound(xarea_, xarea_ + Num + 2, x)) - 1;
74 }
75
76 int HexConfig::YToLine(int y) const
77 {
78         if (y < Margin.top()) {
79                 return -1;
80         }
81         return (y - Margin.top()) / byteHeight();
82 }
83
84 ////////////////////////////////////////
85 // View
86
87 enum {
88         DRAW_ALL = 0,
89         DRAW_LINE,
90         DRAW_AFTER,
91         DRAW_RANGE,     // [begin, end)
92 };
93
94 HexView::HexView(QWidget *parent, Document *doc, Highlight *hi)
95         : ::View(parent, doc, hi)
96         , cursor(new Cursor(doc, this))
97 {
98         // Enable keyboard input
99         setFocusPolicy(Qt::WheelFocus);
100 }
101
102 void HexView::resizeEvent(QResizeEvent *rs)
103 {
104         QSize size(min(rs->size().width(), config.maxWidth()), rs->size().height());
105         QResizeEvent resize(size, rs->oldSize());
106         View::resizeEvent(&resize);
107         pix_.fill(config.Colors[Color::Background]);
108         drawView();
109 }
110
111 void HexView::drawView()
112 {
113         drawView(DRAW_ALL);
114 }
115
116 void HexView::drawView(int type, int line_start, int end)
117 {
118         qDebug("refresh event type:%d line:%d end:%d", type, line_start, end);
119         qDebug(" end:%llu endOld:%llu pos:%llu", cursor->SelEnd, cursor->SelEndOld, cursor->Position);
120
121         // FIXME: refactoring refresh event
122         QPainter painter;
123         painter.begin(&pix_);
124         painter.setFont(config.Font);
125
126         if (!document->length()) {
127                 // TODO: draw Empty Background only
128                 QBrush brush(config.Colors[Color::Background]);
129                 painter.fillRect(0, 0, width(), height(), brush);
130                 painter.end();
131                 // Update screen buffer
132                 update(0, 0, width(), height());
133                 return;
134         }
135
136         Q_ASSERT(0 <= line_start && line_start <= document->length() / HexConfig::Num + 1);
137         Q_ASSERT(0 <= end && end <= document->length() / HexConfig::Num + 1);
138
139         // Get draw range
140         int y_top = config.top();
141         int y = config.top() + config.byteMargin().top();
142         int count_line, max_y;
143
144         // Get minumum drawing area
145         switch (type) {
146         case DRAW_ALL:
147                 count_line = config.drawableLines(height());
148                 break;
149         case DRAW_LINE:
150                 y_top += config.byteHeight() * line_start;
151                 y     += config.byteHeight() * line_start;
152                 count_line = 1;
153                 break;
154         case DRAW_AFTER:
155                 y_top += config.byteHeight() * line_start;
156                 y     += config.byteHeight() * line_start;
157                 max_y = max(y + config.byteHeight(), height());
158                 count_line = config.drawableLines(max_y - y);
159                 break;
160         case DRAW_RANGE:
161                 y_top += config.byteHeight() * line_start;
162                 y     += config.byteHeight() * line_start;
163                 max_y = min(y + config.byteHeight() * end, height());
164                 count_line = config.drawableLines(max_y - y);
165                 break;
166         }
167
168         // Get top position of view
169         const quint64 top = (cursor->Top + line_start) * HexConfig::Num;
170         const uint size = min(document->length() - top, (quint64)HexConfig::Num * count_line);
171
172         // Draw empty area(after end line)
173         if (type == DRAW_ALL || type == DRAW_AFTER) {
174                 QBrush brush(config.Colors[Color::Background]);
175                 const int y_start = y_top + max(0, count_line - 1) * config.byteHeight();
176                 painter.fillRect(0, y_start, width(), height(), brush);
177         }
178
179         // Copy from document
180         if (buff_.capacity() < size) {
181                 buff_.resize(size);
182         }
183         document->get(top, &buff_[0], size);
184
185         // Get selectead area
186         bool selected = false;
187         quint64 sel_begin = 0, sel_end = 0;
188         isSelected(selected, sel_begin, sel_end, top, count_line, size);
189
190         // TODO: Adding cache class for color highligh data
191         ::DrawInfo di(y, top, sel_begin, sel_end, size, selected);
192         getDrawColors(di, dcolors_);
193
194         // Draw lines
195         qDebug("x:%d", (width() - config.Margin.left()) / config.byteWidth());
196         const int x_count_max = (width() - config.Margin.left()) / config.byteWidth() + 1;
197         drawLines(painter, dcolors_, y_top, 0, x_count_max);
198         painter.end();
199
200         // Update screen buffer
201         const int draw_width  = min(width(), config.maxWidth());
202         const int draw_height = count_line * config.byteHeight();
203         update(0, y_top, draw_width, draw_height);
204 }
205
206 inline void HexView::isSelected(bool &selected, quint64 &sel_begin, quint64 &sel_end, quint64 top, int count_line, uint size)
207 {
208         if (!cursor->Selected) {
209                 return;
210         }
211
212         sel_begin = min(cursor->SelBegin, cursor->SelEnd);
213         sel_end   = max(cursor->SelBegin, cursor->SelEnd);
214
215         if (top <= sel_end && sel_begin <= max(top + (HexConfig::Num * count_line), top + size)) {
216                 selected = true;
217         } else {
218                 selected = false;
219         }
220 }
221
222 inline bool HexView::isSelected(quint64 pos)
223 {
224         const quint64 sel_begin = min(cursor->SelBegin, cursor->SelEnd);
225         const quint64 sel_end   = max(cursor->SelBegin, cursor->SelEnd);
226         return sel_begin <= pos && pos <  sel_end;
227 }
228
229 void HexView::drawLines(QPainter &painter, DCIList &dcolors, int y, int x_begin, int x_end)
230 {
231         int index_data = 0, x = 0;
232         bool reset_color = true;
233         QBrush brush;
234         QString hex;
235         hex.resize(2);
236
237         for (DCIList::iterator itr_color = dcolors.begin(); itr_color != dcolors.end(); ) {
238                 // Setup/Update color settings
239                 if (reset_color) {
240                         // Create brush for background
241                         brush = QBrush(config.Colors[itr_color->BackgroundColor]);
242                         // Set color
243                         painter.setBackground(brush);
244                         painter.setPen(config.Colors[itr_color->TextColor]);
245                         reset_color = false;
246                 }
247
248                 // Skip
249                 if (x < x_begin || x_end <= x) {
250                         goto COUNTUP;
251                 }
252
253                 // Draw background
254                 painter.fillRect(config.x(x), y, config.byteWidth(), config.byteHeight(), brush);
255
256                 // Draw text
257                 byteToHex(buff_[index_data], hex);
258                 drawText(painter, hex, config.x(x) + config.ByteMargin.left(), y + config.ByteMargin.top());
259
260 COUNTUP:// Count up
261                 index_data++;
262                 x = (x + 1) % HexConfig::Num;
263
264                 // Iterate color
265                 if (--itr_color->Length <= 0) {
266                         // Move next color
267                         ++itr_color;
268                         // Make color update
269                         reset_color = true;
270                 }
271
272                 // Move next line
273                 if (x == 0) {
274                         y += config.byteHeight();
275                 }
276         }
277
278         // Draw empty area(after end line)
279         if (0 < x && x < x_end && x < HexConfig::Num) {
280                 qDebug("empty: %d", x);
281                 QBrush brush(config.Colors[Color::Background]);
282                 painter.fillRect(config.x(x), y, width(), config.byteHeight(), brush);
283         }
284 }
285
286 inline void HexView::drawText(QPainter &painter, const QString &hex, int x, int y)
287 {
288         painter.drawText(x, y, config.charWidth(2), config.charHeight(), Qt::AlignCenter, hex);
289 }
290
291 void HexView::drawCaret(quint64 pos, int height_max)
292 {
293         // Check out of range
294         if (!(config.top() + config.byteHeight() < height_max)) {
295                 return;
296         }
297
298         // Begin paint
299         QPainter painter;
300         painter.begin(&pix_);
301         painter.setFont(config.Font);
302
303         // Get caret coordinates 
304         const int x = pos % HexConfig::Num;
305         const int y = config.top() + config.byteHeight() * (pos / HexConfig::Num - cursor->Top);
306
307         // Draw shape
308         drawCaretShape(CaretDrawInfo(painter, pos, x, y, pos < document->length()));
309
310         // Finish paint and update screen buffer
311         painter.end();
312         update(config.x(x), y, config.byteWidth(), config.charHeight());
313 }
314
315 void HexView::drawCaretShape(CaretDrawInfo info)
316 {
317         if (info.caret_middle) {
318                 // Copy from document
319                 uchar data;
320                 document->get(info.pos, &data, 1);
321
322                 info.hex.resize(2);
323                 byteToHex(data, info.hex);
324         }
325
326         switch (cursor->CaretShape) {
327         case CARET_LINE:
328                 drawCaretLine(info);
329                 break;
330         case CARET_BLOCK:
331                 drawCaretBlock(info);
332                 break;
333         case CARET_FRAME:
334                 drawCaretFrame(info);
335                 break;
336         case CARET_UNDERBAR:
337                 drawCaretUnderbar(info);
338                 break;
339         }
340 }
341
342 void HexView::drawCaretLine(const CaretDrawInfo &info)
343 {
344         int x;
345         if (cursor->CaretHigh || !info.caret_middle) {
346                 x = config.x(info.x);
347         } else {
348                 x = config.x(info.x) + config.ByteMargin.left() + config.charWidth();
349         }
350         QBrush brush(config.Colors[Color::CaretBackground]);
351         info.painter.fillRect(x, info.y, 2, config.byteHeight(), brush);
352 }
353
354 void HexView::drawCaretUnderbar(const CaretDrawInfo &info)
355 {
356         int width, x;
357         if (cursor->CaretHigh || !info.caret_middle) {
358                 width = config.byteWidth() - 1;
359                 x = config.x(info.x);
360         } else {
361                 width = config.charWidth() + config.ByteMargin.right() - 1;
362                 x = config.x(info.x) + config.ByteMargin.left() + config.charWidth();
363         }
364
365         QBrush brush(config.Colors[Color::CaretBackground]);
366         info.painter.fillRect(x, info.y + config.byteHeight() - 2, width, 2, brush);
367 }
368
369 void HexView::drawCaretFrame(const CaretDrawInfo &info)
370 {
371         int width, x;
372         if (cursor->CaretHigh || !info.caret_middle) {
373                 width = config.byteWidth() - 1;
374                 x = config.x(info.x);
375         } else {
376                 width = config.charWidth() + config.ByteMargin.right() - 1;
377                 x = config.x(info.x) + config.charWidth() + config.ByteMargin.left();
378         }
379         info.painter.setPen(config.Colors[Color::CaretBackground]);
380         info.painter.drawRect(x, info.y, width, config.byteHeight() - 1);
381 }
382
383 void HexView::drawCaretBlock(CaretDrawInfo &info)
384 {
385         if (info.caret_middle) {
386                 if (cursor->CaretHigh) {
387                         // Draw higher
388                         QBrush brush(config.Colors[Color::CaretBackground]);
389                         info.painter.setBackground(brush);
390                         info.painter.setPen(config.Colors[Color::CaretText]);
391                         info.painter.fillRect(config.x(info.x), info.y, config.byteWidth(), config.byteHeight(), brush);
392                         info.painter.drawText(config.x(info.x) + config.ByteMargin.left(), info.y + config.ByteMargin.top(), config.charWidth(2), config.charHeight(), Qt::AlignCenter, info.hex);
393                 } else {
394                         // Draw lowwer
395                         QBrush brush(config.Colors[Color::CaretBackground]);
396                         info.painter.setBackground(brush);
397                         info.painter.setPen(config.Colors[Color::CaretText]);
398                         info.painter.fillRect(config.x(info.x) + config.ByteMargin.left() + config.charWidth(), info.y, config.charWidth() + config.ByteMargin.right(), config.byteHeight(), brush);
399                         info.hex.remove(0, 1);
400                         info.painter.drawText(config.x(info.x) + config.ByteMargin.left() + config.charWidth(), info.y + config.ByteMargin.top(), config.charWidth(2), config.charHeight(), Qt::AlignLeft, info.hex);
401                 }
402         } else {
403                 // Draw caret without data
404                 QBrush brush(config.Colors[Color::CaretBackground]);
405                 info.painter.fillRect(config.x(info.x), info.y, config.byteWidth(), config.byteHeight(), brush);
406         }
407 }
408
409 void HexView::drawCaret(bool visible)
410 {
411         if (visible) {
412                 drawCaret(cursor->Position, height());
413                 emit caretChanged(visible, cursor->Position);
414         } else {
415                 drawCaret(false, cursor->Position);
416         }
417 }
418
419 void HexView::drawCaret(bool visible, quint64 pos)
420 {
421         if (visible) {
422                 // Draw
423                 drawCaret(pos, height());
424         } else {
425                 // Set caret invisible
426                 quint64 line = cursor->Position / HexConfig::Num;
427                 if (cursor->Top <= line && line - cursor->Top < config.drawableLines(height())) {
428                         drawView(DRAW_LINE, line - cursor->Top);
429                 }
430         }
431
432         // Send carete refresh event
433         emit caretChanged(visible, pos);
434 }
435
436 void HexView::byteToHex(uchar c, QString &h)
437 {
438         const uchar H = (c >> 4) & 0xF;
439         if (H <= 9) {
440                 h[0] = QChar('0' + H);
441         } else {
442                 h[0] = QChar('A' + H - 10);
443         }
444         const uchar L = c & 0xF;
445         if (L <= 9) {
446                 h[1] = QChar('0' + L);
447         } else {
448                 h[1] = QChar('A' + L - 10);
449         }
450 }
451
452 void HexView::mousePressEvent(QMouseEvent *ev)
453 {
454         if (ev->button() == Qt::LeftButton) {
455                 qDebug("mosue press pos:%llu end:%llu endO:%llu el:%llu", cursor->Position, cursor->SelEnd, cursor->SelEndOld, cursor->SelEnd / HexConfig::Num);
456                 // Draw selected lines
457                 drawSelected(true);
458
459                 // Set begin position
460                 cursor->SelEndOld = cursor->Position;
461                 cursor->SelBegin = cursor->SelEnd = moveByMouse(ev->pos().x(), ev->pos().y());
462
463                 // Set caret visible
464                 cursor->Toggle = true;
465
466                 // -- Redraw lines if caret moved
467                 if (config.EnableCaret && cursor->SelEnd != cursor->SelEndOld) {
468                         const int pos = (cursor->SelEndOld / HexConfig::Num) - cursor->Top;
469                         if (pos <= config.drawableLines(height())) {
470                                 drawView(DRAW_RANGE, pos, pos + 1);
471                         }
472                 }
473
474                 drawCaret();
475
476                 // Start mouse capture
477                 grabMouse();
478         }
479 }
480
481 void HexView::mouseMoveEvent(QMouseEvent *ev)
482 {
483         // Check mouse captured
484         if (!cursor->Toggle) {
485                 return;
486         }
487
488         qDebug("mouse move");
489
490         // Set moved position to OLD
491         cursor->SelEndOld = cursor->SelEnd;
492
493         // FIXME: move down automatically
494         if (height() < ev->pos().y()) {
495                 return;
496         }
497
498         // Set moved position
499         cursor->SelEnd = moveByMouse(ev->pos().x(), ev->pos().y());
500
501         // Refresh flag
502         cursor->refreshSelected();
503
504         // Redraw updated lines
505         drawSelected(false);
506
507         // -- Redraw lines if caret moved
508         if (config.EnableCaret && cursor->SelEnd != cursor->SelEndOld) {
509                 drawCaret();
510                 cursor->HexCaretVisible = false;
511         }
512 }
513
514 void HexView::mouseReleaseEvent(QMouseEvent *ev)
515 {
516         // Check mouse captured
517         if (!cursor->Toggle) {
518                 return;
519         }
520
521         qDebug("mouse release");
522
523         // End mouse capture
524         releaseMouse();
525
526         // Set moved position
527         cursor->SelEnd = moveByMouse(ev->pos().x(), ev->pos().y());
528         cursor->refreshSelected();
529
530         // Set caret invisible
531         cursor->Toggle = false;
532
533         // Redraw updated lines
534         drawSelected(false);
535
536         // -- Redraw lines if caret moved
537         if (config.EnableCaret && cursor->SelEnd != cursor->SelEndOld) {
538                 drawCaret();
539                 cursor->HexCaretVisible = false;
540         }
541 }
542
543 #define MIN(a,b) ((a) < (b) ? (a) : (b))
544 quint64 HexView::moveByMouse(int xx, int yy)
545 {
546         int x = config.XToPos(xx);
547         int y = config.YToLine(yy);
548
549         if (x < 0) {
550                 x = 0;
551         }
552         if (y < 0) {
553                 x = y = 0;
554         }
555
556         cursor->Position = MIN((cursor->Top + y) * HexConfig::Num + x, document->length());
557         return cursor->Position;
558 }
559 #undef MIN
560
561 void HexView::drawSelected(bool reset)
562 {
563         quint64 begin, end;
564         if (reset && cursor->Selected) {
565                 //--- Reset selected lines
566                 // Get selected lines
567                 begin = min(min(cursor->SelBegin, cursor->SelEnd), cursor->SelEndOld);
568                 end   = max(max(cursor->SelBegin, cursor->SelEnd), cursor->SelEndOld);
569                 const int begin_line = begin / HexConfig::Num - cursor->Top;
570                 const int end_line   = end   / HexConfig::Num - cursor->Top + 1;
571                 cursor->Selected = false;
572
573                 // Redraw lines
574                 drawView(DRAW_RANGE, begin_line, end_line);
575         } else if (cursor->selMoved()) {
576                 // Selected range is changing
577                 if ((cursor->SelBegin < cursor->SelEndOld && cursor->SelBegin >= cursor->SelEnd ||
578                         cursor->SelBegin >= cursor->SelEndOld && cursor->SelBegin < cursor->SelEnd)) {
579                         // Crossing between begin and end
580                         begin = min(min(cursor->SelBegin, cursor->SelEnd), cursor->SelEndOld);
581                         end   = max(max(cursor->SelBegin, cursor->SelEnd), cursor->SelEndOld);
582                 } else {
583                         // Minimum range
584                         begin = min(cursor->SelEnd, cursor->SelEndOld);
585                         end   = max(cursor->SelEnd, cursor->SelEndOld);
586                 }
587
588                 // Get redrawing lines
589                 const int begin_line = begin / HexConfig::Num - cursor->Top;
590                 const int end_line   = end   / HexConfig::Num - cursor->Top + 1;
591                 // Redraw lines
592                 drawView(DRAW_RANGE, begin_line, end_line);
593         }
594 }
595
596 // Enable caret blink
597 void HexView::setCaretBlink(bool enable)
598 {
599         if (!config.EnableCaret || !config.CaretBlinkTime) {
600                 return;
601         }
602         if (enable) {
603                 if (cursor->CaretTimerId == 0) {
604                         cursor->CaretTimerId = startTimer(config.CaretBlinkTime);
605                 }
606         } else {
607                 if (cursor->CaretTimerId != 0) {
608                         killTimer(cursor->CaretTimerId);
609                         cursor->CaretTimerId = 0;
610                 }
611         }
612 }
613
614 void HexView::timerEvent(QTimerEvent *ev)
615 {
616         if (cursor->CaretTimerId == ev->timerId()) {
617                 // Caret blink
618                 drawCaret(cursor->HexCaretVisible);
619                 cursor->HexCaretVisible = !cursor->HexCaretVisible;
620         }
621 }
622
623 void HexView::keyPressEvent(QKeyEvent *ev)
624 {
625         // TODO: support keyboard remap
626
627         quint64 old = cursor->SelEnd;
628         quint64 oldT = cursor->Top;
629         switch (ev->key()) {
630         case Qt::Key_Home:
631                 cursor->Home();
632                 break;
633         case Qt::Key_End:
634                 cursor->End();
635                 break;
636         case Qt::Key_Left:
637                 cursor->Left();
638                 break;
639         case Qt::Key_Right:
640                 cursor->Right();
641                 break;
642         case Qt::Key_Up:
643                 cursor->Up();
644                 break;
645         case Qt::Key_Down:
646                 cursor->Down();
647                 break;
648         case Qt::Key_PageUp:
649                 cursor->PageUp();
650                 break;
651         case Qt::Key_PageDown:
652                 cursor->PageDown();
653                 break;
654         default:
655                 return;
656         }
657         cursor->SelEndOld = cursor->SelEnd;
658
659         if (ev->modifiers() != Qt::SHIFT) {
660                 cursor->resetSelection();
661         }
662
663         // FIXME: refactoring refresh event
664         if (cursor->SelEnd != old || cursor->Top != oldT) {
665                 drawView();
666                 drawCaret();
667         }
668
669         if (ev->modifiers() != Qt::NoModifier) {
670         } else {
671         }
672 }
673
674 HexView::CaretDrawInfo::CaretDrawInfo(QPainter &p, quint64 pos, int x, int y, bool caret_middle)
675         : painter(p)
676 {
677         this->pos = pos;
678         this->x = x;
679         this->y = y;
680         this->caret_middle = caret_middle;
681 }
682
683 }       // namespace