1 | /*************************************************************************** |
2 | * Copyright (C) 2005-2014 by the Quassel Project * |
3 | * devel@quassel-irc.org * |
4 | * * |
5 | * This program is free software; you can redistribute it and/or modify * |
6 | * it under the terms of the GNU General Public License as published by * |
7 | * the Free Software Foundation; either version 2 of the License, or * |
8 | * (at your option) version 3. * |
9 | * * |
10 | * This program is distributed in the hope that it will be useful, * |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * |
13 | * GNU General Public License for more details. * |
14 | * * |
15 | * You should have received a copy of the GNU General Public License * |
16 | * along with this program; if not, write to the * |
17 | * Free Software Foundation, Inc., * |
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * |
19 | ***************************************************************************/ |
20 | |
21 | #include <QApplication> |
22 | #include <QMenu> |
23 | #include <QMessageBox> |
24 | #include <QScrollBar> |
25 | |
26 | #include "actioncollection.h" |
27 | #include "bufferview.h" |
28 | #include "graphicalui.h" |
29 | #include "multilineedit.h" |
30 | #include "tabcompleter.h" |
31 | |
32 | const int leftMargin = 3; |
33 | |
34 | MultiLineEdit::MultiLineEdit(QWidget *parent) |
35 | : MultiLineEditParent(parent), |
36 | _idx(0), |
37 | _mode(SingleLine), |
38 | _singleLine(true), |
39 | _minHeight(1), |
40 | _maxHeight(5), |
41 | _scrollBarsEnabled(true), |
42 | _pasteProtectionEnabled(true), |
43 | _emacsMode(false), |
44 | _lastDocumentHeight(-1) |
45 | { |
46 | #if QT_VERSION >= 0x040500 |
47 | document()->setDocumentMargin(0); // new in Qt 4.5 and we really don't want it here |
48 | #endif |
49 | |
50 | setAcceptRichText(false); |
51 | #ifdef HAVE_KDE |
52 | enableFindReplace(false); |
53 | #endif |
54 | |
55 | setMode(SingleLine); |
56 | setLineWrapEnabled(false); |
57 | reset(); |
58 | |
59 | connect(this, SIGNAL(textChanged()), this, SLOT(on_textChanged())); |
60 | |
61 | _mircColorMap["00" ] = "#ffffff" ; |
62 | _mircColorMap["01" ] = "#000000" ; |
63 | _mircColorMap["02" ] = "#000080" ; |
64 | _mircColorMap["03" ] = "#008000" ; |
65 | _mircColorMap["04" ] = "#ff0000" ; |
66 | _mircColorMap["05" ] = "#800000" ; |
67 | _mircColorMap["06" ] = "#800080" ; |
68 | _mircColorMap["07" ] = "#ffa500" ; |
69 | _mircColorMap["08" ] = "#ffff00" ; |
70 | _mircColorMap["09" ] = "#00ff00" ; |
71 | _mircColorMap["10" ] = "#008080" ; |
72 | _mircColorMap["11" ] = "#00ffff" ; |
73 | _mircColorMap["12" ] = "#4169e1" ; |
74 | _mircColorMap["13" ] = "#ff00ff" ; |
75 | _mircColorMap["14" ] = "#808080" ; |
76 | _mircColorMap["15" ] = "#c0c0c0" ; |
77 | } |
78 | |
79 | |
80 | MultiLineEdit::~MultiLineEdit() |
81 | { |
82 | } |
83 | |
84 | |
85 | void MultiLineEdit::setCustomFont(const QFont &font) |
86 | { |
87 | setFont(font); |
88 | updateSizeHint(); |
89 | } |
90 | |
91 | |
92 | void MultiLineEdit::setMode(Mode mode) |
93 | { |
94 | if (mode == _mode) |
95 | return; |
96 | |
97 | _mode = mode; |
98 | } |
99 | |
100 | |
101 | void MultiLineEdit::setLineWrapEnabled(bool enable) |
102 | { |
103 | setLineWrapMode(enable ? WidgetWidth : NoWrap); |
104 | updateSizeHint(); |
105 | } |
106 | |
107 | |
108 | void MultiLineEdit::setMinHeight(int lines) |
109 | { |
110 | if (lines == _minHeight) |
111 | return; |
112 | |
113 | _minHeight = lines; |
114 | updateSizeHint(); |
115 | } |
116 | |
117 | |
118 | void MultiLineEdit::setMaxHeight(int lines) |
119 | { |
120 | if (lines == _maxHeight) |
121 | return; |
122 | |
123 | _maxHeight = lines; |
124 | updateSizeHint(); |
125 | } |
126 | |
127 | |
128 | void MultiLineEdit::setScrollBarsEnabled(bool enable) |
129 | { |
130 | if (_scrollBarsEnabled == enable) |
131 | return; |
132 | |
133 | _scrollBarsEnabled = enable; |
134 | updateScrollBars(); |
135 | } |
136 | |
137 | |
138 | void MultiLineEdit::updateScrollBars() |
139 | { |
140 | QFontMetrics fm(font()); |
141 | int _maxPixelHeight = fm.lineSpacing() * _maxHeight; |
142 | if (_scrollBarsEnabled && document()->size().height() > _maxPixelHeight) |
143 | setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); |
144 | else |
145 | setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
146 | |
147 | if (!_scrollBarsEnabled || isSingleLine()) |
148 | setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
149 | else |
150 | setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); |
151 | } |
152 | |
153 | |
154 | void MultiLineEdit::resizeEvent(QResizeEvent *event) |
155 | { |
156 | QTextEdit::resizeEvent(event); |
157 | updateSizeHint(); |
158 | updateScrollBars(); |
159 | } |
160 | |
161 | |
162 | void MultiLineEdit::updateSizeHint() |
163 | { |
164 | QFontMetrics fm(font()); |
165 | int minPixelHeight = fm.lineSpacing() * _minHeight; |
166 | int maxPixelHeight = fm.lineSpacing() * _maxHeight; |
167 | int scrollBarHeight = horizontalScrollBar()->isVisible() ? horizontalScrollBar()->height() : 0; |
168 | |
169 | // use the style to determine a decent size |
170 | int h = qMin(qMax((int)document()->size().height() + scrollBarHeight, minPixelHeight), maxPixelHeight) + 2 * frameWidth(); |
171 | QStyleOptionFrameV2 opt; |
172 | opt.initFrom(this); |
173 | opt.rect = QRect(0, 0, 100, h); |
174 | opt.lineWidth = lineWidth(); |
175 | opt.midLineWidth = midLineWidth(); |
176 | opt.state |= QStyle::State_Sunken; |
177 | QSize s = style()->sizeFromContents(QStyle::CT_LineEdit, &opt, QSize(100, h).expandedTo(QApplication::globalStrut()), this); |
178 | if (s != _sizeHint) { |
179 | _sizeHint = s; |
180 | updateGeometry(); |
181 | } |
182 | } |
183 | |
184 | |
185 | QSize MultiLineEdit::sizeHint() const |
186 | { |
187 | if (!_sizeHint.isValid()) { |
188 | MultiLineEdit *that = const_cast<MultiLineEdit *>(this); |
189 | that->updateSizeHint(); |
190 | } |
191 | return _sizeHint; |
192 | } |
193 | |
194 | |
195 | QSize MultiLineEdit::minimumSizeHint() const |
196 | { |
197 | return sizeHint(); |
198 | } |
199 | |
200 | |
201 | void MultiLineEdit::setEmacsMode(bool enable) |
202 | { |
203 | _emacsMode = enable; |
204 | } |
205 | |
206 | |
207 | void MultiLineEdit::setSpellCheckEnabled(bool enable) |
208 | { |
209 | #ifdef HAVE_KDE |
210 | setCheckSpellingEnabled(enable); |
211 | #else |
212 | Q_UNUSED(enable) |
213 | #endif |
214 | } |
215 | |
216 | |
217 | void MultiLineEdit::setPasteProtectionEnabled(bool enable, QWidget *) |
218 | { |
219 | _pasteProtectionEnabled = enable; |
220 | } |
221 | |
222 | |
223 | void MultiLineEdit::historyMoveBack() |
224 | { |
225 | addToHistory(convertRichtextToMircCodes(), true); |
226 | |
227 | if (_idx > 0) { |
228 | _idx--; |
229 | showHistoryEntry(); |
230 | } |
231 | } |
232 | |
233 | |
234 | void MultiLineEdit::historyMoveForward() |
235 | { |
236 | addToHistory(convertRichtextToMircCodes(), true); |
237 | |
238 | if (_idx < _history.count()) { |
239 | _idx++; |
240 | if (_idx < _history.count() || _tempHistory.contains(_idx)) // tempHistory might have an entry for idx == history.count() + 1 |
241 | showHistoryEntry(); |
242 | else |
243 | reset(); // equals clear() in this case |
244 | } |
245 | else { |
246 | addToHistory(convertRichtextToMircCodes()); |
247 | reset(); |
248 | } |
249 | } |
250 | |
251 | |
252 | bool MultiLineEdit::addToHistory(const QString &text, bool temporary) |
253 | { |
254 | if (text.isEmpty()) |
255 | return false; |
256 | |
257 | Q_ASSERT(0 <= _idx && _idx <= _history.count()); |
258 | |
259 | if (temporary) { |
260 | // if an entry of the history is changed, we remember it and show it again at this |
261 | // position until a line was actually sent |
262 | // sent lines get appended to the history |
263 | if (_history.isEmpty() || text != _history[_idx - (int)(_idx == _history.count())]) { |
264 | _tempHistory[_idx] = text; |
265 | return true; |
266 | } |
267 | } |
268 | else { |
269 | if (_history.isEmpty() || text != _history.last()) { |
270 | _history << text; |
271 | _tempHistory.clear(); |
272 | return true; |
273 | } |
274 | } |
275 | return false; |
276 | } |
277 | |
278 | |
279 | bool MultiLineEdit::event(QEvent *e) |
280 | { |
281 | // We need to make sure that global shortcuts aren't eaten |
282 | if (e->type() == QEvent::ShortcutOverride) { |
283 | QKeyEvent *event = static_cast<QKeyEvent *>(e); |
284 | QKeySequence key = QKeySequence(event->key() | event->modifiers()); |
285 | foreach(QAction *action, GraphicalUi::actionCollection()->actions()) { |
286 | if (action->shortcuts().contains(key)) { |
287 | e->ignore(); |
288 | return false; |
289 | } |
290 | } |
291 | } |
292 | |
293 | return MultiLineEditParent::event(e); |
294 | } |
295 | |
296 | |
297 | void MultiLineEdit::keyPressEvent(QKeyEvent *event) |
298 | { |
299 | // Workaround the fact that Qt < 4.5 doesn't know InsertLineSeparator yet |
300 | #if QT_VERSION >= 0x040500 |
301 | if (event == QKeySequence::InsertLineSeparator) { |
302 | #else |
303 | |
304 | # ifdef Q_OS_MAC |
305 | if ((event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) && event->modifiers() & Qt::META) { |
306 | # else |
307 | if ((event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) && event->modifiers() & Qt::SHIFT) { |
308 | # endif |
309 | #endif |
310 | |
311 | if (_mode == SingleLine) { |
312 | event->accept(); |
313 | on_returnPressed(); |
314 | return; |
315 | } |
316 | MultiLineEditParent::keyPressEvent(event); |
317 | return; |
318 | } |
319 | |
320 | switch (event->key()) { |
321 | case Qt::Key_Up: |
322 | if (event->modifiers() & Qt::ShiftModifier) |
323 | break; |
324 | { |
325 | event->accept(); |
326 | if (!(event->modifiers() & Qt::ControlModifier)) { |
327 | int pos = textCursor().position(); |
328 | moveCursor(QTextCursor::Up); |
329 | if (pos == textCursor().position()) // already on top line -> history |
330 | historyMoveBack(); |
331 | } |
332 | else |
333 | historyMoveBack(); |
334 | return; |
335 | } |
336 | |
337 | case Qt::Key_Down: |
338 | if (event->modifiers() & Qt::ShiftModifier) |
339 | break; |
340 | { |
341 | event->accept(); |
342 | if (!(event->modifiers() & Qt::ControlModifier)) { |
343 | int pos = textCursor().position(); |
344 | moveCursor(QTextCursor::Down); |
345 | if (pos == textCursor().position()) // already on bottom line -> history |
346 | historyMoveForward(); |
347 | } |
348 | else |
349 | historyMoveForward(); |
350 | return; |
351 | } |
352 | |
353 | case Qt::Key_Return: |
354 | case Qt::Key_Enter: |
355 | case Qt::Key_Select: |
356 | event->accept(); |
357 | on_returnPressed(); |
358 | return; |
359 | |
360 | // We don't want to have the tab key react even if no completer is installed |
361 | case Qt::Key_Tab: |
362 | event->accept(); |
363 | return; |
364 | |
365 | default: |
366 | ; |
367 | } |
368 | |
369 | if (_emacsMode) { |
370 | if (event->modifiers() & Qt::ControlModifier) { |
371 | switch (event->key()) { |
372 | // move |
373 | case Qt::Key_A: |
374 | moveCursor(QTextCursor::StartOfLine); |
375 | return; |
376 | case Qt::Key_E: |
377 | moveCursor(QTextCursor::EndOfLine); |
378 | return; |
379 | case Qt::Key_F: |
380 | moveCursor(QTextCursor::Right); |
381 | return; |
382 | case Qt::Key_B: |
383 | moveCursor(QTextCursor::Left); |
384 | return; |
385 | |
386 | // modify |
387 | case Qt::Key_Y: |
388 | paste(); |
389 | return; |
390 | case Qt::Key_K: |
391 | moveCursor(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); |
392 | cut(); |
393 | return; |
394 | |
395 | default: |
396 | break; |
397 | } |
398 | } |
399 | else if (event->modifiers() & Qt::MetaModifier || |
400 | event->modifiers() & Qt::AltModifier) |
401 | { |
402 | switch (event->key()) { |
403 | case Qt::Key_Right: |
404 | moveCursor(QTextCursor::WordRight); |
405 | return; |
406 | case Qt::Key_Left: |
407 | moveCursor(QTextCursor::WordLeft); |
408 | return; |
409 | case Qt::Key_F: |
410 | moveCursor(QTextCursor::WordRight); |
411 | return; |
412 | case Qt::Key_B: |
413 | moveCursor(QTextCursor::WordLeft); |
414 | return; |
415 | case Qt::Key_Less: |
416 | moveCursor(QTextCursor::Start); |
417 | return; |
418 | case Qt::Key_Greater: |
419 | moveCursor(QTextCursor::End); |
420 | return; |
421 | |
422 | // modify |
423 | case Qt::Key_D: |
424 | moveCursor(QTextCursor::WordRight, QTextCursor::KeepAnchor); |
425 | cut(); |
426 | return; |
427 | |
428 | case Qt::Key_U: // uppercase word |
429 | moveCursor(QTextCursor::WordRight, QTextCursor::KeepAnchor); |
430 | textCursor().insertText(textCursor().selectedText().toUpper()); |
431 | return; |
432 | |
433 | case Qt::Key_L: // lowercase word |
434 | moveCursor(QTextCursor::WordRight, QTextCursor::KeepAnchor); |
435 | textCursor().insertText(textCursor().selectedText().toLower()); |
436 | return; |
437 | |
438 | case Qt::Key_C: |
439 | { // capitalize word |
440 | moveCursor(QTextCursor::WordRight, QTextCursor::KeepAnchor); |
441 | QString const text = textCursor().selectedText(); |
442 | textCursor().insertText(text.left(1).toUpper() + text.mid(1).toLower()); |
443 | return; |
444 | } |
445 | |
446 | case Qt::Key_T: |
447 | { // transpose words |
448 | moveCursor(QTextCursor::StartOfWord); |
449 | moveCursor(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); |
450 | QString const word1 = textCursor().selectedText(); |
451 | textCursor().clearSelection(); |
452 | moveCursor(QTextCursor::WordRight); |
453 | moveCursor(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); |
454 | QString const word2 = textCursor().selectedText(); |
455 | if (!word2.isEmpty() && !word1.isEmpty()) { |
456 | textCursor().insertText(word1); |
457 | moveCursor(QTextCursor::WordLeft); |
458 | moveCursor(QTextCursor::WordLeft); |
459 | moveCursor(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); |
460 | textCursor().insertText(word2); |
461 | moveCursor(QTextCursor::WordRight); |
462 | moveCursor(QTextCursor::EndOfWord); |
463 | } |
464 | return; |
465 | } |
466 | |
467 | default: |
468 | break; |
469 | } |
470 | } |
471 | } |
472 | |
473 | #ifdef HAVE_KDE |
474 | KTextEdit::keyPressEvent(event); |
475 | #else |
476 | QTextEdit::keyPressEvent(event); |
477 | #endif |
478 | } |
479 | |
480 | |
481 | QString MultiLineEdit::convertRichtextToMircCodes() |
482 | { |
483 | bool underline, bold, italic, color; |
484 | QString mircText, mircFgColor, mircBgColor; |
485 | QTextCursor cursor = textCursor(); |
486 | QTextCursor peekcursor = textCursor(); |
487 | cursor.movePosition(QTextCursor::Start); |
488 | |
489 | underline = bold = italic = color = false; |
490 | |
491 | while (cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor)) { |
492 | if (cursor.selectedText() == QString(QChar(QChar::LineSeparator)) |
493 | || cursor.selectedText() == QString(QChar(QChar::ParagraphSeparator))) { |
494 | if (color) { |
495 | color = false; |
496 | mircText.append('\x03'); |
497 | } |
498 | if (underline) { |
499 | underline = false; |
500 | mircText.append('\x1f'); |
501 | } |
502 | if (italic) { |
503 | italic = false; |
504 | mircText.append('\x1d'); |
505 | } |
506 | if (bold) { |
507 | bold = false; |
508 | mircText.append('\x02'); |
509 | } |
510 | mircText.append('\n'); |
511 | } |
512 | else { |
513 | if (!bold && cursor.charFormat().font().bold()) { |
514 | bold = true; |
515 | mircText.append('\x02'); |
516 | } |
517 | if (!italic && cursor.charFormat().fontItalic()) { |
518 | italic = true; |
519 | mircText.append('\x1d'); |
520 | } |
521 | if (!underline && cursor.charFormat().fontUnderline()) { |
522 | underline = true; |
523 | mircText.append('\x1f'); |
524 | } |
525 | if (!color && (cursor.charFormat().foreground().isOpaque() || cursor.charFormat().background().isOpaque())) { |
526 | color = true; |
527 | mircText.append('\x03'); |
528 | mircFgColor = _mircColorMap.key(cursor.charFormat().foreground().color().name()); |
529 | mircBgColor = _mircColorMap.key(cursor.charFormat().background().color().name()); |
530 | |
531 | if (mircFgColor.isEmpty()) { |
532 | mircFgColor = "01" ; //use black if the current foreground color can't be converted |
533 | } |
534 | |
535 | mircText.append(mircFgColor); |
536 | if (cursor.charFormat().background().isOpaque()) |
537 | mircText.append("," + mircBgColor); |
538 | } |
539 | |
540 | mircText.append(cursor.selectedText()); |
541 | |
542 | peekcursor.setPosition(cursor.position()); |
543 | peekcursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); |
544 | |
545 | if (mircCodesChanged(cursor, peekcursor)) { |
546 | if (color) { |
547 | color = false; |
548 | mircText.append('\x03'); |
549 | } |
550 | if (underline) { |
551 | underline = false; |
552 | mircText.append('\x1f'); |
553 | } |
554 | if (italic) { |
555 | italic = false; |
556 | mircText.append('\x1d'); |
557 | } |
558 | if (bold) { |
559 | bold = false; |
560 | mircText.append('\x02'); |
561 | } |
562 | } |
563 | } |
564 | |
565 | cursor.clearSelection(); |
566 | } |
567 | if (color) { |
568 | color = false; |
569 | mircText.append('\x03'); |
570 | } |
571 | if (underline) { |
572 | underline = false; |
573 | mircText.append('\x1f'); |
574 | } |
575 | if (italic) { |
576 | italic = false; |
577 | mircText.append('\x1d'); |
578 | } |
579 | if (bold) { |
580 | bold = false; |
581 | mircText.append('\x02'); |
582 | } |
583 | |
584 | return mircText; |
585 | } |
586 | |
587 | |
588 | bool MultiLineEdit::mircCodesChanged(QTextCursor &cursor, QTextCursor &peekcursor) |
589 | { |
590 | bool changed = false; |
591 | if (cursor.charFormat().font().bold() != peekcursor.charFormat().font().bold()) |
592 | changed = true; |
593 | if (cursor.charFormat().fontItalic() != peekcursor.charFormat().fontItalic()) |
594 | changed = true; |
595 | if (cursor.charFormat().fontUnderline() != peekcursor.charFormat().fontUnderline()) |
596 | changed = true; |
597 | if (cursor.charFormat().foreground().color() != peekcursor.charFormat().foreground().color()) |
598 | changed = true; |
599 | if (cursor.charFormat().background().color() != peekcursor.charFormat().background().color()) |
600 | changed = true; |
601 | return changed; |
602 | } |
603 | |
604 | |
605 | QString MultiLineEdit::convertMircCodesToHtml(const QString &text) |
606 | { |
607 | QStringList words; |
608 | QRegExp mircCode = QRegExp("(|||)" , Qt::CaseSensitive); |
609 | |
610 | int posLeft = 0; |
611 | int posRight = 0; |
612 | |
613 | for (;;) { |
614 | posRight = mircCode.indexIn(text, posLeft); |
615 | |
616 | if (posRight < 0) { |
617 | words << text.mid(posLeft); |
618 | break; // no more mirc color codes |
619 | } |
620 | |
621 | if (posLeft < posRight) { |
622 | words << text.mid(posLeft, posRight - posLeft); |
623 | posLeft = posRight; |
624 | } |
625 | |
626 | posRight = text.indexOf(mircCode.cap(), posRight + 1); |
627 | words << text.mid(posLeft, posRight + 1 - posLeft); |
628 | posLeft = posRight + 1; |
629 | } |
630 | |
631 | for (int i = 0; i < words.count(); i++) { |
632 | QString style; |
633 | if (words[i].contains('\x02')) { |
634 | style.append(" font-weight:600;" ); |
635 | words[i].replace('\x02', "" ); |
636 | } |
637 | if (words[i].contains('\x1d')) { |
638 | style.append(" font-style:italic;" ); |
639 | words[i].replace('\x1d', "" ); |
640 | } |
641 | if (words[i].contains('\x1f')) { |
642 | style.append(" text-decoration: underline;" ); |
643 | words[i].replace('\x1f', "" ); |
644 | } |
645 | if (words[i].contains('\x03')) { |
646 | int pos = words[i].indexOf('\x03'); |
647 | int len = 3; |
648 | QString fg = words[i].mid(pos + 1, 2); |
649 | QString bg; |
650 | if (words[i][pos+3] == ',') |
651 | bg = words[i].mid(pos+4, 2); |
652 | |
653 | style.append(" color:" ); |
654 | style.append(_mircColorMap[fg]); |
655 | style.append(";" ); |
656 | |
657 | if (!bg.isEmpty()) { |
658 | style.append(" background-color:" ); |
659 | style.append(_mircColorMap[bg]); |
660 | style.append(";" ); |
661 | len = 6; |
662 | } |
663 | words[i].replace(pos, len, "" ); |
664 | words[i].replace('\x03', "" ); |
665 | } |
666 | words[i].replace("&" , "&" ); |
667 | words[i].replace("<" , "<" ); |
668 | words[i].replace(">" , ">" ); |
669 | words[i].replace("\"" , """ ); |
670 | if (style.isEmpty()) { |
671 | words[i] = "<span>" + words[i] + "</span>" ; |
672 | } |
673 | else { |
674 | words[i] = "<span style=\"" + style + "\">" + words[i] + "</span>" ; |
675 | } |
676 | } |
677 | return words.join("" ).replace("\n" , "<br />" ); |
678 | } |
679 | |
680 | |
681 | void MultiLineEdit::on_returnPressed() |
682 | { |
683 | on_returnPressed(convertRichtextToMircCodes()); |
684 | } |
685 | |
686 | |
687 | void MultiLineEdit::on_returnPressed(const QString &text) |
688 | { |
689 | if (!text.isEmpty()) { |
690 | foreach(const QString &line, text.split('\n', QString::SkipEmptyParts)) { |
691 | if (line.isEmpty()) |
692 | continue; |
693 | addToHistory(line); |
694 | emit textEntered(line); |
695 | } |
696 | reset(); |
697 | _tempHistory.clear(); |
698 | } |
699 | else { |
700 | emit noTextEntered(); |
701 | } |
702 | } |
703 | |
704 | |
705 | void MultiLineEdit::on_textChanged() |
706 | { |
707 | QString newText = text(); |
708 | newText.replace("\r\n" , "\n" ); |
709 | newText.replace('\r', '\n'); |
710 | if (_mode == SingleLine) { |
711 | if (!pasteProtectionEnabled()) |
712 | newText.replace('\n', ' '); |
713 | else if (newText.contains('\n')) { |
714 | QStringList lines = newText.split('\n', QString::SkipEmptyParts); |
715 | clear(); |
716 | |
717 | if (lines.count() >= 4) { |
718 | QString msg = tr("Do you really want to paste %n line(s)?" , "" , lines.count()); |
719 | msg += "<p>" ; |
720 | for (int i = 0; i < 4; i++) { |
721 | #if QT_VERSION < 0x050000 |
722 | msg += Qt::escape(lines[i].left(40)); |
723 | #else |
724 | msg += lines[i].left(40).toHtmlEscaped(); |
725 | #endif |
726 | if (lines[i].count() > 40) |
727 | msg += "..." ; |
728 | msg += "<br />" ; |
729 | } |
730 | msg += "...</p>" ; |
731 | QMessageBox question(QMessageBox::NoIcon, tr("Paste Protection" ), msg, QMessageBox::Yes|QMessageBox::No); |
732 | question.setDefaultButton(QMessageBox::No); |
733 | #ifdef Q_OS_MAC |
734 | question.setWindowFlags(question.windowFlags() | Qt::Sheet); |
735 | #endif |
736 | if (question.exec() != QMessageBox::Yes) |
737 | return; |
738 | } |
739 | |
740 | foreach(QString line, lines) { |
741 | clear(); |
742 | insert(line); |
743 | on_returnPressed(); |
744 | } |
745 | } |
746 | } |
747 | |
748 | _singleLine = (newText.indexOf('\n') < 0); |
749 | |
750 | if (document()->size().height() != _lastDocumentHeight) { |
751 | _lastDocumentHeight = document()->size().height(); |
752 | on_documentHeightChanged(_lastDocumentHeight); |
753 | } |
754 | updateSizeHint(); |
755 | ensureCursorVisible(); |
756 | } |
757 | |
758 | |
759 | void MultiLineEdit::on_documentHeightChanged(qreal) |
760 | { |
761 | updateScrollBars(); |
762 | } |
763 | |
764 | |
765 | void MultiLineEdit::reset() |
766 | { |
767 | // every time the MultiLineEdit is cleared we also reset history index |
768 | _idx = _history.count(); |
769 | clear(); |
770 | QTextBlockFormat format = textCursor().blockFormat(); |
771 | format.setLeftMargin(leftMargin); // we want a little space between the frame and the contents |
772 | textCursor().setBlockFormat(format); |
773 | updateScrollBars(); |
774 | } |
775 | |
776 | |
777 | void MultiLineEdit::showHistoryEntry() |
778 | { |
779 | // if the user changed the history, display the changed line |
780 | setHtml(convertMircCodesToHtml(_tempHistory.contains(_idx) ? _tempHistory[_idx] : _history[_idx])); |
781 | QTextCursor cursor = textCursor(); |
782 | QTextBlockFormat format = cursor.blockFormat(); |
783 | format.setLeftMargin(leftMargin); // we want a little space between the frame and the contents |
784 | cursor.setBlockFormat(format); |
785 | cursor.movePosition(QTextCursor::End); |
786 | setTextCursor(cursor); |
787 | updateScrollBars(); |
788 | } |
789 | |