OSDN Git Service

Ver0.26
[gefu/Gefu.git] / textview.cpp
1 #include "global.h"
2 #include "preferences.h"
3 #include "textview.h"
4
5 #include <QApplication>
6 #include <QClipboard>
7 #include <QDebug>
8 #include <QPainter>
9 #include <QPaintEvent>
10 #include <QScrollArea>
11 #include <QTextCodec>
12
13 const int GAP_LINE_TEXT = 1;
14 const int MARGIN_RIGHT = 1;
15
16 ///////////////////////////////////////////////////////////////////////////////
17 /// \brief TextView::TextView
18 /// \param parent   親ウィジェット
19 ///
20 /// コンストラクタ
21 ///
22 TextView::TextView(QScrollArea *parent) :
23     QWidget(parent)
24 {
25     m_scrollArea = parent;
26     m_scrollArea->setWidget(this);
27     setObjectName("textView");
28     setCursor(Qt::IBeamCursor);
29
30     resetSelection(0);
31 }
32
33 ///////////////////////////////////////////////////////////////////////////////
34 /// \brief TextView::setData
35 /// \param data バイト列
36 ///
37 /// データを設定します。
38 ///
39 void TextView::setData(const QByteArray &data)
40 {
41     m_source = data;
42
43     std::string code = detectCode(data.left(1024));
44     convertFrom(code.c_str());
45 }
46
47 ///////////////////////////////////////////////////////////////////////////////
48 /// \brief TextView::adjust
49 ///
50 /// 表示領域を調整します。
51 ///
52 void TextView::adjust()
53 {
54     // 行数をカウントする
55     int lineNum = 1;
56     int offset = 0;
57     for (; offset < m_data.size(); ++offset) {
58         if (m_data[offset] == '\r') {
59             if (offset + 1 < m_data.size() && m_data[offset + 1] == '\n') {
60                 ++offset;
61             }
62             ++lineNum;
63         }
64         else if (m_data[offset] == '\n') {
65             ++lineNum;
66         }
67     }
68
69     // 行番号表示に必要な幅を取得する
70     int lineNumWidth = m_charWidth * lineNumChars(lineNum);
71     // テキストの表示開始位置
72     int xPosText = lineNumWidth + GAP_LINE_TEXT * m_charWidth;
73
74     setMinimumWidth(xPosText + m_charWidth * 4);
75
76     // 文字の表示位置を取得する
77     m_viewPositions.clear();
78     int x = 0;
79     int y = m_charHeight;
80     int lineWidth = 0;
81     int wrapLine = 0;
82     const int lineMaxWidth = width() - xPosText - MARGIN_RIGHT * m_charWidth;
83     for (lineNum = 1, offset = 0; offset < m_data.size(); ++offset) {
84         bool isEOL = false;
85         if (m_data[offset] == '\r') {
86             isEOL = true;
87             if (offset < m_data.size() - 1 && m_data[offset + 1] == '\n') {
88                 ++offset;
89             }
90         }
91         if (m_data[offset] == '\n') {
92             isEOL = true;
93         }
94
95         ViewPosition vPos;
96         vPos.lineNum = lineNum;
97         vPos.offset = offset;
98
99         int charWidth = fontMetrics().width(m_data.mid(offset, 1));
100         int tabIndent = 0;
101         if (isEOL) {
102             charWidth = fontMetrics().width(0x21B5);
103         }
104         else if (m_data[offset] == '\t') {
105             tabIndent = ((lineWidth + m_tabWidth) / m_tabWidth) * m_tabWidth - lineWidth;
106             charWidth = fontMetrics().width("^");
107         }
108
109         if (x + charWidth >= lineMaxWidth) {
110             x = 0;
111             ++wrapLine;
112         }
113
114         vPos.x = xPosText + x;
115         vPos.y = y + wrapLine * m_charHeight;
116         m_viewPositions << vPos;
117
118         if (isEOL) {
119             lineWidth = 0;
120             x = 0;
121             y += (wrapLine + 1) * m_charHeight;
122             wrapLine = 0;
123             ++lineNum;
124         }
125         else {
126             if (m_data[offset] == '\t') {
127                 x += tabIndent;
128                 if (x > lineMaxWidth) {
129                     x = x - lineMaxWidth;
130                     ++wrapLine;
131                 }
132             }
133             else {
134                 x += charWidth;
135             }
136             lineWidth += charWidth + tabIndent;
137         }
138     }
139
140     if (!m_viewPositions.isEmpty()) {
141         setMinimumHeight(m_viewPositions.last().y + m_charHeight);
142     }
143 }
144
145 ///////////////////////////////////////////////////////////////////////////////
146 /// \brief TextView::convertFrom
147 /// \param code 文字コード
148 ///
149 /// 文字コードを変更します。
150 ///
151 void TextView::convertFrom(const char *code)
152 {
153     QTextCodec *codec = QTextCodec::codecForName(code);
154     m_data = codec->toUnicode(m_source);
155     adjust();
156     resetSelection(0);
157     update();
158
159     emit statusChanged(code);
160 }
161
162 ///////////////////////////////////////////////////////////////////////////////
163 /// \brief TextView::cursorPos
164 /// \param pos  カーソル位置
165 /// \return カーソル位置に該当するViewPositionのインデックスを返します。
166 ///
167 int TextView::cursorPos(const QPoint &pos)
168 {
169     int n;
170     int y;
171     for (n = 0; n < m_viewPositions.size(); n++) {
172         if (pos.y() < m_viewPositions[n].y) {
173             y = m_viewPositions[n].y;
174             break;
175         }
176     }
177
178     for(; n < m_viewPositions.size(); n++) {
179         if (pos.x() < m_viewPositions[n].x) {
180             return n - 1;
181         }
182         if (y < m_viewPositions[n].y) {
183             return n;
184         }
185     }
186
187     return n;
188 }
189
190 ///////////////////////////////////////////////////////////////////////////////
191 /// \brief TextView::lineNumChars
192 /// \param lines    行数
193 /// \return 行番号の表示に必要な桁数を返します。
194 ///
195 int TextView::lineNumChars(int lines) const
196 {
197     if (lines == -1) {
198         if (m_viewPositions.isEmpty()) {
199             return 0;
200         }
201         lines = m_viewPositions.last().lineNum;
202     }
203     return QString("%1").arg(lines).size() + 1;
204 }
205
206 ///////////////////////////////////////////////////////////////////////////////
207 /// \brief TextView::resetSelection
208 /// \param index    ViewPositionのインデックス
209 ///
210 /// 選択領域を初期化します。
211 ///
212 void TextView::resetSelection(int index)
213 {
214     m_selectionInit = index;
215     m_selectionBegin = index;
216     m_selectionEnd = index;
217
218     emit copyAvailable(false);
219 }
220
221 ///////////////////////////////////////////////////////////////////////////////
222 /// \brief TextView::setSelection
223 /// \param index    ViewPositionのインデックス
224 ///
225 /// 選択領域を設定します。
226 ///
227 void TextView::setSelection(int index)
228 {
229     if (index > m_selectionInit) {
230         m_selectionBegin = m_selectionInit;
231         m_selectionEnd = index;
232     }
233     else {
234         m_selectionBegin = index;
235         m_selectionEnd = m_selectionInit;
236     }
237
238     emit copyAvailable(m_selectionBegin != m_selectionEnd);
239 }
240
241 ///////////////////////////////////////////////////////////////////////////////
242 /// \brief TextView::onConvertFromEUC
243 ///
244 /// EUC-JPからUnicodeへ変換します。
245 ///
246 void TextView::onConvertFromEUC()
247 {
248     convertFrom("EUC-JP");
249 }
250
251 ///////////////////////////////////////////////////////////////////////////////
252 /// \brief TextView::onConvertFromJIS
253 ///
254 /// ISO-2022-JP(JIS)からUnicodeへ変換します。
255 ///
256 void TextView::onConvertFromJIS()
257 {
258     convertFrom("ISO 2022-JP");
259 }
260
261 ///////////////////////////////////////////////////////////////////////////////
262 /// \brief TextView::onConvertFromSJIS
263 ///
264 /// Shift-JIS(SJIS)からUnicodeへ変換します。
265 ///
266 void TextView::onConvertFromSJIS()
267 {
268     convertFrom("Shift-JIS");
269 }
270
271 ///////////////////////////////////////////////////////////////////////////////
272 /// \brief TextView::onConvertFromUTF8
273 ///
274 /// UTF-8からUnicodeへ変換します。
275 ///
276 void TextView::onConvertFromUTF8()
277 {
278     convertFrom("UTF-8");
279 }
280
281 ///////////////////////////////////////////////////////////////////////////////
282 /// \brief TextView::onConvertFromUTF16
283 ///
284 /// UTF-16からUnicodeへ変換します。
285 ///
286 void TextView::onConvertFromUTF16()
287 {
288     convertFrom("UTF-16");
289 }
290
291 ///////////////////////////////////////////////////////////////////////////////
292 /// \brief TextView::onConvertFromUTF16BE
293 ///
294 /// UTF-16BEからUnicodeへ変換します。
295 ///
296 void TextView::onConvertFromUTF16BE()
297 {
298     convertFrom("UTF-16BE");
299 }
300
301 ///////////////////////////////////////////////////////////////////////////////
302 /// \brief TextView::onConvertFromUTF16LE
303 ///
304 /// UTF-16LEからUnicodeへ変換します。
305 ///
306 void TextView::onConvertFromUTF16LE()
307 {
308     convertFrom("UTF-16LE");
309 }
310
311 ///////////////////////////////////////////////////////////////////////////////
312 /// \brief TextView::onCopy
313 ///
314 /// 選択領域をクリップボードにコピーします。
315 ///
316 void TextView::onCopy()
317 {
318     QString selected;
319     int length = m_viewPositions[m_selectionEnd].offset -
320             m_viewPositions[m_selectionBegin].offset;
321
322     selected = m_data.mid(m_viewPositions[m_selectionBegin].offset, length);
323
324     QClipboard *clipboard = qApp->clipboard();
325     clipboard->setText(selected);
326 }
327
328 ///////////////////////////////////////////////////////////////////////////////
329 /// \brief TextView::onScaleDown
330 ///
331 /// 文字を小さくします。
332 ///
333 void TextView::onScaleDown()
334 {
335     Preferences prefs(this);
336     QFont font = prefs.getTextViewFont();
337     font.setPointSize(font.pointSize() - 1);
338     prefs.setTextViewFont(font);
339
340     setVisible(true);
341     adjust();
342     update();
343 }
344
345 ///////////////////////////////////////////////////////////////////////////////
346 /// \brief TextView::onScaleUp
347 ///
348 /// 文字を大きくします。
349 ///
350 void TextView::onScaleUp()
351 {
352     Preferences prefs(this);
353     QFont font = prefs.getTextViewFont();
354     font.setPointSize(font.pointSize() + 1);
355     prefs.setTextViewFont(font);
356
357     setVisible(true);
358     adjust();
359     update();
360 }
361
362 ///////////////////////////////////////////////////////////////////////////////
363 /// \brief TextView::onSelectAll
364 ///
365 /// 全て選択します。
366 ///
367 void TextView::onSelectAll()
368 {
369     m_selectionInit = 0;
370     m_selectionBegin = 0;
371     m_selectionEnd = m_viewPositions.size();
372 }
373
374 ///////////////////////////////////////////////////////////////////////////////
375 /// \brief TextView::setVisible
376 /// \param visible  表示(true)/非表示(false)
377 ///
378 /// 表示時の処理を行います。
379 ///
380 void TextView::setVisible(bool visible)
381 {
382     if (visible) {
383         Preferences prefs(this);
384         QPalette pal = this->palette();
385         pal.setColor(this->backgroundRole(), prefs.getTextViewBgColor());
386         pal.setColor(this->foregroundRole(), prefs.getTextViewFgColor());
387         pal.setBrush(QPalette::BrightText, prefs.getTextViewCtrlColor());
388         this->setPalette(pal);
389         this->setAutoFillBackground(true);
390         this->setFont(prefs.getTextViewFont());
391
392         m_charHeight = fontMetrics().height() * prefs.getTextViewLineHeight();
393         m_charWidth = fontMetrics().width('9');
394         m_tabWidth = 8 * m_charWidth;
395     }
396
397     QWidget::setVisible(visible);
398 }
399
400 ///////////////////////////////////////////////////////////////////////////////
401 /// \brief TextView::mousePressEvent
402 /// \param e    マウスイベントオブジェクト
403 ///
404 /// マウスクリック時の処理を行います。
405 ///
406 void TextView::mousePressEvent(QMouseEvent *e)
407 {
408     if (e->button() == Qt::LeftButton) {
409         int cPos = cursorPos(e->pos());
410         resetSelection(cPos);
411
412         update();
413     }
414 }
415
416 ///////////////////////////////////////////////////////////////////////////////
417 /// \brief TextView::mouseDoubleClickEvent
418 /// \param e    マウスイベントオブジェクト
419 ///
420 /// ダブルクリック時の処理を行います。
421 ///
422 void TextView::mouseDoubleClickEvent(QMouseEvent *e)
423 {
424     if (e->button() == Qt::LeftButton) {
425         int cPos = cursorPos(e->pos());
426         if (cPos >= m_viewPositions.size()) {
427             cPos = m_viewPositions.size() - 1;
428         }
429         int y = m_viewPositions[cPos].y;
430
431         int n;
432         for (n = cPos; n >= 0; n--) {
433             if (m_viewPositions[n].y != y) {
434                 ++n;
435                 break;
436             }
437         }
438         resetSelection(n);
439
440         for (n = cPos; n < m_viewPositions.size(); n++) {
441             if (m_viewPositions[n].y != y) {
442                 break;
443             }
444         }
445         setSelection(n - 1);
446
447         update();
448     }
449 }
450
451 ///////////////////////////////////////////////////////////////////////////////
452 /// \brief TextView::mouseMoveEvent
453 /// \param e    マウスイベントオブジェクト
454 ///
455 /// マウス移動時の処理を行います。
456 ///
457 void TextView::mouseMoveEvent(QMouseEvent *e)
458 {
459     m_scrollArea->ensureVisible(e->x(), e->y());
460
461     int cPos = cursorPos(e->pos());
462     setSelection(cPos);
463     update();
464 }
465
466 ///////////////////////////////////////////////////////////////////////////////
467 /// \brief TextView::paintEvent
468 /// \param e    ペイントイベントオブジェクト
469 ///
470 /// 描画イベントを処理します。
471 ///
472 void TextView::paintEvent(QPaintEvent *e)
473 {
474     QPainter painter(this);
475
476     // 行番号エリア
477     QRect lineNumRect(e->rect());
478     lineNumRect.setLeft(0);
479     lineNumRect.setWidth(m_charWidth * lineNumChars());
480     painter.fillRect(lineNumRect, Qt::gray);
481
482     int prevLine = -1;
483     int idx;
484     for (idx = 0;
485          idx < m_viewPositions.size() &&
486             m_viewPositions[idx].y < e->rect().top();
487          ++idx)
488     {
489         prevLine = m_viewPositions[idx].lineNum;
490     }
491
492     painter.setPen(this->palette().color(this->foregroundRole()));
493     for (;
494          idx < m_viewPositions.size() &&
495             m_viewPositions[idx].y <= e->rect().bottom() + m_charHeight;
496          ++idx)
497     {
498         const ViewPosition &vPos = m_viewPositions[idx];
499
500         // 行番号
501         if (vPos.lineNum != prevLine) {
502             prevLine = vPos.lineNum;
503             painter.setPen(Qt::black);
504             painter.setBackgroundMode(Qt::TransparentMode);
505             QString lineNumber = QString("%1").arg(vPos.lineNum);
506             painter.drawText(
507                         lineNumRect.right() - fontMetrics().width(lineNumber),
508                         vPos.y,
509                         lineNumber);
510         }
511
512         if (m_selectionBegin <= idx && idx < m_selectionEnd) {
513             painter.setBackground(this->palette().highlight());
514             painter.setBackgroundMode(Qt::OpaqueMode);
515             painter.setPen(this->palette().highlightedText().color());
516         }
517         else {
518             painter.setPen(this->palette().color(this->foregroundRole()));
519             painter.setBackgroundMode(Qt::TransparentMode);
520         }
521
522         QString ch = m_data.mid(vPos.offset, 1);
523         if (ch == "\r" || ch == "\n") {
524             painter.setPen(this->palette().color(QPalette::BrightText));
525             painter.drawText(vPos.x, vPos.y, QChar(0x21B5));
526             painter.setPen(this->palette().color(this->foregroundRole()));
527         }
528         else if (ch == "\t") {
529 //            painter.setPen(this->palette().color(QPalette::BrightText));
530 //            painter.drawText(vPos.x, vPos.y, "^");
531 //            painter.setPen(this->palette().color(this->foregroundRole()));
532         }
533         else {
534             painter.drawText(vPos.x, vPos.y, ch);
535         }
536     }
537 }
538
539 ///////////////////////////////////////////////////////////////////////////////
540 /// \brief TextView::resizeEvent
541 /// \param e    リサイズイベントオブジェクト
542 ///
543 /// リサイズ時の処理を行います。
544 ///
545 void TextView::resizeEvent(QResizeEvent *e)
546 {
547     QWidget::resizeEvent(e);
548
549     adjust();
550 }