1 /****************************************************************************
2 **
3 ** Copyright (C) 2006-2008 fullmetalcoder <fullmetalcoder@hotmail.fr>
4 **
5 ** This file is part of the Edyuk project <http://edyuk.org>
6 **
7 ** This file may be used under the terms of the GNU General Public License
8 ** version 3 as published by the Free Software Foundation and appearing in the
9 ** file GPL.txt included in the packaging of this file.
10 **
11 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
12 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
13 **
14 ****************************************************************************/
15
16 #include "qdocument.h"
17 #include "math.h"
18
19 /*!
20 \file qdocument.cpp
21 \brief Implementation of the QDocument class
22 */
23
24 #include "smallUsefulFunctions.h"
25 #include "encoding.h"
26 #include "latexparser/latexparser.h"
27 #include <QtMath>
28
29 // returns the number of chars/columns from column to the next tab location
30 // for a given tabstop periodicity
31 // e.g. dist(0,4)==4, dist(3,4)==1, dist(4,4)==4, dist(5,4)=3
32
ncolsToNextTabStop(int column,int tabstop)33 inline int ncolsToNextTabStop(int column,int tabstop){
34 return tabstop - (column % tabstop);
35 }
36
37
38 /*
39 Document model :
40
41 Goals :
42 * KISS : Keep It Simple Stupid
43 * FAST : ...
44 * LIGHTWEIGHT : reduce memory usage
45 * FLEXIBLE : allow punctual bidi through QTextLayout
46
47 Implementation :
48 QDocument
49 QDocumentPrivate
50
51 QDocumentLine and QDocumentLineHandle => equivalent of QTextBlock
52 QDocumentCursor and QDocumentCursorHandle => equivalent of QTextCursor
53
54 Note :
55 The KISS principle has been kinda mistreated in the private API due to
56 the addition of some complex features which where not planned to be
57 supported when defining the goals (e.g line wrapping, variable width
58 fonts, ...). Such a compromission is affordable but should be avoided
59 whenever possible in the future. And of course the public API should
60 never suffer from such a thing.
61 */
62
63 /*!
64 \ingroup document
65 @{
66 */
67
68 /*!
69 \class QDocument
70
71 \brief A class storing a document
72
73 QCE uses an architecture very similar to that of QTextEdit/QTextDocument
74 which closely ressembles model/view. The document holds all the textual
75 and formatting data. It offers some (mostly indirect) ways of modifying
76 its content and is usable without any GUI.
77
78 In QCE, a document is merely a list of QDocumentLine objects on which some
79 extra formatting can be applied and which can be wrapped/hidden in various
80 ways.
81
82 The document model has been designed with three goals in mind :
83 <ul>
84 <li>performance
85 <li>flexibility
86 <li>low memory usage
87 </ul>
88
89 QDocument supports Bidi by using QTextLayout on lines that require it and
90 prefers custom rendering in other cases to achieve the above goals.
91
92 All the actual text editing is done through QDocumentCursor objects.
93
94 \see QDocumentLine
95 \see QDocumentCursor
96 */
97
98 #include "qdocument_p.h"
99 #include "qdocumentcommand.h"
100
101 #include "qformat.h"
102 #include "qformatscheme.h"
103 #include "qlanguagedefinition.h"
104 #include "qlinemarksinfocenter.h"
105
106 #include <QPen>
107 #include <QTime>
108 #include <QRect>
109 #include <QLine>
110 #include <QPainter>
111 #include <QPrinter>
112 #include <QTextStream>
113 #include <QTextLayout>
114 #include <QApplication>
115 #include <QVarLengthArray>
116 #include <QMessageBox>
117
118 struct RenderRange
119 {
120 int position;
121 int length;
122 int format;
123 int wrap;
124 };
125
126 template<typename T> struct CacheMeta {
127 static inline bool exists(const T&);
128 static inline void clearArray(T*, int size);
129 };
130
exists(const int & v)131 template<> bool CacheMeta<int>::exists(const int& v) { return v >= 0; }
exists(const qreal & v)132 template<> bool CacheMeta<qreal>::exists(const qreal& v) { return v >= 0; }
exists(const QPixmap & v)133 template<> bool CacheMeta<QPixmap>::exists(const QPixmap& v) { return !v.isNull(); }
134
clearArray(int * block,int size)135 template<> void CacheMeta<int>::clearArray(int* block, int size){ memset(block, -1, size);}
clearArray(qreal * block,int size)136 template<> void CacheMeta<qreal>::clearArray(qreal* block, int size){ memset(block, -1, size);}
clearArray(QPixmap *,int)137 template<> void CacheMeta<QPixmap>::clearArray(QPixmap*, int){ }
138
139 template<typename T>
FastCache()140 FastCache<T>::FastCache(){
141 CacheMeta<T>::clearArray(fastMap, sizeof(fastMap));
142 }
143
144 template<typename T>
insert(const int c,const T & width)145 T* FastCache<T>::insert(const int c, const T& width){
146 if (c > 0 && c < 512) {fastMap[c] = width; return &fastMap[c]; }
147 else { typename QMap<int, T>::iterator it = slowMap.insert(c, width); return &(*it);}
148 }
149
150 template<typename T>
contains(const int c) const151 bool FastCache<T>::contains(const int c) const{
152 if (c > 0 && c < 512) return CacheMeta<T>::exists(fastMap[c]);
153 return slowMap.contains(c);
154 }
155 template<typename T>
value(const int c) const156 T FastCache<T>::value(const int c) const{
157 if (c > 0 && c < 512) return fastMap[c];
158 return slowMap.value(c);
159 }
160
161 template<typename T>
valueIfThere(const int c,const T * & value) const162 bool FastCache<T>::valueIfThere(const int c, const T*& value) const{
163 if (c > 0 && c < 512) {
164 if (CacheMeta<T>::exists(fastMap[c])) { value = &fastMap[c]; return true; }
165 } else {
166 typename QMap<int, T>::const_iterator it = slowMap.find(c);
167 if (it != slowMap.end()) {
168 value = &(*it);
169 return true;
170 }
171 }
172 return false;
173 }
174
175
insert(const QChar & c,const T & width)176 template<typename T> inline T* FastCache<T>::insert(const QChar& c, const T& width){return insert(c.unicode(), width); }
contains(const QChar & c) const177 template<typename T> inline bool FastCache<T>::contains(const QChar& c) const{ return contains(c.unicode()); }
value(const QChar & c) const178 template<typename T> inline T FastCache<T>::value(const QChar& c) const{ return value(c.unicode()); }
valueIfThere(const QChar & c,const T * & value) const179 template<typename T> inline bool FastCache<T>::valueIfThere(const QChar& c, const T*& value) const{ return valueIfThere(c.unicode(),value); }
180
getCache(int format)181 template<typename T> FastCache<T> * CacheCache<T>::getCache(int format){
182 typename QMap<int, FastCache<T>* >::iterator it = caches.find(format);
183 if (it != caches.end())
184 return *it;
185 FastCache<T> *cache=new FastCache<T>();
186 caches.insert(format, cache);
187 return cache;
188 }
189
clear()190 template<typename T> void CacheCache<T>::clear(){
191 qDeleteAll(caches);
192 caches.clear();//there was a comment saying this was necessary here
193 }
194
195 static QList<GuessEncodingCallback> guessEncodingCallbacks;
196
197 static int PICTURE_BORDER = 4;
198
199 /*! check if character c is a letter or nummber or backslah (part of command)
200 */
isWord(QChar c)201 inline static bool isWord(QChar c)
202 {
203 QString extraChars="\\";
204 return c.isLetterOrNumber() || extraChars.contains(c);
205 } // see qnfa.cpp isWord || (c == QLatin1Char('_')); }, _ is no word character in LaTeX
206
207 /*! check if character c is a delimiter ( "(){}$+-*,/;." )
208 */
isDelimiter(QChar c)209 inline static bool isDelimiter(QChar c)
210 {
211 QString delimiters="(){}$+-/*,;.";
212 return delimiters.contains(c);
213 }
214
215 /*! set the line width without modifying any other properties of the pen
216 */
setPainterLineWidth(QPainter * p,int width)217 inline static void setPainterLineWidth(QPainter *p, int width) {
218 QPen pen = p->pen();
219 pen.setWidth(width);
220 p->setPen(pen);
221 }
222
223
224 /*! activate a work-around
225 * DisableFixedPitchMode = 0x01,
226 * DisableWidthCache = 0x02,
227 * DisableLineCache = 0x04,
228 * ForceQTextLayout = 0x08,
229 * ForceSingleCharacterDrawing = 0x10,
230 * QImageCache = 0x20
231 */
setWorkAround(QDocument::WorkAroundFlag workAround,bool newValue)232 void QDocument::setWorkAround(QDocument::WorkAroundFlag workAround, bool newValue){
233 QDocumentPrivate::setWorkAround(workAround, newValue);
234 }
235
236 /*! check if worariound is activated
237 */
hasWorkAround(QDocument::WorkAroundFlag workAround)238 bool QDocument::hasWorkAround(QDocument::WorkAroundFlag workAround){
239 return QDocumentPrivate::hasWorkAround(workAround);
240 }
241
242 /*! check if fixed pitch font is used
243 */
getFixedPitch() const244 bool QDocument::getFixedPitch() const{
245 return m_impl && m_impl->getFixedPitch();
246 }
forceLineWrapCalculation() const247 bool QDocument::forceLineWrapCalculation() const{
248 return m_impl && m_impl->m_forceLineWrapCalculation;
249 }
setForceLineWrapCalculation(bool v)250 void QDocument::setForceLineWrapCalculation(bool v){
251 if (m_impl) m_impl->setForceLineWrapCalculation(v);
252 }
linesMerged(QDocumentLineHandle * dlh,int bo,QDocumentLineHandle * fromLineHandle)253 bool QDocument::linesMerged(QDocumentLineHandle* dlh,int bo,QDocumentLineHandle* fromLineHandle){
254 QDocumentLine ln(dlh);
255 bool hasBookmark=false;
256 int rmid=-1;
257 int i=-1;
258 for(i=-1;i<10;i++){
259 rmid=bookMarkId(i);
260 hasBookmark=ln.hasMark(rmid);
261 if(hasBookmark)
262 break;
263 }
264 if(hasBookmark && bo==0){
265 // line has been removed completely, remove bookmark
266 ln.removeMark(rmid);
267 emit bookmarkRemoved(dlh);
268 hasBookmark=false;
269 }
270 if(hasBookmark)
271 return false; //don't overwrite existing bookmark
272 QDocumentLine fromLine(fromLineHandle);
273 for(i=-1;i<10;i++){
274 rmid=bookMarkId(i);
275 hasBookmark=fromLine.hasMark(rmid);
276 if(hasBookmark)
277 break;
278 }
279 if(hasBookmark){
280 fromLine.removeMark(rmid);
281 emit bookmarkRemoved(fromLineHandle);
282 ln.addMark(rmid);
283 emit bookmarkAdded(dlh,i);
284 }
285 return hasBookmark;
286 }
287
linesUnMerged(QDocumentLineHandle * dlh,QDocumentLineHandle * fromLineHandle)288 void QDocument::linesUnMerged(QDocumentLineHandle *dlh,QDocumentLineHandle *fromLineHandle){
289 QDocumentLine ln(dlh);
290 bool hasBookmark=false;
291 int rmid=-1;
292 int i=-1;
293 for(i=-1;i<10;i++){
294 rmid=bookMarkId(i);
295 hasBookmark=ln.hasMark(rmid);
296 if(hasBookmark)
297 break;
298 }
299 if(hasBookmark){
300 QDocumentLine fromLine(fromLineHandle);
301 ln.removeMark(rmid);
302 emit bookmarkRemoved(dlh);
303 fromLine.addMark(rmid);
304 emit bookmarkAdded(fromLineHandle,i);
305 }
306 }
307
bookMarkId(int bookmarkNumber)308 int QDocument::bookMarkId(int bookmarkNumber) {
309 if (bookmarkNumber==-1) return QLineMarksInfoCenter::instance()->markTypeId("bookmark"); //unnumbered mark
310 else return QLineMarksInfoCenter::instance()->markTypeId("bookmark"+QString::number(bookmarkNumber));
311 }
312
applyHardLineWrap(const QList<QDocumentLineHandle * > & in_handles)313 void QDocument::applyHardLineWrap(const QList<QDocumentLineHandle*>& in_handles){
314 if (in_handles.isEmpty()) return;
315
316 QList<QDocumentLineHandle*> handles = in_handles;
317
318 QDocumentCursor cur(this);
319 cur.beginEditBlock();
320 while (!handles.isEmpty()) {
321 QList<QDocumentLineHandle*> newhandles;
322 foreach ( QDocumentLineHandle* dlh,handles )
323 {
324 int lineNr = this->indexOf(dlh);
325 if (lineNr < 0) continue;
326
327 QList<int> lineBreaks = dlh->getBreaks();
328 if (lineBreaks.isEmpty()) continue;
329
330 QString line=dlh->text();
331 QString indent = line.left(qMax(0, dlh->nextNonSpaceChar(0)));
332 if (indent.size() >= lineBreaks.first()) indent = "";
333
334 //todo: support other languages except latex (either search the comment starting with document()->languageDefinition()->singleLineComment() in the highlighting info, or modify singleLineComment to return a regex matching the comment start
335 QList<int> commentStarts;
336 QString temp = line;
337 int commentStart;
338 while ((commentStart=LatexParser::commentStart(temp)) >= 0 ) {
339 temp = temp.mid(commentStart+1);
340 commentStarts << commentStart + (commentStarts.isEmpty()?0:commentStarts.last());
341 }
342
343
344 while(!lineBreaks.isEmpty()){
345 int last = lineBreaks.takeLast();
346 cur.moveTo(lineNr, last);
347 cur.insertText("\n" + indent);
348 while (!commentStarts.isEmpty() && last <= commentStarts.last())
349 commentStarts.removeLast();
350 if(!commentStarts.isEmpty()) {
351 cur.insertText(QString(commentStarts.size(), '%'));
352 newhandles << cur.line().handle();
353 }
354 }
355 }
356 handles = newhandles;
357 }
358 cur.endEditBlock();
359 }
360
screenColumn(const QChar * d,int l,int tabStop,int column)361 int QDocument::screenColumn(const QChar *d, int l, int tabStop, int column)
362 {
363 if ( tabStop == 1 )
364 return column + l;
365
366 int idx = 0;
367
368 while ( idx < l )
369 {
370 const QChar& c = d[idx];
371
372 if ( c == QLatin1Char('\t') )
373 {
374 column += ncolsToNextTabStop(column, tabStop);
375 } else {
376 ++column;
377 }
378
379 ++idx;
380 }
381
382 //qDebug("%s : %i", qPrintable(QString(d, l)), column);
383
384 return column;
385 }
386
screenable(const QChar * d,int l,int tabStop,int column)387 QString QDocument::screenable(const QChar *d, int l, int tabStop, int column)
388 {
389 if ( tabStop == 1 )
390 return QString(d, l);
391
392 QString fragment;
393 int idx = 0;
394
395 while ( idx < l )
396 {
397 QChar c = d[idx];
398
399 if ( c == QLatin1Char('\t') )
400 {
401 int taboffset = ncolsToNextTabStop(column, tabStop);
402
403 fragment += QString(taboffset, QLatin1Char(' '));
404 column += taboffset;
405 } else {
406 fragment += c;
407 ++column;
408 }
409
410 ++idx;
411 }
412
413 return fragment;
414 }
415
416 struct InitStruct
417 {
InitStructInitStruct418 InitStruct()
419 {
420 qRegisterMetaType<QDocumentIterator>("QDocumentIterator");
421 qRegisterMetaType<QDocumentConstIterator>("QDocumentConstIterator");
422 }
423 };
424
425 static InitStruct init_inst;
426
427 /*!
428 \brief ctor
429 */
QDocument(QObject * p)430 QDocument::QDocument(QObject *p)
431 : QObject(p), m_impl(new QDocumentPrivate(this))
432 {
433 if ( !QDocumentPrivate::m_font )
434 {
435 // must not happen if config dialog plugged in...
436 setBaseFont(QFont("Monospace", 10));
437 }
438
439
440 setText(QString(),false);
441 setLineEndingDirect(QDocument::Conservative);
442
443 connect(&(m_impl->m_commands) , SIGNAL( cleanChanged(bool) ),
444 this , SIGNAL( cleanChanged(bool) ) );
445
446 connect(&(m_impl->m_commands) , SIGNAL( canUndoChanged(bool) ),
447 this , SIGNAL( undoAvailable(bool) ) );
448
449 connect(&(m_impl->m_commands) , SIGNAL( canRedoChanged(bool) ),
450 this , SIGNAL( redoAvailable(bool) ) );
451
452 connect(this ,
453 SIGNAL( lineDeleted(QDocumentLineHandle*,int) ),
454 QLineMarksInfoCenter::instance(),
455 SLOT ( lineDeleted(QDocumentLineHandle*,int) ) );
456
457 }
458
459 /*!
460 \brief dtor
461 */
~QDocument()462 QDocument::~QDocument()
463 {
464 delete m_impl;
465 }
466
467 /*!
468 \brief Clear the content of the document
469 */
clear()470 void QDocument::clear()
471 {
472 setText(QString(), false);
473 }
474
475 /*!
476 \return whether there are commands to undo on the command stack
477 */
canUndo() const478 bool QDocument::canUndo() const
479 {
480 return m_impl ? m_impl->m_commands.canUndo() : false;
481 }
482
483 /*!
484 \return whether there are commands to redo on the command stack
485 */
canRedo() const486 bool QDocument::canRedo() const
487 {
488 return m_impl ? m_impl->m_commands.canRedo() : false;
489 }
490
491 /*!
492 \brief Undo the last editing operation
493 */
undo()494 void QDocument::undo()
495 {
496 if ( m_impl )
497 {
498 m_impl->m_commands.undo();
499 m_impl->m_lastModified = QDateTime::currentDateTime();
500 }
501 }
502
503 /*!
504 \brief Redo the last undone editing operation
505 */
redo()506 void QDocument::redo()
507 {
508 if ( m_impl )
509 {
510 m_impl->m_commands.redo();
511 m_impl->m_lastModified = QDateTime::currentDateTime();
512 }
513
514 }
515
516 /*! clear undo stack
517 */
clearUndo()518 void QDocument::clearUndo()
519 {
520 if ( m_impl )
521 {
522 m_impl->m_commands.clear();
523 }
524 }
525
526 /*!
527 * \brief give current state of undo-stack for debugging
528 *
529 * return the cuurent content of the undo-stack as string
530 * \note This function is used for debugging only
531 * \param limit number of results
532 * \return commands on undo-stack
533 */
debugUndoStack(int limit) const534 QString QDocument::debugUndoStack(int limit) const{
535 if (!m_impl) return QString();
536 const QUndoStack& commands = m_impl->m_commands;
537
538 QStringList result;
539
540 result << QString("Current: %1/%2").arg(commands.index()).arg(commands.count());
541
542 int from = commands.index() - limit, to = commands.index() + limit;
543 if (from < 0) from = 0;
544 if (to >= commands.count()) to = commands.count() - 1;
545
546 for (int i=from; i<=to; i++)
547 result << QString("%1: ").arg(i) << dynamic_cast<const QDocumentCommand*>(commands.command(i))->debugRepresentation();
548
549 QString res = result.join("\n");
550
551 qDebug() << res;
552
553 return res;
554 }
555
556 /*!
557 \return The content of the document
558 \param mode extra processing to perform on text
559 */
text(int mode) const560 QString QDocument::text(int mode) const
561 {
562 QString s;
563
564 if ( !m_impl || m_impl->m_lines.isEmpty() )
565 return s;
566
567 int line = 0;
568 int curIndent = 0, nextIndent = m_impl->m_lines.at(0)->nextNonSpaceChar(0);
569
570 if ( nextIndent < 0 )
571 nextIndent = 0;
572
573 foreach ( const QDocumentLineHandle *l, m_impl->m_lines )
574 {
575 int prevIndent = curIndent;
576 curIndent = nextIndent;
577 bool notLastLine = ++line < m_impl->m_lines.count();
578 nextIndent = notLastLine ? m_impl->m_lines.at(line)->nextNonSpaceChar(0) : 0;
579
580 if ( nextIndent < 0 )
581 nextIndent = 0;
582
583 QString buf = l->text();
584 int avgIndent = qMax(prevIndent, nextIndent);
585
586 if ( (mode & RestoreTrailingIndent) && buf.isEmpty() && avgIndent )
587 {
588 buf = QString(avgIndent, '\t');
589 } else if ( mode & RemoveTrailingWS ) {
590
591 int len = 0, idx = buf.length();
592
593 while ( --idx >= 0 )
594 {
595 if ( !buf.at(idx).isSpace() )
596 break;
597
598 ++len;
599 }
600
601 ++idx;
602
603 if ( len && (idx || !(mode & PreserveIndent)) )
604 buf.remove(idx, len);
605 }
606
607 if (notLastLine)
608 s += buf + m_impl->m_lineEndingString;
609 else if (!buf.isEmpty())
610 s += buf; //last line doesn't ends with \n (it must be possible to create a single line string)
611 }
612
613 //s.chop(m_impl->m_lineEndingString.count());
614 return s;
615 }
616
617 /*!
618 \return The content of the document
619 \param removeTrailing whether to remove trailing whitespaces
620 \param preserveIndent whether to keep trailing whitespaces when they are indent
621 */
text(bool removeTrailing,bool preserveIndent) const622 QString QDocument::text(bool removeTrailing, bool preserveIndent) const
623 {
624 int mode = 0;
625
626 if ( removeTrailing )
627 mode |= RemoveTrailingWS;
628
629 if ( preserveIndent )
630 mode |= PreserveIndent;
631
632 return text(mode);
633 }
634
635 /*!
636 * \return give the complete text as stringlist
637 */
textLines() const638 QStringList QDocument::textLines() const{
639 QStringList res;
640 if ( !m_impl || m_impl->m_lines.isEmpty() )
641 return res;
642 foreach ( const QDocumentLineHandle *l, m_impl->m_lines )
643 res << l->text();
644 return res;
645 }
646
647 /*!
648 \brief Set the content of the document
649 */
setText(const QString & s,bool allowUndo)650 void QDocument::setText(const QString& s, bool allowUndo)
651 {
652 if ( !m_impl )
653 return;
654
655 if (allowUndo) {
656 QDocumentCursor temp(this);
657 temp.movePosition(1, QDocumentCursor::Start);
658 temp.movePosition(1, QDocumentCursor::End, QDocumentCursor::KeepAnchor);
659 temp.replaceSelectedText(s);
660 return;
661 }
662
663 int last = 0, idx = 0;
664
665 m_impl->m_deleting = true;
666
667
668 for(int i=0;i<m_impl->m_lines.size();++i){
669 QDocumentLineHandle *h= m_impl->m_lines.at(i);
670 emit lineDeleted(h,i);
671 }
672
673 foreach ( QDocumentLineHandle *h, m_impl->m_lines )
674 {
675 h->m_doc = nullptr;
676 h->deref();
677 }
678
679 m_impl->discardAutoUpdatedCursors();
680
681 m_impl->m_lines.clear();
682 m_impl->m_marks.clear();
683 m_impl->m_status.clear();
684 m_impl->m_hidden.clear();
685 m_impl->m_wrapped.clear();
686 m_impl->m_matches.clear();
687 m_impl->m_largest.clear();
688 m_impl->m_commands.clear();
689
690 m_impl->m_deleting = false;
691
692 m_impl->_nix = 0;
693 m_impl->_dos = 0;
694 m_impl->_mac = 0;
695
696 while ( idx < s.length() )
697 {
698 if ( s.at(idx) == '\r') {
699 m_impl->m_lines << new QDocumentLineHandle(
700 s.mid(last, idx - last),
701 this
702 );
703 ++idx;
704 if (idx < s.length() && s.at(idx) == '\n') {
705 ++(m_impl->_dos);
706 ++idx;
707 } else ++(m_impl->_mac);
708 last = idx;
709 } else if ( s.at(idx) == '\n') {
710 ++(m_impl->_nix);
711
712 m_impl->m_lines << new QDocumentLineHandle(
713 s.mid(last, idx - last),
714 this
715 );
716 last = ++idx;
717 } else {
718 ++idx;
719 }
720 }
721
722 if ( idx != last )
723 {
724 m_impl->m_lines << new QDocumentLineHandle(
725 s.mid(last, s.length() - last),
726 this
727 );
728
729 } else {
730 Q_ASSERT(s.isEmpty() || s.endsWith("\n") || s.endsWith("\r"));
731 m_impl->m_lines << new QDocumentLineHandle(this); //last character was \n, or empty string
732 }
733 //
734 // if ( (idx > 0) && ((idx - 1) < s.length()) && ((s.at(idx - 1) == '\n') || (s.at(idx - 1) == '\r')) )
735 // m_impl->m_lines << new QDocumentLineHandle(this);
736 //
737
738 //qDebug("[one go] dos : %i; nix : %i", m_impl->_dos, m_impl->_nix);
739
740 m_impl->m_lastModified = QDateTime::currentDateTime();
741
742 if ( lineEnding() == Conservative )
743 setLineEndingDirect(Conservative);
744
745 //m_impl->setWidth(); // will be performed in emitContentsChange
746 m_impl->setHeight();
747
748 emit lineCountChanged(lineCount());
749
750 m_impl->emitContentsChange(0, m_impl->m_lines.count());
751 }
752
guessEncoding(const QByteArray & data)753 QTextCodec* guessEncoding(const QByteArray& data){
754 QTextCodec* guess = nullptr;
755 int sure = 1;
756 guess = Encoding::guessEncodingBasic(data, &sure);
757 if (!guessEncodingCallbacks.empty()){
758 foreach (const GuessEncodingCallback& callback, guessEncodingCallbacks)
759 callback(data, guess, sure);
760 }
761 if (guess!=nullptr) return guess;
762 else return QTextCodec::codecForName("UTF-8"); //default
763 }
764
765 /*!
766 * \brief load text from file using codec
767 * \param file
768 * \param codec
769 */
load(const QString & file,QTextCodec * codec)770 void QDocument::load(const QString& file, QTextCodec* codec){
771 QFile f(file);
772
773 // gotta handle line endings ourselves if we want to detect current line ending style...
774 //if ( !f.open(QFile::Text | QFile::ReadOnly) )
775 if ( !f.open(QFile::ReadOnly) )
776 {
777 setText(QString(), false);
778 return;
779 }
780
781 const qint64 size = f.size();
782 //const int size = m_lastFileState.size = f.size();
783
784 bool slow = (size > 30 * 1024);
785 if (slow) emit slowOperationStarted();
786
787 if ( size < 500000 )
788 {
789 // instant load for files smaller than 500kb
790 QByteArray d = f.readAll();
791 if (codec == nullptr)
792 codec=guessEncoding(d);
793
794 setText(codec->toUnicode(d), false);
795 } else {
796 // load by chunks of 100kb otherwise to avoid huge peaks of memory usage
797 // and driving mad the disk drivers
798
799 int count = 0;
800 QByteArray ba;
801
802 startChunkLoading();
803
804 ba = f.read(100000);
805 if (codec == nullptr)
806 codec=guessEncoding(ba);
807
808 QTextDecoder *dec = codec->makeDecoder();
809 do
810 {
811 count += ba.count();
812 //m_lastFileState.checksum ^= qChecksum(ba.constData(), ba.size());
813 addChunk(dec->toUnicode(ba));
814 ba = f.read(100000);
815 } while ( (count < size) && ba.count() );
816 delete dec;
817 stopChunkLoading();
818 }
819 if (slow) emit slowOperationEnded();
820
821 setCodecDirect(codec);
822 setLastModified(QFileInfo(file).lastModified());
823 }
824
825 /*!
826 \brief Start a chunk loading
827
828 It is possible to load document contents in one piece
829 or by chunks. To achieve the later you have to proceed as follows :
830
831 \code
832 QDocument doc;
833 doc.startChunkLoading();
834
835 // fetch data and add it using doc.addChunk();
836
837 doc.stopChunkLoading();
838 \endcode
839
840 \see addChunk(const QString&)
841 \see stopChunkLoading()
842 */
startChunkLoading()843 void QDocument::startChunkLoading()
844 {
845 if ( !m_impl )
846 return;
847
848 m_impl->m_deleting = true;
849
850 for(int i=0;i<m_impl->m_lines.size();++i){
851 QDocumentLineHandle *h= m_impl->m_lines.at(i);
852 emit lineDeleted(h,i);
853 }
854 foreach ( QDocumentLineHandle *h, m_impl->m_lines )
855 {
856 h->m_doc = nullptr;
857 h->deref();
858 }
859
860 m_impl->discardAutoUpdatedCursors();
861
862 m_impl->m_lines.clear();
863 m_impl->m_marks.clear();
864 m_impl->m_status.clear();
865 m_impl->m_hidden.clear();
866 m_impl->m_wrapped.clear();
867 m_impl->m_matches.clear();
868 m_impl->m_largest.clear();
869 m_impl->m_commands.clear();
870
871 m_impl->m_deleting = false;
872
873 m_impl->_nix = 0;
874 m_impl->_dos = 0;
875 m_impl->_mac = 0;
876
877 m_leftOver.clear();
878 }
879
880 /*!
881 \brief Stop chunk loading
882
883 \see startChunkLoading()
884 */
stopChunkLoading()885 void QDocument::stopChunkLoading()
886 {
887 bool emptyEndingLine = false;
888 if ( m_leftOver.count() )
889 {
890 if (m_leftOver.endsWith('\r')) {
891 emptyEndingLine = true;
892 m_impl->_mac++;
893 m_leftOver.chop(1);
894 }
895 m_impl->m_lines << new QDocumentLineHandle( m_leftOver, this );
896
897 m_leftOver.clear();
898
899 } else emptyEndingLine = true;
900
901 if (emptyEndingLine)
902 m_impl->m_lines << new QDocumentLineHandle(this);
903
904 //qDebug("[chunk] dos : %i; nix : %i", m_impl->_dos, m_impl->_nix);
905
906 m_impl->m_lastModified = QDateTime::currentDateTime();
907
908 if ( lineEnding() == Conservative )
909 setLineEndingDirect(Conservative);
910
911 m_impl->setWidth();
912 m_impl->setHeight();
913
914 emit lineCountChanged(lineCount());
915
916 //emit m_impl->emitContentsChange(0, m_impl->m_lines.count()); //will be called explicitely later by txs itself. Avoid calling twice. Is it really save to not call it ?
917 }
918
919 /*!
920 \return The format scheme used by the document
921 */
formatScheme() const922 QFormatScheme* QDocument::formatScheme() const
923 {
924 return m_impl ? m_impl->m_formatScheme : nullptr;
925 }
926
927 /*!
928 \brief Set the format scheme used by the document
929 */
setFormatScheme(QFormatScheme * f)930 void QDocument::setFormatScheme(QFormatScheme *f)
931 {
932 if ( m_impl )
933 m_impl->setFormatScheme(f);
934 }
935
getBackground() const936 QColor QDocument::getBackground() const{
937 if (m_impl && m_impl->m_formatScheme) {
938 if (m_impl->m_formatScheme->format("background").background.isValid())
939 return m_impl->m_formatScheme->format("background").background; //independent of "normal" format (otherwise it can't be merged with the current line)
940 else if (m_impl->m_formatScheme->format("normal").background.isValid())
941 return m_impl->m_formatScheme->format("normal").background;
942 }
943 return QColor();
944 }
945
getForeground() const946 QColor QDocument::getForeground() const{
947 if (m_impl && m_impl->m_formatScheme) {
948 if (m_impl->m_formatScheme->format("normal").foreground.isValid())
949 return m_impl->m_formatScheme->format("normal").foreground;
950 }
951 return QColor();
952 }
953
954 /*!
955 \return the language definition set to the document
956 */
languageDefinition() const957 QLanguageDefinition* QDocument::languageDefinition() const
958 {
959 return m_impl ? m_impl->m_language : nullptr;
960 }
961
962 /*!
963 \brief Set the language definition
964 */
setLanguageDefinition(QLanguageDefinition * f)965 void QDocument::setLanguageDefinition(QLanguageDefinition *f)
966 {
967 if ( m_impl )
968 m_impl->m_language = f;
969 }
970
971 /*!
972 \brief Update the formatting of the whole document
973 This function is only useful when changing the language definition
974 of a non-empty document. Make sure you do not call it more often
975 than needed.
976 */
highlight()977 void QDocument::highlight()
978 {
979 if ( m_impl )
980 m_impl->emitContentsChange(0, lines());
981 }
982
983 /*!
984 \brief Add a chunk of text to the document
985 */
addChunk(const QString & txt)986 void QDocument::addChunk(const QString& txt)
987 {
988 if ( !m_impl || txt.isEmpty() )
989 return;
990
991 m_leftOver += txt;
992 int idx = 0, last = 0;
993
994 while ( idx < m_leftOver.length() )
995 {
996 if ( m_leftOver.at(idx) == '\r') {
997 ++idx;
998 if (idx >= m_leftOver.length())
999 break; //there might be a \n in the next chunk
1000 m_impl->m_lines << new QDocumentLineHandle(
1001 m_leftOver.mid(last, idx - last - 1),
1002 this
1003 );
1004 if (m_leftOver.at(idx) == '\n') {
1005 ++(m_impl->_dos);
1006 ++idx;
1007 } else ++(m_impl->_mac);
1008 last = idx;
1009 } else if ( m_leftOver.at(idx) == '\n') {
1010 ++(m_impl->_nix);
1011 m_impl->m_lines << new QDocumentLineHandle(
1012 m_leftOver.mid(last, idx - last),
1013 this
1014 );
1015 last = ++idx;
1016 } else {
1017 ++idx;
1018 }
1019 }
1020
1021 if ( idx != last )
1022 m_leftOver = m_leftOver.mid(last);
1023 else
1024 m_leftOver.clear();
1025
1026 }
1027
getFileName() const1028 QString QDocument::getFileName() const{
1029 return m_impl?m_impl->m_fileName:"";
1030 }
getFileInfo() const1031 QFileInfo QDocument::getFileInfo() const{
1032 return m_impl?m_impl->m_fileInfo:QFileInfo();
1033 }
getName() const1034 QString QDocument::getName() const{
1035 return m_impl?m_impl->m_name:"";
1036 }
setFileName_DONOTCALLTHIS(const QString & fileName)1037 void QDocument::setFileName_DONOTCALLTHIS(const QString& fileName){
1038 if (!m_impl) return;
1039 m_impl->m_fileInfo = QFileInfo(fileName);
1040 m_impl->m_fileName = m_impl->m_fileInfo.absoluteFilePath();
1041 m_impl->m_name = m_impl->m_fileInfo.fileName();
1042 }
1043
1044 /*!
1045 \brief Print the content of the document
1046 \param pr printer to use
1047
1048 \note the printer MUST be initialized (probably using a printing dialog)
1049 */
print(QPrinter * pr)1050 void QDocument::print(QPrinter *pr)
1051 {
1052 QRectF fit = pr->pageRect(QPrinter::DevicePixel);
1053
1054 if ( pr->printRange() == QPrinter::Selection )
1055 {
1056 qWarning()<<"printing selection not implemented yet";
1057 return;
1058 }
1059
1060 if ( fit.width() < width() )
1061 {
1062 // TODO: got to temporarily wrap text to fit page size
1063
1064 qWarning()<<"temporary wrapping not implementated yet";
1065 }
1066
1067 const int lineCount = lines();
1068 const int linesPerPage = qFloor(1.0*fit.height() / m_impl->m_lineSpacing);
1069 int pageCount = lineCount / linesPerPage;
1070
1071 if ( lineCount % linesPerPage )
1072 ++pageCount;
1073
1074 //qDebug("%i lines per page -> %i pages", linesPerPage, pageCount);
1075
1076 const int pageWidth = qCeil(fit.width());
1077 const int pageHeight = qCeil(1.0 * linesPerPage * m_impl->m_lineSpacing);
1078
1079 int firstPage = pr->fromPage(), lastPage = pr->toPage();
1080
1081 if ( !lastPage )
1082 lastPage = pageCount - 1;
1083
1084 QPainter p(pr);
1085 PaintContext cxt;
1086 cxt.xoffset = 0;
1087 cxt.yoffset = firstPage * pageHeight;
1088 cxt.width = pageWidth;
1089 cxt.height = 0. + pageHeight - m_impl->m_lineSpacing;
1090 cxt.palette = QApplication::palette();
1091 cxt.fillCursorRect = false;
1092 cxt.blinkingCursor = false;
1093
1094 for ( int i = firstPage; i <= lastPage; ++i )
1095 {
1096 draw(&p, cxt);
1097
1098 cxt.yoffset += pageHeight;
1099
1100 if ( i != lastPage )
1101 {
1102 pr->newPage();
1103 p.translate(0, -pageHeight);
1104 }
1105 }
1106 }
1107
1108 /*!
1109 \return The line ending policy of the document
1110
1111 The line ending policy determines how line endings
1112 are used when saving the document (which includes
1113 fetching the document's text()).
1114
1115 It can either be conservative (auto detect upon loading
1116 and do not modify when saving later on) or enforce
1117 a particular line ending (either local line ending
1118 or a specific value).
1119 */
lineEnding() const1120 QDocument::LineEnding QDocument::lineEnding() const
1121 {
1122 return m_impl ? m_impl->m_lineEnding : Local;
1123 }
1124
1125
1126 /*!
1127 \return the lin endings detected upon loading
1128
1129 This should only ever take the the Window of Linux value
1130 if a document has been loaded. If no content has been
1131 loaded it will fall back to Local.
1132 */
originalLineEnding() const1133 QDocument::LineEnding QDocument::originalLineEnding() const
1134 {
1135 if (!m_impl) return Local;
1136 if (m_impl->_dos > m_impl->_nix && m_impl->_dos > m_impl->_mac) return Windows;
1137 if (m_impl->_nix > m_impl->_dos && m_impl->_nix > m_impl->_mac) return Unix;
1138 if (m_impl->_mac > m_impl->_dos && m_impl->_mac > m_impl->_nix) return Mac;
1139 return Local;
1140 }
1141
lineEndingString() const1142 QString QDocument::lineEndingString() const{
1143 return m_impl?m_impl->m_lineEndingString:"\n";
1144 }
1145
1146 /*!
1147 \brief Set the line ending policy of the document
1148 */
setLineEnding(LineEnding le)1149 void QDocument::setLineEnding(LineEnding le){
1150 if (!m_impl) return;
1151 execute(new QDocumentChangeMetaDataCommand(this, le));
1152 }
1153
1154 /*!
1155 \brief Set the line ending policy of the document
1156 */
setLineEndingDirect(LineEnding le,bool dontSendEmit)1157 void QDocument::setLineEndingDirect(LineEnding le,bool dontSendEmit)
1158 {
1159 if ( !m_impl )
1160 return;
1161
1162 m_impl->m_lineEnding = le;
1163 QString& les = m_impl->m_lineEndingString;
1164
1165 switch ( le )
1166 {
1167 case Conservative :
1168
1169 switch (originalLineEnding()) {
1170 case Windows: les = "\r\n"; break;
1171 case Mac: les = "\r"; break;
1172 default: les = "\n";
1173 }
1174
1175 break;
1176
1177 case Local :
1178 #ifdef Q_OS_WIN
1179 les = "\r\n";
1180 //#elif defined(Q_OS_MAC)
1181 //les = "\r";
1182 #else
1183 les = "\n";
1184 #endif
1185 break;
1186
1187 case Unix :
1188 les = "\n";
1189 break;
1190
1191 case Mac :
1192 les = "\r";
1193 break;
1194
1195 case Windows :
1196 les = "\r\n";
1197 break;
1198
1199 default :
1200 les = "\n";
1201 break;
1202 }
1203
1204 if(!dontSendEmit){
1205 emit lineEndingChanged(le);
1206 }
1207 }
1208
codec() const1209 QTextCodec* QDocument::codec() const{
1210 return (m_impl && m_impl->m_codec)?m_impl->m_codec:QDocumentPrivate::m_defaultCodec;
1211 }
setCodec(QTextCodec * codec)1212 void QDocument::setCodec(QTextCodec* codec){
1213 if (!m_impl) return;
1214 execute(new QDocumentChangeMetaDataCommand(this, codec));
1215 }
setCodecDirect(QTextCodec * codec)1216 void QDocument::setCodecDirect(QTextCodec* codec){
1217 if (!m_impl) return;
1218 m_impl->m_codec=codec;
1219 }
1220
isReadOnly() const1221 bool QDocument::isReadOnly() const
1222 {
1223 if (!m_impl) return true;
1224 return m_impl->m_readOnly;
1225 }
1226
setReadOnly(bool b)1227 void QDocument::setReadOnly(bool b)
1228 {
1229 if (!m_impl) return;
1230 m_impl->m_readOnly = b;
1231 }
1232
1233
1234 /*!
1235 \return the font used by ALL documents to render their content
1236
1237 This font is also used to do calculations (such as converting
1238 (line, column) cursor position to (x, y) document position (or
1239 the inverse transformation))
1240
1241 \note this limitation is historic and may disappear
1242 in future versions
1243 */
font()1244 QFont QDocument::font()
1245 {
1246 return *(QDocumentPrivate::m_font);
1247 }
1248
baseFont()1249 QFont QDocument::baseFont()
1250 {
1251 return *(QDocumentPrivate::m_baseFont);
1252 }
1253
fontSizeModifier()1254 int QDocument::fontSizeModifier()
1255 {
1256 return QDocumentPrivate::m_fontSizeModifier;
1257 }
1258
1259 /*!
1260 \brief Set the font of ALL documents
1261
1262 \note this limitation is historic and may disappear
1263 in future versions
1264 */
setBaseFont(const QFont & f,bool forceUpdate)1265 void QDocument::setBaseFont(const QFont& f, bool forceUpdate)
1266 {
1267 QDocumentPrivate::setBaseFont(f, forceUpdate);
1268 //emit contentsChanged();
1269 }
1270
1271 /*!
1272 \brief A constant to modify the pointSize of the current font. This value is used for zooming.
1273
1274 It holds font.pointSize = baseFont.pointSize + fontSizeModifier
1275 \param n
1276 */
setFontSizeModifier(int m,bool forceUpdate)1277 void QDocument::setFontSizeModifier(int m, bool forceUpdate)
1278 {
1279 QDocumentPrivate::setFontSizeModifier(m, forceUpdate);
1280 }
1281
1282 /*!
1283 \return The font metrics used by ALL documents
1284
1285 \note this limitation is historic and may disappear
1286 in future versions
1287 */
1288 /*const QFontMetrics QDocument::fontMetrics()
1289 {
1290 return QFontMetrics(*QDocumentPrivate::m_font);
1291 }*/
1292
1293 /*!
1294 \return The line spacing used by ALL documents, it is identically to
1295 the vertical distance of the top pixels of two non-wrapped, successive lines
1296
1297 \note this limitation is historic and may disappear
1298 in future versions
1299 */
getLineSpacing()1300 qreal QDocument::getLineSpacing()
1301 {
1302 return QDocumentPrivate::m_lineSpacing;
1303 }
1304
setLineSpacingFactor(double scale)1305 void QDocument::setLineSpacingFactor(double scale)
1306 {
1307 if(qFuzzyCompare(scale,QDocumentPrivate::m_lineSpacingFactor)){
1308 return; // don't set fonts when spacing is not changed !
1309 }
1310 QDocumentPrivate::m_lineSpacingFactor = (scale<1.0)?1.0:scale;
1311
1312 if ( !QDocumentPrivate::m_font ) return;
1313
1314 // update m_leading and m_lineSpacing
1315 QDocumentPrivate::setFont(*QDocumentPrivate::m_font, true);
1316 // It's a bit more costly than necessary, because we do not change any width.
1317 // If performance needs improvement, one could extract the height calculation
1318 // to a separate method and call it here and in setFont.
1319 // Then we need to notify the documents for the changes:
1320 // foreach ( QDocumentPrivate *d, QDocumentPrivate::m_documents )
1321 // d->emitFormatsChanged();
1322 }
1323
setCenterDocumentInEditor(bool center)1324 void QDocument::setCenterDocumentInEditor(bool center)
1325 {
1326 m_impl->setCenterDocumentInEditor(center);
1327 }
1328
1329 /*!
1330 \return The default tab stop common to ALL documents
1331
1332 \note this limitation is historic and may disappear
1333 in future versions
1334 */
tabStop()1335 int QDocument::tabStop()
1336 {
1337 return QDocumentPrivate::m_defaultTabStop;
1338 }
1339
1340 /*!
1341 \brief Set the default tab stop common to all documents
1342
1343 \note this limitation is historic and may disappear
1344 in future versions
1345 */
setTabStop(int n)1346 void QDocument::setTabStop(int n)
1347 {
1348 QDocumentPrivate::m_defaultTabStop = n;
1349
1350 foreach ( QDocumentPrivate *d, QDocumentPrivate::m_documents )
1351 {
1352 d->m_tabStop = n;
1353 d->emitFormatsChanged();
1354 }
1355 }
1356
1357 /*!
1358 \return the whitesapce display mode
1359 */
showSpaces()1360 QDocument::WhiteSpaceMode QDocument::showSpaces()
1361 {
1362 return QDocumentPrivate::m_showSpaces;
1363 }
1364
1365 /*!
1366 \brief Set the whitespace display mode
1367 */
setShowSpaces(WhiteSpaceMode m)1368 void QDocument::setShowSpaces(WhiteSpaceMode m)
1369 {
1370 QDocumentPrivate::m_showSpaces = m;
1371
1372 foreach ( QDocumentPrivate *d, QDocumentPrivate::m_documents )
1373 d->emitFormatsChanged();
1374
1375 }
1376
1377 /*!
1378 \brief Set the edit cursor
1379
1380 Archaic concept designed for use in QEditor
1381 (is it still used???)
1382 */
editCursor() const1383 QDocumentCursor* QDocument::editCursor() const
1384 {
1385 return m_impl ? m_impl->m_editCursor : nullptr;
1386 }
1387
1388 /*!
1389 \brief Set the edit cursor
1390
1391 \see editCursor()
1392 */
setEditCursor(QDocumentCursor * c)1393 void QDocument::setEditCursor(QDocumentCursor *c)
1394 {
1395 if ( m_impl )
1396 m_impl->m_editCursor = c;
1397
1398 }
1399
1400 /*!
1401 \return the width of the document, in pixels
1402
1403 The width of the document is that of longest text line,
1404 or the maximal width if a width constraint is set.
1405 */
width() const1406 qreal QDocument::width() const
1407 {
1408 return m_impl ? m_impl->m_width : 0;
1409 }
1410
1411 /*!
1412 \return the height of the document, in pixels
1413 */
height() const1414 qreal QDocument::height() const
1415 {
1416 return m_impl ? m_impl->m_height : 0;
1417 }
1418
1419 /*!
1420 \return The width constraint imposed on that document
1421
1422 Setting a width constraint on a document achieves line
1423 wrapping.
1424 */
widthConstraint() const1425 qreal QDocument::widthConstraint() const
1426 {
1427 return (m_impl && m_impl->m_constrained) ? m_impl->m_width : 100000000;
1428 }
1429
1430 /*!
1431 \return the number of text lines in the document
1432
1433 The number of visual lines may differ from that of text
1434 lines as soon as line wrapping and/or folding are enabled.
1435
1436 \deprecated Use lineCount() instead
1437 */
lines() const1438 int QDocument::lines() const
1439 {
1440 return m_impl ? m_impl->m_lines.count() : 0;
1441 }
1442
1443 /*!
1444 \return the number of text lines in the document
1445
1446 The number of visual lines may differ from that of text
1447 lines as soon as line wrapping and/or folding are enabled.
1448 */
lineCount() const1449 int QDocument::lineCount() const
1450 {
1451 return m_impl ? m_impl->m_lines.count() : 0;
1452 }
1453
1454 /*!
1455 \return the number of visual lines in the document
1456 \deprecated Use visualLineCount() instead
1457 */
visualLines() const1458 int QDocument::visualLines() const
1459 {
1460 return m_impl ? m_impl->visualLine(m_impl->m_lines.count() - 1) : 0;
1461 }
1462
1463 /*!
1464 \return the number of visual lines in the document
1465 */
visualLineCount() const1466 int QDocument::visualLineCount() const
1467 {
1468 return m_impl ? m_impl->visualLine(m_impl->m_lines.count() - 1) : 0;
1469 }
1470
1471 /*!
1472 \brief Convert a text (logical) line number int a visual line number
1473
1474 \note this is not a 1:1 mapping as logical lines can span over several visual lines
1475 */
visualLineNumber(int textLineNumber) const1476 int QDocument::visualLineNumber(int textLineNumber) const
1477 {
1478 return m_impl ? m_impl->visualLine(textLineNumber) : -1;
1479 }
1480
1481 /*!
1482 \brief Convert a visual line number int a text (logical) line number
1483
1484 \note this is not a 1:1 mapping as logical lines can span over several visual lines
1485 */
textLineNumber(int visualLineNumber) const1486 int QDocument::textLineNumber(int visualLineNumber) const
1487 {
1488 return m_impl ? m_impl->textLine(visualLineNumber) : -1;
1489 }
1490
1491 /*!
1492 \brief Clear the width constraint, if any
1493 */
clearWidthConstraint()1494 void QDocument::clearWidthConstraint()
1495 {
1496 if ( m_impl )
1497 m_impl->setWidth(0);
1498 }
1499
1500 /*!
1501 \brief Set a new width constraint
1502 \param width maximum width to allow
1503
1504 Passing a value inferior (or equal) to zero clear the width constraint, if any.
1505 */
setWidthConstraint(int width)1506 void QDocument::setWidthConstraint(int width)
1507 {
1508 if ( m_impl )
1509 m_impl->setWidth(qMax(0, width));
1510 }
1511
markFormatCacheDirty()1512 void QDocument::markFormatCacheDirty(){
1513 if ( m_impl )
1514 m_impl->markFormatCacheDirty();
1515 }
1516
1517 /*!
1518 \return the line object at a given line number
1519 \param line Text line to acces
1520 */
line(int line) const1521 QDocumentLine QDocument::line(int line) const
1522 {
1523 return QDocumentLine(m_impl ? m_impl->at(line) : nullptr);
1524 }
1525
1526 /*!
1527 \return the line number corresponding to a given document y coordinate
1528 \param ypos Y document coordinate of the target
1529 \param wrap if not null, will be set to the wrap offset (position of the
1530 visual line among the sublines of the wrapped text line).
1531
1532 */
lineNumber(qreal ypos,int * wrap) const1533 int QDocument::lineNumber(qreal ypos, int *wrap) const
1534 {
1535 int ln = qRound(ypos / m_impl->m_lineSpacing -0.45);
1536
1537 return m_impl->textLine(ln, wrap);
1538 }
1539
1540 /*!
1541 \return the line object to which an iterator points
1542 */
line(QDocumentConstIterator iterator) const1543 QDocumentLine QDocument::line(QDocumentConstIterator iterator) const
1544 {
1545 return (m_impl && (m_impl->constEnd() != iterator)) ? QDocumentLine(*iterator) : QDocumentLine();
1546 }
1547
1548 /*! \return Line number of an handle */
indexOf(const QDocumentLineHandle * h,int hint) const1549 int QDocument::indexOf(const QDocumentLineHandle* h, int hint) const{
1550 return m_impl->indexOf(h, hint);
1551 }
1552
indexOf(const QDocumentLine & l,int hint) const1553 int QDocument::indexOf(const QDocumentLine& l, int hint) const{
1554 return m_impl->indexOf(l.handle(), hint);
1555 }
1556
1557 /*!
1558 \return A cursor operating on the document, placed at a given position
1559 This method has three functions:
1560 cursor(l, c) Creates a cursor at (l, c)
1561 cursor(la, ca, lt) Creates a cursor with anchor (la, ca) selecting to (lt, length of lt)
1562 cursor(la, ca, lt, ct) Creates a cursor with anchor (la, ca) selecting to (lt, lc)
1563
1564 \param line cursor line number (text line)
1565 \param column cursor text column
1566 \param lineTo selection ending line. If this is not given, it is set to line
1567 \param columnTo selection ending column. If this is not given it is set to the end of lineTo (if that is given) or to column
1568 */
cursor(int line,int column,int lineTo,int columnTo) const1569 QDocumentCursor QDocument::cursor(int line, int column, int lineTo, int columnTo) const
1570 {
1571 return QDocumentCursor(const_cast<QDocument*>(this), line, column, lineTo, columnTo);
1572 }
1573
1574
1575 /*!
1576 \return the document line which contains a given (document-wide) text position
1577
1578 \note The sole purpose of this method is to have an API close to that of QTextDocument.
1579 This method being ridiculously slow it is recommended to avoid it whenever possible.
1580 */
findLine(int & position) const1581 QDocumentLine QDocument::findLine(int& position) const
1582 {
1583 if ( !m_impl )
1584 return QDocumentLine();
1585
1586 return QDocumentLine(m_impl->lineForPosition(position));
1587 }
1588
findLineContaining(const QString & searchText,const int & startLine,const Qt::CaseSensitivity cs,const bool backward) const1589 int QDocument::findLineContaining(const QString &searchText, const int& startLine, const Qt::CaseSensitivity cs, const bool backward) const{
1590 if(backward){
1591 for (int i=startLine;i>-1;i--)
1592 if(line(i).text().contains(searchText,cs))
1593 return i;
1594 } else {
1595 for (int i=startLine;i<lines();i++)
1596 if(line(i).text().contains(searchText,cs))
1597 return i;
1598 }
1599 return -1;
1600 }
1601
findLineRegExp(const QString & searchText,const int & startLine,const Qt::CaseSensitivity cs,const bool wholeWord,const bool useRegExp) const1602 int QDocument::findLineRegExp(const QString &searchText, const int& startLine, const Qt::CaseSensitivity cs, const bool wholeWord, const bool useRegExp) const{
1603
1604 QRegExp m_regexp=generateRegExp(searchText,cs==Qt::CaseSensitive,wholeWord,useRegExp);
1605
1606 for (int i=startLine;i<lines();i++){
1607 if(m_regexp.indexIn(line(i).text(),0)>-1)
1608 return i;
1609 }
1610
1611 return -1;
1612 }
1613
1614 /*!
1615 * \brief finds the closest line with text equal to lineText by interleavingly checking lines before and after startLine
1616 * \param lineText
1617 * \param startLine line number to start from
1618 * \return line number of the found line, else -1
1619 */
findNearLine(const QString & lineText,int startLine) const1620 int QDocument::findNearLine(const QString &lineText, int startLine) const {
1621 int lineNum = - 1;
1622 for (int delta=0; delta<lines(); delta++) {
1623 if (startLine+delta < lines()) {
1624 if (line(startLine+delta).text() == lineText) {
1625 lineNum = startLine + delta;
1626 break;
1627 }
1628 }
1629 if (startLine-delta >= 0) {
1630 if (line(startLine-delta).text() == lineText) {
1631 lineNum = startLine - delta;
1632 break;
1633 }
1634 }
1635 }
1636 return lineNum;
1637 }
1638
1639 /*!
1640 \return The Y document coordinate of a given line
1641 \param ln textual line number
1642 */
y(int ln) const1643 qreal QDocument::y(int ln) const
1644 {
1645 if ( !m_impl )
1646 return -1;
1647
1648 return m_impl->m_lineSpacing * m_impl->visualLine(ln);
1649 }
1650
1651 /*!
1652 \return the rectangle (in document position) occupied by the line
1653 \param line textual line number
1654
1655 \note the width of the returned rectangle is the DOCUMENT's width
1656 */
lineRect(int line) const1657 QRectF QDocument::lineRect(int line) const
1658 {
1659 const qreal yoff = y(line);
1660
1661 return (yoff != -1) ? QRectF(0, yoff, width(), 1. * this->line(line).lineSpan() * m_impl->m_lineSpacing) : QRectF();
1662 }
1663
1664 /*!
1665 \return the line at a given document position
1666 */
lineAt(const QPointF & p) const1667 QDocumentLine QDocument::lineAt(const QPointF& p) const
1668 {
1669 if ( !m_impl )
1670 return QDocumentLine();
1671
1672 return line(lineNumber(p.y()));
1673 }
1674
1675 /*!
1676 \return a document iterator pointing to the first line
1677 */
begin() const1678 QDocumentConstIterator QDocument::begin() const
1679 {
1680 Q_ASSERT(m_impl);
1681
1682 return m_impl->m_lines.constBegin();
1683 }
1684
1685 /*!
1686 \return a document iterator pointing past the last line
1687 */
end() const1688 QDocumentConstIterator QDocument::end() const
1689 {
1690 Q_ASSERT(m_impl);
1691
1692 return m_impl->m_lines.constEnd();
1693 }
1694
1695 /*!
1696 \return a document iterator pointing to a given line
1697 */
iterator(int ln) const1698 QDocumentConstIterator QDocument::iterator(int ln) const
1699 {
1700 Q_ASSERT(m_impl);
1701
1702 return begin() + ln;
1703 }
1704
1705 /*!
1706 \overload
1707 \note If you can avoid using this method, do so unless performance really isn't an issue
1708 */
iterator(const QDocumentLine & l) const1709 QDocumentConstIterator QDocument::iterator(const QDocumentLine& l) const
1710 {
1711 Q_ASSERT(m_impl);
1712
1713 QDocumentConstIterator it = begin(), e = end();
1714
1715 while ( (*it != l.handle()) && (it != e) )
1716 ++it;
1717
1718 return it;
1719 }
1720
1721 /*!
1722 \brief Convert a document (or viewport) (x, y) position to a (line, column) cursor position
1723 \param p document position
1724 \param line where the line number will be stored
1725 \param column where the column (text position within line) will be stored
1726 */
cursorForDocumentPosition(const QPointF & p,int & line,int & column,bool disallowPositionBeyondLine) const1727 void QDocument::cursorForDocumentPosition(const QPointF& p, int& line, int& column, bool disallowPositionBeyondLine) const
1728 {
1729 if ( !m_impl )
1730 return;
1731
1732 int wrap = 0;
1733 line = lineNumber(p.y(), &wrap);
1734 QDocumentLine l = this->line(line);
1735
1736 if ( !l.isValid() )
1737 return;
1738
1739 //qDebug("%i %i", line, wrap);
1740 column = l.documentOffsetToCursor(p.x(), 1. * wrap * QDocumentPrivate::m_lineSpacing,disallowPositionBeyondLine);
1741
1742 //qDebug("(%i, %i) -> (%i [+%i], %i)", p.x(), p.y(), line, wrap, column);
1743 }
1744
1745 /*!
1746 \return The cursor nearest to a document (x, y) position
1747 */
cursorAt(const QPointF & p,bool disallowPositionBeyondLine) const1748 QDocumentCursor QDocument::cursorAt(const QPointF& p, bool disallowPositionBeyondLine) const
1749 {
1750 int ln = -1, col = -1;
1751
1752 cursorForDocumentPosition(p, ln, col,disallowPositionBeyondLine);
1753
1754 return QDocumentCursor(const_cast<QDocument*>(this), ln, col);
1755 }
1756
1757 /*!
1758 \brief Draw the contents of the document
1759 \param p painter to use
1760 \param cxt paint context (specifies what part of the document to draw, among other things)
1761 */
draw(QPainter * p,PaintContext & cxt)1762 void QDocument::draw(QPainter *p, PaintContext& cxt)
1763 {
1764 m_impl->draw(p, cxt);
1765 }
1766
1767 /*!
1768 \brief Creates a html document from the code (incuding highlighting)
1769 \param maxLineWidth long lines are wrapped by introducing <br> after after at most this many chars.
1770 \param maxWrap number of <br> wrappings that can be introduced by maxLineWidth limit before a document line is truncated with "..."
1771 */
exportAsHtml(const QDocumentCursor & range,bool includeFullHeader,bool simplifyCSS,int maxLineWidth,int maxWrap) const1772 QString QDocument::exportAsHtml(const QDocumentCursor& range, bool includeFullHeader, bool simplifyCSS, int maxLineWidth, int maxWrap) const{
1773 return m_impl->exportAsHtml(range.isValid()?range:cursor(0,0,lineCount()-1), includeFullHeader, simplifyCSS, maxLineWidth, maxWrap);
1774 }
1775
1776 /*!
1777 \brief Execute a document command (editing operation)
1778 */
execute(QDocumentCommand * cmd)1779 void QDocument::execute(QDocumentCommand *cmd)
1780 {
1781 Q_ASSERT(m_impl || !cmd);
1782 if ( m_impl && cmd && !isReadOnly() )
1783 m_impl->execute(cmd);
1784
1785 }
1786
1787 /*!
1788 \return The default line ending policy
1789 */
defaultLineEnding()1790 QDocument::LineEnding QDocument::defaultLineEnding()
1791 {
1792 return QDocumentPrivate::m_defaultLineEnding;
1793 }
1794
1795 /*!
1796 \brief Set the default line ending policy
1797
1798 \note The line ending policy of existing documents is changed accordingly
1799 */
setDefaultLineEnding(QDocument::LineEnding le)1800 void QDocument::setDefaultLineEnding(QDocument::LineEnding le)
1801 {
1802 QDocumentPrivate::m_defaultLineEnding = le;
1803
1804 foreach ( QDocumentPrivate *d, QDocumentPrivate::m_documents )
1805 {
1806 d->m_doc->setLineEndingDirect(le);
1807 }
1808 }
1809
1810 /*!
1811 \return The default text codec used to load and save document contents
1812
1813 \note a null pointer indicates auto detection
1814 */
defaultCodec()1815 QTextCodec* QDocument::defaultCodec(){
1816 return QDocumentPrivate::m_defaultCodec;
1817 }
setDefaultCodec(QTextCodec * codec)1818 void QDocument::setDefaultCodec(QTextCodec* codec){
1819 QDocumentPrivate::m_defaultCodec=codec;
1820 }
1821
addGuessEncodingCallback(const GuessEncodingCallback & callback)1822 void QDocument::addGuessEncodingCallback(const GuessEncodingCallback& callback){
1823 if ( !guessEncodingCallbacks.contains(callback) )
1824 guessEncodingCallbacks.append(callback);
1825 }
removeGuessEncodingCallback(const GuessEncodingCallback & callback)1826 void QDocument::removeGuessEncodingCallback(const GuessEncodingCallback& callback){
1827 guessEncodingCallbacks.removeAll(callback);
1828 }
1829 /*!
1830 \return The default format scheme used to draw document contents
1831 */
defaultFormatScheme()1832 QFormatScheme* QDocument::defaultFormatScheme()
1833 {
1834 return QDocumentPrivate::m_defaultFormatScheme;
1835 }
1836
1837 /*!
1838 \brief Set the default format scheme
1839
1840 \note Existing documents using the default format scheme will see their format scheme changed
1841 */
setDefaultFormatScheme(QFormatScheme * f)1842 void QDocument::setDefaultFormatScheme(QFormatScheme *f)
1843 {
1844 foreach ( QDocumentPrivate *d, QDocumentPrivate::m_documents )
1845 {
1846 if ( d->m_formatScheme == QDocumentPrivate::m_defaultFormatScheme )
1847 d->setFormatScheme(f);
1848 }
1849
1850 QDocumentPrivate::m_defaultFormatScheme = f;
1851 }
1852
getFormatId(const QString & id)1853 int QDocument::getFormatId(const QString& id){
1854 if (!m_impl) return 0;
1855 QFormatScheme *scheme = formatScheme();
1856 if (!scheme) scheme = QDocument::defaultFormatScheme();
1857 if (!scheme) return 0;
1858 return scheme->id(id);
1859 }
1860
1861 /*!
1862 \brief Begin a macro
1863
1864 Macro in QDocument map directly to macro in the underlying undo stack
1865 */
beginMacro()1866 void QDocument::beginMacro()
1867 {
1868 if ( m_impl )
1869 m_impl->beginChangeBlock();
1870
1871 }
1872
1873 /*!
1874 \brief End a macro
1875 */
endMacro()1876 void QDocument::endMacro()
1877 {
1878 if ( m_impl )
1879 m_impl->endChangeBlock();
1880
1881 }
1882
1883
beginDelayedUpdateBlock()1884 void QDocument::beginDelayedUpdateBlock(){
1885 if ( m_impl )
1886 m_impl->beginDelayedUpdateBlock();
1887 }
1888
endDelayedUpdateBlock()1889 void QDocument::endDelayedUpdateBlock(){
1890 if ( m_impl )
1891 m_impl->endDelayedUpdateBlock();
1892 }
1893
1894
1895 /*!
1896 \brief Is a macro active
1897 */
hasMacros()1898 bool QDocument::hasMacros(){
1899 return m_impl?m_impl->hasChangeBlocks():false;
1900 }
1901
1902 /*!
1903 \brief Get an available group id for matches
1904 */
getNextGroupId()1905 int QDocument::getNextGroupId()
1906 {
1907 return m_impl ? m_impl->getNextGroupId() : -1;
1908 }
1909
releaseGroupId(int groupId)1910 void QDocument::releaseGroupId(int groupId)
1911 {
1912 if ( m_impl )
1913 m_impl->releaseGroupId(groupId);
1914 }
1915
1916 /*!
1917 \brief Clear matches
1918 */
clearMatches(int gid)1919 void QDocument::clearMatches(int gid)
1920 {
1921 if ( m_impl )
1922 {
1923 m_impl->clearMatches(gid);
1924 }
1925 }
1926
1927 /*void QDocument:: clearMatchesFromToWhenFlushing(int groupId, int firstMatch, int lastMatch);
1928 if ( m_impl )
1929 {
1930 m_impl->clearMatchesFromToWhenFlushing(gid,firstMatch,lastMatch);
1931 }
1932 }*/
1933
1934 /*!
1935 \brief Highlight the matched sequences
1936
1937 \note Both position are BEFORE the matched characters (cursor position).
1938 */
addMatch(int gid,int line,int pos,int len,int format)1939 void QDocument::addMatch(int gid, int line, int pos, int len, int format)
1940 {
1941 if ( m_impl )
1942 {
1943 m_impl->addMatch(gid, line, pos, len, format);
1944 }
1945 }
1946
1947 /*!
1948 \
1949 */
flushMatches(int gid)1950 void QDocument::flushMatches(int gid)
1951 {
1952 if ( m_impl )
1953 {
1954 m_impl->flushMatches(gid);
1955 }
1956 }
1957
1958
clearLanguageMatches()1959 void QDocument::clearLanguageMatches()
1960 {
1961 if (languageDefinition()) languageDefinition()->clearMatches(this);
1962 }
1963
1964 /*!
1965 \return Whether the document is in a clean state
1966
1967 This is undo stak terminology. A clean document is one
1968 whose undo stack is at the same index it was upon last
1969 save/load. In other words : clean = !modified
1970 */
isClean() const1971 bool QDocument::isClean() const
1972 {
1973 return m_impl ? m_impl->m_commands.isClean() : true;
1974 }
1975
expand(int line)1976 void QDocument::expand(int line){
1977 if (!languageDefinition()) return;
1978 languageDefinition()->expand(this, line);
1979 }
collapse(int line)1980 void QDocument::collapse(int line){
1981 if (!languageDefinition()) return;
1982 languageDefinition()->collapse(this, line);
1983 }
expandParents(int l)1984 void QDocument::expandParents(int l){
1985 if (!languageDefinition()) return;
1986 int prevLine=-1;
1987 while (line(l).isHidden()) {
1988 QMap<int,int>::const_iterator it=m_impl->m_hidden.upperBound(prevLine);
1989 prevLine=-1;
1990 for (;it!=m_impl->m_hidden.constEnd();++it){
1991 if (it.key()<l && it.key()+ it.value()>=l){
1992 prevLine=it.key();
1993 expand(it.key());
1994 break;
1995 }
1996 }
1997 if (prevLine==-1) //don't loop forever
1998 break;
1999 }
2000 }
2001
2002 //Collapse at the first possible point before/at the line
foldBlockAt(bool unFold,int l)2003 void QDocument::foldBlockAt(bool unFold, int l) {
2004 if (unFold) {
2005 //search nearest line which is folded and unfold it
2006 for (;l>=0;l--)
2007 if (line(l).hasFlag(QDocumentLine::CollapsedBlockStart)) {
2008 languageDefinition()->expand(this,l);
2009 break;
2010 }
2011 } else {
2012 //search latest line before l which can be folded and is not folded
2013 int foldAt=-1;
2014 QFoldedLineIterator fli = languageDefinition()->foldedLineIterator(this);
2015 while (fli.lineNr<=l) {
2016 if (fli.open && !fli.collapsedBlockStart) foldAt=fli.lineNr;
2017 ++fli;
2018 }
2019 //fold it
2020 if (foldAt!=-1)
2021 languageDefinition()->collapse(this,foldAt);
2022 }
2023 }
2024
2025 /*!
2026 (Internal method), returns if one of the lines between from and to (inclusive) belong to
2027 a folded block (they can still be all visible if one of them starts/ends a hidden block)
2028 */
linesPartiallyFolded(int fromInc,int toInc)2029 bool QDocument::linesPartiallyFolded(int fromInc, int toInc){
2030 while ( fromInc <= toInc )
2031 {
2032 if (line(fromInc).hasAnyFlag(QDocumentLine::Hidden | QDocumentLine::CollapsedBlockStart | QDocumentLine::CollapsedBlockEnd) )
2033 return true;
2034
2035 ++fromInc;
2036 }
2037 return false;
2038 }
2039
2040 /*!
2041 Correct the folding
2042 i.e., it ensures that no line is hidden which is not in an collapsable block
2043 (useful if the blocks have changed)
2044 */
correctFolding(int fromInc,int toInc,bool forceCorrection)2045 void QDocument::correctFolding(int fromInc, int toInc, bool forceCorrection){
2046 Q_UNUSED(fromInc)
2047 Q_UNUSED(toInc)
2048 //TODO: Optimize it, use fromInc/toInc to handle it locally (problem: behaviour of closing brackets depends on open brackets
2049 // earlier in the document), merge the redunant folding correction in removeLines to this (problem: if all folded
2050 // lines are removed, the hidden flags which are checked by ld->correctFolding are already all correct, but the m_hidden
2051 // map won't be correct if it has not been corrected by the checks in removeLines)
2052
2053 QLanguageDefinition* ld=languageDefinition();
2054 if (!ld)
2055 return;
2056
2057 bool cf = ld->correctFolding(this);
2058 if (!cf && !forceCorrection)
2059 return;
2060
2061 //recalculate the map of hidden lines (=cache) from the hidden flag of the lines
2062 m_impl->m_hidden.clear();
2063 QList<QPair<int,int> > blockStartList;
2064 for (QFoldedLineIterator fli = ld->foldedLineIterator(this); fli.line.isValid(); ++fli){
2065 if (fli.collapsedBlockEnd){
2066 Q_ASSERT(!blockStartList.empty());
2067 int c=fli.close;
2068 while (blockStartList.size()>0 && blockStartList.last().second<=c){
2069 c-=blockStartList.last().second;
2070 if (fli.hiddenCollapsedBlockEnd)
2071 m_impl->m_hidden.insert(blockStartList.last().first, fli.lineNr-blockStartList.last().first);
2072 else
2073 m_impl->m_hidden.insert(blockStartList.last().first, fli.lineNr-blockStartList.last().first-1);
2074 blockStartList.removeLast();
2075 }
2076 if (c>0 && !blockStartList.empty()) blockStartList.last().second-=c;
2077 }
2078 if (fli.collapsedBlockStart)
2079 blockStartList << QPair<int,int>(fli.lineNr, fli.open);
2080 }
2081 for (int i=0;i<blockStartList.size();i++)
2082 m_impl->m_hidden.insert(blockStartList[i].first,lines()-1-blockStartList[i].first);
2083
2084 m_impl->setHeight();
2085 //emitFormatsChange(line, count);
2086 m_impl->emitFormatsChanged();
2087 }
2088
foldedLines()2089 QList<int> QDocument::foldedLines() {
2090 QList<int> lines;
2091 QLanguageDefinition *ld = languageDefinition();
2092 if (ld) {
2093 QFoldedLineIterator fli = ld->foldedLineIterator(this);
2094 for (; fli.lineNr<this->lineCount(); ++fli) {
2095 if (fli.collapsedBlockStart) lines << fli.lineNr;
2096 }
2097 }
2098 return lines;
2099 }
2100
2101 // fold only lines if they are at the exact specified positions
foldLines(QList<int> & lines)2102 void QDocument::foldLines(QList<int> &lines) {
2103 if (lines.isEmpty())
2104 return;
2105
2106 std::sort(lines.begin(),lines.end());
2107 QFoldedLineIterator fli = languageDefinition()->foldedLineIterator(this);
2108 while (fli.lineNr <= lines.last()) {
2109 if (fli.open && !fli.collapsedBlockStart && lines.contains(fli.lineNr)) {
2110 languageDefinition()->collapse(this, fli.lineNr);
2111 }
2112 ++fli;
2113 }
2114 }
2115
adjustWidth(int line)2116 void QDocument::adjustWidth(int line){
2117 if (m_impl) m_impl->adjustWidth(line);
2118 }
2119
2120 /*!
2121 \brief Set the document to clean state
2122
2123 This method does not go back to clean state but tell
2124 the stack that the current state is to be considered
2125 as the clean state.
2126 */
setClean()2127 void QDocument::setClean()
2128 {
2129 if ( m_impl )
2130 {
2131 m_impl->m_commands.setClean();
2132 //m_impl->m_status.clear();
2133
2134 QHash<QDocumentLineHandle*, QPair<int, int> >::iterator it = m_impl->m_status.begin();
2135
2136 while ( it != m_impl->m_status.end() )
2137 {
2138 it->second = it->first;
2139 ++it;
2140 }
2141 }
2142 }
2143
2144 /*!
2145 \return Whether a given line has been modified since last save/load
2146 */
isLineModified(const QDocumentLine & l) const2147 bool QDocument::isLineModified(const QDocumentLine& l) const
2148 {
2149 if ( !m_impl )
2150 return false;
2151
2152 QHash<QDocumentLineHandle*, QPair<int, int> >::const_iterator it = m_impl->m_status.constFind(l.handle());
2153
2154 //if ( it != m_impl->m_status.constEnd() )
2155 // qDebug("%i vs %i", it->first, it->second);
2156
2157 return it != m_impl->m_status.constEnd() ? it->first != it->second : false;
2158 }
2159
2160 /*!
2161 \return Whether a given line has been modified since last load
2162 */
hasLineEverBeenModified(const QDocumentLine & l) const2163 bool QDocument::hasLineEverBeenModified(const QDocumentLine& l) const
2164 {
2165 return m_impl ? m_impl->m_status.contains(l.handle()) : false;
2166 }
2167
2168 /*!
2169 \return the maximum number of marks on a single line
2170
2171 This is meant for the line mark panel to smartly adapt its size.
2172 */
maxMarksPerLine() const2173 int QDocument::maxMarksPerLine() const
2174 {
2175 return m_impl ? m_impl->maxMarksPerLine() : 0;
2176 }
2177
2178 /*!
2179 \brief Find the next mark of a given type
2180 \return the line on which a mark was found
2181 \param id mark identifier to find
2182 \param from line from which to start search
2183 \param until line until which to search
2184
2185 \a from and \a until can be negatives, in which case they
2186 indicate positions from the end of the document (i.e -1 is
2187 last line, -2 the line before last line and so on).
2188
2189 If \a until is inferior to \a from and no matching mark
2190 is found in the [from, end] range then the [0, until]
2191 range will also be searched.
2192 */
findNextMark(int id,int from,int until) const2193 int QDocument::findNextMark(int id, int from, int until) const
2194 {
2195 return m_impl ? m_impl->findNextMark(id, from, until) : -1;
2196 }
2197
2198 /*!
2199 \brief Find the previous mark of a given type
2200 \return the line on which a mark was found
2201 \param id mark identifier to find
2202 \param from line from which to start search
2203 \param until line until which to search
2204
2205 \a from and \a until can be negatives, in which case they
2206 indicate positions from the end of the document (i.e -1 is
2207 last line, -2 the line before last line and so on).
2208
2209 If \a until is superior to \a from and no matching mark
2210 is found in the [0, from] range then the [until, end]
2211 range will also be searched (both range being searched backward,
2212 of course).
2213 */
findPreviousMark(int id,int from,int until) const2214 int QDocument::findPreviousMark(int id, int from, int until) const
2215 {
2216 return m_impl ? m_impl->findPreviousMark(id, from, until) : -1;
2217 }
2218
removeMarks(int id)2219 void QDocument::removeMarks(int id){
2220 if (m_impl) m_impl->removeMarks(id);
2221 }
2222
marks(QDocumentLineHandle * dlh) const2223 QList<int> QDocument::marks(QDocumentLineHandle *dlh) const
2224 {
2225 //return QList<int>() << 1; //testcase
2226
2227 return m_impl ? m_impl->marks(dlh) : QList<int>();
2228 }
2229
removeMark(QDocumentLineHandle * dlh,int mid)2230 void QDocument::removeMark(QDocumentLineHandle *dlh, int mid){
2231 if(m_impl)
2232 m_impl->removeMark(dlh,mid);
2233 }
2234
2235 /*!
2236 \return the date/time of the last modification of the document
2237
2238 If the document has not been modified since its load the date/time
2239 of last modification (as reported by QFileInfo) will be returned.
2240 */
lastModified() const2241 QDateTime QDocument::lastModified() const
2242 {
2243 return m_impl ? m_impl->m_lastModified : QDateTime();
2244 }
2245
2246 /*!
2247 \brief set the date/time of the last modification of the document
2248
2249 You should not need to use that EVER. It is only provided for use
2250 in QEditor (and possibly in some panels).
2251 */
setLastModified(const QDateTime & d)2252 void QDocument::setLastModified(const QDateTime& d)
2253 {
2254 if ( m_impl )
2255 m_impl->m_lastModified = d;
2256 }
2257
setOverwriteMode(bool overwrite)2258 void QDocument::setOverwriteMode(bool overwrite)
2259 {
2260 if(m_impl)
2261 m_impl->setOverwriteMode(overwrite);
2262 }
2263
2264 /////////////////////////
2265 // QDocumentLineHandle
2266 /////////////////////////
2267 //static quint32 q_line_counter = 0;
2268
2269 /*!
2270 \class QDocumentLineHandle
2271 \brief Private implementation of a document line
2272 */
2273
2274 /*!
2275 \
2276 */
QDocumentLineHandle(QDocument * d)2277 QDocumentLineHandle::QDocumentLineHandle(QDocument *d)
2278 : m_doc(d)
2279 , m_ref(1)
2280 , m_indent(0)
2281 , m_state(QDocumentLine::LayoutDirty)
2282 , m_layout(nullptr)
2283 , lineHasSelection(QDocumentLineHandle::noSel)
2284 , mTicket(0)
2285 {
2286
2287 }
2288
2289 /*!
2290 \
2291 */
QDocumentLineHandle(const QString & s,QDocument * d)2292 QDocumentLineHandle::QDocumentLineHandle(const QString& s, QDocument *d)
2293 : m_text(s)
2294 , m_doc(d)
2295 , m_ref(1)
2296 , m_indent(0)
2297 , m_state(QDocumentLine::LayoutDirty)
2298 , m_layout(nullptr)
2299 , lineHasSelection(QDocumentLineHandle::noSel)
2300 , mTicket(0)
2301 {
2302
2303 }
2304
~QDocumentLineHandle()2305 QDocumentLineHandle::~QDocumentLineHandle()
2306 {
2307 Q_ASSERT(!m_ref.loadAcquire());
2308
2309 if ( m_doc && m_doc->impl() )
2310 m_doc->impl()->emitLineDeleted(this);
2311 }
2312
count() const2313 int QDocumentLineHandle::count() const
2314 {
2315 return m_text.count();
2316 }
2317
length() const2318 int QDocumentLineHandle::length() const
2319 {
2320 return m_text.length();
2321 }
2322
2323 /*int QDocumentLineHandle::line() const
2324 {
2325 return (m_doc && m_doc->impl()) ? m_doc->impl()->indexOf(this) : -1;
2326 }*/
2327
position() const2328 int QDocumentLineHandle::position() const
2329 {
2330 return (m_doc && m_doc->impl()) ? m_doc->impl()->position(this) : -1;
2331 }
2332
text() const2333 QString QDocumentLineHandle::text() const
2334 {
2335 return m_text;
2336 }
2337
indent() const2338 int QDocumentLineHandle::indent() const
2339 {
2340 int l = nextNonSpaceChar(0);
2341 return QDocument::screenColumn(m_text.constData(), l == -1 ? m_text.length() : l, m_doc->tabStop());
2342 }
2343
nextNonSpaceChar(uint pos) const2344 int QDocumentLineHandle::nextNonSpaceChar(uint pos) const
2345 {
2346 QReadLocker locker(&mLock);
2347 return nextNonSpaceCharNoLock(pos);
2348 }
2349
nextNonSpaceCharNoLock(uint pos) const2350 int QDocumentLineHandle::nextNonSpaceCharNoLock(uint pos) const
2351 {
2352 const int len = m_text.length();
2353 const QChar *unicode = m_text.unicode();
2354
2355 for ( int i = pos; i < len; ++i )
2356 {
2357 if ( !unicode[i].isSpace() )
2358 return i;
2359 }
2360
2361 return -1;
2362 }
2363
previousNonSpaceChar(int pos) const2364 int QDocumentLineHandle::previousNonSpaceChar(int pos) const
2365 {
2366 QReadLocker locker(&mLock);
2367 return previousNonSpaceCharNoLock(pos);
2368 }
2369
previousNonSpaceCharNoLock(int pos) const2370 int QDocumentLineHandle::previousNonSpaceCharNoLock(int pos) const
2371 {
2372 const int len = m_text.length();
2373 const QChar *unicode = m_text.unicode();
2374
2375 if ( pos < 0 )
2376 pos = 0;
2377
2378 if ( pos >= len )
2379 pos = len - 1;
2380
2381 for ( int i = pos; i >= 0; --i )
2382 {
2383 if ( !unicode[i].isSpace() )
2384 return i;
2385 }
2386
2387 return -1;
2388 }
2389
document() const2390 QDocument* QDocumentLineHandle::document() const
2391 {
2392 return m_doc;
2393 }
2394
hasFlag(int s) const2395 bool QDocumentLineHandle::hasFlag(int s) const
2396 {
2397 return m_state & s;
2398 }
2399
setFlag(int s,bool y) const2400 void QDocumentLineHandle::setFlag(int s, bool y) const
2401 {
2402 if ( y )
2403 m_state |= s;
2404 else
2405 m_state &= ~s;
2406 }
2407
getBreaks()2408 QList<int> QDocumentLineHandle::getBreaks(){
2409 QReadLocker locker(&mLock);
2410 QList<int> res;
2411 QPair<int, int> elem;
2412 foreach(elem,m_frontiers){
2413 res << elem.first;
2414 }
2415 return res;
2416 }
2417
updateWrap(int lineNr) const2418 void QDocumentLineHandle::updateWrap(int lineNr) const
2419 {
2420 QReadLocker locker(&mLock);
2421 m_indent = 0;
2422 m_frontiers.clear();
2423
2424 if ( !m_doc->impl()->m_constrained && !m_doc->impl()->m_forceLineWrapCalculation)
2425 {
2426 if ( m_layout )
2427 setFlag(QDocumentLine::LayoutDirty, true);
2428 return;
2429 }
2430
2431 const int maxWidth = m_doc->widthConstraint();
2432
2433 if ( m_layout )
2434 {
2435 layout(lineNr);
2436 return;
2437 }
2438
2439 QDocumentPrivate *d = m_doc->impl();
2440
2441 QList<RenderRange> ranges;
2442 splitAtFormatChanges(&ranges);
2443
2444 if (ranges.isEmpty()){
2445 if (hasCookie(QDocumentLine::PICTURE_COOKIE)) {
2446 qreal h = getPictureCookieHeight();
2447 QPair<int,int> l(text().length(), 0);
2448 for (int i=0;i<qRound(1.*h/QDocumentPrivate::m_lineSpacing);i++) { l.second++; l.first++; m_frontiers << l; }
2449 }
2450 return;
2451 }
2452
2453 int idx = 0, column = 0, indent = 0;
2454 qreal minx = QDocumentPrivate::m_leftPadding;
2455
2456 int tempFmts[3]; QFormat tempFormats[3]; int fontFormat;
2457
2458 if ( ranges.first().format & FORMAT_SPACE ) {
2459 d->m_formatScheme->extractFormats(ranges.first().format, tempFmts, tempFormats, fontFormat);
2460 int columnDelta;
2461 minx += d->getRenderRangeWidth(columnDelta, column, ranges.first(), fontFormat, m_text);
2462 indent = ranges.first().length;
2463 }
2464
2465 qreal x = QDocumentPrivate::m_leftPadding; //x position
2466 qreal rx = x; //real x position (where it would be without word wrapping)
2467
2468 if ( (minx + QDocumentPrivate::m_spaceWidth) >= maxWidth )
2469 {
2470 //qWarning("Please stop shrinking so aggressively.\nNo attempt will be made to show something decent");
2471
2472 indent = idx = 0;
2473 minx = QDocumentPrivate::m_leftPadding;
2474 }
2475
2476 m_indent = minx - QDocumentPrivate::m_leftPadding;
2477
2478 int lastBreak = 0;
2479 qreal lastX = 0; //last position a break would fit nicely
2480 int lastActualBreak = indent; //last position a break was inserted (indent has nothing to do with m_indent)
2481 for (int i = 0; i < ranges.size(); i++) {
2482 const RenderRange& r = ranges[i];
2483 d->m_formatScheme->extractFormats(r.format, tempFmts, tempFormats, fontFormat);
2484 int columnDelta;
2485 qreal xDelta = d->getRenderRangeWidth(columnDelta, column, r, fontFormat, m_text);
2486
2487 if ( x + xDelta > maxWidth ) {
2488 if (r.format & FORMAT_SPACE) {
2489 if (lastBreak > lastActualBreak) {
2490 m_frontiers << qMakePair(lastBreak, lastX);
2491 lastActualBreak = lastBreak;
2492
2493 rx += xDelta;
2494 x = minx + (rx - lastX);
2495 column += columnDelta;
2496 idx += r.length;
2497
2498 continue;
2499 }
2500
2501 //cut in space range
2502 qreal cwidth = d->textWidth(fontFormat, " ");
2503 foreach (const QChar& c, m_text.mid(r.position, r.length)) {
2504 int coff = QDocument::screenColumn(&c, 1, d->m_tabStop, column) - column;
2505 qreal xoff = coff * cwidth;
2506 if (x + xoff > maxWidth) {
2507 m_frontiers << qMakePair(idx, rx);
2508 lastActualBreak = idx;
2509 x = minx;
2510 }
2511 rx += xoff;
2512 x += xoff;
2513 column += coff;
2514 idx++;
2515 }
2516 lastBreak = idx;
2517 lastX = rx;
2518 continue;
2519 }
2520
2521 while ( idx < r.position + r.length )
2522 {
2523 const QChar& c = m_text.at(idx);
2524
2525 const QChar::Category cat = c.category();
2526
2527 qreal cwidth;
2528 if ( cat == QChar::Other_Surrogate || cat == QChar::Mark_Enclosing || cat == QChar::Mark_NonSpacing || cat == QChar::Mark_SpacingCombining ) {
2529 int len = idx - r.position + 1;
2530 cwidth = d->textWidth(fontFormat, m_text.mid(r.position, len)) - d->textWidth(fontFormat, m_text.mid(r.position, len - 1));
2531 } else
2532 cwidth = d->textWidth(fontFormat, c);
2533
2534 ++column;
2535
2536 if ( x + cwidth > maxWidth )
2537 {
2538
2539 ushort uc = c.unicode();
2540 // CJK char detection for wrapping
2541 // first check if its part of the unicode BMP
2542 bool isCJK = (
2543 !(c.isLowSurrogate()||c.isHighSurrogate()) && // compatibility with qt4
2544 ((0x4E00 <= uc && uc <= 0x9FFF) || // CJK Unified Ideographs
2545 (0x3000 <= uc && uc <= 0x4DBF) || // CJK Punctuation, ..., Unified Ideographs Extension
2546 (0xF900 <= uc && uc <= 0xFAFF)) // CJK Compatibility Ideographs
2547 ); // see http://en.wikipedia.org/wiki/CJK_Symbols_and_Punctuation
2548 // additionally check if its a surrogate
2549 if (!isCJK && c.isHighSurrogate() && idx+1 < m_text.count()) {
2550 QChar cLow = m_text.at(idx+1);
2551 if (cLow.isLowSurrogate()) {
2552 uint uic = joinUnicodeSurrogate(c, cLow);
2553 isCJK = ((0x20000 <= uic && uic <= 0x2B81F) || // CJK Unified Ideographs Extension B-D)
2554 (0x2F800 <= uic && uic <= 0x2FA1F)); // CJK Compatibility Ideographs Supplement
2555 }
2556 }
2557
2558 if ( lastBreak <= lastActualBreak || isCJK )
2559 {
2560 /* a single regular word is longer than maximal available line space - no reasonable wrapping possible
2561 * or Chinese/Japanese/Korean char (in which case we may wrap inside the "word" not only at breaks (e.g. spaces))
2562 * Note: there are more complex rules in CJK which characters may not appear at the start or at the end of a line
2563 * but supporting these would be quite complex (http://en.wikipedia.org/wiki/Line_breaking_rules_in_East_Asian_languages)
2564 *
2565 Two possible strategies:
2566 i) allow the word to exceed the line width
2567 ii) agressively wrap inside the word.
2568
2569 ii) can be used for soft wrapping, because the wrap is only visual. In hard wrapping
2570 it would introduce a newline into the word which changes its meaning.
2571 */
2572
2573 if (!d->m_hardLineWrap || isCJK) {
2574 // agressive wrap inside the word
2575 m_frontiers << qMakePair(idx, rx);
2576 lastActualBreak = idx;
2577 lastBreak = idx;
2578 lastX = rx;
2579 x = minx;
2580 }
2581 } else {
2582 Q_ASSERT(lastBreak <= idx);
2583 Q_ASSERT(lastBreak > 0);
2584 // wrap at last possible break
2585 m_frontiers << qMakePair(lastBreak, lastX);
2586 lastActualBreak = lastBreak;
2587 x = minx + (rx - lastX);
2588 }
2589 }
2590
2591 rx += cwidth;
2592 x += cwidth;
2593 ++idx;
2594 }
2595 } else {
2596 rx += xDelta;
2597 x += xDelta;
2598 column += columnDelta;
2599 idx += r.length;
2600
2601 if ( r.format & FORMAT_SPACE ){
2602 lastBreak = idx;
2603 lastX = rx;
2604 }
2605 }
2606 }
2607
2608 if (hasCookie(QDocumentLine::PICTURE_COOKIE)) {
2609 qreal h = getPictureCookieHeight();
2610 QPair<int,qreal> l(text().length(), rx);
2611 for (int i=0;i<qRound(1.*h/QDocumentPrivate::m_lineSpacing);i++) { l.second++; l.first++; m_frontiers << l; }
2612 }
2613 }
2614
updateWrapAndNotifyDocument(int line) const2615 void QDocumentLineHandle::updateWrapAndNotifyDocument(int line) const{
2616 if ( !m_doc ) return;
2617 int oldLW = m_frontiers.count();
2618 updateWrap(line);
2619 int lw = m_frontiers.count();
2620 if ( lw == oldLW ) return;
2621
2622 if ( lw ) m_doc->impl()->m_wrapped[line] = lw;
2623 else m_doc->impl()->m_wrapped.remove(line);
2624 m_doc->impl()->m_height += 1.*(lw-oldLW)*m_doc->impl()->m_lineSpacing;
2625 }
2626
cursorToX(int cpos) const2627 qreal QDocumentLineHandle::cursorToX(int cpos) const
2628 {
2629 QReadLocker locker(&mLock);
2630 return cursorToXNoLock(cpos);
2631 }
2632
cursorToXNoLock(int cpos) const2633 qreal QDocumentLineHandle::cursorToXNoLock(int cpos) const
2634 {
2635 cpos = qBound(0, cpos, m_text.length());
2636
2637 if ( m_layout )
2638 {
2639 qreal xoff = m_doc->impl()->leftMarginAndPadding();
2640 int coff = 0;
2641 int line = m_frontiers.count();
2642
2643 for ( int i = 0; i < m_frontiers.count(); ++i )
2644 {
2645 if ( m_frontiers.at(i).first >= cpos )
2646 {
2647 line = i;
2648
2649 break;
2650 }
2651 }
2652
2653 if ( line )
2654 {
2655 //coff = m_frontiers.at(line - 1).first;
2656 xoff = m_frontiers.at(line - 1).second;
2657 }
2658
2659 //qDebug("c:%i (wrap:%i) => c2x(x - %i) + %i", cpos, line, coff, xoff);
2660 qreal result=m_layout->lineAt(line).cursorToX(cpos - coff) + xoff;
2661 return result;
2662 }
2663
2664 int tabStop = m_doc->impl()->m_tabStop;
2665
2666 if ( QDocumentPrivate::m_fixedPitch )
2667 {
2668 qreal result=QDocument::screenColumn(m_text.constData(), cpos, tabStop)
2669 * QDocumentPrivate::m_spaceWidth
2670 + m_doc->impl()->leftMarginAndPadding();
2671 return result;
2672 }
2673
2674 //qDebug("c->x(%i) unsafe computations...", cpos);
2675
2676 QVector<int> composited = compose();
2677 const QVector<QFont>& fonts = m_doc->impl()->m_fonts;
2678
2679 if ( (composited.count() < cpos) || fonts.isEmpty() ){
2680 qreal result=UtilsUi::getFmWidth(QFontMetricsF(*QDocumentPrivate::m_font), m_text.left(cpos));
2681 return result;
2682 }
2683
2684 int idx = 0, column = 0;
2685 qreal cwidth;
2686 qreal screenx = m_doc->impl()->leftMarginAndPadding();
2687
2688 while ( idx < cpos )
2689 {
2690 QChar c = m_text.at(idx);
2691 int fmt = idx < composited.count() ? composited[idx] : 0;
2692 QFontMetricsF fm(fmt < fonts.count() ? fonts.at(fmt) : m_doc->font());
2693
2694 if ( c == '\t' )
2695 {
2696 int taboffset = ncolsToNextTabStop(column, tabStop);
2697
2698 column += taboffset;
2699 cwidth = UtilsUi::getFmWidth(fm, ' ') * taboffset;
2700 } else {
2701 ++column;
2702 cwidth = UtilsUi::getFmWidth(fm, c);
2703 }
2704
2705 screenx += cwidth;
2706 ++idx;
2707 }
2708
2709 //qDebug("cursorToX(%i) = %i", cpos, screenx);
2710 return screenx;
2711 }
2712
xToCursor(qreal xpos) const2713 int QDocumentLineHandle::xToCursor(qreal xpos) const
2714 {
2715 //qDebug("x->c(%i) unsafe computations...", xpos);
2716 QReadLocker locker(&mLock);
2717 if ( m_layout )
2718 {
2719 qreal xoff = m_doc->impl()->leftMarginAndPadding();
2720 int coff = 0;
2721 int line = m_frontiers.count();
2722
2723 for ( int i = 0; i < m_frontiers.count(); ++i )
2724 {
2725 if ( m_frontiers.at(i).second >= xpos )
2726 {
2727 line = i;
2728 break;
2729 }
2730 }
2731
2732 if ( line )
2733 {
2734 //coff = m_frontiers.at(line - 1).first;
2735 xoff = m_frontiers.at(line - 1).second;
2736 }
2737
2738 //qDebug("x:%i (wrap:%i) => x2c(x - %i) + %i", xpos, line, xoff, coff);
2739 return m_layout->lineAt(line).xToCursor(xpos - xoff) + coff;
2740 }
2741
2742 qreal screenx = xpos;
2743 int tabStop = m_doc->impl()->m_tabStop;
2744 const QVector<QFont>& fonts = m_doc->impl()->m_fonts;
2745
2746 if ( QDocumentPrivate::m_fixedPitch )
2747 {
2748 int screenPos = (screenx - QDocumentPrivate::m_leftPadding / 2) / QDocumentPrivate::m_spaceWidth;
2749 if ( tabStop == 1 ){
2750 return screenPos;
2751 }
2752 int idx = 0, column = 0;
2753 while ( (column < screenPos) && (idx < m_text.length()) )
2754 {
2755 QChar c = m_text.at(idx);
2756
2757 if ( c == QLatin1Char('\t') )
2758 {
2759 column += ncolsToNextTabStop(column, tabStop);
2760 } else {
2761 ++column;
2762 }
2763
2764 ++idx;
2765 }
2766 return idx;
2767 } else {
2768
2769 QVector<int> composited = compose();
2770
2771 int idx = 0, column = 0;
2772 qreal x = 0. ,cwidth;
2773 screenx -= m_doc->impl()->leftMarginAndPadding();
2774 if (screenx < 0)
2775 return 0;
2776
2777 while ( idx < m_text.length() )
2778 {
2779 int fmt = idx < composited.count() ? composited[idx] : 0;
2780 QFontMetricsF fm(fmt < fonts.count() ? fonts.at(fmt) : m_doc->font());
2781
2782 if ( m_text.at(idx) == '\t' )
2783 {
2784 int taboffset = ncolsToNextTabStop(column, tabStop);
2785
2786 column += taboffset;
2787 cwidth = UtilsUi::getFmWidth(fm, ' ') * taboffset;
2788 } else {
2789 ++column;
2790 cwidth = UtilsUi::getFmWidth(fm, m_text.at(idx));
2791 }
2792
2793 qreal mid = (x + (cwidth / 2) + 1);
2794
2795 if ( screenx <= mid ){
2796 return idx;
2797 }
2798 else if ( screenx <= (x + cwidth) ){
2799 return idx + 1;
2800 }
2801 x += cwidth;
2802 ++idx;
2803 }
2804 int result=m_text.length();
2805
2806 return result;
2807 }
2808 }
2809
wrappedLineForCursor(int cpos) const2810 int QDocumentLineHandle::wrappedLineForCursor(int cpos) const
2811 {
2812 QReadLocker locker(&mLock);
2813 return wrappedLineForCursorNoLock(cpos);
2814 }
2815
wrappedLineForCursorNoLock(int cpos) const2816 int QDocumentLineHandle::wrappedLineForCursorNoLock(int cpos) const
2817 {
2818 int wrap = m_frontiers.count();
2819
2820 for ( int i = 0; i < m_frontiers.count(); ++i )
2821 {
2822 if ( m_frontiers.at(i).first > cpos )
2823 {
2824 wrap = i;
2825 break;
2826 }
2827 }
2828 return wrap;
2829 }
2830
2831 /*! \return the cursor column in the given line associated with
2832 * the given document coordinates.
2833 * The vertical coordinates start at y=0 (top)
2834 * The horizonal coorindates are including leftMargin and leftPadding,
2835 * i.e. x = leftMargin + leftPadding + charWidth results in column 1
2836 * Positions smaller than leftMargin + leftPadding always result in column 0
2837 */
documentOffsetToCursor(qreal x,qreal y,bool disallowPositionBeyondLine) const2838 int QDocumentLineHandle::documentOffsetToCursor(qreal x, qreal y, bool disallowPositionBeyondLine) const
2839 {
2840 //qDebug("documentOffsetToCursor(%i, %i)", x, y);
2841 QReadLocker locker(&mLock);
2842 QDocumentPrivate *d = m_doc->impl();
2843
2844 x -= d->leftMarginAndPadding(); // remove margin and padding
2845
2846 int wrap = qRound(y / QDocumentPrivate::m_lineSpacing);
2847
2848 if ( wrap > m_frontiers.count() )
2849 {
2850 // return an invalid value instead?
2851 //qDebug("a bit too far : (%i, %i)", x, y);
2852 //wrap = m_frontiers.count();
2853 return m_text.length();
2854 }
2855
2856 if ( m_frontiers.count() )
2857 {
2858 //qDebug("(%i, %i) : %i", x, y, wrap);
2859 x = qMin(x, 1.*m_doc->widthConstraint());
2860 }
2861
2862 if ( m_layout )
2863 {
2864 return m_layout->lineAt(wrap).xToCursor(x);
2865 }
2866
2867 int cpos = 0;
2868 int max = m_text.length();
2869
2870 if ( wrap < m_frontiers.count() )
2871 max = m_frontiers.at(wrap).first - 1;
2872
2873 if ( wrap > 0 )
2874 cpos = m_frontiers.at(wrap - 1).first;
2875
2876 if ( wrap )
2877 x -= m_indent;
2878
2879 if ( x <= 0 ){
2880 return cpos;
2881 }
2882
2883 qreal rx = 0;
2884 int column = 0;
2885 QList<RenderRange> ranges;
2886 splitAtFormatChanges(&ranges, nullptr, cpos, max);
2887
2888 qreal lastCharacterWidth = QDocumentPrivate::m_spaceWidth;
2889
2890 foreach ( const RenderRange& r, ranges ) {
2891 qreal xDelta;
2892 int columnDelta;
2893 int tempFmts[FORMAT_MAX_COUNT]; QFormat tempFormats[FORMAT_MAX_COUNT]; int newFont;
2894 d->m_formatScheme->extractFormats(r.format, tempFmts, tempFormats, newFont);
2895 xDelta = d->getRenderRangeWidth(columnDelta, column, r, newFont, m_text);
2896
2897 if ( !d->m_fixedPitch && rx + xDelta > x) {
2898 //update threshold if the render range is close to the position
2899 qreal newcw = 0;
2900 for (int i=r.length+r.position-1; i>=r.position && newcw == 0 ; i-- )
2901 newcw = d->textWidth(newFont, m_text.at(i));
2902 if (newcw > 1)
2903 lastCharacterWidth = newcw;
2904 }
2905
2906 if ( rx + xDelta - lastCharacterWidth/3 > x ) {
2907 RenderRange rcopied = r;
2908 qreal oldxDelta = 0;
2909 for ( int i = 0; i < r.length; i++ ) {
2910 rcopied.length = i;
2911 xDelta = d->getRenderRangeWidth(columnDelta, column, rcopied, newFont, m_text);
2912 int cwidth = xDelta - oldxDelta;
2913 oldxDelta = xDelta;
2914 if ( cwidth > 1 )
2915 lastCharacterWidth = cwidth;
2916 if ( rx + xDelta - lastCharacterWidth / 3 > x ) {
2917 rcopied.length--;
2918 break;
2919 }
2920 }
2921 cpos += rcopied.length;
2922 if (cpos < 0) cpos = 0;
2923 /*for (int i = 0;)
2924 if ( r.format & FORMAT_SPACE ) {
2925 for (int i = const QChar& c, subText ) {
2926 int len = QDocument::screenColumn(&c, 1, d->m_tabStop, column) - column;
2927 if ( rx + cw*len/2 >= x ) break;
2928 rx += cw*len;
2929 column += len;
2930 cpos++;
2931 }
2932 } else {
2933 foreach ( const QChar& c, subText ){
2934 int cw = d->textWidth(newFont, c);
2935 if ( rx + 2*cw/3 >= x ) break;
2936 rx += cw;
2937 cpos++;
2938 }
2939 }*/
2940
2941 //move the cursor out of multi-byte ucs-2 characters
2942 while (cpos < m_text.length() && m_text.at(cpos).category() == QChar::Mark_NonSpacing)
2943 cpos++;
2944 if (cpos < m_text.length() && m_text.at(cpos).isLowSurrogate() && cpos > 0 && m_text.at(cpos - 1).isHighSurrogate())
2945 cpos++;
2946
2947 return cpos;
2948 }
2949 cpos += r.length;
2950 column += columnDelta;
2951 rx += xDelta;
2952 }
2953 if(disallowPositionBeyondLine && rx<x) {
2954 // don't trigger hover if cursor is beyond text end
2955 cpos=-1;
2956 }
2957 return cpos;
2958 }
2959
cursorToDocumentOffset(int cpos,qreal & x,qreal & y) const2960 void QDocumentLineHandle::cursorToDocumentOffset(int cpos, qreal& x, qreal& y) const
2961 {
2962 QReadLocker locker(&mLock);
2963 QDocumentPrivate *d = m_doc->impl();
2964
2965 if ( cpos > m_text.length() )
2966 cpos = m_text.length();
2967 else if ( cpos < 0 )
2968 cpos = 0;
2969
2970 int idx = 0;
2971 int wrap = wrappedLineForCursorNoLock(cpos);
2972
2973 x = d->leftMarginAndPadding();
2974 y = 1. * wrap * QDocumentPrivate::m_lineSpacing;
2975
2976 if ( wrap )
2977 {
2978 idx = m_frontiers.at(wrap - 1).first;
2979 }
2980
2981 if ( m_layout )
2982 {
2983 // workaround on qt5.3 bug
2984 QTextLine tl=m_layout->lineAt(wrap);
2985 if(tl.textStart() + tl.textLength()>=cpos)
2986 x += int(tl.cursorToX(cpos));
2987 else
2988 x += int(tl.width());
2989 } else {
2990 if ( wrap )
2991 x += m_indent;
2992
2993 QList<RenderRange> ranges;
2994 splitAtFormatChanges(&ranges, nullptr, idx, qMin(text().length(), cpos));
2995
2996 int column=0;
2997 foreach (const RenderRange& r, ranges) {
2998 int tempFmts[FORMAT_MAX_COUNT]; QFormat tempFormats[FORMAT_MAX_COUNT]; int newFont;
2999 d->m_formatScheme->extractFormats(r.format, tempFmts, tempFormats, newFont);
3000 int columnDelta;
3001 x += d->getRenderRangeWidth(columnDelta, column, r, newFont, m_text);
3002 column += columnDelta;
3003 }
3004 }
3005 //qDebug("cursorToDocumentOffset(%i) -> (%i, %i)", cpos, x, y);
3006 }
3007
cursorToDocumentOffset(int cpos) const3008 QPointF QDocumentLineHandle::cursorToDocumentOffset(int cpos) const
3009 {
3010 QPointF p;
3011 cursorToDocumentOffset(cpos, p.rx(), p.ry()); //locked here
3012 return p;
3013 }
3014
clearOverlays()3015 void QDocumentLineHandle::clearOverlays()
3016 {
3017 QWriteLocker locker(&mLock);
3018
3019 m_overlays.clear();
3020
3021 setFlag(QDocumentLine::FormatsApplied, false);
3022 }
3023
clearOverlays(int format)3024 void QDocumentLineHandle::clearOverlays(int format){
3025 QWriteLocker locker(&mLock);
3026 bool changed=false;
3027 for ( int i = m_overlays.size()-1; i>=0; i-- )
3028 if ( m_overlays[i].format == format ){
3029 m_overlays.removeAt(i);
3030 changed=true;
3031 }
3032 if (changed){
3033 setFlag(QDocumentLine::FormatsApplied, false);
3034 }
3035 }
3036
clearOverlays(QList<int> formats)3037 void QDocumentLineHandle::clearOverlays(QList<int> formats){
3038 QWriteLocker locker(&mLock);
3039 bool changed=false;
3040 for ( int i = m_overlays.size()-1; i>=0; i-- )
3041 if ( formats.contains(m_overlays[i].format) ){
3042 m_overlays.removeAt(i);
3043 changed=true;
3044 }
3045
3046 if (changed){
3047 setFlag(QDocumentLine::FormatsApplied, false);
3048 }
3049
3050 }
3051
addOverlay(const QFormatRange & over)3052 void QDocumentLineHandle::addOverlay(const QFormatRange& over)
3053 {
3054 QWriteLocker locker(&mLock);
3055 addOverlayNoLock(over);
3056 }
3057
addOverlayNoLock(const QFormatRange & over)3058 void QDocumentLineHandle::addOverlayNoLock(const QFormatRange& over)
3059 {
3060 m_overlays << over;
3061
3062 setFlag(QDocumentLine::FormatsApplied, false);
3063 }
3064
removeOverlay(const QFormatRange & over)3065 void QDocumentLineHandle::removeOverlay(const QFormatRange& over)
3066 {
3067 QWriteLocker locker(&mLock);
3068 int i = m_overlays.removeAll(over);
3069
3070 if ( i )
3071 setFlag(QDocumentLine::FormatsApplied, false);
3072 }
3073
hasOverlay(int id)3074 bool QDocumentLineHandle::hasOverlay(int id){
3075 QReadLocker locker(&mLock);
3076 for (int i =0; i<m_overlays.size();i++){
3077 if (m_overlays[i].format==id) {
3078 return true;
3079 }
3080 }
3081 return false;
3082 }
3083
getOverlays(int preferredFormat) const3084 QList<QFormatRange> QDocumentLineHandle::getOverlays(int preferredFormat) const {
3085 QReadLocker locker(&mLock);
3086 QList<QFormatRange> result;
3087 if (preferredFormat==-1) {
3088 return m_overlays;
3089 }
3090
3091 for (int i=0;i<m_overlays.size();i++)
3092 if (m_overlays[i].format==preferredFormat) result.append(m_overlays[i]);
3093
3094 return result;
3095 }
3096
getOverlayAt(int index,int preferredFormat) const3097 QFormatRange QDocumentLineHandle::getOverlayAt(int index, int preferredFormat) const {
3098 QReadLocker locker(&mLock);
3099
3100 QFormatRange best;
3101 foreach (QFormatRange fr, m_overlays)
3102 if (fr.offset<=index && fr.offset+fr.length>=index && (fr.format==preferredFormat || (preferredFormat==-1)))
3103 if (best.length<fr.length) best=fr;
3104
3105 return best;
3106 }
3107
getFirstOverlay(int start,int end,int preferredFormat) const3108 QFormatRange QDocumentLineHandle::getFirstOverlay(int start, int end, int preferredFormat) const {
3109 QReadLocker locker(&mLock);
3110
3111 QFormatRange best;
3112 foreach (QFormatRange fr, m_overlays)
3113 if ((end==-1 || fr.offset<=end) && fr.offset+fr.length>=start && (fr.format==preferredFormat || (preferredFormat==-1)))
3114 if (fr.offset<best.offset || best.length==0) best=fr;
3115
3116 return best;
3117 }
getLastOverlay(int start,int end,int preferredFormat) const3118 QFormatRange QDocumentLineHandle::getLastOverlay(int start, int end, int preferredFormat) const {
3119 QReadLocker locker(&mLock);
3120 QFormatRange best;
3121 foreach (QFormatRange fr, m_overlays)
3122 if ((end==-1 || fr.offset<=end) && fr.offset+fr.length>=start && (fr.format==preferredFormat || (preferredFormat==-1)))
3123 if (fr.offset>best.offset|| best.length==0) best=fr;
3124
3125 return best;
3126 }
3127
shiftOverlays(int position,int offset)3128 void QDocumentLineHandle::shiftOverlays(int position, int offset)
3129 {
3130 // needs to be locked at calling function !!!
3131 if ( offset > 0 )
3132 {
3133 for ( int i = 0; i < m_overlays.count(); ++i )
3134 {
3135 QFormatRange& r = m_overlays[i];
3136
3137 if ( r.offset >= position )
3138 {
3139 r.offset += offset;
3140 } else if ( r.offset + r.length > position ) {
3141 m_overlays.removeAt(i);
3142 --i;
3143 }
3144 }
3145 } else if ( offset < 0 ) {
3146 const int max = position - offset;
3147
3148 for ( int i = 0; i < m_overlays.count(); ++i )
3149 {
3150 QFormatRange& r = m_overlays[i];
3151
3152 if ( r.offset >= max )
3153 {
3154 r.offset += offset;
3155 } else if ( r.offset + r.length >= position ) {
3156 m_overlays.removeAt(i);
3157 --i;
3158 }
3159 }
3160 }
3161
3162 setFlag(QDocumentLine::FormatsApplied, false);
3163 }
3164
setFormats(const QVector<int> & fmts)3165 void QDocumentLineHandle::setFormats(const QVector<int>& fmts)
3166 {
3167 QWriteLocker locker(&mLock);
3168 m_formats = fmts;
3169
3170 while ( m_formats.count() > m_text.length() )
3171 m_formats.pop_back();
3172
3173 while ( m_formats.count() < m_text.length() )
3174 m_formats.append(0);
3175
3176 setFlag(QDocumentLine::FormatsApplied, false);
3177 }
3178
getFormats() const3179 QVector<int> QDocumentLineHandle::getFormats() const
3180 {
3181 QReadLocker locker(&mLock);
3182 return m_formats;
3183 }
3184
getCachedFormats() const3185 QVector<int> QDocumentLineHandle::getCachedFormats() const
3186 {
3187 QReadLocker locker(&mLock);
3188 return m_cache;
3189 }
3190
3191 /*!
3192 * \brief Returns the specified cookie type associated with this line.
3193 * \details Returns the specified cookie type associated with this line.
3194 * Not thread safe. Caller must hold a read lock of the line.
3195 * \param[in] type The type of the returned cookie.
3196 * \return Returns the cookie of the specified type.
3197 */
getCookie(int type) const3198 QVariant QDocumentLineHandle::getCookie(int type) const
3199 {
3200 return mCookies.value(type,QVariant());
3201 }
3202
3203 /*!
3204 * \brief Returns the specified cookie type associated with this line.
3205 * \details Returns the specified cookie type associated with this line.
3206 * Thread safe. Obtains a read lock for the duration of the call.
3207 * \param[in] type The type of the returned cookie.
3208 * \return Returns the cookie of the specified type.
3209 */
getCookieLocked(int type) const3210 QVariant QDocumentLineHandle::getCookieLocked(int type) const
3211 {
3212 QReadLocker locker(&mLock);
3213 return mCookies.value(type,QVariant());
3214 }
3215
3216 /*!
3217 * \brief Sets the specified cookie type for this line to the specified value.
3218 * \details Sets the specified cookie type for this line to the specified value.
3219 * Not thread safe. Caller must hold a write lock of the line.
3220 * \param[in] type The type of the cookie to be set.
3221 * \param[in] data The value of the cookie to be set.
3222 */
setCookie(int type,QVariant data)3223 void QDocumentLineHandle::setCookie(int type,QVariant data)
3224 {
3225 mCookies.insert(type,data);
3226 }
3227
3228 /*!
3229 * \brief Checks if the line has a cookie of the specified type.
3230 * \details Checks if the line has a cookie of the specified type.
3231 * Not thread safe. Caller must hold a read lock of the line.
3232 * \param[in] type The type of the checked cookie.
3233 * \return Returns a boolean flag indicating if the line has a cookie of the
3234 * specified type.
3235 */
hasCookie(int type) const3236 bool QDocumentLineHandle::hasCookie(int type) const
3237 {
3238 return mCookies.contains(type);
3239 }
3240
3241 /*!
3242 * \brief Removes the specified cookie type for this line.
3243 * \details Removes the specified cookie type for this line.
3244 * If this line does not have a cookie of the specified type then does nothing.
3245 * Not thread safe. Caller must hold a write lock of the line.
3246 * \param[in] type The type of the cookie to be removed.
3247 * \return Returns a boolean flag indicating if the line had a cookie of the
3248 * specified type.
3249 */
removeCookie(int type)3250 bool QDocumentLineHandle::removeCookie(int type)
3251 {
3252 return mCookies.remove(type);
3253 }
3254
isRTLByLayout() const3255 bool QDocumentLineHandle::isRTLByLayout() const{
3256 if (!m_layout) return false;
3257 else {
3258 QReadLocker locker(&mLock);
3259 return m_layout && m_layout->textOption().textDirection() == Qt::RightToLeft;
3260 }
3261 }
3262
3263
isRTLByText() const3264 bool QDocumentLineHandle::isRTLByText() const{
3265 bool needLayout = false;
3266 static QList<QChar::Direction> m_layoutRequirements = QList<QChar::Direction>()
3267 << QChar::DirR
3268 << QChar::DirAL
3269 << QChar::DirRLE
3270 << QChar::DirRLO
3271 << QChar::DirPDF
3272 << QChar::DirAN;
3273
3274 QString text = m_text; //does this need locking ?
3275
3276 for ( int i = 0; (i < text.length()) && !needLayout; ++i )
3277 {
3278 QChar c = text.at(i);
3279
3280 needLayout = m_layoutRequirements.contains(c.direction());
3281 }
3282
3283 return needLayout;
3284 }
3285
3286
compose() const3287 QVector<int> QDocumentLineHandle::compose() const
3288 {
3289 // don't do locking here as it is mainly called by draw !!!!
3290 if ( hasFlag(QDocumentLine::FormatsApplied) ){
3291 return m_cache;
3292 }
3293
3294 m_cache.resize(m_text.length());
3295
3296 for ( int i = 0; i < qMin(m_formats.count(), m_text.length()); ++i )
3297 m_cache[i] = m_formats.at(i);
3298
3299 for ( int i = m_formats.count(); i < m_text.length(); ++i )
3300 m_cache[i] = 0;
3301
3302 // compositing formats and overlays
3303 //QDocumentPrivate * d = document()->impl();
3304 foreach ( const QFormatRange& r, m_overlays )
3305 {
3306 int beg = qMax(0, r.offset);
3307 int end = qMin(r.offset + r.length, m_cache.count());
3308
3309 for ( int i = beg; i < end; ++i )
3310 QFormatScheme::mergeFormats(m_cache[i], r.format);
3311 }
3312 //qDebug("\n");
3313 setFlag(QDocumentLine::FormatsApplied, true);
3314 setFlag(QDocumentLine::LayoutDirty, true);
3315
3316 return m_cache;
3317 }
3318
3319 // After we switch to Qt5.6+, the return type should become QVector<QTextLayout::FormatRange>
3320 template <template<class T> class CONTAINER_TYPE>
decorations() const3321 CONTAINER_TYPE<QTextLayout::FormatRange> QDocumentLineHandle::decorations() const
3322 {
3323 // don't do locking here as it is mainly called by draw (and locked there) !!!!
3324 if ( !hasFlag(QDocumentLine::FormatsApplied) )
3325 compose();
3326
3327 // turning format "map" into ranges that QTextLayout can understand...
3328 CONTAINER_TYPE<QTextLayout::FormatRange> m_ranges;
3329
3330 QTextLayout::FormatRange r;
3331 r.start = r.length = -1;
3332
3333
3334 bool skipNormalText = m_doc->formatScheme()->format(0) == QFormat();
3335
3336 int i = 0;
3337
3338 //if ( m_cache.isEmpty() )
3339 // qWarning("empty cache...");
3340
3341 while ( i < m_cache.count() )
3342 {
3343 if (skipNormalText)
3344 while ( (i < m_cache.count()) && !m_cache[i] )
3345 ++i;
3346
3347 if ( i >= m_cache.count() )
3348 break;
3349
3350 int fid = m_cache[i];
3351
3352 int fmts[FORMAT_MAX_COUNT];
3353 QFormat formats[FORMAT_MAX_COUNT];
3354 int newFont;
3355 m_doc->impl()->m_formatScheme->extractFormats(fid, fmts, formats, newFont);
3356
3357 r.start = i;
3358 r.format = m_doc->formatScheme()->format(newFont).toTextCharFormat();
3359 if (formats[0].foreground.isValid()) { if (newFont != 0) { r.format.setForeground(formats[0].foreground); } }
3360 else if (formats[1].foreground.isValid()) { if (newFont != 1) { r.format.setForeground(formats[1].foreground); } }
3361 else if (formats[2].foreground.isValid()) { if (newFont != 2) { r.format.setForeground(formats[2].foreground); } }
3362 if (formats[0].background.isValid()) { if (newFont != 0) { r.format.setBackground(formats[0].background); } }
3363 else if (formats[1].background.isValid()) { if (newFont != 1) { r.format.setBackground(formats[1].background); } }
3364 else if (formats[2].background.isValid()) { if (newFont != 2) { r.format.setBackground(formats[2].background); } }
3365
3366 while ( (i < m_cache.count()) && (m_cache[i] == fid) )
3367 ++i;
3368
3369 if ( i >= m_cache.count() )
3370 break;
3371
3372 r.length = i - r.start;
3373 m_ranges << r;
3374
3375 r.start = r.length = -1;
3376 }
3377
3378 if ( r.start != -1 )
3379 {
3380 r.length = m_cache.count() - r.start;
3381 m_ranges << r;
3382 }
3383
3384 return m_ranges;
3385 }
3386
applyOverlays() const3387 void QDocumentLineHandle::applyOverlays() const
3388 {
3389 if ( !m_layout )
3390 return;
3391
3392 //m_layout->setAdditionalFormats(decorations());
3393
3394 //setFlag(QDocumentLine::FormatsApplied, true);
3395 }
3396
layout(int lineNr) const3397 void QDocumentLineHandle::layout(int lineNr) const
3398 {
3399 //needs locking by caller
3400 bool needLayout = ( m_doc->impl()->m_workArounds & QDocument::ForceQTextLayout )
3401 || isRTLByText();
3402
3403 if ( needLayout )
3404 {
3405 //qDebug("layout needed at line %i", this->line());
3406
3407 setFlag(QDocumentLine::LayoutedByQTextLayout, true);
3408
3409 if ( !m_layout )
3410 {
3411 m_layout = new QTextLayout(m_text, QDocument::font());
3412 } else {
3413 m_layout->setText(m_text);
3414 m_layout->setFont(QDocument::font());
3415 }
3416
3417 m_layout->setCacheEnabled(false);
3418 // Initial setup of the QTextLayout.
3419
3420 // Tab width
3421 QTextOption opt;
3422 if (!m_text.isRightToLeft()) {
3423 // We would like to include trailing spaces in the layout, however, QTextLayout does
3424 // not process them correctly (QTBUG-48587) which results in false behavior for RTL
3425 // text: https://sourceforge.net/p/texstudio/bugs/1503/
3426 // therefore we do not include the trailing spaces for RTL text.
3427 opt.setFlags(QTextOption::IncludeTrailingSpaces);
3428 }
3429 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
3430 opt.setTabStopDistance(
3431 #else
3432 opt.setTabStop(
3433 #endif
3434 m_doc->tabStop() * QDocumentPrivate::m_spaceWidth
3435 );
3436
3437 //opt.setWrapMode(QTextOption::NoWrap);
3438 opt.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
3439
3440 // Find the first strong character in the string.
3441 // If it is an RTL character, set the base layout direction of the string to RTL.
3442 //
3443 // See http://www.unicode.org/reports/tr9/#The_Paragraph_Level (Sections P2 & P3).
3444 // Qt's text renderer ("scribe") version 4.2 assumes a "higher-level protocol"
3445 // (such as KatePart) will specify the paragraph level, so it does not apply P2 & P3
3446 // by itself. If this ever change in Qt, the next code block could be removed.
3447 if ( m_text.isRightToLeft() )
3448 opt.setTextDirection(Qt::RightToLeft);
3449
3450 m_layout->setTextOption(opt);
3451
3452 // Syntax highlighting, inbuilt and arbitrary
3453 #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
3454 m_layout->setFormats(decorations<QVector>());
3455 #else
3456 m_layout->setAdditionalFormats(decorations<QList>());
3457 #endif
3458 setFlag(QDocumentLine::FormatsApplied, true);
3459
3460 // Begin layouting
3461 m_layout->beginLayout();
3462
3463
3464 int oldLW = m_frontiers.size();
3465 m_frontiers.clear();
3466
3467 int i = 0;
3468 qreal rx = 0, height = 0, minwidth = 0;
3469
3470 forever
3471 {
3472 QTextLine line = m_layout->createLine();
3473
3474 if ( !line.isValid() )
3475 {
3476 //if ( m_layout->lineCount() > 1 )
3477 // qWarning("Troubles expected on line %i", this->line());
3478
3479 break;
3480 }
3481
3482 if ( m_doc->impl()->m_constrained )
3483 line.setLineWidth(m_doc->widthConstraint() - QDocumentPrivate::m_leftPadding);
3484 else
3485 line.setNumColumns(m_text.length());
3486
3487 rx += line.naturalTextWidth(); //qRound(line.cursorToX(line.textLength()));
3488
3489 if ( m_doc->impl()->m_constrained && m_layout->textOption().textDirection() == Qt::RightToLeft )
3490 {
3491 line.setPosition(QPointF(qreal(m_doc->widthConstraint() - 2 * QDocumentPrivate::m_leftPadding) - line.naturalTextWidth(), height));
3492 } else {
3493 line.setPosition(QPointF(minwidth, height));
3494
3495 if ( !i && m_doc->impl()->m_constrained )
3496 {
3497 m_indent = minwidth = cursorToXNoLock(nextNonSpaceCharNoLock(0)) - QDocumentPrivate::m_leftPadding;
3498
3499 if ( minwidth < 0 || minwidth >= m_doc->widthConstraint() )
3500 minwidth = 0;
3501 }
3502 }
3503
3504 m_frontiers << qMakePair(line.textStart() + line.textLength(), rx);
3505
3506 ++i;
3507
3508 height += QDocumentPrivate::m_lineSpacing;
3509 //height += QDocument::fontMetrics().height();
3510 }
3511
3512 m_frontiers.pop_back();
3513
3514 m_layout->endLayout();
3515
3516 int lw = m_frontiers.size();
3517 if ( m_doc && lw != oldLW ) {
3518 if ( lw ) m_doc->impl()->m_wrapped[lineNr] = lw;
3519 else m_doc->impl()->m_wrapped.remove(lineNr);
3520 m_doc->impl()->m_height += 1.*(lw-oldLW)*m_doc->impl()->m_lineSpacing;
3521 }
3522 } else {
3523 delete m_layout;
3524
3525 m_layout = nullptr;
3526
3527 // fix https://sourceforge.net/p/texstudio/bugs/2255/
3528 if (hasFlag(QDocumentLine::LayoutedByQTextLayout)) {
3529 setFlag(QDocumentLine::LayoutedByQTextLayout, false);
3530 updateWrap(lineNr);
3531 }
3532 }
3533
3534 setFlag(QDocumentLine::LayoutDirty, false);
3535 }
3536
3537
3538
splitAtFormatChanges(QList<RenderRange> * ranges,const QVector<int> * sel,int from,int until) const3539 void QDocumentLineHandle::splitAtFormatChanges(QList<RenderRange>* ranges, const QVector<int>* sel, int from, int until) const{
3540 if (until == -1 || until > m_text.count())
3541 until = m_text.count();
3542
3543 if (from == until){
3544 return;
3545 }
3546
3547 QVector<int> m_composited = compose();
3548
3549 QVector<int> merged;
3550 merged.fill(0, until);
3551
3552 // merge selection ranges with the rest (formats + overlays)
3553 //int max = qMin(m_text.count(), m_composited.count());
3554
3555 for ( int i = from; i < until; ++i )
3556 {
3557 if ( m_composited.count() > i )
3558 merged[i] = m_composited.at(i);
3559
3560 // separate spaces to ease rendering loop
3561 if ( m_text.at(i).isSpace() )
3562 merged[i] |= FORMAT_SPACE;
3563 }
3564
3565 if (sel) {
3566 for ( int i = 0; i < sel->count(); i += 2 )
3567 {
3568 int max = m_text.length();
3569
3570 if ( (i + 1) < sel->count() )
3571 max = qMin(sel->at(i + 1), max);
3572
3573 for ( int j = sel->at(i); j < max; ++j )
3574 merged[j] |= FORMAT_SELECTION;
3575 }
3576 }
3577
3578 // generate render ranges
3579 if ( merged.count() )
3580 {
3581 int i = from, wrap = 0;
3582 int frontier = m_frontiers.count() ? m_frontiers.first().first : until;
3583
3584 if (from != 0)
3585 while (i >= frontier && frontier < until) {
3586 ++wrap;
3587 frontier = wrap < m_frontiers.count() ? m_frontiers.at(wrap).first : until;
3588 }
3589
3590 if ( frontier > until )
3591 frontier = until;
3592
3593
3594 while ( i < until )
3595 {
3596 RenderRange r;
3597 r.position = i;
3598 r.length = 1;
3599 r.wrap = wrap;
3600 r.format = merged.at(i);
3601
3602 while ( ((i + 1) < frontier) && (merged.at(i + 1) == r.format) )
3603 {
3604 ++r.length;
3605 ++i;
3606 }
3607
3608 *ranges << r;
3609 ++i;
3610
3611 if ( i == frontier )
3612 {
3613 ++wrap;
3614 if (wrap < m_frontiers.count())
3615 frontier = qMin(m_frontiers.at(wrap).first, until);
3616 else
3617 frontier = until;
3618 }
3619 }
3620 } else {
3621 // neither formatting nor line wrapping : simple drawText()
3622 RenderRange r;
3623 r.position = 0;
3624 r.length = m_text.length();
3625 r.format = (lineHasSelection==fullSel) ? FORMAT_SELECTION : 0;
3626
3627 *ranges << r;
3628 }
3629 }
3630
3631 /*! \return the height of the associated PICTURE_COOKIE or 0.
3632 * The height is a multiple of QDocumentPrivate::m_lineSpacing
3633 *
3634 * Access is not using a mutex. Locking needs to be done before calling this.
3635 */
getPictureCookieHeight() const3636 qreal QDocumentLineHandle::getPictureCookieHeight() const{
3637 if (!hasCookie(QDocumentLine::PICTURE_COOKIE)) return 0;
3638 qreal h = 2*PICTURE_BORDER + getCookie(QDocumentLine::PICTURE_COOKIE).value<QPixmap>().height();
3639 h = qCeil(1.*h/QDocumentPrivate::m_lineSpacing)*QDocumentPrivate::m_lineSpacing;
3640 //if (fmod(h,QDocumentPrivate::m_lineSpacing) > 0) h += QDocumentPrivate::m_lineSpacing - h % qRound(QDocumentPrivate::m_lineSpacing);
3641 return h;
3642 }
3643
3644
3645 /*!
3646 * Draw the left and right border lines if the document has a limited width.
3647 */
drawBorders(QPainter * p,qreal yStart,qreal yEnd) const3648 void QDocumentLineHandle::drawBorders(QPainter *p, qreal yStart, qreal yEnd) const
3649 {
3650 QDocumentPrivate *d = m_doc->impl();
3651 if (d->hardLineWrap() || d->lineWidthConstraint()) {
3652 QColor linescolor = QDocumentPrivate::m_formatScheme->format("background").linescolor;
3653 if (!linescolor.isValid()) {
3654 return;
3655 }
3656 p->save();
3657 p->setPen(linescolor);
3658 if (d->m_leftMargin > 0)
3659 p->drawLine(QPointF(0, yStart), QPointF(0, yEnd)); // left border line
3660 p->drawLine(QPointF(d->width(), yStart), QPointF(d->width() , yEnd)); // right border line
3661 p->restore();
3662 }
3663 }
3664
draw(int lineNr,QPainter * p,qreal xOffset,qreal vWidth,const QVector<int> & selectionBoundaries,const QPalette & pal,bool fullSel,qreal yStart,qreal yEnd) const3665 void QDocumentLineHandle::draw(int lineNr, QPainter *p,
3666 qreal xOffset,
3667 qreal vWidth,
3668 const QVector<int>& selectionBoundaries,
3669 const QPalette& pal,
3670 bool fullSel,
3671 qreal yStart,
3672 qreal yEnd) const
3673 {
3674 QReadLocker locker(&mLock);
3675 if ( hasFlag(QDocumentLine::LayoutDirty) ||
3676 ( m_layout && !hasFlag(QDocumentLine::FormatsApplied) ) ) // formats need to added before splitting lines, as they could change the linewidthes ...
3677 layout(lineNr);
3678
3679 if ( m_layout )
3680 {
3681 //if (!hasFlag(QDocumentLine::FormatsApplied))
3682 //m_layout->setAdditionalFormats(decorations()); (this causes a crash on qt>5.3)
3683
3684 //if ( !hasFlag(QDocumentLine::FormatsApplied) )
3685 // applyOverlays();
3686
3687 const qreal lineSpacing = QDocumentPrivate::m_lineSpacing;
3688
3689 QVector<QTextLayout::FormatRange> selections;
3690
3691 QTextCharFormat fmt;
3692 fmt.setBackground(pal.highlight());
3693 fmt.setForeground(pal.highlightedText());
3694
3695 QTextLayout::FormatRange range;
3696 if ( fullSel )
3697 {
3698 range.start = 0;
3699 range.format = fmt;
3700 range.length = m_text.length();
3701 selections << range;
3702 } else {
3703 for ( int i = 0; i < selectionBoundaries.count(); ++i )
3704 {
3705 range.start = selectionBoundaries[i];
3706 range.format = fmt;
3707
3708 if ( (i + 1) < selectionBoundaries.count() )
3709 {
3710 // regular selection subset
3711 range.length = selectionBoundaries[++i] - range.start;
3712
3713 } else if ( m_layout->lineCount() ) {
3714 // span to end of line, not only text
3715 range.length = m_text.length() - range.start;
3716 qreal lineWidth = m_layout->lineAt(m_layout->lineCount() - 1).naturalTextWidth();
3717 const qreal endX = QDocumentPrivate::m_leftPadding + lineWidth - xOffset;
3718
3719 QRectF area(endX, lineSpacing * i, vWidth - endX, lineSpacing);
3720
3721 p->fillRect(area, fmt.background());
3722 }
3723
3724 selections << range;
3725 }
3726 }
3727
3728 QPointF off(QDocumentPrivate::m_leftPadding, 0);
3729
3730 m_layout->draw(p, off, selections);
3731
3732 //m_layout->clearAdditionalFormats();
3733 } else if ( m_text.isEmpty() ) {
3734 // enforce selection drawing on empty lines
3735 if ( selectionBoundaries.count() == 1 ){
3736 p->fillRect(QRectF(qMax(xOffset, QDocumentPrivate::m_leftPadding),0,vWidth,QDocumentPrivate::m_lineSpacing),
3737 pal.highlight()
3738 );
3739 }else{
3740 if(!fullSel){
3741 //QDocumentPrivate *d = m_doc->impl();
3742 foreach(QFormatRange overlay,m_overlays){
3743 QFormat format=QDocumentPrivate::m_formatScheme->format(overlay.format);
3744 if(format.wrapAround){
3745 p->fillRect(QRectF(qMax(xOffset, QDocumentPrivate::m_leftPadding),0,vWidth,QDocumentPrivate::m_lineSpacing),format.background);
3746 }
3747 }
3748 }
3749 }
3750
3751 } else {
3752 QList<RenderRange> ranges;
3753 splitAtFormatChanges(&ranges, &selectionBoundaries);
3754
3755 // find start of trailing whitespaces
3756 int last = m_text.length();
3757
3758 while ( (last > 0) && m_text.at(last - 1).isSpace() )
3759 --last;
3760
3761
3762 int fmt = fullSel ? FORMAT_SELECTION : 0;
3763 int lastFont = -1;
3764 QDocumentPrivate *d = m_doc->impl();
3765 const int ts = d->m_tabStop;
3766 const qreal maxWidth = xOffset + vWidth;
3767 const qreal maxDocWidth = xOffset + m_doc->width();
3768 const bool hasUnboundedSelection = selectionBoundaries.count() & 1;
3769 // check if wraparound format is active
3770 int wrapAroundHighlight = 0;
3771 int length=m_text.length();
3772 QList<int> foundFormats;
3773 if(!fullSel){
3774 foreach(QFormatRange fng,m_overlays){
3775 if(fng.offset+fng.length==length){
3776 QFormat format=QDocumentPrivate::m_formatScheme->format(fng.format);
3777 if(format.wrapAround){
3778 foundFormats<<fng.format;
3779 }
3780 }
3781 }
3782 if(!foundFormats.isEmpty()){
3783 int lineNr=d->indexOf(this)+1;
3784 QDocumentLineHandle *nextHandle=d->at(lineNr);
3785 int priority=-100;
3786 if(nextHandle){
3787 foreach(QFormatRange fng,nextHandle->m_overlays){
3788 if(fng.offset==0){
3789 QFormat format=QDocumentPrivate::m_formatScheme->format(fng.format);
3790 if(format.wrapAround && foundFormats.contains(fng.format) && format.priority>=priority){
3791 wrapAroundHighlight=fng.format;
3792 }
3793 }
3794 }
3795 }
3796 }
3797 }
3798 const QColor highlightedTextColor = pal.highlightedText().color();
3799
3800 const bool showTabs = QDocument::showSpaces() & QDocument::ShowTabs,
3801 showLeading = QDocument::showSpaces() & QDocument::ShowLeading,
3802 showTrailing = QDocument::showSpaces() & QDocument::ShowTrailing;
3803
3804 //const int fns = nextNonSpaceChar(0);
3805 qreal indent = qMax(0., m_indent) + QDocumentPrivate::m_leftPadding;
3806
3807 int rngIdx = 0;
3808 int column = 0;
3809 #ifndef Q_OS_WIN
3810 bool continuingWave = false, brokenWave = false;
3811 bool dir = false; // false = down; true = up
3812 #endif
3813 int wrap = 0;
3814 qreal xpos = QDocumentPrivate::m_leftPadding;
3815 qreal ypos = 0.0;
3816 bool leading = ranges.first().format & FORMAT_SPACE;
3817
3818 qreal mergeXpos=-1;
3819 int mergeFormat=-1;
3820 QString mergeText;
3821 foreach ( const RenderRange& r, ranges )
3822 {
3823 ++rngIdx;
3824
3825 if ( wrap != r.wrap )
3826 {
3827 //flush mergedRange
3828 if(mergeXpos>=0){
3829 p->restore();
3830 p->drawText(QPointF(mergeXpos, ypos + QDocumentPrivate::m_ascent), mergeText);
3831 mergeXpos=-1;
3832 mergeText.clear();
3833 }
3834
3835 #ifndef Q_OS_WIN
3836 continuingWave = false;
3837 #endif
3838 if ( fmt & FORMAT_SELECTION )
3839 {
3840 // finish selection
3841 p->fillRect(
3842 QRectF(xpos, ypos,maxDocWidth - xpos, QDocumentPrivate::m_lineSpacing),
3843 pal.highlight()
3844 );
3845
3846 }
3847
3848 ++wrap;
3849 column = 0;
3850 ypos += QDocumentPrivate::m_lineSpacing;
3851 if(ypos>yEnd) {
3852 break;
3853 }
3854 xpos = indent;
3855
3856 if ( r.format & FORMAT_SELECTION )
3857 {
3858 // finish selection
3859 p->fillRect(
3860 QRectF(QDocumentPrivate::m_leftPadding, ypos, xpos, QDocumentPrivate::m_lineSpacing),
3861 pal.highlight()
3862 );
3863
3864 }
3865 }
3866 if ( ypos < yStart ) continue;
3867
3868 if ( leading && !(r.format & FORMAT_SPACE) )
3869 {
3870 //indent = xpos;
3871 leading = false;
3872 }
3873
3874 // TODO : clip more accurately (i.e inside ranges)
3875 if ( xpos > maxWidth ){
3876 if( d->hardLineWrap()||d->lineWidthConstraint() ) continue;
3877 else if (m_frontiers.isEmpty()) break;
3878 //else break;
3879 }
3880
3881 fmt = r.format;
3882
3883 if(mergeXpos>=0 && (fmt&(~FORMAT_SPACE)) != (mergeFormat&(~FORMAT_SPACE))){
3884 // flush
3885 p->restore();
3886 p->drawText(QPointF(mergeXpos, ypos + QDocumentPrivate::m_ascent), mergeText);
3887 mergeXpos=-1;
3888 mergeText.clear();
3889 }
3890
3891 int fmts[FORMAT_MAX_COUNT];
3892 QFormat formats[FORMAT_MAX_COUNT];
3893 int newFont = lastFont;
3894 d->m_formatScheme->extractFormats(fmt, fmts, formats, newFont);
3895
3896 if (newFont != lastFont) {
3897 d->tunePainter(p, newFont);
3898 lastFont = newFont;
3899 }
3900
3901 qreal rwidth = 0;
3902 int tcol = column;
3903 const QString rng = m_text.mid(r.position, r.length);
3904
3905 if ( r.format & FORMAT_SPACE )
3906 {
3907 qreal currentSpaceWidth = d->textWidth(newFont, " ");
3908 foreach ( QChar c, rng )
3909 {
3910 if ( c.unicode() == '\t' )
3911 {
3912 int toff = ncolsToNextTabStop(tcol, ts);
3913 rwidth += toff * currentSpaceWidth;
3914 tcol += toff;
3915 } else {
3916 rwidth += currentSpaceWidth;
3917 ++tcol;
3918 }
3919 }
3920 } else {
3921 column += r.length;
3922 #if defined(Q_OS_OSX) && QT_VERSION_MAJOR<6
3923 rwidth = p->fontMetrics().horizontalAdvance(rng);
3924 #else
3925 rwidth = d->textWidth(lastFont, rng);
3926 #endif
3927 }
3928
3929 if ( (xpos + rwidth) <= xOffset )
3930 {
3931 xpos += rwidth;
3932
3933 if ( r.format & FORMAT_SPACE )
3934 column = tcol;
3935
3936 continue;
3937 }
3938
3939 qreal xspos = xpos;
3940 //const QPen oldpen = p->pen();
3941 const qreal baseline = ypos + QDocumentPrivate::m_ascent;
3942
3943
3944 const bool currentSelected = (fullSel || (fmt & FORMAT_SELECTION));
3945 if ( currentSelected )
3946 {
3947 p->setPen(highlightedTextColor);
3948 p->fillRect(QRectF(xpos, ypos,rwidth, QDocumentPrivate::m_lineSpacing),
3949 pal.highlight()
3950 );
3951 } else {
3952 QColor fg(pal.text().color());
3953 int priority=-100;
3954 for(int i=0;i<3;i=i+1){
3955 if ( formats[i].foreground.isValid() && formats[i].priority>priority){
3956 fg=formats[i].foreground;
3957 priority=formats[i].priority;
3958 }
3959 }
3960 p->setPen(fg);
3961
3962 // not sure whether fg/bg should be handled separately concerning priority
3963 QColor bg;
3964 priority=-100;
3965 for(int i=0;i<3;i=i+1){
3966 if ( formats[i].background.isValid() && formats[i].priority>priority) {
3967 bg=formats[i].background;
3968 priority=formats[i].priority;
3969 }
3970 }
3971 if(priority>-100){
3972 p->fillRect(QRectF(xpos, ypos,rwidth,QDocumentPrivate::m_lineSpacing),
3973 bg
3974 );
3975 }
3976
3977 }
3978
3979 if ( r.format & FORMAT_SPACE )
3980 {
3981 // spaces
3982 int max = r.position + r.length;
3983
3984 if ( rngIdx == ranges.count() )
3985 ++max;
3986
3987 qreal currentSpaceWidth = d->textWidth(newFont, " ");
3988 for ( int i = r.position; i < max; ++i )
3989 {
3990 if ( i == r.position + r.length )
3991 break;
3992
3993 bool isTab = m_text.at(i).unicode() == '\t';
3994
3995 if ( isTab )
3996 {
3997 int toff = ncolsToNextTabStop(column, ts);
3998 column += toff;
3999 qreal xoff = toff * currentSpaceWidth;
4000
4001
4002 if ( showTabs )
4003 {
4004 // draw tab marker
4005 p->save();
4006 p->setPen(Qt::lightGray);
4007 int headSize = qMin(QDocumentPrivate::m_lineHeight/8, currentSpaceWidth-2);
4008 p->translate(xpos+xoff-2, ypos + QDocumentPrivate::m_lineHeight/2);
4009 p->drawLine(QPointF(-xoff+3,0),QPointF(0,0));
4010 p->drawLine(QPointF(-headSize,-headSize),QPointF(0,0));
4011 p->drawLine(QPointF(-headSize, headSize),QPointF(0,0));
4012 p->restore();
4013 }
4014
4015 xpos += xoff;
4016 if(mergeXpos>=0){
4017 mergeText+=QString(toff,' ');
4018 }
4019 } else {
4020 ++column;
4021
4022 if (
4023 (
4024 leading
4025 &&
4026 showLeading
4027 )
4028 ||
4029 (
4030 (r.position >= last)
4031 &&
4032 showTrailing
4033 )
4034 )
4035 {
4036 // draw space marker
4037 p->save();
4038 p->setPen(Qt::lightGray);
4039 // old: manually drawn dot
4040 //use old solution as qt5 is sh***y when finding font substitution
4041
4042 p->drawText(QPointF(xpos, baseline), QString(static_cast<ushort>(0xb7)));
4043 p->restore();
4044 }
4045
4046 xpos += currentSpaceWidth;
4047 if(mergeXpos>=0){
4048 mergeText+=" ";
4049 }
4050 }
4051 }
4052
4053
4054 } else {
4055 if (d->m_workArounds & QDocument::ForceSingleCharacterDrawing ) {
4056 QColor color;
4057 if (currentSelected) color = highlightedTextColor;
4058 // TODO: somehow this breaks extra highlighting of environment names such as "foo" in \begin{foo}
4059 else if (newFont == fmts[0]) color = formats[0].foreground;
4060 else if (newFont == fmts[1]) color = formats[1].foreground;
4061 else if (newFont == fmts[2]) color = formats[2].foreground;
4062 if (!color.isValid()) color = pal.text().color();
4063 d->drawText(*p, newFont, color, currentSelected, xpos, ypos, rng); //ypos instead of baseline
4064 } else {
4065 //merge ranges if possible
4066 mergeText+=rng;
4067 if(mergeXpos<0){
4068 mergeXpos=xpos;
4069 mergeFormat=fmt;
4070 p->save();
4071 }
4072 xpos += rwidth;
4073 }
4074 }
4075
4076 //qDebug("underline pos : %i", p->fontMetrics().underlinePos());
4077
4078 if ( formats[0].linescolor.isValid() ) p->setPen(formats[0].linescolor);
4079 else if ( formats[1].linescolor.isValid() ) p->setPen(formats[1].linescolor);
4080 else if ( formats[2].linescolor.isValid() ) p->setPen(formats[2].linescolor);
4081
4082 QFontMetricsF fm=QFontMetricsF(p->font());
4083 const qreal ydo = qMin(baseline + fm.underlinePos(), ypos + QDocumentPrivate::m_lineSpacing - 1);
4084 const qreal yin = baseline - fm.strikeOutPos();
4085 const qreal yup = qMax(baseline - fm.overlinePos() + 1, ypos);
4086
4087
4088 p->save();
4089 setPainterLineWidth(p, p->fontMetrics().lineWidth()); // TODO: maybe we can do this in tunePainter()?
4090
4091 if ( formats[0].overline || formats[1].overline || formats[2].overline )
4092 {
4093 p->drawLine(QPointF(xspos, yup), QPointF(xpos, yup));
4094 }
4095
4096 if ( formats[0].strikeout || formats[1].strikeout || formats[2].strikeout )
4097 {
4098 p->drawLine(QPointF(xspos, yin), QPointF(xpos, yin));
4099 }
4100
4101 if ( formats[0].underline || formats[1].underline || formats[2].underline )
4102 {
4103 p->drawLine(QPointF(xspos, ydo), QPointF(xpos, ydo));
4104 }
4105 p->restore();
4106
4107 if ( formats[0].waveUnderline || formats[1].waveUnderline || formats[2].waveUnderline )
4108 {
4109 /*
4110 those goddamn font makers take liberties with common sense
4111 and make it so damn harder to figure proper y offset for wave
4112 underline (keeping the regular underline pos make it look
4113 weird or even invisible on some fonts... reference rendering
4114 with DejaVu Sans Mono)
4115 */
4116
4117 // we used fixed wave amplitude of 3 (well strictly speaking
4118 // amplitude would be 1.5 but let's forget about waves and
4119 // focus on getting that code to work...
4120
4121 // gotta center things
4122 //const int ycenter = ypos + QDocumentPrivate::m_lineSpacing - 3;
4123 /*
4124 qMin(
4125 ypos + (QDocumentPrivate::m_ascent + QDocumentPrivate::m_lineSpacing) / 2,
4126 ypos + QDocumentPrivate::m_lineSpacing - 3
4127 );*/
4128
4129 //if (format.waveUnderlineForeground.isValid())
4130 // p->setPen(format.waveUnderlineForeground);
4131 #ifdef Q_OS_WIN
4132 QPen pn2=p->pen();
4133 QVector<qreal>pattern2;
4134 pattern2 << 1.0 << 3.0;
4135 pn2.setDashPattern(pattern2);
4136 p->setPen(pn2);
4137 p->drawLine(QPointF(xspos, ydo), QPointF(xpos, ydo));
4138 p->drawLine(QPointF(xspos+1, ydo-1), QPointF(xpos, ydo-1));
4139 p->drawLine(QPointF(xspos+2, ydo), QPointF(xpos, ydo));
4140 p->drawLine(QPointF(xspos+3, ydo+1), QPointF(xpos, ydo+1));
4141 /*
4142 QColor cl=p->pen().color();
4143 QImage wv(4,3,QImage::Format_ARGB32);
4144 wv.fill(0x00ffffff);
4145 wv.setPixel(0,0,cl.rgba());
4146 wv.setPixel(1,1,cl.rgba());
4147 wv.setPixel(2,2,cl.rgba());
4148 wv.setPixel(3,1,cl.rgba());
4149
4150 QBrush bwv(wv);
4151 QTransform tf;
4152 tf.translate(0,ycenter%3);
4153 bwv.setTransform(tf);
4154
4155 p->save();
4156 p->setBrush(bwv);
4157 p->setPen(Qt::NoPen);
4158 p->drawRect(xspos,ycenter,rwidth,3);
4159 p->restore();
4160 */
4161 }
4162 #else
4163 const qreal ps=p->fontInfo().pointSizeF();
4164 const qreal amp=ps/10;
4165 const qreal ycenter = ypos + QDocumentPrivate::m_lineSpacing - 2*amp;
4166 p->save();
4167 QPen pen=p->pen();
4168 pen.setWidthF(amp);
4169 p->setPen(pen);
4170
4171 qreal cp = 0;
4172 brokenWave = false;
4173 QVector<QPointF> lstOfPoints;
4174 lstOfPoints<<QPointF(xspos,ycenter);
4175
4176 while ( cp < rwidth )
4177 {
4178 if ( cp<0.1 && !continuingWave )
4179 {
4180 dir = false;
4181 //p->drawLine(xspos, ycenter, xspos + 1, ycenter + 1);
4182 lstOfPoints<<QPointF(xspos+amp,ycenter+amp);
4183 cp+=amp;
4184 } else if ( cp<0.1 && brokenWave ) {
4185 if ( !dir ){
4186 //p->drawLine(xspos, ycenter, xspos + 1, ycenter + 1);
4187 lstOfPoints<<QPointF(xspos+amp,ycenter+amp);
4188 }else{
4189 //p->drawLine(xspos, ycenter, xspos + 1, ycenter - 1);
4190 lstOfPoints<<QPointF(xspos+amp,ycenter-amp);
4191 }
4192
4193 } else {
4194 if ( cp + 2* amp > rwidth)
4195 {
4196 if ( !dir )
4197 lstOfPoints<<QPointF(xspos+cp+amp,ycenter);
4198 //p->drawLine(xspos + cp, ycenter - 1, xspos + cp + 1, ycenter);
4199 else
4200 lstOfPoints<<QPointF(xspos+cp+amp,ycenter);
4201 //p->drawLine(xspos + cp, ycenter + 1, xspos + cp + 1, ycenter);
4202
4203 // trick to keep current direction
4204 dir = !dir;
4205
4206 brokenWave = true;
4207 cp+=amp;
4208 break;
4209 } else {
4210 if ( !dir )
4211 lstOfPoints<<QPointF(xspos+cp+2*amp,ycenter+amp);
4212 //p->drawLine(xspos + cp, ycenter - 1, xspos + cp + 2, ycenter + 1);
4213 else
4214 lstOfPoints<<QPointF(xspos+cp+2*amp,ycenter-amp);
4215 //p->drawLine(xspos + cp, ycenter + 1, xspos + cp + 2, ycenter - 1);
4216 cp += 2*amp;
4217 }
4218 }
4219
4220 dir = !dir;
4221 }
4222 p->drawPolyline(lstOfPoints.data(),lstOfPoints.count());
4223 p->restore();
4224
4225 continuingWave = true;
4226 } else {
4227 continuingWave = false;
4228 dir = false;
4229
4230 }
4231 #endif
4232
4233 //p->setPen(oldpen);
4234 }
4235 if(mergeXpos>=0){
4236 //final flush
4237 p->restore();
4238 p->drawText(QPointF(mergeXpos, ypos + QDocumentPrivate::m_ascent), mergeText);
4239 }
4240
4241 if (hasUnboundedSelection || wrapAroundHighlight) {
4242 // fill the rest of the line because the selection or highlight continues
4243 QBrush brush = pal.highlight();
4244 if (!hasUnboundedSelection) {
4245 QFormat format = m_doc->impl()->m_formatScheme->format(wrapAroundHighlight);
4246 brush = QBrush(format.background);
4247 }
4248 p->fillRect(QRectF(xpos, ypos, maxDocWidth - xpos, QDocumentPrivate::m_lineSpacing), brush);
4249 }
4250 }
4251 drawBorders(p, yStart, yEnd);
4252 }
4253
exportAsHtml(int fromOffset,int toOffset,int maxLineWidth,int maxWrap) const4254 QString QDocumentLineHandle::exportAsHtml(int fromOffset, int toOffset, int maxLineWidth, int maxWrap) const{
4255 QReadLocker locker(&mLock);
4256 if ( !document()->formatScheme() ) {
4257 return text();
4258 }
4259 if (toOffset == -1) toOffset = m_text.length();
4260 QList<RenderRange> ranges;
4261 splitAtFormatChanges(&ranges,nullptr);
4262 QString result = "<pre>";
4263 int col = 0;
4264 int wrapCount = 0;
4265 foreach ( const RenderRange& r, ranges ) {
4266 if ( r.position + r.length < fromOffset ) continue;
4267 if ( r.position > toOffset ) break;
4268
4269 if ((maxLineWidth > 0) && (col + r.length > maxLineWidth)) {
4270 if (wrapCount >= maxWrap) {
4271 result += "...";
4272 break;
4273 }
4274 result += "<br>";
4275 wrapCount++;
4276 col = 0;
4277 } else {
4278 col += r.length;
4279 }
4280
4281 int fmt = r.format;
4282 int fmts[FORMAT_MAX_COUNT];
4283 QFormat formats[FORMAT_MAX_COUNT];
4284 int newFont;
4285 document()->formatScheme()->extractFormats(fmt, fmts, formats, newFont);
4286 for (int i=FORMAT_MAX_COUNT-1;i>=0;i--)
4287 if ( fmts[i] )
4288 result+=QString("<span class=\"fmt%1\">").arg(fmts[i]);
4289 //result += QString("<span class=\"fmt%1\">").arg(newFont);
4290 result += m_text.mid(r.position, r.length).replace("&","&").replace("<","<");
4291 //result += "</span>";
4292 for ( int i=0; i<FORMAT_MAX_COUNT; i++)
4293 if ( fmts[i])
4294 result+=QString("</span>");
4295 }
4296 return result+" </pre>";
4297 }
4298
4299 //////////////////
4300
4301
4302 /////////////////////////
4303 // QDocumentCursorHandle
4304 /////////////////////////
QDocumentCursorHandle(QDocument * d,int line)4305 QDocumentCursorHandle::QDocumentCursorHandle(QDocument *d, int line)
4306 : m_flags(0), //no columnmemory, can be slow and is usually not needed
4307 m_doc(d),
4308 m_ref(0),
4309 m_begOffset(0), m_endOffset(0), m_savedX(0), m_begLine(line), m_endLine(-1)
4310 {
4311
4312 }
4313
QDocumentCursorHandle(QDocument * d,int line,int column,int lineTo,int columnTo)4314 QDocumentCursorHandle::QDocumentCursorHandle(QDocument *d, int line, int column, int lineTo, int columnTo)
4315 : m_flags(0), //no columnmemory, can be slow and is usually not needed
4316 m_doc(d),
4317 m_ref(0),
4318 m_savedX(0)
4319 {
4320 select(line, column, lineTo, columnTo);
4321 }
4322
4323
~QDocumentCursorHandle()4324 QDocumentCursorHandle::~QDocumentCursorHandle()
4325 {
4326 //qDebug("Cursor handle deleted : 0x%x", this);
4327 Q_ASSERT(!m_ref.loadAcquire());
4328
4329 if (isAutoUpdated())
4330 setAutoUpdated(false);
4331 }
4332
copy(const QDocumentCursorHandle * c)4333 void QDocumentCursorHandle::copy(const QDocumentCursorHandle *c)
4334 {
4335 if ( !c )
4336 return;
4337
4338 if ( isAutoUpdated() )
4339 setAutoUpdated(false);
4340
4341 m_begLine = c->m_begLine;
4342 m_begOffset = c->m_begOffset;
4343 m_endLine = c->m_endLine;
4344 m_endOffset = c->m_endOffset;
4345 m_flags = c->m_flags & ~AutoUpdated; //copy isn't automatically autoupdated
4346 m_savedX = c->m_savedX;
4347 }
4348
document() const4349 QDocument* QDocumentCursorHandle::document() const
4350 {
4351 return m_doc;
4352 }
4353
clone(bool cloneAutoUpdatedFlag) const4354 QDocumentCursorHandle* QDocumentCursorHandle::clone(bool cloneAutoUpdatedFlag) const
4355 {
4356 QDocumentCursorHandle *c = new QDocumentCursorHandle(m_doc);
4357 c->copy(this);
4358
4359 if (cloneAutoUpdatedFlag)
4360 c->setAutoUpdated(isAutoUpdated());
4361
4362 return c;
4363 }
4364
atEnd() const4365 bool QDocumentCursorHandle::atEnd() const
4366 {
4367 if ( !m_doc )
4368 return true;
4369
4370 bool atLineEnd;
4371 QDocumentLine l = m_doc->line(m_begLine);
4372
4373 //qDebug("Cursor handle : 0x%x->atEnd() => 0x%x", this, m_begLine.handle());
4374
4375 if ( l.isValid() )
4376 {
4377 atLineEnd = m_begOffset == l.length();
4378 l = m_doc->line(m_begLine + 1);
4379 } else {
4380 //qWarning("Invalid cursor...");
4381 return true;
4382 }
4383
4384 return l.isNull() && atLineEnd;
4385 }
4386
atStart() const4387 bool QDocumentCursorHandle::atStart() const
4388 {
4389 if ( !m_doc )
4390 return true;
4391
4392 QDocumentLine l = m_doc->line(m_begLine - 1);
4393
4394 return l.isNull() && !m_begOffset;
4395 }
4396
atBlockEnd() const4397 bool QDocumentCursorHandle::atBlockEnd() const
4398 {
4399 return atLineEnd();
4400 }
4401
atBlockStart() const4402 bool QDocumentCursorHandle::atBlockStart() const
4403 {
4404 return atLineStart();
4405 }
4406
atLineEnd() const4407 bool QDocumentCursorHandle::atLineEnd() const
4408 {
4409 if ( !m_doc )
4410 return true;
4411
4412 QDocumentLine l = m_doc->line(m_begLine);
4413
4414 return l.isValid() ? l.length() == m_begOffset : false;
4415 }
4416
atLineStart() const4417 bool QDocumentCursorHandle::atLineStart() const
4418 {
4419 if ( !m_doc )
4420 return true;
4421
4422 QDocumentLine l = m_doc->line(m_begLine);
4423
4424 return l.isValid() ? !m_begOffset : false;
4425 }
4426
hasSelection() const4427 bool QDocumentCursorHandle::hasSelection() const
4428 {
4429 if ( !m_doc )
4430 return false;
4431
4432 QDocumentLine l1 = m_doc->line(m_begLine), l2 = m_doc->line(m_endLine);
4433
4434 return l1.isValid() && l2.isValid();
4435 }
4436
isForwardSelection() const4437 bool QDocumentCursorHandle::isForwardSelection() const
4438 {
4439 if ( !hasSelection() )
4440 return false;
4441 if (m_endLine < m_begLine)
4442 return true;
4443 if (m_endLine == m_begLine && m_endOffset < m_begOffset)
4444 return true;
4445 return false;
4446 }
4447
isSilent() const4448 bool QDocumentCursorHandle::isSilent() const
4449 {
4450 return hasFlag(Silent);
4451 }
4452
setSilent(bool y)4453 void QDocumentCursorHandle::setSilent(bool y)
4454 {
4455 if ( y )
4456 setFlag(Silent);
4457 else
4458 clearFlag(Silent);
4459 }
4460
isAutoUpdated() const4461 bool QDocumentCursorHandle::isAutoUpdated() const
4462 {
4463 return hasFlag(AutoUpdated);
4464 }
4465
setAutoUpdated(bool y)4466 void QDocumentCursorHandle::setAutoUpdated(bool y)
4467 {
4468 if ( isAutoUpdated() == y || !m_doc )
4469 return;
4470 if ( y ) m_doc->impl()->addAutoUpdatedCursor(this);
4471 else m_doc->impl()->removeAutoUpdatedCursor(this);
4472 }
4473
isAutoErasable() const4474 bool QDocumentCursorHandle::isAutoErasable() const{
4475 return hasFlag(AutoErasable);
4476 }
setAutoErasable(bool y)4477 void QDocumentCursorHandle::setAutoErasable(bool y){
4478 if (y) setFlag(AutoErasable);
4479 else clearFlag(AutoErasable);
4480 }
4481
4482
line() const4483 QDocumentLine QDocumentCursorHandle::line() const
4484 {
4485 if ( !m_doc )
4486 return QDocumentLine();
4487
4488 return m_doc->line(m_begLine);
4489 }
4490
anchorLine() const4491 QDocumentLine QDocumentCursorHandle::anchorLine() const
4492 {
4493 if ( !m_doc )
4494 return QDocumentLine();
4495
4496 return m_endLine != -1 ? m_doc->line(m_endLine) : line();
4497 }
4498
lineNumber() const4499 int QDocumentCursorHandle::lineNumber() const
4500 {
4501 return m_begLine;
4502 }
4503
anchorLineNumber() const4504 int QDocumentCursorHandle::anchorLineNumber() const
4505 {
4506 return m_endLine != -1 ? m_endLine : m_begLine;
4507 }
4508
anchorColumnNumber() const4509 int QDocumentCursorHandle::anchorColumnNumber() const
4510 {
4511 if ( !m_doc )
4512 return -1;
4513
4514 return m_doc->line(m_endLine).isValid() ? m_endOffset : m_begOffset;
4515 }
4516
4517
startLineNumber() const4518 int QDocumentCursorHandle::startLineNumber() const{
4519 if (m_endLine == -1)
4520 return m_begLine;
4521 return qMin(m_begLine, m_endLine);
4522 }
startColumnNumber() const4523 int QDocumentCursorHandle::startColumnNumber() const{
4524 if (m_endLine == -1)
4525 return m_begOffset;
4526 if (m_begLine == m_endLine)
4527 return qMin(m_begOffset, m_endOffset);
4528 else if (m_begLine < m_endLine)
4529 return m_begOffset;
4530 else
4531 return m_endOffset;
4532 }
4533
endLineNumber() const4534 int QDocumentCursorHandle::endLineNumber() const{
4535 if (m_endLine == -1)
4536 return m_begLine;
4537 return qMax(m_begLine, m_endLine);
4538 }
endColumnNumber() const4539 int QDocumentCursorHandle::endColumnNumber() const{
4540 if (m_endLine == -1)
4541 return m_begOffset;
4542 if (m_begLine == m_endLine)
4543 return qMax(m_begOffset, m_endOffset);
4544 else if (m_begLine > m_endLine)
4545 return m_begOffset;
4546 else
4547 return m_endOffset;
4548 }
4549
4550
visualColumnNumber() const4551 int QDocumentCursorHandle::visualColumnNumber() const
4552 {
4553 return QDocument::screenColumn(
4554 line().text().constData(),
4555 m_begOffset,
4556 QDocument::tabStop()
4557 );
4558
4559 }
4560
columnNumber() const4561 int QDocumentCursorHandle::columnNumber() const
4562 {
4563 return m_begOffset;
4564 }
4565
setLineNumber(int l,int m)4566 void QDocumentCursorHandle::setLineNumber(int l, int m)
4567 {
4568 if ( !m_doc )
4569 return;
4570
4571 QDocumentLine /*l1 = m_doc->line(m_begLine), */l2 = m_doc->line(m_endLine);
4572 QDocumentLine ln = m_doc->line(l);
4573
4574 if (ln.isNull())
4575 return;
4576
4577 if ( m & QDocumentCursor::KeepAnchor )
4578 {
4579 if ( l2.isNull() )
4580 {
4581 m_endLine = m_begLine;
4582 m_endOffset = m_begOffset;
4583 }
4584
4585 m_begLine = l; //qBound(0, c, l1.length());
4586 } else {
4587 m_endLine = -1;
4588 m_endOffset = 0;
4589 m_begLine = l;
4590 }
4591
4592 refreshColumnMemory();
4593 }
4594
4595
setColumnNumber(int c,int m)4596 void QDocumentCursorHandle::setColumnNumber(int c, int m)
4597 {
4598 if ( !m_doc )
4599 return;
4600
4601 QDocumentLine /*l1 = m_doc->line(m_begLine), */l2 = m_doc->line(m_endLine);
4602
4603 if ( m & QDocumentCursor::KeepAnchor )
4604 {
4605 if ( l2.isNull() )
4606 {
4607 m_endLine = m_begLine;
4608 m_endOffset = m_begOffset;
4609 }
4610
4611 m_begOffset = c; //qBound(0, c, l1.length());
4612 } else {
4613 m_endLine = -1;
4614 m_endOffset = 0;
4615 m_begOffset = c; //qBound(0, c, l1.length());
4616 }
4617
4618 refreshColumnMemory();
4619 }
4620
setAnchorLineNumber(int l)4621 void QDocumentCursorHandle::setAnchorLineNumber(int l){
4622 if ( !m_doc )
4623 return;
4624
4625 if ( m_doc->line(l).isNull() )
4626 return;
4627
4628 m_endLine = l;
4629 }
4630
setAnchorColumnNumber(int c)4631 void QDocumentCursorHandle::setAnchorColumnNumber(int c){
4632 m_endOffset = c;
4633 }
4634
4635
documentPosition() const4636 QPointF QDocumentCursorHandle::documentPosition() const
4637 {
4638 if ( !m_doc )
4639 return QPointF();
4640
4641 return QPointF(0, m_doc->y(m_begLine)) + m_doc->line(m_begLine).cursorToDocumentOffset(m_begOffset);
4642 }
4643
anchorDocumentPosition() const4644 QPointF QDocumentCursorHandle::anchorDocumentPosition() const
4645 {
4646 if ( !m_doc )
4647 return QPointF();
4648
4649 if ( m_endLine < 0 || m_endOffset < 0 )
4650 return documentPosition();
4651
4652 return QPointF(0, m_doc->y(m_endLine)) + m_doc->line(m_endLine).cursorToDocumentOffset(m_endOffset);
4653 }
4654
documentRegion() const4655 QPolygonF QDocumentCursorHandle::documentRegion() const
4656 {
4657 QPolygonF poly;
4658 QPointF p = documentPosition(), ap = anchorDocumentPosition();
4659
4660 int w = m_doc->width();
4661
4662 const qreal lm = m_doc->impl()->m_leftMargin;
4663 const qreal ls = m_doc->impl()->m_lineSpacing;
4664
4665 if ( p == ap )
4666 {
4667 poly
4668 << p
4669 << QPointF(p.x() + 1, p.y())
4670 << QPointF(p.x() + 1, p.y() + ls)
4671 << QPointF(p.x(), p.y() + ls);
4672 } else if ( p.y() == ap.y() ) {
4673 poly
4674 << p
4675 << ap
4676 << QPointF(ap.x(), ap.y() + ls)
4677 << QPointF(p.x(), p.y() + ls);
4678 } else if ( p.y() < ap.y() ) {
4679 poly
4680 << p
4681 << QPointF(w+lm, p.y());
4682
4683 if ( ap.x() < w )
4684 poly << QPointF(w+lm, ap.y()) << ap;
4685
4686 poly
4687 << QPointF(ap.x(), ap.y() + ls)
4688 << QPointF(lm, ap.y() + ls)
4689 << QPointF(lm, p.y() + ls);
4690
4691 if ( p.x() > lm )
4692 poly << QPointF(p.x(), p.y() + ls);
4693 } else {
4694 poly
4695 << ap
4696 << QPointF(w+lm, ap.y());
4697
4698 if ( p.x() < w )
4699 poly << QPointF(w+lm, p.y()) << p;
4700
4701 poly
4702 << QPointF(p.x(), p.y() + ls)
4703 << QPointF(lm, p.y() + ls)
4704 << QPointF(lm, ap.y() + ls);
4705
4706 if ( ap.x() > lm )
4707 poly << QPointF(ap.x(), ap.y() + ls);
4708 }
4709
4710 return poly;
4711 }
4712
shift(int offset)4713 void QDocumentCursorHandle::shift(int offset)
4714 {
4715 if ( !m_doc )
4716 return;
4717
4718 QDocumentLine l1 = m_doc->line(m_begLine), l2 = m_doc->line(m_endLine);
4719
4720 if ( l1.isValid() )
4721 m_begOffset = qBound(0, m_begOffset + offset, l1.length());
4722
4723 if ( l2.isValid() )
4724 m_endOffset = qBound(0, m_endOffset + offset, l2.length());
4725 }
4726
refreshColumnMemory()4727 void QDocumentCursorHandle::refreshColumnMemory()
4728 {
4729 if (m_doc && hasFlag(ColumnMemory)) {
4730 m_savedX = m_doc->line(m_begLine).cursorToDocumentOffset(m_begOffset).x();
4731 }
4732 }
4733
hasColumnMemory() const4734 bool QDocumentCursorHandle::hasColumnMemory() const
4735 {
4736 return hasFlag(ColumnMemory);
4737 }
4738
setColumnMemory(bool y)4739 void QDocumentCursorHandle::setColumnMemory(bool y)
4740 {
4741 if ( hasFlag(ColumnMemory) == y )
4742 return;
4743 if ( y )
4744 setFlag(ColumnMemory);
4745 else
4746 clearFlag(ColumnMemory);
4747 refreshColumnMemory();
4748 }
4749
isRTL() const4750 bool QDocumentCursorHandle::isRTL() const{
4751 QDocumentLine l = line();
4752 return l.isRTLByLayout(); //todo: also check for column position?
4753 }
4754
setPosition(int pos,int m)4755 void QDocumentCursorHandle::setPosition(int pos, int m)
4756 {
4757 Q_UNUSED(pos)
4758 Q_UNUSED(m)
4759
4760 qWarning("Set position to cursor using character index : forbidden...");
4761 /*
4762 if ( m == QDocumentCursor::MoveAnchor )
4763 {
4764 m_begLine = m_doc->findLine(pos);
4765 m_begOffset = (m_begLine.isValid() ? pos : 0);
4766
4767 m_endLine = QDocumentLine();
4768 m_endOffset = 0;
4769
4770 m_savedX = m_begLine.cursorToX(m_begOffset);
4771 } else {
4772 m_endLine = m_doc->findLine(pos);
4773 m_endOffset = (m_begLine.isValid() ? pos : 0);
4774 }
4775 */
4776 }
4777
4778
movePosition(int count,int op,const QDocumentCursor::MoveMode & m)4779 bool QDocumentCursorHandle::movePosition(int count, int op, const QDocumentCursor::MoveMode& m)
4780 {
4781 if ( !m_doc )
4782 return false;
4783
4784 QDocumentLine l, l1 = m_doc->line(m_begLine), l2 = m_doc->line(m_endLine);
4785
4786 int origLine = m_begLine;
4787 int origOffset = m_begOffset;
4788 int &line = m_begLine;
4789 int &offset = m_begOffset;
4790
4791 static QRegExp wordStart("\\b\\w+$"), wordEnd("^\\w+\\b");
4792 static QRegExp wordOrCommandStart("\\\\?\\b\\w+$"), wordOrCommandEnd("^\\\\?\\w+\\b");
4793
4794 if ( !(m & QDocumentCursor::KeepAnchor) )
4795 {
4796 m_endLine = -1;
4797 m_endOffset = 0;
4798 } else if ( !l2.isValid() ) {
4799 m_endLine = m_begLine;
4800 m_endOffset = m_begOffset;
4801 }
4802
4803 if (offset < 0) offset = 0;
4804 else if (offset > l1.length()) offset = l1.length();
4805
4806 int beg = 0, end = m_doc->lines();
4807
4808 if (l1.isRTLByLayout()) {
4809 int tempOffset = m_begOffset;
4810 switch (op) {
4811 case QDocumentCursor::Left:
4812 for (int i=0;i<count;i++)
4813 tempOffset = l1.leftCursorPosition(tempOffset);
4814 count = m_begOffset - tempOffset;
4815 if (count < 0) { count = - count; op = QDocumentCursor::NextCharacter; }
4816 else if (count == 0) {
4817 QPointF current = l1.cursorToDocumentOffset(m_begOffset);
4818 if (current.y() == 0
4819 && current.x() == l1.cursorToX(l1.xToCursor(0))) //cursor is at start of line (compare x-position instead index because there might be multiple characters at the same position)
4820 count = m_begOffset + 1; //jump to previous line
4821 else {
4822 count = 1;
4823
4824 //test if moving in plus/minus direction moves the cursor left
4825 QPointF pp = l1.cursorToDocumentOffset(m_begOffset+1);
4826 QPointF pm = l1.cursorToDocumentOffset(m_begOffset-1);
4827 if ((pp.y() < pm.y() || (pp.y() == pm.y() && pp.x() < pm.x())))
4828 op = QDocumentCursor::NextCharacter;
4829 }
4830 }
4831 break;
4832 case QDocumentCursor::Right:
4833 for (int i=0;i<count;i++)
4834 tempOffset = l1.rightCursorPosition(tempOffset);
4835 count = tempOffset - m_begOffset;
4836 if (count < 0) { count = - count; op = QDocumentCursor::PreviousCharacter; }
4837 else if (count == 0) {
4838 QPointF current = l1.cursorToDocumentOffset(m_begOffset);
4839 qreal lineHeight = 1.*(l1.getLayout()->lineCount() - 1) * QDocumentPrivate::m_lineSpacing;
4840 if (qFuzzyCompare(current.y(),lineHeight)
4841 && qFuzzyCompare(current.x(),l1.cursorToDocumentOffset(l1.documentOffsetToCursor(document()->width()+5, lineHeight + QDocumentPrivate::m_lineSpacing / 2)).x()))
4842 count = l1.length() - m_begOffset + 1;
4843 else {
4844 count = 1;
4845 //test if moving in plus/minus direction moves the cursor right
4846 QPointF pp = l1.cursorToDocumentOffset(m_begOffset+1);
4847 QPointF pm = l1.cursorToDocumentOffset(m_begOffset-1);
4848 if ((pp.y() < pm.y() || (pp.y() == pm.y() && pp.x() < pm.x())))
4849 op = QDocumentCursor::PreviousCharacter;
4850 }
4851 }
4852 break;
4853 case QDocumentCursor::WordLeft:
4854 if (l1.leftCursorPosition(tempOffset) > tempOffset)
4855 op = QDocumentCursor::NextWord;
4856 break;
4857 case QDocumentCursor::WordRight:
4858 if (l1.rightCursorPosition(tempOffset) < tempOffset)
4859 op = QDocumentCursor::PreviousWord;
4860 break;
4861 }
4862 }
4863
4864
4865 switch ( op )
4866 {
4867 case QDocumentCursor::Left:
4868 case QDocumentCursor::PreviousCharacter :
4869 {
4870 if ( atStart() )
4871 return false;
4872
4873 do
4874 {
4875 if ( offset >= count ) // cursorCol is larger than required count of left steps -> just reduce col
4876 {
4877 offset -= count;
4878
4879 const QString& textline = m_doc->line(line).text();
4880 if (offset < textline.length())
4881 while (
4882 offset > 0 && ((textline.at(offset).category() == QChar::Mark_NonSpacing) ||
4883 (textline.at(offset).isLowSurrogate() && textline.at(offset-1).isHighSurrogate()))
4884 ) {
4885 offset--;
4886 }
4887
4888 break;
4889 } else if ( line == beg ) { // not enough way to move: undo (no partial operation)
4890 line = origLine;
4891 offset = origOffset;
4892 return false;
4893 }
4894
4895 do
4896 {
4897 --line;
4898 } while ( (line > beg) && m_doc->line(line).hasFlag(QDocumentLine::Hidden) && !(m & QDocumentCursor::ThroughFolding));
4899
4900 //*line = *it;
4901
4902 count -= offset + 1; // +1: jumping a line is one char
4903 offset = m_doc->line(line).length();
4904 } while ( count );
4905
4906 refreshColumnMemory();
4907
4908 break;
4909 }
4910
4911 case QDocumentCursor::Right :
4912 case QDocumentCursor::NextCharacter:
4913 {
4914 if ( atEnd() )
4915 return false;
4916
4917 int remaining = m_doc->line(line).length() - offset;
4918
4919 do
4920 {
4921 if ( remaining >= count ) // enough line left -> just increase col
4922 {
4923 offset += count;
4924
4925 const QString& textline = m_doc->line(line).text();
4926 if (offset > 0)
4927 while (
4928 offset < textline.length() && ((textline.at(offset).category() == QChar::Mark_NonSpacing) ||
4929 (textline.at(offset).isLowSurrogate() && textline.at(offset-1).isHighSurrogate()))
4930 ) {
4931 offset++;
4932 }
4933 break;
4934 } else if ( (line + 1) == end ) { // not enough way to move: undo (no partial operation)
4935 line = origLine;
4936 offset = origOffset;
4937 return false;
4938 }
4939
4940 do
4941 {
4942 ++line;
4943 } while ( ((line+1) < end) && m_doc->line(line).hasFlag(QDocumentLine::Hidden) && !(m & QDocumentCursor::ThroughFolding));
4944
4945 //*line = *it;
4946
4947 offset = 0;
4948 count -= remaining + 1; // +1: jumping a line is one char
4949 remaining = m_doc->line(line).length();
4950 } while ( count );
4951
4952 refreshColumnMemory();
4953
4954 break;
4955 }
4956
4957 case QDocumentCursor::Up :
4958 {
4959 if ( atStart() )
4960 return false;
4961
4962 //qDebug("%i, %i : up", line, offset);
4963
4964 if ( m & QDocumentCursor::ThroughWrap )
4965 {
4966 QPointF p = documentPosition();
4967
4968 if (hasColumnMemory()) {
4969 p.rx() = m_savedX;
4970 }
4971 p.ry() -= QDocumentPrivate::m_lineSpacing * count;
4972
4973 while (p.y() >= 0) {
4974 m_doc->cursorForDocumentPosition(p, line, offset);
4975 if ( offset <= this->line().length() )
4976 return true;
4977 //pseudo wrapping
4978 p.ry() -= QDocumentPrivate::m_lineSpacing; //todo: optimize with image height
4979 }
4980
4981 if (p.y() < 0) {
4982 line = 0;
4983 offset = 0;
4984 }
4985
4986
4987
4988 return true;
4989 }
4990
4991 while ( count && (line > beg) )
4992 {
4993 --line;
4994
4995 if ( !m_doc->line(line).hasFlag(QDocumentLine::Hidden) || (m & QDocumentCursor::ThroughFolding))
4996 --count;
4997
4998 }
4999
5000 l = m_doc->line(line);
5001
5002 if ( count )
5003 offset = 0;
5004 else if ( m == QDocumentCursor::MoveAnchor )
5005 offset = l.xToCursor(
5006 hasColumnMemory() ?
5007 m_savedX :
5008 l.cursorToX(qMin(offset, l.length()))
5009 );
5010 else
5011 offset = qMin(l.length(), offset);
5012
5013 break;
5014 }
5015
5016 case QDocumentCursor::Down :
5017 {
5018 if ( atEnd() )
5019 return false;
5020
5021 if ( m & QDocumentCursor::ThroughWrap )
5022 {
5023 QPointF p = documentPosition();
5024
5025 if (hasColumnMemory()) {
5026 p.rx() = m_savedX;
5027 }
5028
5029 p.ry() += QDocumentPrivate::m_lineSpacing * count;
5030
5031 int oldLine = line, oldCol = offset;
5032 m_doc->cursorForDocumentPosition(p, line, offset);
5033 if ( oldLine == line && oldCol == offset )
5034 offset = m_doc->line(line).length();
5035 else while (offset > this->line().length() && line < this->document()->lines() ) {
5036 //pseudo wrapping, todo: optimize
5037 p.ry() += QDocumentPrivate::m_lineSpacing;
5038 m_doc->cursorForDocumentPosition(p, line, offset);
5039 }
5040 if ( line >= end ) {
5041 line = end - 1;
5042 offset = m_doc->line(line).length();
5043 }
5044 return true;
5045 }
5046
5047 while ( count && ((line + 1) < end) )
5048 {
5049 ++line;
5050
5051 if ( !m_doc->line(line).hasFlag(QDocumentLine::Hidden) || (m & QDocumentCursor::ThroughFolding))
5052 --count;
5053
5054 }
5055
5056 //*line = QDocumentLine(*it);
5057 l = m_doc->line(line);
5058
5059 if ( count )
5060 offset = l.length();
5061 else if ( m == QDocumentCursor::MoveAnchor )
5062 offset = l.xToCursor(
5063 hasColumnMemory() ?
5064 m_savedX :
5065 l.cursorToX(qMin(offset, l.length()))
5066 );
5067 else
5068 offset = qMin(l.length(), offset);
5069
5070 break;
5071 }
5072
5073 case QDocumentCursor::Start :
5074 if ( atStart() )
5075 return false;
5076
5077 m_savedX = offset = 0;
5078 line = 0;
5079 break;
5080
5081 case QDocumentCursor::End :
5082 if ( atEnd() )
5083 return false;
5084
5085 line = end - 1; //QDocumentLine(*(m_doc->impl()->end() - 1));
5086 offset = m_doc->line(line).length();
5087 refreshColumnMemory();
5088 break;
5089
5090 case QDocumentCursor::StartOfBlock :
5091 if ( l1.isRTLByLayout() ) { //todo: test if this also works for non-rtl
5092 const int targetPosition = document()->width()+5; //it is rtl
5093
5094 QPointF curPos = l1.cursorToDocumentOffset(m_begOffset);
5095 int target = l1.documentOffsetToCursor(targetPosition, curPos.y());
5096 if (m_begOffset == target) target = l1.documentOffsetToCursor(targetPosition, 0);
5097 if (m_begOffset == target) return false;
5098 m_begOffset = target;
5099 refreshColumnMemory(); //??
5100 return true;
5101 }
5102
5103
5104 if ( atBlockStart() )
5105 return false;
5106
5107 if ( m & QDocumentCursor::ThroughWrap && m_doc->line(line).cursorToDocumentOffset(offset).y()==m_doc->line(line).cursorToDocumentOffset(offset-1).y() ){
5108 QPointF p = documentPosition();
5109 p.rx() = 0;
5110
5111 m_doc->cursorForDocumentPosition(p, line, offset);
5112 m_savedX = 0;//w.line start, avoiding 0 bug
5113 return true;
5114 }
5115
5116 m_savedX = offset = 0;
5117 break;
5118
5119 case QDocumentCursor::EndOfBlock :
5120 if ( l1.isRTLByLayout() ) {
5121 const int targetPosition = 0; //it is rtl
5122
5123 QPointF curPos = l1.cursorToDocumentOffset(offset);
5124 int target = l1.documentOffsetToCursor(targetPosition, curPos.y());
5125 QPointF newPosition = l1.cursorToDocumentOffset(target);
5126 if (newPosition.y() > curPos.y()) { //it is usually moved one character to far to the right in the next line
5127 QPointF p = l1.cursorToDocumentOffset(target+1);
5128 QPointF m = l1.cursorToDocumentOffset(target-1);
5129 if (p.y() == curPos.y()) target += 1;
5130 else if (m.y() == curPos.y()) target -= 1;
5131 }
5132 if (m_begOffset == target) target = l1.documentOffsetToCursor(targetPosition, 1.*(l1.getLayout()->lineCount() - 1 + 1) * QDocumentPrivate::m_lineSpacing);
5133 if (m_begOffset == target) return false;
5134 m_begOffset = target;
5135 refreshColumnMemory(); //??
5136 return true;
5137 }
5138
5139 if ( atBlockEnd() )
5140 return false;
5141
5142 if ( m & QDocumentCursor::ThroughWrap &&
5143 m_doc->line(line).cursorToDocumentOffset(offset+1).y()==m_doc->line(line).cursorToDocumentOffset(offset).y())//not at w. line end
5144 //m_doc->line(line).cursorToDocumentOffset(offset).y()/QDocumentPrivate::m_lineSpacing+1<m_doc->line(line).lineSpan()) //not in the last
5145 {
5146 int curline=line;
5147 //goto next line start
5148 if (m_doc->line(line).cursorToDocumentOffset(offset+1).y()!=m_doc->line(line).cursorToDocumentOffset(offset-1).y()){
5149 offset++; //can this ever happen?
5150 }
5151 QPointF p = documentPosition();
5152 p.rx() = -1;
5153 p.ry() += QDocumentPrivate::m_lineSpacing;
5154 //if (wlinestart) //must move down to solve problem with documentPosition() at w. line start
5155 // p.ry() += QDocumentPrivate::m_lineSpacing;
5156
5157 m_doc->cursorForDocumentPosition(p, line, offset);
5158
5159 //one left in the w. line before
5160 offset--;
5161 m_savedX = m_doc->line(line).cursorToDocumentOffset(offset).x();
5162 if ((curline != line)||(m_doc->height()==p.ry())) line=curline; //jumped to far, work around for wrapped last line
5163 else if (offset>0) return true;
5164 }
5165
5166 offset = m_doc->line(line).length();
5167 refreshColumnMemory();
5168 break;
5169
5170 case QDocumentCursor::NextBlock :
5171
5172 if ( atEnd() )
5173 return false;
5174
5175 while ( ((line + 1) < end) && count )
5176 {
5177 ++line;
5178
5179 if ( !m_doc->line(line).hasFlag(QDocumentLine::Hidden) || (m & QDocumentCursor::ThroughFolding))
5180 --count;
5181
5182 }
5183
5184 if ( !count )
5185 {
5186 //*line = *it;
5187 offset = 0;
5188 } else {
5189 //*line = QDocumentLine(*(m_doc->impl()->end() - 1));
5190 offset = m_doc->line(line).length();
5191 }
5192
5193 break;
5194
5195 case QDocumentCursor::PreviousBlock :
5196
5197 if ( atStart() )
5198 return false;
5199
5200 offset = 0;
5201
5202 while ( (line > beg) && count )
5203 {
5204 --line;
5205
5206 if ( !m_doc->line(line).hasFlag(QDocumentLine::Hidden) || (m & QDocumentCursor::ThroughFolding))
5207 --count;
5208
5209 }
5210
5211 if ( !count )
5212 {
5213 //*line = *it;
5214 offset = m_doc->line(line).length();
5215 } else {
5216 offset = 0;
5217 //*line = QDocumentLine(*(m_doc->impl()->begin()));
5218 }
5219
5220 //*line = *it;
5221
5222 break;
5223
5224 case QDocumentCursor::WordLeft :
5225 case QDocumentCursor::PreviousWord :
5226 {
5227 if ( atStart() )
5228 return false;
5229
5230 l = m_doc->line(line);
5231
5232 //for ( int loop = 0; loop <= 1; ++loop )
5233 //{
5234 // while ( l.isValid() )
5235
5236 // -- patch --
5237 /* eats up white space */
5238 while ( (offset > 0) && !isWord(l.text().at(offset - 1)) && !isDelimiter(l.text().at(offset - 1)) )
5239 --offset;
5240
5241 /* start of line */
5242 if ( offset == 0 )
5243 {
5244 /* first line, first char => nothing to do */
5245 if( line == beg )
5246 return true;
5247
5248 do
5249 // -- patch --
5250 {
5251 // //offset = qMin(offset, l.length() - 1);
5252 // bool next = (l.length() && offset >= 0) ? isWord(l.text().at(offset)) : true;
5253 //
5254 // if ( loop )
5255 // next = !next;
5256 //
5257 // if ( !next )
5258 // break;
5259 //
5260 // if ( offset > 0 )
5261 // {
5262 // --offset;
5263 // } else if ( line != beg ) {
5264 // do
5265 // {
5266 // //*line = *(--it);
5267 // --line;
5268 // l = m_doc->line(line);
5269 // offset = l.length() - 1;
5270 // } while ( l.isValid() && (line != beg) && l.hasFlag(QDocumentLine::Hidden) );
5271 // } else {
5272 // break;
5273 // }
5274 // }
5275 // }
5276 //
5277 // while ( l.isValid() )
5278 // {
5279 // offset = qMin(offset, l.length());
5280 // bool next = (offset <= 0) ? false : isWord(l.text().at(offset - 1));
5281 //
5282 // if ( !next )
5283 // break;
5284 //
5285 // --offset;
5286
5287 // -- patch --
5288 --line;
5289 l = m_doc->line(line);
5290 offset = l.length();
5291 } while ( (line != beg) && l.isValid() && l.hasFlag(QDocumentLine::Hidden) && !(m & QDocumentCursor::ThroughFolding));
5292 return true;
5293 // -- patch --
5294 }
5295
5296 // -- patch --
5297 /* eats up delimiters */
5298 bool delimiter_used=false;
5299 while ( (offset > 0) && isDelimiter(l.text().at(offset-1)) ){
5300 --offset;
5301 delimiter_used=true;
5302 }
5303 /* eats up whole word */
5304 if(!delimiter_used){
5305 while ( (offset > 0) && isWord(l.text().at(offset - 1)) )
5306 --offset;
5307 }
5308 // -- patch --
5309
5310 refreshColumnMemory();
5311
5312 break;
5313 }
5314
5315 case QDocumentCursor::WordRight :
5316 case QDocumentCursor::NextWord :
5317 {
5318 if ( atEnd() )
5319 return false;
5320
5321 l = m_doc->line(line);
5322 int lineLength = l.text().length();
5323
5324 // for ( int loop = 0; loop <= 1; ++loop )
5325 // -- patch --
5326 /* end of line */
5327 if ( offset == lineLength )
5328 {
5329 // while ( l.isValid() )
5330 /* last line, last char => nothing to do */
5331 if ( line == end )
5332 return true;
5333 // -- patch --
5334 do
5335 {
5336 // //offset = qBound(0, offset, l.length() - 1);
5337 // bool next = (offset < l.length()) ? isWord(l.text().at(offset)) : true;
5338 //
5339 // if ( loop )
5340 // next = !next;
5341 //
5342 // if ( !next )
5343 // break;
5344 //
5345 // if ( offset < l.length() )
5346 // {
5347 // ++offset;
5348 // } else if ( (line + 1) != end ) {
5349 // offset = 0;
5350 // do
5351 // {
5352 // ++line;
5353 // l = m_doc->line(line);
5354 // } while ( l.isValid() && ((line + 1) != end) && (l.hasFlag(QDocumentLine::Hidden) || !l.length()) );
5355 // } else {
5356 // -- patch --
5357 ++line;
5358 l = m_doc->line(line);
5359 offset = 0;
5360 } while ( (line != end) && l.isValid() && l.hasFlag(QDocumentLine::Hidden) && !(m & QDocumentCursor::ThroughFolding));
5361
5362 lineLength = l.text().length();
5363 /* empty line */
5364 if ( lineLength == 0 )
5365 return true;
5366
5367 /* eats up white space */
5368 while ( !isWord(l.text().at(offset)) )
5369 {
5370 ++offset;
5371 /* move to end of line */
5372 if ( offset == lineLength )
5373 break;
5374 // -- patch --
5375 // }
5376 }
5377 // -- patch --
5378 return true;
5379 // -- patch --
5380 }
5381
5382 // -- patch --
5383 /* next char */
5384 //++offset;
5385 bool delimiter_used=false;
5386 while ( (offset < lineLength) && isDelimiter(l.text().at(offset)) ){
5387 ++offset;
5388 delimiter_used=true;
5389 }
5390
5391 /* eats up whole word */
5392 if(!delimiter_used){
5393 while ( (offset < lineLength) && isWord(l.text().at(offset)) )
5394 ++offset;
5395 }
5396
5397 /* eats up white space */
5398 while ( (offset < lineLength) && !isWord(l.text().at(offset))&&!isDelimiter(l.text().at(offset)) )
5399 ++offset;
5400 // -- patch --
5401
5402 refreshColumnMemory();
5403
5404
5405 break;
5406 }
5407
5408 case QDocumentCursor::StartOfWord :
5409 {
5410 int x = wordStart.indexIn(m_doc->line(line).text().left(offset));
5411
5412 if ( x != -1 )
5413 {
5414 offset = x;
5415 } else {
5416 //qDebug("failed to find SOW : %i + %i != %i", x, wordStart.matchedLength(), offset);
5417 return false;
5418 }
5419
5420 refreshColumnMemory();
5421
5422 break;
5423 }
5424
5425 case QDocumentCursor::EndOfWord :
5426 {
5427 int x = wordEnd.indexIn(m_doc->line(line).text(), offset, QRegExp::CaretAtOffset);
5428
5429 if ( x == offset )
5430 {
5431 offset += wordEnd.matchedLength();
5432 } else {
5433 //qDebug("failed to find EOW");
5434 return false;
5435 }
5436
5437 refreshColumnMemory();
5438
5439 break;
5440 }
5441
5442 case QDocumentCursor::StartOfWordOrCommand :
5443 {
5444 int x = wordOrCommandStart.indexIn(m_doc->line(line).text().left(offset+1)); // offset+1 because we would not match if we would cut-off at the cursor if it is directly behind a slash like this: \|command
5445
5446 if ( x != -1 )
5447 {
5448 offset = x;
5449 } else {
5450 //qDebug("failed to find SOWC : %i + %i != %i", x, wordOrCommandStart.matchedLength(), offset);
5451 return false;
5452 }
5453
5454 refreshColumnMemory();
5455
5456 break;
5457 }
5458
5459 case QDocumentCursor::EndOfWordOrCommand :
5460 {
5461
5462 int x = wordOrCommandEnd.indexIn(m_doc->line(line).text(), offset, QRegExp::CaretAtOffset);
5463
5464 if ( x == offset )
5465 {
5466 offset += wordOrCommandEnd.matchedLength();
5467 } else {
5468 //qDebug("failed to find EOWC");
5469 return false;
5470 }
5471
5472 refreshColumnMemory();
5473
5474 break;
5475 }
5476
5477 case QDocumentCursor::StartOfParenthesis :
5478 {
5479 QStringList possibleOpeningParentheses = QStringList() << "{" << "(" << "[";
5480 QStringList possibleClosingParentheses = QStringList() << "}" << ")" << "]";
5481
5482 QString text = m_doc->line(line).text();
5483 QStringList closingParenthesesStack;
5484 bool found = false;
5485 for (int i = offset; i >= 0; i--) {
5486 foreach(const QString &closing, possibleClosingParentheses) {
5487 if (text.mid(i).startsWith(closing) && (i+closing.length() < offset)) {
5488 closingParenthesesStack.prepend(closing);
5489 break;
5490 }
5491 }
5492 foreach(const QString &opening, possibleOpeningParentheses) {
5493 if (text.mid(i).startsWith(opening)) {
5494 if (closingParenthesesStack.isEmpty()) {
5495 offset = i;
5496 found = true;
5497 break;
5498 } else {
5499 QString matchingClosingForOpening = possibleClosingParentheses.at(possibleOpeningParentheses.indexOf(opening));
5500 if (closingParenthesesStack.first() == matchingClosingForOpening) {
5501 closingParenthesesStack.removeFirst();
5502 } else {
5503 return false; // unmatched inner parentheses
5504 }
5505 }
5506 }
5507 }
5508 if (found) break;
5509 }
5510 if (!found) return false; // not within parentheses
5511
5512 refreshColumnMemory();
5513
5514 break;
5515 }
5516
5517 default:
5518 qWarning()<<"Unhandled move operation...";
5519 return false;
5520 };
5521
5522 return true;
5523 }
5524
moveTo(const QDocumentCursor & c,const QDocumentCursor::MoveMode & m)5525 void QDocumentCursorHandle::moveTo(const QDocumentCursor &c, const QDocumentCursor::MoveMode& m)
5526 {
5527 if ( !c.isValid() || !m_doc )
5528 return;
5529
5530 if(!(m&QDocumentCursor::KeepAnchor)){
5531 m_begLine = c.handle()->m_begLine;
5532 m_begOffset = c.handle()->m_begOffset;
5533
5534 m_endLine = -1;
5535 m_endOffset = 0;
5536 }else{
5537 m_endLine = c.handle()->m_begLine;
5538 m_endOffset = c.handle()->m_begOffset;
5539 }
5540
5541 refreshColumnMemory();
5542 }
5543
moveTo(int line,int column,const QDocumentCursor::MoveMode & m)5544 void QDocumentCursorHandle::moveTo(int line, int column, const QDocumentCursor::MoveMode& m)
5545 {
5546 if(!(m&QDocumentCursor::KeepAnchor)){
5547 m_begLine = line;
5548 m_begOffset = column;
5549
5550 m_endLine = -1;
5551 m_endOffset = 0;
5552 }else{
5553 m_endLine = line;
5554 m_endOffset = column;
5555 }
5556
5557 refreshColumnMemory();
5558 }
5559
5560
insertText(const QString & s,bool keepAnchor)5561 void QDocumentCursorHandle::insertText(const QString& s, bool keepAnchor)
5562 {
5563 if ( !m_doc || s.isEmpty() || m_doc->line(m_begLine).isNull() )
5564 return;
5565
5566 bool sel = hasSelection();
5567
5568 if ( sel )
5569 {
5570 beginEditBlock();
5571 removeSelectedText(keepAnchor);
5572 }
5573
5574 QDocumentCommand *command = new QDocumentInsertCommand(
5575 m_begLine,
5576 m_begOffset,
5577 s,
5578 m_doc
5579 );
5580
5581 command->setKeepAnchor(keepAnchor);
5582 command->setTargetCursor(this);
5583 execute(command);
5584
5585 if ( sel )
5586 endEditBlock();
5587 }
5588
eraseLine()5589 void QDocumentCursorHandle::eraseLine()
5590 {
5591 if ( !m_doc )
5592 return;
5593
5594 QDocumentCommand *command = nullptr;
5595
5596 if((m_begLine>m_doc->lineCount())||(m_begLine<0)) return; // return if cursor is out of range
5597 if((m_endLine>m_doc->lineCount())) m_endLine=m_doc->lineCount()-1;
5598
5599 int cursorLine = m_begLine;
5600 int anchorLine = m_endLine < 0 ? m_begLine : m_endLine;
5601 int startLine = qMin(cursorLine, anchorLine);
5602 int endLine = qMax(cursorLine, anchorLine);
5603
5604 if (endLine < m_doc->lineCount()-1)
5605 {
5606 command = new QDocumentEraseCommand(
5607 startLine,
5608 0,
5609 endLine + 1,
5610 0,
5611 m_doc
5612 );
5613 } else if (startLine > 0) {
5614 // special handling to remove a selection including the last line
5615 // QDocumentEraseCommand leaves an empty line if the end (==endLine+1)
5616 // is beyond the last line. As a workaround, we change the selection
5617 // range and include the newline before the selection in the erase action
5618 command = new QDocumentEraseCommand(
5619 startLine-1,
5620 m_doc->line(startLine-1).length(),
5621 endLine,
5622 m_doc->line(endLine).length(),
5623 m_doc
5624 );
5625 } else {
5626 // very special case
5627 // document contains only one line which is to be deleted
5628 // as a document should always contain 1 line, content is deleted
5629 command = new QDocumentEraseCommand(
5630 startLine,
5631 0,
5632 startLine,
5633 m_doc->line(startLine).length(),
5634 m_doc
5635 );
5636 }
5637 command->setTargetCursor(this);
5638 execute(command);
5639 }
5640
nextChar() const5641 QChar QDocumentCursorHandle::nextChar() const
5642 {
5643 if ( !m_doc )
5644 return QChar();
5645
5646 QDocumentLine l = m_doc->line(m_begLine);
5647
5648 if ( !l.isValid() || m_begOffset < 0 )
5649 return QChar();
5650
5651 return m_begOffset < l.length() ? l.text().at(m_begOffset) : (atEnd()?QLatin1Char('\0'):QLatin1Char('\n'));
5652 }
5653
previousChar() const5654 QChar QDocumentCursorHandle::previousChar() const
5655 {
5656 if ( !m_doc || (m_begLine <= 0 && m_begOffset <= 0) )
5657 return QChar();
5658
5659 QDocumentLine l = m_doc->line(m_begLine);
5660
5661 if ( !l.isValid() || m_begOffset > l.length() )
5662 return QChar();
5663
5664 return m_begOffset ? l.text().at(m_begOffset - 1) : QLatin1Char('\n');
5665 }
5666
deleteChar()5667 void QDocumentCursorHandle::deleteChar()
5668 {
5669 if ( !m_doc )
5670 return;
5671
5672 QDocumentLine l = m_doc->line(m_begLine);
5673
5674 if ( l.isNull() || atEnd() )
5675 return;
5676
5677 QDocumentCommand *command = nullptr;
5678
5679 if ( !atLineEnd() )
5680 {
5681 int charCount = 1;
5682 if (m_begOffset >= 0 && m_begOffset + 1 < l.length() && l.text().at(m_begOffset).isHighSurrogate() && l.text().at(m_begOffset + 1).isLowSurrogate())
5683 charCount = 2;
5684
5685 command = new QDocumentEraseCommand(
5686 m_begLine,
5687 m_begOffset,
5688 m_begLine,
5689 m_begOffset + charCount,
5690 m_doc
5691 );
5692
5693 } else {
5694 // merge two blocks...
5695 command = new QDocumentEraseCommand(
5696 m_begLine,
5697 m_begOffset,
5698 m_begLine + 1,
5699 0,
5700 m_doc
5701 );
5702
5703 }
5704
5705 command->setTargetCursor(this);
5706 command->setUndoOffset(-1);
5707 execute(command);
5708 }
5709
deletePreviousChar()5710 void QDocumentCursorHandle::deletePreviousChar()
5711 {
5712 if ( !m_doc )
5713 return;
5714
5715 QDocumentLine l = m_doc->line(m_begLine);
5716
5717 if ( l.isNull() || atStart() )
5718 return;
5719
5720 QDocumentCommand *command = nullptr;
5721
5722 if ( !atLineStart() )
5723 {
5724 int charCount = 1;
5725 if (m_begOffset >= 2 && m_begOffset <= l.length() && l.text().at(m_begOffset - 1).isLowSurrogate() && l.text().at(m_begOffset - 2).isHighSurrogate())
5726 charCount = 2;
5727
5728 command = new QDocumentEraseCommand(
5729 m_begLine,
5730 m_begOffset - charCount,
5731 m_begLine,
5732 m_begOffset,
5733 m_doc
5734 );
5735
5736 } else {
5737 // merge two blocks...
5738 QDocumentLine prev = m_doc->line(m_begLine - 1);
5739
5740 command = new QDocumentEraseCommand(
5741 m_begLine - 1,
5742 prev.length(),
5743 m_begLine,
5744 m_begOffset,
5745 m_doc
5746 );
5747
5748 }
5749
5750 command->setTargetCursor(this);
5751 execute(command);
5752 }
5753
execute(QDocumentCommand * c)5754 void QDocumentCursorHandle::execute(QDocumentCommand *c)
5755 {
5756 Q_ASSERT(m_doc);
5757
5758 if ( !m_doc || m_doc->isReadOnly() )
5759 return; //returning means c will never freed
5760
5761 if ( isSilent() && !c->isSilent() )
5762 c->setSilent(isSilent());
5763
5764 if ( m_blocks.count() )
5765 {
5766 c->redo();
5767 m_blocks.top()->addCommand(c);
5768
5769 } else if ( m_doc ) {
5770 //qDebug("Cursor handle executing command : 0x%x", this);
5771
5772 m_doc->execute(c);
5773 }
5774 }
5775
beginEditBlock()5776 void QDocumentCursorHandle::beginEditBlock()
5777 {
5778 m_blocks.push(new QDocumentCommandBlock(m_doc));
5779 }
5780
endEditBlock()5781 void QDocumentCursorHandle::endEditBlock()
5782 {
5783 if ( !m_doc || m_blocks.isEmpty() )
5784 return;
5785
5786 //qDebug("Cursor handle executing command : 0x%x [block]", this);
5787
5788 QDocumentCommandBlock *block = m_blocks.pop();
5789
5790 // special trick to prevent double redo() while getting rid of
5791 // bugs occuring in when inserting/erasing in overlapping lines
5792 // inside a command block
5793 block->setWeakLock(true);
5794
5795 execute(block);
5796 }
5797
selectionStart() const5798 QDocumentCursor QDocumentCursorHandle::selectionStart() const
5799 {
5800 if ( !m_doc )
5801 return QDocumentCursor();
5802
5803 if ( !hasSelection() )
5804 return QDocumentCursor(clone(false));
5805
5806 QDocumentCursor pos(m_doc, m_begLine, m_begOffset),
5807 anc(m_doc, m_endLine, m_endOffset);
5808
5809 return (pos < anc) ? pos : anc;
5810 }
5811
selectionEnd() const5812 QDocumentCursor QDocumentCursorHandle::selectionEnd() const
5813 {
5814 if ( !m_doc )
5815 return QDocumentCursor();
5816
5817 if ( !hasSelection() )
5818 return QDocumentCursor(clone(false));
5819
5820 QDocumentCursor pos(m_doc, m_begLine, m_begOffset),
5821 anc(m_doc, m_endLine, m_endOffset);
5822
5823 return (pos > anc) ? pos : anc;
5824 }
5825
anchorCursor() const5826 QDocumentCursor QDocumentCursorHandle::anchorCursor() const
5827 {
5828 if ( !m_doc )
5829 return QDocumentCursor();
5830
5831 return QDocumentCursor(m_doc, m_endLine, m_endOffset);
5832 }
5833
eq(const QDocumentCursorHandle * h)5834 bool QDocumentCursorHandle::eq(const QDocumentCursorHandle *h)
5835 {
5836 return (m_begLine == h->m_begLine) && (m_begOffset == h->m_begOffset);
5837 /*
5838 if ( !hasSelection() )
5839 return (m_begLine == h->m_begLine) && (m_begOffset == h->m_begOffset);
5840
5841 return
5842 (m_begLine == h->m_begLine)
5843 &&
5844 (m_begOffset == h->m_begOffset)
5845 &&
5846 (m_endLine == h->m_endLine)
5847 &&
5848 (m_endOffset == h->m_endOffset)
5849 ;
5850 */
5851 }
5852
lt(const QDocumentCursorHandle * h)5853 bool QDocumentCursorHandle::lt(const QDocumentCursorHandle *h)
5854 {
5855 return
5856 (m_begLine < h->m_begLine)
5857 ||
5858 ((m_begLine == h->m_begLine) && (m_begOffset < h->m_begOffset))
5859 ;
5860 }
5861
gt(const QDocumentCursorHandle * h)5862 bool QDocumentCursorHandle::gt(const QDocumentCursorHandle *h)
5863 {
5864 return
5865 (m_begLine > h->m_begLine)
5866 ||
5867 ((m_begLine == h->m_begLine) && (m_begOffset > h->m_begOffset))
5868 ;
5869 }
5870
selectedText() const5871 QString QDocumentCursorHandle::selectedText() const
5872 {
5873 if ( !m_doc )
5874 return QString();
5875
5876 QDocumentLine l1 = m_doc->line(m_begLine), l2 = m_doc->line(m_endLine);
5877
5878 if ( l1.isNull() || l2.isNull() )
5879 return QString();
5880
5881 QString s;
5882
5883 if ( m_begLine == m_endLine )
5884 {
5885 int min = qMin(m_begOffset, m_endOffset),
5886 max = qMax(m_begOffset, m_endOffset);
5887
5888 s = l1.text().mid(min, max - min);
5889 } else if ( m_begLine < m_endLine ) {
5890 s = l1.text().mid(m_begOffset);
5891
5892 int it = m_begLine;
5893 //QDocumentConstIterator it = m_doc->impl()->index(m_begLine.handle());
5894
5895 while ( ++it < m_endLine )
5896 {
5897 s += '\n';
5898 s += m_doc->line(it).text();
5899 }
5900
5901 s += '\n';
5902 s += l2.text().left(m_endOffset);
5903 } else {
5904 s = l2.text().mid(m_endOffset);
5905
5906 int it = m_endLine;
5907 //QDocumentConstIterator it = m_doc->impl()->index(m_endLine.handle());
5908
5909 while ( ++it < m_begLine )
5910 {
5911 s += '\n';
5912 s += m_doc->line(it).text();
5913 }
5914
5915 s += '\n';
5916 s += l1.text().left(m_begOffset);
5917 }
5918
5919 return s;
5920 }
5921
clearSelection()5922 void QDocumentCursorHandle::clearSelection()
5923 {
5924 if ( m_doc && m_doc->line(m_endLine).isValid() )
5925 {
5926 //m_begLine = m_endLine;
5927 //m_begOffset = m_endOffset;
5928
5929 m_endLine = -1;
5930 m_endOffset = -1;
5931 }
5932 }
5933
flipSelection()5934 void QDocumentCursorHandle::flipSelection()
5935 {
5936 if ( m_doc && m_doc->line(m_endLine).isValid() )
5937 {
5938 int tmpLine = m_begLine;
5939 int tmpOffset = m_begOffset;
5940 m_begLine = m_endLine;
5941 m_begOffset = m_endOffset;
5942 m_endLine = tmpLine;
5943 m_endOffset = tmpOffset;
5944 }
5945 }
5946
replaceSelectedText(const QString & text)5947 void QDocumentCursorHandle::replaceSelectedText(const QString& text)
5948 {
5949 int begline, begcol;
5950 beginBoundary(begline, begcol);
5951
5952 bool atStart = (begline == m_begLine && begcol == m_begOffset);
5953
5954 if ( text.isEmpty() )
5955 {
5956 removeSelectedText();
5957 } else {
5958 insertText(text, true);
5959
5960 /*
5961 Adjust selection around the new text
5962 */
5963 if ( atStart )
5964 {
5965 m_endLine = m_begLine;
5966 m_begLine = begline;
5967 m_endOffset = m_begOffset;
5968 m_begOffset = begcol;
5969 } else {
5970 m_endLine = begline;
5971 m_endOffset = begcol;
5972 }
5973 }
5974
5975 //qDebug("[%i, %i] => ( (%i, %i), (%i, %i) )", begline, begcol, m_begLine, m_begOffset, m_endLine, m_endOffset);
5976 }
5977
select(QDocumentCursor::SelectionType t)5978 void QDocumentCursorHandle::select(QDocumentCursor::SelectionType t)
5979 {
5980 if ( !m_doc || !m_doc->line(m_begLine).isValid() )
5981 return;
5982
5983 if ( t == QDocumentCursor::LineUnderCursor )
5984 {
5985 movePosition(1, QDocumentCursor::StartOfLine, QDocumentCursor::MoveAnchor);
5986 movePosition(1, QDocumentCursor::EndOfLine, QDocumentCursor::KeepAnchor);
5987
5988 } else if ( t == QDocumentCursor::WordUnderCursor ) {
5989
5990 movePosition(1, QDocumentCursor::StartOfWord, QDocumentCursor::MoveAnchor);
5991 movePosition(1, QDocumentCursor::EndOfWord, QDocumentCursor::KeepAnchor);
5992
5993 } else if ( t == QDocumentCursor::WordOrCommandUnderCursor ) {
5994
5995 movePosition(1, QDocumentCursor::StartOfWordOrCommand, QDocumentCursor::MoveAnchor);
5996 movePosition(1, QDocumentCursor::EndOfWordOrCommand, QDocumentCursor::KeepAnchor);
5997
5998 } else if ( t == QDocumentCursor::ParenthesesInner || t == QDocumentCursor::ParenthesesOuter ) {
5999
6000 bool maximal = (t == QDocumentCursor::ParenthesesOuter);
6001 QDocumentCursor orig, to;
6002 getMatchingPair(orig, to, maximal);
6003 if (!orig.isValid() || !to.isValid()) {
6004 if (movePosition(1, QDocumentCursor::StartOfParenthesis, QDocumentCursor::MoveAnchor)) {
6005 getMatchingPair(orig, to, false);
6006 }
6007 }
6008
6009 if (orig.isValid() && to.isValid()) {
6010 QDocumentCursor::sort(orig, to);
6011 if (maximal) {
6012 if (orig.hasSelection()) orig = orig.selectionStart();
6013 if (to.hasSelection()) to = to.selectionEnd();
6014 } else {
6015 if (orig.hasSelection()) orig = orig.selectionEnd();
6016 if (to.hasSelection()) to = to.selectionStart();
6017 }
6018 select(orig.lineNumber(), orig.columnNumber(), to.lineNumber(), to.columnNumber());
6019 }
6020
6021 }
6022 }
6023
expandSelect(QDocumentCursor::SelectionType t)6024 void QDocumentCursorHandle::expandSelect(QDocumentCursor::SelectionType t) {
6025 if ( !m_doc || !m_doc->line(m_begLine).isValid() )
6026 return;
6027
6028 bool isReverse = !isForwardSelection();
6029
6030 if ( t == QDocumentCursor::LineUnderCursor )
6031 {
6032 if (isReverse) flipSelection();
6033 movePosition(1, QDocumentCursor::EndOfLine, QDocumentCursor::KeepAnchor);
6034 flipSelection();
6035 movePosition(1, QDocumentCursor::StartOfLine, QDocumentCursor::KeepAnchor);
6036 if (!isReverse) flipSelection();
6037
6038 } else if ( t == QDocumentCursor::WordUnderCursor ) {
6039
6040 if (isReverse) flipSelection();
6041 movePosition(1, QDocumentCursor::EndOfWord, QDocumentCursor::KeepAnchor);
6042 flipSelection();
6043 movePosition(1, QDocumentCursor::StartOfWord, QDocumentCursor::KeepAnchor);
6044 if (!isReverse) flipSelection();
6045
6046 } else if ( t == QDocumentCursor::WordOrCommandUnderCursor ) {
6047
6048 if (isReverse) flipSelection();
6049 movePosition(1, QDocumentCursor::EndOfWordOrCommand, QDocumentCursor::KeepAnchor);
6050 flipSelection();
6051 movePosition(1, QDocumentCursor::StartOfWordOrCommand, QDocumentCursor::KeepAnchor);
6052 if (!isReverse) flipSelection();
6053
6054 }
6055 }
6056
setSelectionBoundary(const QDocumentCursor & c)6057 void QDocumentCursorHandle::setSelectionBoundary(const QDocumentCursor& c)
6058 {
6059 if (
6060 !m_doc
6061 ||
6062 (m_begLine == -1)
6063 ||
6064 (
6065 (c.lineNumber() == m_begLine)
6066 &&
6067 (c.columnNumber() == m_begOffset)
6068 )
6069 )
6070 return;
6071
6072 //qDebug("setting new selection boundary... ");
6073
6074 if ( !hasSelection() )
6075 {
6076 m_endLine = m_begLine;
6077 m_endOffset = m_begOffset;
6078 }
6079
6080 m_begLine = c.lineNumber();
6081 m_begOffset = c.columnNumber();
6082 }
6083
select(int line,int column,int lineTo,int columnTo)6084 void QDocumentCursorHandle::select(int line, int column, int lineTo, int columnTo){
6085 if ( lineTo == -1 || !m_doc )
6086 moveTo(line,column,QDocumentCursor::MoveAnchor);
6087 else {
6088 m_endLine = line;
6089 m_endOffset = column;
6090
6091 if (lineTo < m_doc->lineCount()) {
6092 m_begLine = lineTo;
6093 if (columnTo != -1) m_begOffset = columnTo;
6094 else m_begOffset = m_doc->line(lineTo).length();
6095 } else {
6096 m_begLine = m_doc->lineCount()-1;
6097 m_begOffset = m_doc->line(m_begLine).length();
6098 }
6099
6100 refreshColumnMemory();
6101 }
6102 }
6103
isWithinSelection(const QDocumentCursor & c) const6104 bool QDocumentCursorHandle::isWithinSelection(const QDocumentCursor& c) const
6105 {
6106 if ( !hasSelection() || c.isNull() ) //|| c.hasSelection() )
6107 return false;
6108
6109 int minOff, maxOff, min, max;
6110
6111 if ( m_begLine > m_endLine )
6112 {
6113 max = m_begLine;
6114 maxOff = m_begOffset;
6115
6116 min = m_endLine;
6117 minOff = m_endOffset;
6118 } else {
6119 min = m_begLine;
6120 minOff = m_begOffset;
6121
6122 max = m_endLine;
6123 maxOff = m_endOffset;
6124 }
6125
6126 return (m_begLine == m_endLine)
6127 ?
6128 (
6129 (c.lineNumber() == m_begLine)
6130 &&
6131 (qMin(m_begOffset, m_endOffset) <= c.columnNumber())
6132 &&
6133 (qMax(m_begOffset, m_endOffset) >= c.columnNumber())
6134 )
6135 :
6136 (
6137 (
6138 (c.lineNumber() > min)
6139 &&
6140 (c.lineNumber() < max)
6141 )
6142 ||
6143 (
6144 (c.lineNumber() == min)
6145 &&
6146 (minOff <= c.columnNumber())
6147 )
6148 ||
6149 (
6150 (c.lineNumber() == max)
6151 &&
6152 (maxOff >= c.columnNumber())
6153 )
6154 )
6155 ;
6156
6157 }
6158
6159 /*
6160 beware when modifying these as their current form handle the special
6161 case of no selection (m_endLine == -1) and a hasty change may break
6162 that behavior : no selection -> both boundary are the cursor pos = (m_begLine, m_begOffset)
6163 */
beginBoundary(int & begline,int & begcol) const6164 void QDocumentCursorHandle::beginBoundary(int& begline, int& begcol) const
6165 {
6166 if ( m_begLine == m_endLine ) {
6167 begline = m_begLine;
6168 if ( m_begOffset < m_endOffset /*&& (m_begLine!=-1)*/)
6169 begcol = m_begOffset;
6170 else
6171 begcol = m_endOffset;
6172 } else if ( m_begLine < m_endLine || m_endLine==-1) {
6173 begline = m_begLine;
6174 begcol = m_begOffset;
6175 } else {
6176 begline = m_endLine;
6177 begcol = m_endOffset;
6178 }
6179 }
6180
endBoundary(int & endline,int & endcol) const6181 void QDocumentCursorHandle::endBoundary(int& endline, int& endcol) const
6182 {
6183 if ( m_begLine == m_endLine )
6184 {
6185 endline = m_endLine;
6186 if ( m_begOffset < m_endOffset /*&& (m_endOffset!=-1)*/)
6187 endcol = m_endOffset;
6188 else
6189 endcol = m_begOffset;
6190 } else if ( m_begLine < m_endLine) {
6191 endline = m_endLine;
6192 endcol = m_endOffset;
6193 } else {
6194 endline = m_begLine;
6195 endcol = m_begOffset;
6196 }
6197 }
6198
boundaries(int & begline,int & begcol,int & endline,int & endcol) const6199 void QDocumentCursorHandle::boundaries(int& begline, int& begcol, int& endline, int& endcol) const
6200 {
6201 beginBoundary(begline, begcol);
6202 endBoundary(endline, endcol);
6203
6204 /*
6205 if ( m_begLine == m_endLine )
6206 {
6207 endline = m_endLine;
6208 if ( m_begOffset < m_endOffset //&& (m_endOffset!=-1)\\)
6209 endcol = m_endOffset;
6210 else
6211 endcol = m_begOffset;
6212 } else if ( m_begLine < m_endLine) {
6213 endline = m_endLine;
6214 endcol = m_endOffset;
6215 } else {
6216 endline = m_begLine;
6217 endcol = m_begOffset;
6218 }
6219 */
6220 }
6221
substractBoundaries(int lbeg,int cbeg,int lend,int cend)6222 void QDocumentCursorHandle::substractBoundaries(int lbeg, int cbeg, int lend, int cend)
6223 {
6224 int tlmin, tlmax, tcmin, tcmax;
6225
6226 boundaries(tlmin, tcmin, tlmax, tcmax);
6227
6228 bool begFirst = tlmin == m_begLine && tcmin == m_begOffset;
6229
6230 if ( tlmax < lbeg || tlmin > lend || (tlmax == lbeg && tcmax < cbeg) || (tlmin == lend && tcmin > cend) )
6231 {
6232 // no intersection
6233 return;
6234 }
6235
6236 //references so we don't have to difference between the cases
6237 int &rtlbeg = begFirst?m_begLine:m_endLine;
6238 int &rtcbeg = begFirst?m_begOffset:m_endOffset;
6239 int &rtlend = begFirst?m_endLine:m_begLine;
6240 int &rtcend = begFirst?m_endOffset:m_begOffset;
6241
6242 int numLines = lend - lbeg;
6243 bool beyondBeg = (tlmin > lbeg || (tlmin == lbeg && tcmin >= cbeg));
6244 bool beyondEnd = (tlmax < lend || (tlmax == lend && tcmax <= cend));
6245
6246 if ( beyondBeg && beyondEnd )
6247 {
6248 //qDebug("(%i, %i : %i, %i) erased as in (%i, %i : %i, %i)", tlmin, tcmin, tlmax, tcmax, lbeg, cbeg, lend, cend);
6249 // cursor erased...
6250 m_begLine = m_endLine = lbeg;
6251 m_begOffset = m_endOffset = cbeg;
6252 } else if ( beyondEnd ) {
6253 //qDebug("beyond end");
6254 rtlend = lbeg;
6255 rtcend = cbeg;
6256 } else if ( beyondBeg ) {
6257 //qDebug("beyond beg");
6258
6259 rtlbeg = lbeg; //selection is moved upwards
6260 rtcbeg = cbeg;//to the begin of the removed part
6261 rtlend -= numLines; //end line moved upwards by the count of removed lines
6262 if ( tlmax == lend)
6263 rtcend = rtcend - cend + cbeg; //end column moved forward by the count of removed characters
6264 } else {
6265 rtlend -= numLines;
6266 if ( tlmax == lend )
6267 rtcend -= cend - cbeg;
6268 }
6269
6270 //qDebug("(%i, %i : %i, %i) corrected to (%i, %i : %i, %i) after subtracting of (%i, %i : %i, %i)", tlmin, tcmin, tlmax, tcmax, m_begLine, m_begOffset, m_endLine, m_endOffset, lbeg, cbeg, lend, cend);
6271 }
6272
6273 /*!
6274 Sets the selection given by lbeg/cbeg/lend/cend to the largest selection which is contained in
6275 the selection of this cursor and the passed boundaries
6276 (it sets all to -1 if such a selection doesn't exists)
6277 */
intersectBoundaries(int & lbeg,int & cbeg,int & lend,int & cend) const6278 void QDocumentCursorHandle::intersectBoundaries(int& lbeg, int& cbeg, int& lend, int& cend) const
6279 {
6280 int tlmin, tlmax, tcmin, tcmax, clmin, clmax, ccmin, ccmax;
6281
6282 boundaries(tlmin, tcmin, tlmax, tcmax);
6283 clmin = lbeg;
6284 clmax = lend;
6285 ccmin = cbeg;
6286 ccmax = cend;
6287
6288 if ( tlmax < clmin || tlmin > clmax || (tlmax == clmin && tcmax < ccmin) || (tlmin == clmax && tcmin > ccmax) )
6289 {
6290 lbeg = cbeg = lend = cend = -1;
6291 return;
6292 }
6293
6294 if ( tlmin == clmin )
6295 {
6296 lbeg = tlmin;
6297 cbeg = qMax(tcmin, ccmin);
6298 } else if ( tlmin < clmin ) {
6299 lbeg = clmin;
6300 cbeg = ccmin;
6301 } else {
6302 lbeg = tlmin;
6303 cbeg = tcmin;
6304 }
6305
6306 if ( tlmax == clmax )
6307 {
6308 lend = tlmax;
6309 cend = qMin(tcmax, ccmax);
6310 } else if ( tlmax < clmax ) {
6311 lend = tlmax;
6312 cend = tcmax;
6313 } else {
6314 lend = clmax;
6315 cend = ccmax;
6316 }
6317 }
6318
intersectBoundaries(QDocumentCursorHandle * h,int & lbeg,int & cbeg,int & lend,int & cend) const6319 void QDocumentCursorHandle::intersectBoundaries(QDocumentCursorHandle *h, int& lbeg, int& cbeg, int& lend, int& cend) const
6320 {
6321 int tlmin, tlmax, tcmin, tcmax, clmin, clmax, ccmin, ccmax;
6322
6323 boundaries(tlmin, tcmin, tlmax, tcmax);
6324 h->boundaries(clmin, ccmin, clmax, ccmax);
6325
6326 if ( tlmax < clmin || tlmin > clmax || (tlmax == clmin && tcmax < ccmin) || (tlmin == clmax && tcmin > ccmax) )
6327 {
6328 lbeg = cbeg = lend = cend = -1;
6329 return;
6330 }
6331
6332 if ( tlmin == clmin )
6333 {
6334 lbeg = tlmin;
6335 cbeg = qMax(tcmin, ccmin);
6336 } else if ( tlmin < clmin ) {
6337 lbeg = clmin;
6338 cbeg = ccmin;
6339 } else {
6340 lbeg = tlmin;
6341 cbeg = tcmin;
6342 }
6343
6344 if ( tlmax == clmax )
6345 {
6346 lend = tlmax;
6347 cend = qMin(tcmax, ccmax);
6348 } else if ( tlmax < clmax ) {
6349 lend = tlmax;
6350 cend = tcmax;
6351 } else {
6352 lend = clmax;
6353 cend = ccmax;
6354 }
6355 }
6356
6357
6358 /*!
6359 Creates a new cursor whose selection is the largest region which is contained in the selection of both
6360 (returns c if c has no selection but is within the selection of c and returns an invalid cursor if
6361 this has no selection)
6362 */
intersect(const QDocumentCursor & c) const6363 QDocumentCursor QDocumentCursorHandle::intersect(const QDocumentCursor& c) const
6364 {
6365 if ( !hasSelection() )
6366 {
6367 //if ( c.hasSelection() && c.isWithinSelection(QDocumentCursor(this)) )
6368 // return QDocumentCursor(clone());
6369
6370 } else if ( !c.hasSelection() ) {
6371
6372 if ( isWithinSelection(c) )
6373 return c;
6374
6375 } else {
6376 QDocumentCursorHandle *h = c.handle();
6377
6378 int lbeg, lend, cbeg, cend;
6379 intersectBoundaries(h, lbeg, cbeg, lend, cend);
6380
6381 if ( lbeg != -1 )
6382 {
6383 QDocumentCursor c(m_doc, lbeg, cbeg);
6384
6385
6386 if ( lend != -1 && (lbeg != lend || cbeg != cend) )
6387 {
6388 c.setSelectionBoundary(QDocumentCursor(m_doc, lend, cend));
6389 }
6390
6391 return c;
6392 }
6393 }
6394
6395 return QDocumentCursor();
6396 }
6397
equalBoundaries(const QDocumentCursorHandle * c)6398 bool QDocumentCursorHandle::equalBoundaries(const QDocumentCursorHandle* c){
6399 REQUIRE_RET(c, false);
6400 int l1, c1, l2, c2;
6401 beginBoundary(l1,c1);
6402 c->beginBoundary(l2, c2);
6403 if (l1 != l2 || c1 != c2) return false;
6404 endBoundary(l1,c1);
6405 c->endBoundary(l2, c2);
6406 if (l1 != l2 || c1 != c2) return false;
6407 return true;
6408 }
6409
equal(const QDocumentCursorHandle * c)6410 bool QDocumentCursorHandle::equal(const QDocumentCursorHandle* c){
6411 REQUIRE_RET(c,false);
6412 if (lineNumber() != c->lineNumber()) return false;
6413 if (columnNumber() != c->columnNumber()) return false;
6414 if (anchorLineNumber() != c->anchorLineNumber()) return false;
6415 if (anchorColumnNumber() != c->anchorColumnNumber()) return false;
6416 return true;
6417 }
6418
getMatchingPair(QDocumentCursor & from,QDocumentCursor & to,bool maximal) const6419 void QDocumentCursorHandle::getMatchingPair(QDocumentCursor& from, QDocumentCursor& to, bool maximal) const {
6420 if (!m_doc || !m_doc->languageDefinition()) {
6421 from = to = QDocumentCursor();
6422 return;
6423 }
6424 const QDocumentCursor orig = hasSelection()?selectionEnd():QDocumentCursor(const_cast<QDocumentCursorHandle*>(this));
6425 QList<QList<QDocumentCursor> > matches = m_doc->languageDefinition()->getMatches(orig);
6426 if (matches.isEmpty()) {
6427 from = to = QDocumentCursor();
6428 return;
6429 }
6430 Q_ASSERT(matches[0].size()==2);
6431
6432 int selMa = -1, selMaLD = 0, selMaCD = 0;
6433 for (int i=0; i < matches.size(); i++) {
6434 Q_ASSERT(matches[i].size()==2);
6435 int ld = qAbs(matches[i][0].lineNumber()-matches[i][1].lineNumber());
6436 int cd;
6437 if (ld == 0) cd = qAbs(matches[i][0].columnNumber()-matches[i][1].columnNumber());
6438 else if (matches[i][0].lineNumber()<matches[i][1].lineNumber())
6439 cd = matches[i][0].line().length() - matches[i][0].columnNumber() + matches[i][1].columnNumber();
6440 else
6441 cd = matches[i][1].line().length() - matches[i][1].columnNumber() + matches[i][0].columnNumber();
6442 if (
6443 selMa == -1 ||
6444 (maximal && (selMaLD < ld || (selMaLD == ld && selMaCD < cd))) ||
6445 (!maximal && (selMaLD > ld || (selMaLD == ld && selMaCD > cd)))
6446 ){
6447 selMa = i;
6448 selMaLD = ld;
6449 selMaCD = cd;
6450 }
6451 }
6452 if (matches[selMa][1].isWithinSelection(orig)) {
6453 from = matches[selMa][1];
6454 to = matches[selMa][0];
6455 } else {
6456 from = matches[selMa][0];
6457 to = matches[selMa][1];
6458 }
6459 }
6460
removeSelectedText(bool keepAnchor)6461 void QDocumentCursorHandle::removeSelectedText(bool keepAnchor)
6462 {
6463 if ( !m_doc )
6464 return;
6465
6466 QDocumentLine l1 = m_doc->line(m_begLine), l2 = m_doc->line(m_endLine);
6467
6468 if ( l1.isNull() || l2.isNull() )
6469 return;
6470
6471 QDocumentCommand *c;
6472
6473 if ( m_begLine < m_endLine )
6474 {
6475 c = new QDocumentEraseCommand(
6476 m_begLine,
6477 m_begOffset,
6478 m_endLine,
6479 m_endOffset,
6480 m_doc
6481 );
6482
6483 } else if ( m_begLine > m_endLine ) {
6484 c = new QDocumentEraseCommand(
6485 m_endLine,
6486 m_endOffset,
6487 m_begLine,
6488 m_begOffset,
6489 m_doc
6490 );
6491
6492 //m_begLine = m_endLine;
6493 //m_begOffset = m_endOffset;
6494
6495 } else {
6496 c = new QDocumentEraseCommand(
6497 m_begLine,
6498 qMin(m_begOffset, m_endOffset),
6499 m_endLine,
6500 qMax(m_begOffset, m_endOffset),
6501 m_doc
6502 );
6503
6504 //m_begOffset = qMin(m_begOffset, m_endOffset);
6505 //m_endLine = -1;
6506 //m_endOffset = -1;
6507 }
6508
6509 c->setKeepAnchor(keepAnchor);
6510 c->setTargetCursor(this);
6511 execute(c);
6512 }
6513
6514
6515 //////////////////
6516
6517 /////////////////////////
6518 // QDocumentPrivate
6519 /////////////////////////
6520
getStaticDefault()6521 template <class T> T* getStaticDefault() { static T _globStatInst; return &_globStatInst; }
6522
6523 QTextCodec* QDocumentPrivate::m_defaultCodec = nullptr;
6524
6525 QFont* QDocumentPrivate::m_font = nullptr;// = QApplication::font();
6526 QFont* QDocumentPrivate::m_baseFont = nullptr;
6527 int QDocumentPrivate::m_fontSizeModifier = 0;
6528 QFormatScheme* QDocumentPrivate::m_formatScheme = nullptr;// = QApplication::font();
6529 CacheCache<qreal> QDocumentPrivate::m_fmtWidthCache;
6530 CacheCache<QPixmap> QDocumentPrivate::m_fmtCharacterCache[2];
6531 QVector<QFont> QDocumentPrivate::m_fonts;
6532 QList<QFontMetricsF> QDocumentPrivate::m_fontMetrics;
6533
6534 int QDocumentPrivate::m_defaultTabStop = 4;
6535 QFormatScheme* QDocumentPrivate::m_defaultFormatScheme = getStaticDefault<QFormatScheme>();
6536
6537 QList<QDocumentPrivate*> QDocumentPrivate::m_documents;
6538
6539 bool QDocumentPrivate::m_fixedPitch;
6540 QDocument::WorkAroundMode QDocumentPrivate::m_workArounds=QDocument::WorkAroundMode();
6541 double QDocumentPrivate::m_lineSpacingFactor = 1.0;
6542 int QDocumentPrivate::m_staticCachesLogicalDpiY = -1;// resolution for which the caches are valid (depends on OS gui scaling)
6543 qreal QDocumentPrivate::m_ascent;// = m_fontMetrics.ascent();
6544 qreal QDocumentPrivate::m_descent;// = m_fontMetrics.descent();
6545 qreal QDocumentPrivate::m_leading;// = m_fontMetrics.leading();
6546 qreal QDocumentPrivate::m_spaceWidth;// = m_fontMetrics.width(' ');
6547 qreal QDocumentPrivate::m_lineHeight;// = m_fontMetrics.height();
6548 qreal QDocumentPrivate::m_lineSpacing;// = m_fontMetrics.lineSpacing();
6549
6550 qreal QDocumentPrivate::m_leftPadding = 5;
6551 QDocument::WhiteSpaceMode QDocumentPrivate::m_showSpaces = QDocument::ShowNone;
6552 QDocument::LineEnding QDocumentPrivate::m_defaultLineEnding = QDocument::Conservative;
6553
QDocumentPrivate(QDocument * d)6554 QDocumentPrivate::QDocumentPrivate(QDocument *d)
6555 : m_doc(d),
6556 m_editCursor(nullptr),
6557 m_drawCursorBold(true),
6558 m_deleting(false),
6559 m_delayedUpdateBlocks(0),
6560 m_lastGroupId(-1),
6561 m_constrained(false),
6562 m_hardLineWrap(false),
6563 m_lineWidthConstraint(false),
6564 m_leftMargin(0),
6565 m_width(0),
6566 m_height(0),
6567 m_tabStop(m_defaultTabStop),
6568 m_centerDocumentInEditor(false),
6569 m_language(nullptr),
6570 m_maxMarksPerLine(0),
6571 _nix(0),
6572 _dos(0),
6573 _mac(0),
6574 m_lineEnding(m_defaultLineEnding),
6575 m_codec(m_defaultCodec),
6576 m_readOnly(false),
6577 m_lineCacheXOffset(0), m_lineCacheWidth(0),
6578 m_instanceCachesLogicalDpiY(-1),
6579 m_forceLineWrapCalculation(false),
6580 m_overwrite(false)
6581 {
6582 m_documents << this;
6583 }
6584
~QDocumentPrivate()6585 QDocumentPrivate::~QDocumentPrivate()
6586 {
6587 m_marks.clear();
6588 m_largest.clear();
6589
6590 m_deleting = true;
6591
6592 //qDeleteAll(m_lines);
6593 foreach ( QDocumentLineHandle *h, m_lines )
6594 h->deref();
6595
6596 discardAutoUpdatedCursors(true);
6597
6598 m_lines.clear();
6599
6600 m_deleting = false;
6601
6602 m_commands.clear();
6603
6604 m_documents.removeAll(this);
6605 }
6606
findNextMark(int id,int from,int until)6607 int QDocumentPrivate::findNextMark(int id, int from, int until)
6608 {
6609 if ( from < 0 ) {
6610 from += m_lines.count();
6611 if (from < 0) from=m_lines.count()-1;
6612 } else if (from >= m_lines.count())
6613 from=m_lines.count()-1;
6614
6615 QHash<QDocumentLineHandle*, QList<int> >::const_iterator e = m_marks.constEnd();
6616
6617 int max = until;
6618
6619 if ( max < 0 )
6620 max += m_lines.count();
6621 else if ( max < from )
6622 max = m_lines.count() - 1;
6623
6624 for ( int i = from; i <= max; ++i )
6625 {
6626 QDocumentLineHandle *h = m_lines.at(i);
6627
6628 QHash<QDocumentLineHandle*, QList<int> >::const_iterator it = m_marks.constFind(h);
6629
6630 if ( it != e && !it->isEmpty() && (id==-1 || it->contains(id)) )
6631 return i;
6632
6633 }
6634
6635 if ( until > 0 && until < from )
6636 {
6637 for ( int i = 0; i <= until; ++i )
6638 {
6639 QDocumentLineHandle *h = m_lines.at(i);
6640
6641 QHash<QDocumentLineHandle*, QList<int> >::const_iterator it = m_marks.constFind(h);
6642
6643 if ( it != e && !it->isEmpty() && (id==-1 || it->contains(id)) )
6644 return i;
6645
6646 }
6647 }
6648
6649 return -1;
6650 }
6651
findPreviousMark(int id,int from,int until)6652 int QDocumentPrivate::findPreviousMark(int id, int from, int until)
6653 {
6654 if ( from < 0 ) {
6655 from += m_lines.count();
6656 if (from < 0) from = m_lines.count() - 1;
6657 } else if (from >= m_lines.count())
6658 from = m_lines.count()-1;
6659
6660 if ( until < 0 )
6661 {
6662 until += m_lines.count();
6663 } else if ( until >= m_lines.count() ) {
6664 until = m_lines.count() - 1;
6665 }
6666
6667 QHash<QDocumentLineHandle*, QList<int> >::const_iterator e = m_marks.constEnd();
6668
6669 int min = until;
6670
6671 if ( min > from )
6672 min = 0;
6673
6674 for ( int i = from; i >= min; --i )
6675 {
6676 QDocumentLineHandle *h = m_lines.at(i);
6677
6678 QHash<QDocumentLineHandle*, QList<int> >::const_iterator it = m_marks.constFind(h);
6679
6680 if ( it != e && !it->isEmpty() && (id==-1 || it->contains(id)))
6681 return i;
6682
6683 }
6684
6685 if ( until > 0 && until > from )
6686 {
6687 for ( int i = m_lines.count() - 1; i >= until; --i )
6688 {
6689 QDocumentLineHandle *h = m_lines.at(i);
6690
6691 QHash<QDocumentLineHandle*, QList<int> >::const_iterator it = m_marks.constFind(h);
6692
6693 if ( it != e && !it->isEmpty() && (id==-1 || it->contains(id)))
6694 return i;
6695
6696 }
6697 }
6698
6699 return -1;
6700 }
6701
removeMarks(int id)6702 void QDocumentPrivate::removeMarks(int id){
6703 QList<QDocumentLineHandle*> changed;
6704
6705 QHash<QDocumentLineHandle*, QList<int> >::iterator
6706 it = m_marks.begin(),
6707 end = m_marks.end();
6708 //change all silently
6709 m_maxMarksPerLine = 0;
6710 while (it!=end) {
6711 int n = it->removeAll(id);
6712 if (n) changed << it.key();
6713 if ( it->isEmpty() ) it=m_marks.erase(it);
6714 else {
6715 if (it->count() > m_maxMarksPerLine) m_maxMarksPerLine = it->count();
6716 ++it;
6717 }
6718 }
6719
6720 //then notify
6721 for (int i=0; i<changed.size();i++){
6722 emitMarkChanged(changed[i], id, false);
6723 changed[i]->setFlag(QDocumentLine::LayoutDirty,true);
6724 }
6725 }
6726
execute(QDocumentCommand * cmd)6727 void QDocumentPrivate::execute(QDocumentCommand *cmd)
6728 {
6729 if ( !cmd || m_readOnly)
6730 return;
6731
6732 m_lastModified = QDateTime::currentDateTime();
6733
6734 if ( m_macros.count() )
6735 {
6736 cmd->redo();
6737 m_macros.top()->addCommand(cmd);
6738 } else {
6739 m_commands.push(cmd);
6740 }
6741 }
6742
draw(QPainter * p,QDocument::PaintContext & cxt)6743 void QDocumentPrivate::draw(QPainter *p, QDocument::PaintContext& cxt)
6744 {
6745 //QTime t;
6746 //t.start();
6747
6748 p->setFont(*m_font);
6749 updateStaticCaches(p->device());
6750 updateInstanceCaches(p->device(), cxt);
6751
6752 int firstLine = qMax(0, qFloor(cxt.yoffset / m_lineSpacing));
6753 int lastLine = qMax(0, qCeil((cxt.yoffset+cxt.height) / m_lineSpacing));
6754
6755 if ( fmod(cxt.height,m_lineSpacing)>0.1 )
6756 ++lastLine;
6757
6758 QFormatScheme* scheme = m_formatScheme;
6759 if (!scheme) scheme = QDocument::defaultFormatScheme();
6760 if (!scheme) return;
6761
6762 QBrush base = cxt.palette.base();
6763 //QBrush alternate = QLineMarksInfoCenter::instance()->markType("current").color;
6764 QBrush alternate = scheme->format("current").toTextCharFormat().background(); //current line
6765
6766 QColor repBackground = m_doc->getBackground();
6767 if ( repBackground.isValid() )
6768 base.setColor(repBackground);
6769
6770 if ( !alternate.color().isValid() )
6771 alternate = cxt.palette.alternateBase();
6772
6773
6774 int wrap = 0;
6775 DrawTextLineContext lcxt = { /* docLineNr */ 0,
6776 /* editLineNr */ 0,
6777 /* firstLine */ 0,
6778 /* pos */ 0,
6779 /* visiblePos */ 0,
6780 /* inSelection */ false,
6781 /* base */ base,
6782 /* alternate */ alternate};
6783
6784 lcxt.docLineNr = textLine(firstLine, &wrap);
6785
6786 if( lcxt.docLineNr < 0 || lcxt.docLineNr >= m_lines.count() ){
6787 return;
6788 }
6789
6790 firstLine -= wrap;
6791 lcxt.editLineNr = firstLine;
6792 lcxt.firstLine = firstLine;
6793
6794 //qDebug("lines [%i, %i]", firstLine, lastLine);
6795
6796 lcxt.pos = 1. * firstLine * m_lineSpacing;
6797 lcxt.visiblePos = lcxt.pos;
6798 if (lcxt.visiblePos < cxt.yoffset) {
6799 int n = qRound((cxt.yoffset-lcxt.visiblePos) / m_lineSpacing);
6800 lcxt.visiblePos = lcxt.pos + 1. * n * m_lineSpacing;
6801 }
6802
6803 // adjust first line to take selections into account...
6804 foreach ( const QDocumentSelection& s, cxt.selections )
6805 {
6806 if ( (s.startLine < lcxt.docLineNr) && (s.endLine >= lcxt.docLineNr) )
6807 {
6808 lcxt.inSelection = true;
6809 break;
6810 }
6811 }
6812
6813 m_leftMargin = 0;
6814 if (m_centerDocumentInEditor && m_doc->widthConstraint())
6815 m_leftMargin = qMax(0., (cxt.width - m_doc->width()) / 2);
6816 //qDebug("QDocumentPrivate::draw, leftMargin=%i", m_leftMargin);
6817 p->translate(m_leftMargin, 0); // for simplicity, all drawing of lines is shifted by the leftMargin,
6818 // so that the painter x coordinate starts at the edge of the document.
6819 // leftPadding is still included, because here the background has to be drawn
6820
6821
6822 for ( ; lcxt.editLineNr <= lastLine; ++lcxt.docLineNr )
6823 {
6824 if ( lcxt.docLineNr >= m_lines.count() )
6825 {
6826 //qDebug("line %i not valid", i);
6827 break;
6828 }
6829 drawTextLine(p, cxt, lcxt);
6830 }
6831 p->translate(-m_leftMargin, 0);
6832 //qDebug("painting done in %i ms...", t.elapsed());
6833
6834 drawPlaceholders(p, cxt);
6835 drawCursors(p, cxt);
6836 //qDebug("QDocumentPrivate::draw finished");
6837 }
6838
drawTextLine(QPainter * p,QDocument::PaintContext & cxt,DrawTextLineContext & lcxt)6839 void QDocumentPrivate::drawTextLine(QPainter *p, QDocument::PaintContext &cxt, DrawTextLineContext &lcxt)
6840 {
6841 QBrush selectionBackground = cxt.palette.highlight();
6842
6843 QDocumentLineHandle *dlh = m_lines.at(lcxt.docLineNr);
6844
6845 // ugly workaround..., disabled 20.12.'09 because it slows down rendering speed on mac considerably and i don't see its function
6846 //if( !m_fixedPitch && !h->hasFlag(QDocumentLine::Hidden))
6847 // adjustWidth(i);
6848
6849 const int wrap = dlh->m_frontiers.count();
6850 const bool wrapped = wrap;
6851
6852 //if ( wrapped )
6853 // qDebug("line %i is wrapped over %i sublines", i, *wit);
6854
6855 // selections stuff (must do it before whatever the visibility...)
6856 bool fullSelection = false;
6857 QVector<int> selectionBoundaries(0);
6858 if ( lcxt.inSelection )
6859 selectionBoundaries.prepend(0);
6860
6861 foreach ( const QDocumentSelection& s, cxt.selections )
6862 {
6863 if ( lcxt.docLineNr == s.startLine )
6864 {
6865 if ( !(selectionBoundaries.count() & 1) )
6866 selectionBoundaries.append(s.start);
6867
6868 if ( lcxt.docLineNr == s.endLine )
6869 {
6870 selectionBoundaries.append(s.end);
6871 } else {
6872 //++selLevel;
6873 lcxt.inSelection = true;
6874 //selEnd = h->m_text.length();
6875 }
6876 } else if ( lcxt.inSelection && (lcxt.docLineNr == s.endLine) ) {
6877
6878 if ( selectionBoundaries.count() % 2 )
6879 selectionBoundaries.append(s.end);
6880
6881 //--selLevel;
6882 lcxt.inSelection = false;
6883 }
6884 }
6885
6886
6887 if ( lcxt.inSelection && selectionBoundaries.count() == 1 && selectionBoundaries.at(0) == 0 )
6888 {
6889 selectionBoundaries.clear();
6890 fullSelection = true;
6891 }
6892
6893 if ( dlh->hasFlag(QDocumentLine::Hidden) )
6894 {
6895 return;
6896 } else
6897 ++lcxt.editLineNr;
6898
6899 if ( wrapped )
6900 lcxt.editLineNr += wrap;
6901
6902
6903 QBrush background = lcxt.base;
6904
6905 // cursor(s) stuff
6906 bool cursorOnLine = false;
6907 bool currentLine=false;
6908
6909 for (QList<QDocumentCursorHandle*>::iterator cit = cxt.cursors.begin(); cit != cxt.cursors.end() && !currentLine; ++cit)
6910 if ( (*cit)->lineNumber() == lcxt.docLineNr )
6911 {
6912 if ( cxt.blinkingCursor )
6913 cursorOnLine = true;
6914
6915 if ( cxt.fillCursorRect ){
6916 if (lcxt.alternate.color().alpha() != 0) // keep standard background, if alternate is fully transparent
6917 background = lcxt.alternate;
6918 currentLine=true;
6919 }
6920
6921 break;
6922 }
6923
6924
6925 if ( cxt.blinkingCursor)
6926 for (QList<QDocumentCursorHandle*>::iterator cit = cxt.extra.begin(); cit != cxt.extra.end() && !cursorOnLine; ++cit)
6927 if ( (*cit)->lineNumber() == lcxt.docLineNr )
6928 cursorOnLine = true;
6929
6930 QList<int> m = marks(dlh);
6931
6932 // line marks stuff
6933 if ( m.count() )
6934 {
6935 QLineMarksInfoCenter *mic = QLineMarksInfoCenter::instance();
6936
6937 QColor c = mic->markType(mic->priority(m)).color;
6938 if ( c.isValid() && c.alpha()!=0 )
6939 background = c;
6940
6941 }
6942
6943 if ( lcxt.editLineNr < lcxt.firstLine ) // TODO: can this happen? Otherwise we don't need lineCxt.firstLine at all.
6944 return;
6945
6946 bool curSelectionState=lcxt.inSelection || (!selectionBoundaries.empty()) || (cursorOnLine);
6947
6948 if (fullSelection && dlh->lineHasSelection != QDocumentLineHandle::fullSel) {
6949 dlh->setFlag(QDocumentLine::LayoutDirty, true);
6950 dlh->lineHasSelection = QDocumentLineHandle::fullSel;
6951 }
6952 if (!fullSelection && curSelectionState) {
6953 dlh->setFlag(QDocumentLine::LayoutDirty, true);
6954 dlh->lineHasSelection = QDocumentLineHandle::partialSel;
6955 }
6956 if (!curSelectionState && dlh->lineHasSelection != QDocumentLineHandle::noSel) {
6957 dlh->setFlag(QDocumentLine::LayoutDirty, true);
6958 dlh->lineHasSelection = QDocumentLineHandle::noSel;
6959 }
6960
6961 p->save(); // every line get's its own standard pointer to prevent leaking of pointer state
6962
6963 // simplify line drawing
6964 p->translate(QPointF(0, lcxt.pos));
6965
6966 // draw text with caching
6967 int pseudoWrap = 0;
6968 dlh->lockForRead();
6969 if (dlh->hasCookie(QDocumentLine::PICTURE_COOKIE)) {
6970 QPixmap pm = dlh->getCookie(QDocumentLine::PICTURE_COOKIE).value<QPixmap>();
6971
6972 const qreal reservedHeight = dlh->getPictureCookieHeight();
6973 dlh->unlock(); // readLock
6974
6975 pseudoWrap = qRound(reservedHeight / m_lineSpacing);
6976 qreal x = qMax(-m_leftMargin, (m_width - pm.width()) / 2);
6977 // special treatment if line width > viewport width (e.g. no line wrap)
6978 x = qMin(x,(cxt.width-pm.width())/2);
6979 if(pm.width()>m_width){
6980 // special treatment if pximap does not fit in line
6981 if(m_constrained){
6982 //line wrap
6983 x=m_leftMargin;
6984 qreal pixelRatio=pm.devicePixelRatio()*pm.width()/m_width;
6985 pm.setDevicePixelRatio(pixelRatio);
6986 }else{
6987 x=m_leftMargin;
6988 }
6989 }
6990
6991 qreal y = m_lineSpacing*(wrap+1-pseudoWrap) + (reservedHeight - pm.height()) / 2.;
6992 p->fillRect(QRectF(x - PICTURE_BORDER, y - PICTURE_BORDER, pm.width() + 2*PICTURE_BORDER, pm.height() + 2* PICTURE_BORDER), Qt::white);
6993 p->drawPixmap(QPointF(x, y), pm);
6994
6995 dlh->lockForWrite();
6996 dlh->setCookie(QDocumentLine::PICTURE_COOKIE_DRAWING_POS, QRect(QPoint(x+m_leftMargin, y+lcxt.pos), pm.size())); // +pos : correct for painter translation, saved point is in doc coordinates
6997 dlh->unlock(); // writeLock
6998 } else {
6999 dlh->unlock(); // readLock
7000 }
7001
7002 bool useLineCache = !currentLine && !(m_workArounds & QDocument::DisableLineCache);
7003
7004 bool imageCache = (m_workArounds & QDocument::QImageCache);
7005
7006 if(
7007 useLineCache
7008 && !dlh->hasFlag(QDocumentLine::LayoutDirty)
7009 && dlh->hasFlag(QDocumentLine::FormatsApplied)
7010 && (m_LineCache.contains(dlh) || m_LineCacheAlternative.contains(dlh))
7011 ) {
7012 // cache is activated, available, and up-to-date: simply draw the cached object
7013 if (imageCache) {
7014 p->drawImage(QPointF(m_lineCacheXOffset, 0), *m_LineCacheAlternative.object(dlh));
7015 } else {
7016 p->drawPixmap(QPointF(m_lineCacheXOffset, 0), *m_LineCache.object(dlh));
7017 }
7018 } else {
7019 qreal ht = m_lineSpacing*(wrap+1 - pseudoWrap);
7020 QImage *image = nullptr;
7021 QPixmap *pixmap = nullptr;
7022 QPainter *pr = nullptr;
7023 if (useLineCache) {
7024 if (imageCache) {
7025
7026 qreal pixelRatio = p->device()->devicePixelRatio();
7027 image = new QImage(qCeil(pixelRatio * m_lineCacheWidth), qCeil(pixelRatio * ht), QImage::Format_RGB888);
7028 image->setDevicePixelRatio(pixelRatio);
7029
7030 if (fullSelection) {
7031 image->fill(selectionBackground.color().rgb());
7032 }else{
7033 image->fill(background.color().rgb());
7034 }
7035 pr = new QPainter(image);
7036 } else {
7037
7038 qreal pixelRatio = p->device()->devicePixelRatio();
7039 pixmap = new QPixmap(qCeil(pixelRatio * m_lineCacheWidth), qCeil(pixelRatio * ht));
7040 pixmap->setDevicePixelRatio(pixelRatio);
7041 // TODO: The pixmap always has a logicalDpi of the primary screen. This needs to be fixed for
7042 // correct drawing on secondary screens with different scaling factors.
7043
7044 if (fullSelection) {
7045 pixmap->fill(selectionBackground.color());
7046 } else {
7047 pixmap->fill(background.color());
7048 }
7049 pr = new QPainter(pixmap);
7050 }
7051 pr->setRenderHints(p->renderHints());
7052 pr->setFont(p->font());
7053 } else {
7054 pr = p;
7055 }
7056
7057 // draw the background
7058 if (useLineCache) {
7059 pr->translate(-cxt.xoffset,0);
7060 pr->fillRect(QRectF(0, 0, m_leftPadding, ht), background);
7061 } else if (fullSelection) {
7062 pr->fillRect(QRectF(0, 0, m_leftPadding, ht), background);
7063 pr->fillRect(QRectF(m_leftPadding, 0, m_width - m_leftPadding, ht), selectionBackground);
7064 } else
7065 pr->fillRect(QRectF(0, 0, m_width, ht), background);
7066
7067 qreal y = 0;
7068 if (!useLineCache && lcxt.visiblePos > lcxt.pos)
7069 y = lcxt.visiblePos - lcxt.pos;
7070 if(!useLineCache && ((lcxt.pos + ht) > (cxt.yoffset + cxt.height))) {
7071 while ((lcxt.pos + ht) > (cxt.yoffset + cxt.height)) {
7072 ht -= m_lineSpacing;
7073 }
7074 ht += m_lineSpacing;
7075 }
7076 dlh->draw(lcxt.docLineNr, pr, cxt.xoffset, m_lineCacheWidth, selectionBoundaries, cxt.palette, fullSelection,y,ht);
7077
7078 if (useLineCache) {
7079 if(imageCache) {
7080 p->drawImage(QPointF(cxt.xoffset, 0.), *image);
7081 delete pr;
7082 m_LineCacheAlternative.insert(dlh, image);
7083 }else{
7084 p->drawPixmap(QPointF(cxt.xoffset, 0), *pixmap);
7085 delete pr;
7086 m_LineCache.insert(dlh, pixmap);
7087 }
7088 } else {
7089 m_LineCache.remove(dlh);
7090 m_LineCacheAlternative.remove(dlh);
7091 }
7092 }
7093
7094 // draw fold rect indicator
7095 if ( dlh->hasFlag(QDocumentLine::CollapsedBlockStart) )
7096 {
7097 QColor linescolor = m_formatScheme->format("background").linescolor;
7098 if (linescolor.isValid()) {
7099 p->save();
7100 p->setPen(linescolor);
7101
7102 qreal y = m_lineSpacing * (wrap + 1) - 1;
7103 p->drawLine(0, y, m_width, y);
7104 p->restore();
7105 }
7106 }
7107
7108 // see above
7109 p->translate(QPointF(0, -lcxt.pos));
7110 p->restore();
7111
7112 // shift pos to the end of the line (ready for next line)
7113 lcxt.pos += m_lineSpacing;
7114 if ( wrapped ) {
7115 lcxt.pos += m_lineSpacing * wrap;
7116 }
7117
7118 //qDebug("drawing line %i in %i ms", i, t.elapsed());
7119
7120 }
7121
drawPlaceholders(QPainter * p,QDocument::PaintContext & cxt)7122 void QDocumentPrivate::drawPlaceholders(QPainter *p, QDocument::PaintContext &cxt)
7123 {
7124 p->save();
7125
7126 //mark placeholder which will probably be removed
7127 if (
7128 cxt.lastPlaceHolder >=0
7129 && cxt.lastPlaceHolder < cxt.placeHolders.count()
7130 && cxt.lastPlaceHolder != cxt.curPlaceHolder
7131 ){
7132 const PlaceHolder& ph = cxt.placeHolders.at(cxt.lastPlaceHolder);
7133 if (!ph.autoRemove) cxt.lastPlaceHolder = -1;
7134 else if (!ph.cursor.line().isHidden()) {
7135 p->setPen(QColor(0, 0, 0));
7136 p->setPen(Qt::DotLine);
7137 p->drawConvexPolygon(ph.cursor.documentRegion());
7138 p->setPen(Qt::SolidLine);
7139 }
7140 }
7141
7142 //draw placeholders
7143 for (int i=0; i < cxt.placeHolders.count(); i++)
7144 if (i != cxt.curPlaceHolder && i!=cxt.lastPlaceHolder && !cxt.placeHolders[i].autoOverride && !cxt.placeHolders[i].cursor.line().isHidden())
7145 p->drawConvexPolygon(cxt.placeHolders[i].cursor.documentRegion());
7146
7147 //mark active placeholder
7148 if ( cxt.curPlaceHolder >= 0 && cxt.curPlaceHolder < cxt.placeHolders.count() )
7149 {
7150 const PlaceHolder& ph = cxt.placeHolders.at(cxt.curPlaceHolder);
7151 if (!ph.cursor.line().isHidden()){
7152 p->setPen(QColor(255,0,0));
7153 p->drawConvexPolygon(ph.cursor.documentRegion());
7154 }
7155 p->setPen(QColor(0,0,255));
7156 foreach ( const QDocumentCursor& m, ph.mirrors )
7157 {
7158 if ( m.isValid() && !m.line().isHidden())
7159 p->drawConvexPolygon(m.documentRegion());
7160 }
7161 }
7162 for (int i=0; i < cxt.placeHolders.count(); i++)
7163 if (cxt.placeHolders[i].autoOverride && !cxt.placeHolders[i].cursor.line().isHidden())
7164 p->drawConvexPolygon(cxt.placeHolders[i].cursor.documentRegion());
7165
7166 p->restore();
7167 }
7168
drawCursors(QPainter * p,const QDocument::PaintContext & cxt)7169 void QDocumentPrivate::drawCursors(QPainter *p, const QDocument::PaintContext &cxt)
7170 {
7171 p->save();
7172
7173 QColor repForeground = m_doc->getForeground(); // color for cursor line
7174 if ( !repForeground.isValid() )
7175 repForeground = cxt.palette.text().color(); // Fallback
7176 p->setPen(repForeground);
7177
7178 foreach(QDocumentCursor cur, QList<QDocumentCursorHandle*>() << cxt.cursors << cxt.extra) {
7179 if (!cur.line().isHidden()) {
7180 if (cxt.blinkingCursor) {
7181 if (m_overwrite && !cur.hasSelection()) {
7182 // block cursor for overwrite
7183 p->setPen(Qt::NoPen);
7184 QColor col = repForeground;
7185 col.setAlpha(160);
7186 QBrush brush(col);
7187 p->setBrush(brush);
7188 QPointF pt = cur.documentPosition();
7189 QDocumentCursor curHelper(cur, false);
7190 curHelper.movePosition(1);
7191 QPointF pt2 = curHelper.documentPosition();
7192 qreal width = 0;
7193 if (pt.y() == pt2.y()) {
7194 width = pt2.x() - pt.x();
7195 }
7196 if (width == 0) {
7197 width = textWidth(0, " ");
7198 }
7199 p->drawRect(QRectF(pt.x(), pt.y(), width, QDocumentPrivate::m_lineSpacing));
7200 }else{
7201 // regular line cursor
7202 QPointF pt = cur.documentPosition();
7203 QPointF curHt(0, QDocumentPrivate::m_lineSpacing-1.);
7204 QPen pen(p->pen());
7205 if (m_drawCursorBold) {
7206 pen.setWidthF(2.);
7207 }
7208 p->setPen(pen);
7209 p->drawLine(pt, pt + curHt);
7210 /*if (m_drawCursorBold) {
7211 pt.setX(pt.x() + 1);
7212 p->drawLine(pt, pt + curHt);
7213 }*/
7214 }
7215 }
7216 }
7217 }
7218 p->restore();
7219 }
7220
exportAsHtml(const QDocumentCursor & range,bool includeHeader,bool simplifyCSS,int maxLineWidth,int maxWrap) const7221 QString QDocumentPrivate::exportAsHtml(const QDocumentCursor& range, bool includeHeader, bool simplifyCSS, int maxLineWidth, int maxWrap) const{
7222 QString result;
7223 if (includeHeader) {
7224 result += "<html><head>";
7225 if ( m_formatScheme ) {
7226 result += "<style type=\"text/css\">";
7227 result += QString("pre { margin: %1px }\n").arg(simplifyCSS?0:1);
7228 result += m_formatScheme->exportAsCSS(simplifyCSS);
7229 result += "</style>";
7230 }
7231 result += "</head><body>";
7232 }
7233 QDocumentSelection sel = range.selection();
7234 REQUIRE_RET(sel.startLine >= 0 && sel.startLine < m_lines.size(),"");
7235 REQUIRE_RET(sel.endLine >= 0 && sel.endLine < m_lines.size(),"");
7236
7237 // remove surrounding empty lines
7238 int line = sel.startLine;
7239 while (line < sel.endLine && m_lines[line]->length()==0) line++;
7240 if (line < sel.endLine) {
7241 sel.startLine = line;
7242 sel.start = 0;
7243 }
7244 line = sel.endLine;
7245 while (line > sel.startLine && m_lines[line]->length()==0) line--;
7246 if (line > sel.startLine) {
7247 sel.endLine = line;
7248 sel.end = -1;
7249 }
7250
7251 result += m_lines[sel.startLine]->exportAsHtml(sel.start, -1, maxLineWidth, maxWrap)+"\n";
7252 for (int i=sel.startLine+1; i<sel.endLine; i++)
7253 result += m_lines[i]->exportAsHtml(0, -1, maxLineWidth, maxWrap) + "\n";
7254 if (sel.startLine != sel.endLine)
7255 result += m_lines[sel.endLine]->exportAsHtml(0, sel.end, maxLineWidth, maxWrap);
7256
7257 if (includeHeader)
7258 result += "</body></html>";
7259 return result;
7260 }
7261
7262
position(const QDocumentLineHandle * l) const7263 int QDocumentPrivate::position(const QDocumentLineHandle *l) const
7264 {
7265 int pos = 0;
7266
7267 int idx = m_lines.indexOf(const_cast<QDocumentLineHandle*>(l));
7268
7269 if ( idx == -1 )
7270 return -1;
7271
7272 for ( int i = 0; i < idx; i++ )
7273 pos += m_lines.at(i)->length();
7274
7275 pos += m_lineEndingString.length()*idx;
7276
7277 return pos;
7278 }
7279
lineForPosition(int & position) const7280 QDocumentLineHandle* QDocumentPrivate::lineForPosition(int& position) const
7281 {
7282 int pos = 0, idx = 0;
7283
7284 while ( (pos + m_lines.at(idx)->length()) < position )
7285 pos += m_lines.at(idx++)->length();
7286
7287
7288 return nullptr;
7289 }
7290 /*!
7291 * \brief set HardLineWrap mode
7292 * \param wrap
7293 */
setHardLineWrap(bool wrap)7294 void QDocumentPrivate::setHardLineWrap(bool wrap)
7295 {
7296 m_hardLineWrap=wrap;
7297 }
7298 /*!
7299 * \brief set center document mode
7300 * Only effective if text width is constraint to be smaller then view width
7301 * \param center
7302 */
setCenterDocumentInEditor(bool center)7303 void QDocumentPrivate::setCenterDocumentInEditor(bool center){
7304 m_centerDocumentInEditor=center;
7305 }
7306
setLineWidthConstraint(bool wrap)7307 void QDocumentPrivate::setLineWidthConstraint(bool wrap)
7308 {
7309 m_lineWidthConstraint=wrap;
7310 }
7311
setCursorBold(bool bold)7312 void QDocumentPrivate::setCursorBold(bool bold)
7313 {
7314 m_drawCursorBold = bold;
7315 }
7316
7317
setWidth(qreal width)7318 void QDocumentPrivate::setWidth(qreal width)
7319 {
7320 if(m_width==width){
7321 return; // no change if width is not changed
7322 }
7323
7324 bool oldConstraint = m_constrained;
7325 m_constrained = width > 0. ;
7326
7327 if ( m_constrained || m_forceLineWrapCalculation )
7328 {
7329 qreal oldWidth = m_width;
7330
7331 m_width = width;
7332
7333 if ( oldConstraint && oldWidth < width && m_constrained )
7334 {
7335 // expand : simply remove old wraps if possible
7336
7337 QMap<int, int>::iterator it = m_wrapped.begin();
7338
7339 while ( it != m_wrapped.end() )
7340 {
7341 QDocumentLineHandle *h = it.key() < m_lines.count() ? m_lines.at(it.key()) : nullptr;
7342
7343 if ( h )
7344 h->updateWrap(it.key());
7345
7346 int sz = h ? h->m_frontiers.count() : 0;
7347
7348 if ( sz )
7349 {
7350 //qDebug("changing wrap at line %i from %i to %i", it.key(), *it, sz);
7351 *it = sz;
7352 ++it;
7353 } else {
7354 //qDebug("removing wrap at line %i", it.key());
7355 it = m_wrapped.erase(it);
7356 }
7357 }
7358 } else if ( oldWidth > width || m_forceLineWrapCalculation ) {
7359 // shrink : scan whole document and create new wraps wherever needed
7360 //qDebug("global width scan [constraint on]");
7361 //m_wrapped.clear();
7362 setWidth();
7363 }
7364 } else {
7365 //qDebug("global width scan [constraint off]");
7366 m_wrapped.clear();
7367 setWidth();
7368 }
7369
7370 if ( m_editCursor )
7371 {
7372 m_editCursor->refreshColumnMemory();
7373 }
7374
7375 emitWidthChanged();
7376 setHeight();
7377
7378 emitFormatsChanged();
7379 markFormatCacheDirty();
7380 }
7381
removeWrap(int i)7382 void QDocumentPrivate::removeWrap(int i){
7383 m_wrapped.remove(i);
7384 }
7385
testGetHiddenLines()7386 QList<int> QDocumentPrivate::testGetHiddenLines(){
7387 QSet<int> res;
7388 for (QMap<int, int>::iterator it = m_hidden.begin(); it != m_hidden.end(); ++it )
7389 for (int i=1;i<=it.value();i++)
7390 res.insert(i+it.key());
7391 QList<int> tmp = res.values();
7392 std::sort(tmp.begin(),tmp.end());
7393 return tmp;
7394 }
7395
setWidth()7396 void QDocumentPrivate::setWidth()
7397 {
7398 m_largest.clear();
7399 const int max = m_lines.count();
7400
7401 if ( m_constrained || m_forceLineWrapCalculation )
7402 {
7403 int first = -1;
7404
7405 for ( int i = 0; i < max; ++i )
7406 {
7407 QDocumentLineHandle *l = m_lines.at(i);
7408 int olw = l->m_frontiers.count();
7409
7410 l->updateWrap(i);
7411
7412 int lw = l->m_frontiers.count();
7413
7414 if ( olw == lw )
7415 continue;
7416
7417 if ( lw )
7418 {
7419 //qDebug("added wrap on line %i", line);
7420 m_wrapped[i] = lw;
7421 } else {
7422 //qDebug("removed wrap on line %i", line);
7423 m_wrapped.remove(i);
7424 }
7425
7426 if ( first == -1 )
7427 first = i;
7428 }
7429
7430 if ( first != -1 && m_constrained )
7431 emitFormatsChange(first, -1);
7432 }
7433 if (!m_constrained){
7434 qreal oldWidth = m_width;
7435
7436 m_width = 0;
7437
7438 foreach ( QDocumentLineHandle *l, m_lines )
7439 {
7440 if ( l->hasFlag(QDocumentLine::Hidden) )
7441 continue;
7442
7443 if (!m_forceLineWrapCalculation)
7444 l->m_frontiers.clear();
7445
7446 int w = l->cursorToX(l->length());
7447
7448 if ( w > m_width )
7449 {
7450 m_width = w;
7451
7452 m_largest.clear();
7453 m_largest << qMakePair(l, w);
7454 }
7455 }
7456
7457 if ( m_width != oldWidth )
7458 emitWidthChanged();
7459 }
7460 }
7461
7462 // static const int widthCacheSize = 5; // unused ...
7463
adjustWidth(int line)7464 void QDocumentPrivate::adjustWidth(int line)
7465 {
7466 if ( line < 0 || line >= m_lines.count() )
7467 return;
7468
7469 QDocumentLineHandle *l = m_lines.at(line);
7470
7471 if ( m_constrained || m_forceLineWrapCalculation )
7472 {
7473 int olw = l->m_frontiers.count();
7474
7475 l->updateWrap(line);
7476
7477 int lw = l->m_frontiers.count();
7478
7479 if ( olw != lw ) {
7480 if ( l->m_layout )
7481 l->setFlag(QDocumentLine::LayoutDirty);
7482
7483 if ( lw )
7484 {
7485 //qDebug("added wrap on line %i", line);
7486 m_wrapped[line] = lw;
7487 } else {
7488 //qDebug("removed wrap on line %i", line);
7489 m_wrapped.remove(line);
7490 }
7491
7492 emitFormatsChange(line, -1);
7493 setHeight();
7494 }
7495 }
7496 if ( !m_constrained ) {
7497 if ( !m_forceLineWrapCalculation )
7498 l->m_frontiers.clear();
7499
7500 int w = l->cursorToX(l->length());
7501
7502 if ( w > m_width )
7503 {
7504 m_width = w;
7505 emitWidthChanged();
7506
7507 m_largest.clear();
7508 m_largest << qMakePair(l, w);
7509 } else if ( m_largest.count() && (m_largest.at(0).first == l) ) {
7510 int old = m_largest.at(0).second;
7511
7512 if ( w < old )
7513 setWidth();
7514 }
7515 }
7516 }
7517
setHeight()7518 void QDocumentPrivate::setHeight()
7519 {
7520 qreal oldHeight = m_height;
7521 int last = visualLine(m_lines.count() - 1) + 1;
7522
7523 if ( m_lines.count() )
7524 last += m_lines.last()->m_frontiers.count();
7525
7526 m_height = 1. * last * m_lineSpacing;
7527
7528 if ( oldHeight != m_height )
7529 emitHeightChanged();
7530 }
7531
setBaseFont(const QFont & f,bool forceUpdate)7532 void QDocumentPrivate::setBaseFont(const QFont &f, bool forceUpdate)
7533 {
7534 m_baseFont = new QFont(f);
7535 QFont fMod = f;
7536 if (m_fontSizeModifier + m_baseFont->pointSize() < 1) {
7537 // prevent actual font sizes to be <= 0
7538 m_fontSizeModifier = - m_baseFont->pointSize() + 1;
7539 }
7540 fMod.setPointSize(fMod.pointSize() + m_fontSizeModifier);
7541 setFont(fMod, forceUpdate);
7542 }
7543
setFontSizeModifier(int m,bool forceUpdate)7544 void QDocumentPrivate::setFontSizeModifier(int m, bool forceUpdate)
7545 {
7546 if (m + m_baseFont->pointSize() < 1) {
7547 // prevent actual font sizes to be <= 0
7548 m = - m_baseFont->pointSize() + 1;
7549 }
7550 m_fontSizeModifier = m;
7551 QFont fMod = QFont(*m_baseFont);
7552 fMod.setPointSize(fMod.pointSize() + m_fontSizeModifier);
7553 setFont(fMod, forceUpdate);
7554 }
7555
setFont(const QFont & f,bool forceUpdate)7556 void QDocumentPrivate::setFont(const QFont& f, bool forceUpdate)
7557 {
7558 m_fmtCharacterCache[0].clear();
7559 m_fmtCharacterCache[1].clear();
7560 m_fmtWidthCache.clear();
7561
7562 if ( !m_font )
7563 {
7564 m_font = new QFont;
7565 }
7566
7567
7568 QFont modifiedF = f;
7569 // set the styling so that if the font is not found Courier one will be used
7570 #if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
7571 modifiedF.setStyleHint(QFont::Courier);
7572 #else
7573 modifiedF.setStyleHint(QFont::Courier, QFont::ForceIntegerMetrics);
7574 #endif
7575
7576 //disable kerning because words are drawn at once, but their width is calculated character
7577 //by character (in functions which calculate the cursor position)
7578 modifiedF.setKerning(false);
7579
7580 if ( *m_font == modifiedF && !forceUpdate )
7581 return;
7582
7583
7584 *m_font = modifiedF;
7585
7586
7587 //QFontMetrics fm(*m_font);
7588 updateStaticCaches(nullptr);
7589
7590 foreach ( QDocumentPrivate *d, m_documents )
7591 {
7592 d->setWidth();
7593 d->setHeight();
7594 d->m_LineCache.clear();
7595 d->emitFontChanged();
7596 }
7597 }
7598
7599 /*!
7600 * Check that the used caches are suitable for the given painter by checking its logicalDpiY
7601 * If not, the cached information is updated / resetted.
7602 * This will happen if screens or scaling is switched.
7603 */
updateStaticCaches(const QPaintDevice * pd)7604 void QDocumentPrivate::updateStaticCaches(const QPaintDevice *pd)
7605 {
7606 if (!pd || m_staticCachesLogicalDpiY != pd->logicalDpiY()) {
7607 const QPaintDevice *device = pd ? pd : QApplication::activeWindow();
7608 if (device) {
7609 //qDebug() << "invalidate static caches. old dpi:" << m_staticCachesLogicalDpiY << "new dpi:" << device->logicalDpiY();
7610 m_staticCachesLogicalDpiY = device->logicalDpiY();
7611 }
7612
7613 // need to get the font metrics in the context of the paint device to get correct UI scaling
7614 QFontMetricsF fm = QFontMetricsF(*m_font, const_cast<QPaintDevice *>(pd));
7615 m_spaceWidth = UtilsUi::getFmWidth(fm, ' ');
7616 m_ascent = fm.ascent();
7617 m_descent = fm.descent();
7618 m_lineHeight = fm.height();
7619 m_leading = fm.leading() + (m_lineSpacingFactor-1.0)*m_lineHeight;
7620 m_lineSpacing = m_leading+m_lineHeight;
7621 //if ( !m_fixedPitch )
7622 // qDebug("unsafe computations...");
7623
7624 if(m_lineHeight>m_lineSpacing) m_lineSpacing=m_lineHeight;
7625
7626 m_fmtWidthCache.clear();
7627 m_fmtCharacterCache[0].clear();
7628 m_fmtCharacterCache[1].clear();
7629
7630 updateFormatCache(device);
7631 }
7632 }
7633
updateInstanceCaches(const QPaintDevice * pd,QDocument::PaintContext & cxt)7634 void QDocumentPrivate::updateInstanceCaches(const QPaintDevice *pd, QDocument::PaintContext &cxt)
7635 {
7636 if (!pd || m_instanceCachesLogicalDpiY != pd->logicalDpiY() || m_lineCacheXOffset != cxt.xoffset || m_lineCacheWidth < cxt.width) {
7637 const QPaintDevice *device = pd ? pd : QApplication::activeWindow();
7638 if (device) {
7639 //qDebug() << "invalidate instance caches. old dpi:" << m_instanceCachesLogicalDpiY << "new dpi:" << device->logicalDpiY();
7640 m_instanceCachesLogicalDpiY = device->logicalDpiY();
7641 }
7642
7643 m_LineCacheAlternative.clear();
7644 m_LineCache.clear();
7645
7646 m_lineCacheXOffset = cxt.xoffset;
7647 if (m_width) m_lineCacheWidth = cxt.width;
7648 else m_lineCacheWidth = cxt.width;//(cxt.width+15) & (~16); //a little bit larger if not wrapped ???
7649 }
7650 }
7651
setFormatScheme(QFormatScheme * f)7652 void QDocumentPrivate::setFormatScheme(QFormatScheme *f)
7653 {
7654 bool updateFont = m_formatScheme != f && f;
7655 m_formatScheme = f;
7656 if (updateFont)
7657 setFont(*m_font, true);
7658 }
7659
tunePainter(QPainter * p,int fid)7660 void QDocumentPrivate::tunePainter(QPainter *p, int fid)
7661 {
7662 if ( fid < m_fonts.count() )
7663 {
7664 p->setFont(m_fonts.at(fid));
7665 //p->setPen(m_colors.at(fid));
7666 } else {
7667 p->setFont(*m_font);
7668 //p->setPen(Qt::black);
7669 }
7670 }
7671
7672
textWidth(int fid,const QString & text)7673 qreal QDocumentPrivate::textWidth(int fid, const QString& text){
7674 if ( fid < 0 || fid >= m_fonts.size() || text.isEmpty()) return 0;
7675
7676 if (m_workArounds & QDocument::ForceSingleCharacterDrawing )
7677 return textWidthSingleLetterFallback(fid, text);
7678
7679 /*
7680 There are three different ways to calculate the width:
7681 1. String length * Character Width if fixedPitch && no surrogates && no asian characters
7682 2. Sum of all character widths if !DisableWidthCache && no surrogates
7683 3. QFontMetric::width else
7684 */
7685 bool containsSurrogates = false;
7686 if ( QDocumentPrivate::m_fixedPitch ) {
7687 bool containsAsianChars = false;
7688 foreach (const QChar& c, text){
7689 const QChar::Category cat = c.category();
7690 if (cat == QChar::Letter_Other || cat == QChar::Punctuation_Other)
7691 containsAsianChars = true; //character which can have a different width even in fixed pitch fonts
7692 else if (cat == QChar::Other_Surrogate || cat == QChar::Mark_Enclosing || cat == QChar::Mark_NonSpacing || cat == QChar::Mark_SpacingCombining)
7693 containsSurrogates = true; //strange characters (e.g. 0xbcd, 0x1d164)
7694 else if (c < QChar(0x20))
7695 containsAsianChars = true;
7696 }
7697 if (!containsAsianChars && !containsSurrogates)
7698 // TODO: we've blacklisted certain characters from which we know they may have non-standard text width
7699 // so they are not treated here. However it would be more safe to whilelist standard characters
7700 return text.length() * QDocumentPrivate::m_spaceWidth;
7701 } else {
7702 //only check for the strange characters
7703 foreach (const QChar& c, text){
7704 const QChar::Category cat = c.category();
7705 if (cat == QChar::Other_Surrogate || cat == QChar::Mark_Enclosing || cat == QChar::Mark_NonSpacing || cat == QChar::Mark_SpacingCombining)
7706 containsSurrogates = true;
7707 }
7708 }
7709
7710 if ( containsSurrogates || (m_workArounds & QDocument::DisableWidthCache) )
7711 return UtilsUi::getFmWidth(m_fontMetrics[fid], text);
7712
7713 qreal rwidth=0;
7714
7715 FastCache<qreal> *cache = m_fmtWidthCache.getCache(fid);
7716 foreach(const QChar& c, text){
7717 const qreal *cwidth;
7718 if (!cache->valueIfThere(c, cwidth))
7719 cwidth = cache->insert(c,UtilsUi::getFmWidth(m_fontMetrics[fid], c));
7720 rwidth+=*cwidth;
7721 }
7722 return rwidth;
7723 }
7724
getRenderRangeWidth(int & columnDelta,int curColumn,const RenderRange & r,const int newFont,const QString & text)7725 qreal QDocumentPrivate::getRenderRangeWidth(int &columnDelta, int curColumn, const RenderRange& r, const int newFont, const QString& text){
7726 const QString& subText = text.mid(r.position, r.length);
7727 if (r.format & FORMAT_SPACE) {
7728 int realLength = QDocument::screenColumn(subText.constData(), subText.length(), m_tabStop, curColumn) - curColumn;
7729 columnDelta = realLength;
7730 return textWidth(newFont, " ") * realLength;
7731 } else {
7732 columnDelta = r.length;
7733 return textWidth(newFont, subText);
7734 }
7735 }
7736
textWidthSingleLetterFallback(int fid,const QString & text)7737 qreal QDocumentPrivate::textWidthSingleLetterFallback(int fid, const QString& text){
7738 FastCache<qreal> *cache = m_fmtWidthCache.getCache(fid);
7739 QChar lastSurrogate;
7740 int rwidth = 0;
7741 foreach (const QChar& c, text){
7742 const QChar::Category cat = c.category();
7743 int char_id;
7744 if (cat == QChar::Other_Surrogate) {
7745 if (c.isHighSurrogate()) {
7746 lastSurrogate = c;
7747 continue;
7748 } else
7749 char_id = QChar::surrogateToUcs4(lastSurrogate, c);
7750 } else char_id = c.unicode();
7751
7752 const qreal *cwidth;
7753 if (!cache->valueIfThere(char_id, cwidth)) {
7754 qreal nwidth;
7755 if (cat == QChar::Other_Surrogate) nwidth = UtilsUi::getFmWidth(m_fontMetrics[fid], QString(lastSurrogate)+c);
7756 else nwidth = UtilsUi::getFmWidth(m_fontMetrics[fid], c);
7757 cwidth = cache->insert(char_id, nwidth);
7758 }
7759 rwidth+=*cwidth;
7760 }
7761 return rwidth;
7762 }
7763
7764
drawText(QPainter & p,int fid,const QColor & baseColor,bool selected,qreal & xpos,qreal ypos,const QString & text)7765 void QDocumentPrivate::drawText(QPainter& p, int fid, const QColor& baseColor, bool selected, qreal& xpos, qreal ypos, const QString& text){
7766 FastCache<QPixmap> *cache = m_fmtCharacterCache[selected?1:0].getCache(fid);
7767 p.setBackgroundMode(Qt::OpaqueMode);
7768
7769 QChar lastSurrogate;
7770 foreach (const QChar& c, text){
7771 const QChar::Category cat = c.category();
7772 int char_id;
7773 if (cat == QChar::Other_Surrogate) {
7774 if (c.isHighSurrogate()) {
7775 lastSurrogate = c;
7776 continue;
7777 } else
7778 char_id = QChar::surrogateToUcs4(lastSurrogate, c);
7779 } else char_id = c.unicode();
7780 const QPixmap* px;
7781 if (!cache->valueIfThere(char_id, px)){
7782 qreal cw;
7783 if (cat == QChar::Other_Surrogate) cw = UtilsUi::getFmWidth(m_fontMetrics[fid], QString(lastSurrogate)+c);
7784 else cw = UtilsUi::getFmWidth(m_fontMetrics[fid], c);
7785 QPixmap pm(cw,m_lineSpacing);
7786 pm.fill(QColor::fromRgb(255,255,255,0)); //transparent background (opaque background would be twice as fast, but then we need much more pixmaps)
7787 QPainter pmp(&pm);
7788 pmp.setPen(baseColor);
7789 tunePainter(&pmp, fid);
7790 pmp.drawText(QPointF(0, m_ascent), cat == QChar::Other_Surrogate?(QString(lastSurrogate)+c):c);
7791 px = cache->insert(char_id, pm);
7792 }
7793 p.drawPixmap(xpos, ypos, *px);
7794 xpos += px->width();
7795 }
7796 }
7797
updateFormatCache(const QPaintDevice * pd)7798 void QDocumentPrivate::updateFormatCache(const QPaintDevice *pd)
7799 {
7800 if ( !m_font )
7801 return;
7802
7803 m_fixedPitch = !hasWorkAround(QDocument::DisableFixedPitchMode) && QFontInfo(*m_font).fixedPitch();
7804
7805 m_fonts.clear();
7806 m_fontMetrics.clear();
7807
7808 if ( !m_formatScheme )
7809 {
7810 m_fonts << *m_font;
7811 m_fontMetrics << QFontMetricsF(*m_font, const_cast<QPaintDevice *>(pd)); // const_cast: workaround because QFontMetrics() is missing the const qualifier
7812 return;
7813 }
7814
7815 const int end = m_formatScheme->formatCount();
7816
7817 m_fonts.reserve(end);
7818
7819 for ( int i = 0; i < end; i++ )
7820 {
7821 QFormat fmt = m_formatScheme->format(i);
7822
7823 QFont f(*m_font);
7824 Q_ASSERT(!f.kerning());
7825 f.setWeight(fmt.weight);
7826 f.setItalic(fmt.italic);
7827 if ( !fmt.fontFamily.isEmpty() && !(fmt.fontFamily=="<default>")){
7828 f.setFamily(fmt.fontFamily);
7829 m_fixedPitch = false;
7830 }
7831 if ( fmt.pointSize ){
7832 f.setPointSize(fmt.pointSize);
7833 m_fixedPitch = false;
7834 }
7835
7836 m_fonts << f;
7837 m_fontMetrics << QFontMetricsF(f, const_cast<QPaintDevice *>(pd)); // const_cast: workaround because QFontMetrics() is missing the const qualifier
7838 }
7839
7840 foreach ( QDocumentPrivate *d, m_documents )
7841 d->emitFormatsChanged();
7842
7843 //emitFormatsChanged();
7844 }
7845
emitWidthChanged()7846 void QDocumentPrivate::emitWidthChanged()
7847 {
7848 if ( !m_doc )
7849 return;
7850
7851 emit m_doc->widthChanged(m_width);
7852
7853 emit m_doc->sizeChanged(QSize(m_width, m_height));
7854 }
7855
emitHeightChanged()7856 void QDocumentPrivate::emitHeightChanged()
7857 {
7858 if ( !m_doc )
7859 return;
7860
7861 emit m_doc->heightChanged(m_height);
7862
7863 emit m_doc->sizeChanged(QSize(m_width, m_height));
7864 }
7865
emitFontChanged()7866 void QDocumentPrivate::emitFontChanged()
7867 {
7868 if ( !m_doc )
7869 return;
7870
7871 emit m_doc->fontChanged(*m_font);
7872 }
7873
insertLines(int after,const QList<QDocumentLineHandle * > & l)7874 void QDocumentPrivate::insertLines(int after, const QList<QDocumentLineHandle*>& l)
7875 {
7876 //qDebug("inserting : %i, %i", after, l.count());
7877
7878 int i = 0;
7879
7880 foreach ( QDocumentLineHandle *h, l )
7881 {
7882 h->setFlag(QDocumentLine::Hidden, false);
7883 h->setFlag(QDocumentLine::CollapsedBlockStart, false);
7884 h->setFlag(QDocumentLine::CollapsedBlockEnd, false);
7885 h->m_frontiers.clear();
7886 }
7887
7888 QMap<int, int>::iterator it = m_hidden.begin();
7889
7890 while ( it != m_hidden.end() )
7891 {
7892 if ( (it.key() <= after) && ((it.key() + *it) > after) )
7893 {
7894 *it += l.count();
7895
7896
7897 foreach ( QDocumentLineHandle *h, l )
7898 h->setFlag(QDocumentLine::Hidden, true);
7899 }
7900
7901 ++it;
7902 }
7903
7904 ++after;
7905 updateHidden(after, l.count());
7906 updateWrapped(after, l.count());
7907
7908 while ( i < l.count() )
7909 {
7910 // TODO : move (and abstract somehow) inside the line (handle?)
7911 l.at(i)->m_context.reset();
7912
7913 m_lines.insert(after + i, l.at(i));
7914
7915 ++i;
7916 }
7917
7918 emit m_doc->lineCountChanged(m_lines.count());
7919 }
7920
removeLines(int after,int n)7921 void QDocumentPrivate::removeLines(int after, int n)
7922 {
7923 if ( (after >= 0) && (after < m_lines.count()) )
7924 m_lines.at(after)->setFlag(QDocumentLine::CollapsedBlockStart, false);
7925
7926
7927 //qDebug("translating %i", visualLine);
7928 //remove/resize the m_hidden cache if the removed lines are within a hidden block
7929 //buggy (test {\n}\ndeleted\n{\n}) and not needed if the qdocumentcommand corrects
7930 //the folding of modified lines
7931
7932
7933 //Turned out: it is important, if a whole folded block is deleted. TODO: Try to fix it???
7934
7935 /*{ QMap<int, int>::iterator it = m_hidden.begin();
7936 while ( it != m_hidden.end() )
7937 {
7938 if ( (it.key() > after) && (it.key() <= (after + n)) )
7939 {
7940 //folded block starting line within the removed lines
7941 int i = it.key(), end = i + *it, depth = 0;
7942
7943 while ( i <= end )
7944 {
7945 if ( !depth )
7946 m_lines.at(i)->setFlag(QDocumentLine::Hidden, false);
7947
7948 if ( m_lines.at(i)->hasFlag(QDocumentLine::CollapsedBlockStart) )
7949 ++depth;
7950 else if ( m_lines.at(i)->hasFlag(QDocumentLine::CollapsedBlockEnd) )
7951 --depth;
7952
7953 ++i;
7954 }
7955
7956 it = m_hidden.erase(it);
7957
7958 } else if ( (it.key() < after) && (it.key() + *it) > after ) {
7959 //folded starting before the removed lines and containing them
7960 if ( (it.key() + *it) > (after + n) )
7961 {
7962 // fully inside
7963 *it -= n;
7964 ++it;
7965 } else {
7966 // goes beyond...
7967 int i = it.key(), end = i + *it, depth = 0;
7968
7969 while ( i <= end )
7970 {
7971 if ( !depth )
7972 m_lines.at(i)->setFlag(QDocumentLine::Hidden, false);
7973
7974 if ( m_lines.at(i)->hasFlag(QDocumentLine::CollapsedBlockStart) )
7975 ++depth;
7976 else if ( m_lines.at(i)->hasFlag(QDocumentLine::CollapsedBlockEnd) )
7977 --depth;
7978
7979 ++i;
7980 }
7981
7982 it = m_hidden.erase(it);
7983 }
7984 } else {
7985 ++it;
7986 }
7987 }
7988 }*/
7989
7990 QMap<int, int>::iterator it = m_wrapped.begin();
7991
7992 while ( it != m_wrapped.end() )
7993 {
7994 if ( (it.key() > after) && (it.key() <= (after + n)) )
7995 {
7996 //qDebug("eraser %i", it.key());
7997 it = m_wrapped.erase(it);
7998 } else {
7999 ++it;
8000 }
8001 }
8002
8003
8004 ++after;
8005 updateHidden(after, -n);
8006 updateWrapped(after, -n);
8007 for(int i=after;i<after+n;i++){
8008 emit m_doc->lineRemoved(m_lines[i]);
8009 }
8010 m_lines.remove(after, n);
8011
8012 emit m_doc->lineCountChanged(m_lines.count());
8013 setHeight();
8014 }
8015
at(int line) const8016 QDocumentLineHandle* QDocumentPrivate::at(int line) const
8017 {
8018 return ((line >= 0) && (line < m_lines.count())) ? m_lines.at(line) : nullptr;
8019 }
hintedIndexOf(const QVector<T> & list,const T & elem,int hint)8020 template <typename T> inline int hintedIndexOf (const QVector<T>& list, const T& elem, int hint) {
8021 if (hint < 2) return list.indexOf(elem);
8022 int backward = hint, forward = hint + 1;
8023 for (;backward >= 0 && forward < list.size(); backward--, forward++) {
8024 if (list[backward] == elem) return backward;
8025 if (list[forward] == elem) return forward;
8026 }
8027 if (backward >= list.size()) backward = list.size() - 1;
8028 for (;backward >= 0; backward--)
8029 if (list[backward] == elem) return backward;
8030 if (forward < 0) forward = 0;
8031 for (;forward < list.size(); forward++)
8032 if (list[forward] == elem) return forward;
8033 return -1;
8034 }
8035
indexOf(const QDocumentLineHandle * l,int hint) const8036 int QDocumentPrivate::indexOf(const QDocumentLineHandle *l, int hint) const
8037 {
8038 return hintedIndexOf<QDocumentLineHandle*>(m_lines, const_cast<QDocumentLineHandle *>(l), hint);
8039 }
8040
index(const QDocumentLineHandle * l)8041 QDocumentIterator QDocumentPrivate::index(const QDocumentLineHandle *l)
8042 {
8043 QDocumentIterator i = m_lines.begin();
8044
8045 int idx = indexOf(l);
8046
8047 return (idx != -1) ? i + idx : m_lines.end();
8048 }
8049
index(const QDocumentLineHandle * l) const8050 QDocumentConstIterator QDocumentPrivate::index(const QDocumentLineHandle *l) const
8051 {
8052 QDocumentConstIterator i = m_lines.constBegin();
8053
8054 int idx = indexOf(l);
8055
8056 return (idx != -1) ? i + idx : m_lines.end();
8057 }
8058
next(const QDocumentLineHandle * l) const8059 QDocumentLineHandle* QDocumentPrivate::next(const QDocumentLineHandle *l) const
8060 {
8061 if ( !l ) {
8062 return m_lines.count() ? m_lines.first() : nullptr;
8063 }
8064
8065 int idx = m_lines.indexOf(const_cast<QDocumentLineHandle*>(l));
8066
8067 return ((idx != -1) && ((idx + 1) < m_lines.count())) ? m_lines.at(idx + 1) : nullptr;
8068 }
8069
previous(const QDocumentLineHandle * l) const8070 QDocumentLineHandle* QDocumentPrivate::previous(const QDocumentLineHandle *l) const
8071 {
8072 if ( !l ) {
8073 return m_lines.count() ? m_lines.last() : nullptr;
8074 }
8075
8076 int idx = m_lines.indexOf(const_cast<QDocumentLineHandle*>(l));
8077
8078 return (idx > 0) ? m_lines.at(idx - 1) : nullptr;
8079 }
8080
beginChangeBlock()8081 void QDocumentPrivate::beginChangeBlock()
8082 {
8083 QDocumentCommandBlock *b = new QDocumentCommandBlock(m_doc);
8084
8085 m_macros.push(b);
8086 }
8087
endChangeBlock()8088 void QDocumentPrivate::endChangeBlock()
8089 {
8090 if ( !m_macros.count() )
8091 return;
8092
8093 QDocumentCommandBlock *b = m_macros.pop();
8094 b->setWeakLock(true);
8095
8096 execute(b);
8097 }
8098
hasChangeBlocks()8099 bool QDocumentPrivate::hasChangeBlocks(){
8100 return m_macros.count()!=0;
8101 }
8102
beginDelayedUpdateBlock()8103 void QDocumentPrivate::beginDelayedUpdateBlock(){
8104 m_delayedUpdateBlocks++;
8105 }
8106
endDelayedUpdateBlock()8107 void QDocumentPrivate::endDelayedUpdateBlock(){
8108 m_delayedUpdateBlocks--;
8109 if (m_delayedUpdateBlocks <= 0){
8110 QList<QPair<int,int> > c = m_delayedUpdates; //make a copy, emitContentsChange can call everything
8111 m_delayedUpdates.clear();
8112 for (int i=0;i<c.size();i++)
8113 emitContentsChange(c[i].first,c[i].second);
8114 }
8115 }
8116
8117
8118 /*!
8119 \brief Acquire group id
8120 */
getNextGroupId()8121 int QDocumentPrivate::getNextGroupId()
8122 {
8123 if ( m_freeGroupIds.count() )
8124 return m_freeGroupIds.takeFirst();
8125
8126 return ++m_lastGroupId;
8127 }
8128
8129 /*!
8130 \brief Relase group id
8131 */
releaseGroupId(int groupId)8132 void QDocumentPrivate::releaseGroupId(int groupId)
8133 {
8134 if ( groupId == m_lastGroupId )
8135 {
8136 --m_lastGroupId;
8137 while ( m_freeGroupIds.removeAll(m_lastGroupId) )
8138 {
8139 --m_lastGroupId;
8140 }
8141 } else {
8142 m_freeGroupIds << groupId;
8143 }
8144 }
8145
8146 /*!
8147 \brief Clear matches
8148 */
clearMatches(int groupId)8149 void QDocumentPrivate::clearMatches(int groupId)
8150 {
8151 QHash<int, MatchList>::iterator mit = m_matches.find(groupId);
8152
8153 if ( mit == m_matches.end() )
8154 {
8155 return;
8156 }
8157
8158 MatchList& matches = *mit;
8159
8160 foreach ( const Match& m, matches )
8161 {
8162 m.h->removeOverlay(m.range);
8163 m_LineCache.remove(m.h);
8164 }
8165
8166 matches.removeStart = 0;
8167 matches.removeLength = matches.count();
8168 }
8169
8170 /*void QDocumentPrivate::clearMatchesFromToWhenFlushing(gid,firstMatch,lastMatch){
8171 QHash<int, MatchList>::iterator mit = m_matches.find(groupId);
8172
8173 if ( mit == m_matches.end() )
8174 {
8175 return;
8176 }
8177
8178 MatchList& matches = *mit;
8179 if (firstMatch>=matches.length()) firstMatch=matches.length()-1;
8180 if (lastMatch>=matches.length()) lastMatch=matches.length()-1;
8181 for (int i=firstMatch; i<=lastMatch; i++){
8182 matches[i].h->removeOverlay(matches[i].range);
8183 }
8184 matches.removeStart=firstMatch;
8185 matches.removeLength=lastMatch-firstMatch;
8186 }*/
8187 /*!
8188 \brief Highlight the matched sequences
8189
8190 \note Both position are BEFORE the matched characters (cursor position).
8191 */
addMatch(int groupId,int line,int pos,int len,int format)8192 void QDocumentPrivate::addMatch(int groupId, int line, int pos, int len, int format)
8193 {
8194 //qDebug("match (%i, %i, %i)", line, pos, len);
8195
8196 Match m;
8197 m.line = line;
8198 m.h = at(line);
8199 if (!m.h) return;
8200 m_LineCache.remove(m.h);
8201 m.range = QFormatRange(pos, len, format);
8202 m_matches[groupId] << m;
8203
8204 m.h->addOverlay(m.range);
8205 }
8206
flushMatches(int groupId)8207 void QDocumentPrivate::flushMatches(int groupId)
8208 {
8209 QHash<int, MatchList>::iterator mit = m_matches.find(groupId);
8210
8211 if ( mit == m_matches.end() )
8212 {
8213 return;
8214 }
8215
8216 MatchList& matches = *mit;
8217
8218 QMap<int, int> areas;
8219
8220 foreach ( const Match& m, matches )
8221 {
8222 int n = 1;
8223 int l = m.line;
8224
8225 //qDebug("simple:(%i, %i)", l, 1);
8226
8227 QMap<int, int>::iterator tmp,tmp2, it = areas.find(l);
8228
8229 if ( it != areas.end() )
8230 continue;
8231
8232 it = areas.insert(m.line, n);
8233 #if QT_VERSION<QT_VERSION_CHECK(5,15,0)
8234 if ( it != areas.end() && it != areas.begin() )
8235 {
8236 tmp = it - 1;
8237 int off = tmp.key() + *tmp - l;
8238
8239 if ( off >= 0 && (off < n) )
8240 {
8241 *tmp += n - off;
8242 it = areas.erase(it) - 1;
8243 }
8244 }
8245
8246 if ( it != areas.end() && (it + 1) != areas.end() )
8247
8248 {
8249 tmp = it + 1;
8250 int off = it.key() + *it - tmp.key();
8251
8252 if ( off >= 0 && (off < *tmp) )
8253 {
8254 *it += *tmp;
8255 areas.erase(tmp);
8256 }
8257 }
8258 //emitFormatsChange(m.line, 1);
8259 }
8260
8261 // remove old matches
8262 while ( matches.removeLength )
8263 {
8264 matches.removeAt(matches.removeStart);
8265 --matches.removeLength;
8266 }
8267
8268 // send update messages
8269 QMap<int, int>::const_iterator it = areas.constBegin();
8270
8271 while ( it != areas.constEnd() )
8272 {
8273 //qDebug("merged:(%i, %i)", it.key(), *it);
8274 emitFormatsChange(it.key(), *it);
8275
8276 ++it;
8277 }
8278 #else
8279 //TODO ?
8280 if ( it != areas.end() && it != areas.begin() )
8281 {
8282 tmp = it;
8283 --tmp;
8284 int off = tmp.key() + *tmp - l;
8285
8286 if ( off >= 0 && (off < n) )
8287 {
8288 *tmp += n - off;
8289 it = areas.erase(it);
8290 --it;
8291 }
8292 }
8293
8294 if ( it != areas.end() )
8295 {
8296 tmp2=tmp;
8297 tmp = it;
8298 ++tmp;
8299 if(tmp!= areas.end()){
8300 int off = it.key() + *it - tmp.key();
8301
8302 if ( off >= 0 && (off < *tmp) )
8303 {
8304 *it += *tmp;
8305 areas.erase(tmp);
8306 }
8307 }else{
8308 tmp=tmp2;
8309 }
8310 }
8311 //emitFormatsChange(m.line, 1);
8312 }
8313
8314 // remove old matches
8315 while ( matches.removeLength )
8316 {
8317 matches.removeAt(matches.removeStart);
8318 --matches.removeLength;
8319 }
8320
8321 // send update messages
8322 QMap<int, int>::const_iterator it = areas.constBegin();
8323
8324 while ( it != areas.constEnd() )
8325 {
8326 //qDebug("merged:(%i, %i)", it.key(), *it);
8327 emitFormatsChange(it.key(), *it);
8328
8329 ++it;
8330 }
8331 #endif
8332 // update storage "meta-data"
8333 if ( matches.isEmpty() )
8334 {
8335 m_matches.remove(groupId);
8336
8337 releaseGroupId(groupId);
8338 }
8339 //qDebug("done with matches");
8340 }
8341
marks(QDocumentLineHandle * h) const8342 QList<int> QDocumentPrivate::marks(QDocumentLineHandle *h) const
8343 {
8344 //return QList<int>() << 1; //testcase
8345
8346 return m_marks.contains(h) ? m_marks.value(h) : QList<int>();
8347 }
8348
addMark(QDocumentLineHandle * h,int mid)8349 void QDocumentPrivate::addMark(QDocumentLineHandle *h, int mid)
8350 {
8351 QList<int>& l = m_marks[h];
8352
8353 if (l.empty()) l << mid;
8354 else {
8355 int p=QLineMarksInfoCenter::instance()->priority(mid);
8356 int i;
8357 for (i=0;i<l.size();i++)
8358 if (QLineMarksInfoCenter::instance()->priority(l[i])>=p) {
8359 l.insert(i,mid);
8360 break;
8361 }
8362 if (i==l.size()) l << mid;
8363 }
8364
8365 m_maxMarksPerLine = qMax(l.count(), m_maxMarksPerLine);
8366
8367 emitMarkChanged(h, mid, true);
8368 }
8369
toggleMark(QDocumentLineHandle * h,int mid)8370 void QDocumentPrivate::toggleMark(QDocumentLineHandle *h, int mid)
8371 {
8372 if ( m_marks.value(h).contains(mid) )
8373 {
8374 removeMark(h, mid);
8375 } else {
8376 addMark(h, mid);
8377 }
8378 }
8379
removeMark(QDocumentLineHandle * h,int mid)8380 void QDocumentPrivate::removeMark(QDocumentLineHandle *h, int mid)
8381 {
8382 QHash<QDocumentLineHandle*, QList<int> >::iterator it = m_marks.find(h);
8383
8384 if ( it == m_marks.end() )
8385 return;
8386
8387 int count = it->count();
8388 int n = it->removeAll(mid);
8389
8390 if ( it->isEmpty() )
8391 m_marks.erase(it);
8392
8393 if ( n && (count == m_maxMarksPerLine) )
8394 {
8395 QHash<QDocumentLineHandle*, QList<int> >::const_iterator
8396 rit = m_marks.constBegin(),
8397 end = m_marks.constEnd();
8398
8399 m_maxMarksPerLine = 0;
8400
8401 while ( rit != end )
8402 {
8403 m_maxMarksPerLine = qMax(rit->count(), m_maxMarksPerLine);
8404 ++rit;
8405 }
8406 }
8407
8408 emitMarkChanged(h, mid, false);
8409 }
8410
visualLine(int textLine) const8411 int QDocumentPrivate::visualLine(int textLine) const
8412 {
8413 if ( textLine < 0 )
8414 return 0;
8415
8416 int hiddenLines = 0, wrappedLines = 0;
8417 QMap<int, int>::const_iterator hit, wit, he, we;
8418 hit = m_hidden.constBegin();
8419 wit = m_wrapped.constBegin();
8420 he = m_hidden.constEnd();
8421 we = m_wrapped.constEnd();
8422
8423 //qDebug("translating %i", visualLine);
8424
8425 while ( hit != he || wit != we )
8426 {
8427 if ( hit != he && (wit == we || hit.key() <= wit.key()) )
8428 {
8429 int hl = hit.key();
8430
8431 if ( hl >= textLine )
8432 break;
8433
8434 int max = 0;
8435
8436 do
8437 {
8438 max = qMax(max, hit.key() - hl + *hit);
8439 ++hit;
8440 } while ( (hit != he) && (hit.key() <= hl + max) );
8441
8442 hiddenLines += max;
8443
8444 if ( wit != we && wit.key() == hl )
8445 {
8446 wrappedLines += *wit;
8447 ++wit;
8448 }
8449
8450 while ( wit != we )
8451 {
8452 if ( wit.key() > hl + max )
8453 break;
8454
8455 ++wit;
8456 }
8457
8458 } else {
8459 if ( wit.key() >= textLine )
8460 break;
8461
8462 if ( m_lines.at(wit.key())->hasFlag(QDocumentLine::Hidden) )
8463 {
8464 ++wit;
8465 continue;
8466 }
8467
8468 wrappedLines += *wit;
8469 ++wit;
8470 }
8471 }
8472
8473 //qDebug("translating %i => %i", textLine, textLine - hiddenLines + wrappedLines);
8474
8475 return textLine - hiddenLines + wrappedLines;
8476 }
8477
textLine(int visualLine,int * wrap) const8478 int QDocumentPrivate::textLine(int visualLine, int *wrap) const
8479 {
8480 if ( visualLine < 0 )
8481 return 0;
8482
8483 int hiddenLines = 0, wrappedLines = 0, vis = 0, txt = 0, mess = 0;
8484 QMap<int, int>::const_iterator
8485 h = m_hidden.constBegin(),
8486 w = m_wrapped.constBegin(),
8487 he = m_hidden.constEnd(),
8488 we = m_wrapped.constEnd();
8489
8490 //qDebug("translating %i", visualLine);
8491
8492 while ( vis < visualLine )
8493 {
8494 if ( h != he )
8495 {
8496 int hl = h.key();
8497
8498 if ( w == we || hl <= w.key() )
8499 {
8500 if ( visualLine + mess <= hl )
8501 break;
8502
8503 if ( w != we && w.key() == hl )
8504 {
8505 //qDebug("trying to solve : h=(%i, %i), w=(%i, %i)", hl, *h, w.key(), *w);
8506 const int off = (visualLine + mess) - hl;
8507 if ( off <= *w )
8508 {
8509 //qDebug("%i -> %i + %i", visualLine, hl, off);
8510 if ( wrap )
8511 *wrap = off;
8512
8513 return hl;
8514 }
8515 }
8516
8517 int max = 0;
8518
8519 do
8520 {
8521 max = qMax(max, h.key() - hl + *h);
8522 ++h;
8523 } while ( (h != he) && (h.key() <= hl + max) );
8524
8525 // very important : do not forget possible wrapping on folded block start
8526 if ( w != we && w.key() == hl )
8527 {
8528 wrappedLines += *w;
8529 ++w;
8530 }
8531
8532 while ( w != we )
8533 {
8534 if ( w.key() > txt + max )
8535 break;
8536
8537 ++w;
8538 }
8539
8540 hiddenLines += max;
8541
8542 } else {
8543 txt = w.key();
8544
8545 if ( m_lines.at(txt)->hasFlag(QDocumentLine::Hidden) )
8546 {
8547 ++w;
8548 continue;
8549 }
8550
8551 if ( visualLine + mess < txt )
8552 break;
8553
8554 wrappedLines += *w;
8555 ++w;
8556 }
8557 } else if ( w != we ) {
8558 txt = w.key();
8559
8560 if ( m_lines.at(txt)->hasFlag(QDocumentLine::Hidden) )
8561 {
8562 ++w;
8563 continue;
8564 }
8565
8566 if ( visualLine + mess < txt )
8567 break;
8568
8569 wrappedLines += *w;
8570 ++w;
8571 } else {
8572 break;
8573 }
8574
8575 mess = hiddenLines - wrappedLines;
8576 vis = txt - mess;
8577 }
8578
8579 we = m_wrapped.constBegin();
8580
8581 if ( m_wrapped.count() && w != we )
8582 {
8583 --w;
8584
8585 int wl = w.key();
8586 bool hwrap = m_lines.at(wl)->hasFlag(QDocumentLine::Hidden);
8587
8588 if ( !hwrap )
8589 {
8590 int base = this->visualLine(wl);
8591
8592 if ( visualLine >= base && visualLine <= base + *w )
8593 {
8594 //qDebug("rebased : %i to %i", visualLine, base);
8595
8596 if ( wrap )
8597 *wrap = visualLine - base;
8598
8599 return wl;
8600 }
8601 }
8602 }
8603
8604 //qDebug("[visual:text] : %i : %i", visualLine, visualLine + mess);
8605 int off = visualLine + mess - (m_lines.count() - 1);
8606
8607 if ( off > 0 )
8608 {
8609 if ( wrap )
8610 *wrap = m_lines.last()->m_frontiers.count();
8611
8612 return m_lines.count();
8613 }
8614
8615 return visualLine + mess;
8616 }
8617
hideEvent(int line,int count)8618 void QDocumentPrivate::hideEvent(int line, int count)
8619 {
8620 m_hidden.insert(line, count);
8621
8622 setHeight();
8623 //emitFormatsChange(line, count);
8624 emitFormatsChanged();
8625 }
8626
showEvent(int line,int count)8627 void QDocumentPrivate::showEvent(int line, int count)
8628 {
8629 QMap<int, int>::iterator it = m_hidden.find(line);
8630
8631 while ( (it != m_hidden.end()) && (it.key() == line) )
8632 {
8633 if ( *it == count || count == -1)
8634 {
8635 // qDebug("showing %i lines from %i", count, line);
8636 it = m_hidden.erase(it);
8637 } else
8638 ++it;
8639 }
8640
8641 setHeight();
8642 //emitFormatsChange(line, count);
8643 emitFormatsChanged();
8644 }
8645
updateHidden(int line,int count)8646 void QDocumentPrivate::updateHidden(int line, int count)
8647 {
8648 if ( m_hidden.isEmpty() || (line > (--m_hidden.constEnd()).key() ) )
8649 return;
8650
8651 QMap<int, int> prev = m_hidden;
8652 m_hidden.clear();
8653
8654 QMap<int, int>::const_iterator it = prev.constBegin();
8655
8656 //qDebug("shifting by %i from %i", count, line);
8657
8658 while ( it != prev.constEnd() )
8659 {
8660 if ( it.key() < line )
8661 {
8662 m_hidden.insert(it.key(), *it);
8663 } else {
8664 m_hidden.insert(it.key() + count, *it);
8665 }
8666
8667 ++it;
8668 }
8669 }
8670
updateWrapped(int line,int count)8671 void QDocumentPrivate::updateWrapped(int line, int count)
8672 {
8673 if ( m_wrapped.isEmpty() || (line > (--m_wrapped.constEnd()).key() ) )
8674 return;
8675
8676 QMap<int, int> prev = m_wrapped;
8677 QMap<int, int>::iterator it = prev.begin();
8678
8679 m_wrapped.clear();
8680
8681 //qDebug("shifting by %i from %i", count, line);
8682
8683 while ( it != prev.end() )
8684 {
8685 if ( it.key() < line )
8686 {
8687 m_wrapped.insert(it.key(), *it);
8688 } else {
8689 //qDebug("moved wrap from line %i to line %i", it.key(), it.key() + count);
8690 m_wrapped.insert(it.key() + count, *it);
8691 }
8692
8693 ++it;
8694 }
8695 }
8696
emitFormatsChange(int line,int lines)8697 void QDocumentPrivate::emitFormatsChange(int line, int lines)
8698 {
8699 emit m_doc->formatsChange(line, lines);
8700 }
8701
emitContentsChange(int line,int lines)8702 void QDocumentPrivate::emitContentsChange(int line, int lines)
8703 {
8704 if (m_delayedUpdateBlocks > 0){
8705 if (
8706 m_delayedUpdates.isEmpty()
8707 || m_delayedUpdates.last().first + m_delayedUpdates.last().second < line
8708 )
8709 m_delayedUpdates << QPair<int,int>(line,lines); //after
8710 else if (m_delayedUpdates.last().first <= line) //intersect
8711 m_delayedUpdates.last().second = qMax(m_delayedUpdates.last().second, line + lines - m_delayedUpdates.last().first);
8712 else
8713 m_delayedUpdates << QPair<int,int>(line,lines); //shouldn't happen (but can easily happen if the api is misused)
8714 return;
8715 }
8716
8717 int n = lines;
8718
8719 if ( m_language )
8720 {
8721 n = m_language->tokenize(m_doc, line, lines);
8722 }
8723
8724 for (int i=line; i<line+lines; i++)
8725 adjustWidth(i);
8726 if (lines > 1) setHeight();
8727 //qDebug("%i, %i : %i", line, lines, n);
8728
8729 emit m_doc->contentsChange(line, lines);
8730 emit m_doc->contentsChanged();
8731
8732 if ( n > lines )
8733 emitFormatsChange(line + lines, n - lines);
8734 }
8735
markFormatCacheDirty()8736 void QDocumentPrivate::markFormatCacheDirty(){
8737 foreach(QDocumentLineHandle *dlh,m_lines){
8738 dlh->setFlag(QDocumentLine::FormatsApplied,false);
8739 }
8740 }
8741
8742 //adds an auto updated cursor
addAutoUpdatedCursor(QDocumentCursorHandle * c)8743 void QDocumentPrivate::addAutoUpdatedCursor(QDocumentCursorHandle* c){
8744 if ( c->hasFlag(QDocumentCursorHandle::AutoUpdated) ) return;
8745 m_autoUpdatedCursorIndex.insert(c, m_autoUpdatedCursorList.size());
8746 m_autoUpdatedCursorList << c;
8747 c->setFlag( QDocumentCursorHandle::AutoUpdated );
8748 }
8749
8750 //removes an auto updated cursor
removeAutoUpdatedCursor(QDocumentCursorHandle * c)8751 void QDocumentPrivate::removeAutoUpdatedCursor(QDocumentCursorHandle* c){
8752 if ( !c->hasFlag(QDocumentCursorHandle::AutoUpdated) ) return;
8753 int size = m_autoUpdatedCursorList.size();
8754 int pos = m_autoUpdatedCursorIndex.value(c, size);
8755 if ( pos>=size ) return; //not found
8756 if ( pos != size-1) {
8757 //fast delete, order doesn't matter
8758 m_autoUpdatedCursorList[pos] = m_autoUpdatedCursorList.last();
8759 m_autoUpdatedCursorIndex.insert(m_autoUpdatedCursorList[pos], pos);
8760 }
8761 m_autoUpdatedCursorList.removeLast();
8762 m_autoUpdatedCursorIndex.remove(c);
8763 c->clearFlag( QDocumentCursorHandle::AutoUpdated );
8764 }
8765
8766 //clears auto updated cursors
discardAutoUpdatedCursors(bool documentDeleted)8767 void QDocumentPrivate::discardAutoUpdatedCursors(bool documentDeleted){
8768 foreach (QDocumentCursorHandle* h, m_autoUpdatedCursorList){
8769 h->clearFlag(QDocumentCursorHandle::AutoUpdated);
8770 if (documentDeleted) h->m_doc = nullptr;
8771 }
8772 m_autoUpdatedCursorList.clear();
8773 }
8774
setWorkAround(QDocument::WorkAroundFlag workAround,bool newValue)8775 void QDocumentPrivate::setWorkAround(QDocument::WorkAroundFlag workAround, bool newValue){
8776 if (m_workArounds.testFlag(workAround) == newValue) return;
8777 if (newValue) m_workArounds |= workAround;
8778 else m_workArounds &= ~workAround;
8779 if (workAround == QDocument::DisableFixedPitchMode)
8780 updateFormatCache(QApplication::activeWindow());
8781
8782 }
8783
hasWorkAround(QDocument::WorkAroundFlag workAround)8784 bool QDocumentPrivate::hasWorkAround(QDocument::WorkAroundFlag workAround){
8785 return (m_workArounds & workAround);
8786 }
8787
getFixedPitch()8788 bool QDocumentPrivate::getFixedPitch(){
8789 return m_fixedPitch;
8790 }
8791
setForceLineWrapCalculation(bool v)8792 void QDocumentPrivate::setForceLineWrapCalculation(bool v){
8793 if (m_forceLineWrapCalculation == v) return;
8794 m_forceLineWrapCalculation = v;
8795 if (v && !m_constrained)
8796 setWidth(0);
8797 }
8798
emitFormatsChanged()8799 void QDocumentPrivate::emitFormatsChanged()
8800 {
8801 emit m_doc->formatsChanged();
8802 }
8803
emitContentsChanged()8804 void QDocumentPrivate::emitContentsChanged()
8805 {
8806 //emit m_doc->contentsChanged();
8807 }
8808
emitLineDeleted(QDocumentLineHandle * h)8809 void QDocumentPrivate::emitLineDeleted(QDocumentLineHandle *h)
8810 {
8811 if ( !m_deleting )
8812 {
8813 m_marks.remove(h);
8814 m_status.remove(h);
8815
8816 int idx = m_lines.indexOf(h);
8817
8818 if ( idx != -1 )
8819 {
8820 //qDebug("removing line %i", idx);
8821
8822 m_lines.remove(idx);
8823
8824 if ( m_largest.count() && (m_largest.at(0).first == h) )
8825 {
8826 m_largest.remove(0);
8827 setWidth();
8828 }
8829
8830 m_hidden.remove(idx);
8831 m_wrapped.remove(idx);
8832
8833 setHeight();
8834 }
8835 }
8836
8837 emit m_doc->lineDeleted(h,-1);
8838 }
8839
emitMarkChanged(QDocumentLineHandle * l,int m,bool on)8840 void QDocumentPrivate::emitMarkChanged(QDocumentLineHandle *l, int m, bool on)
8841 {
8842 emitFormatsChanged();
8843 emit m_doc->markChanged(l, m, on);
8844 }
8845
8846 /*! @} */
8847
8848
8849