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("&","&amp;").replace("<","&lt;");
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