1 /****************************************************************************
2 **
3 ** Copyright (C) 2006-2009 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 "qeditor.h"
17 #include <QtMath>
18 
19 /*!
20 	\file qeditor.cpp
21 	\brief Implementation of the QEditor class
22 */
23 
24 #include "qeditorinputbindinginterface.h"
25 
26 #include "qdocument.h"
27 #include "qdocument_p.h"
28 #include "qdocumentline.h"
29 #include "qdocumentcursor.h"
30 #include "qformatscheme.h"
31 
32 #include "qlanguagedefinition.h"
33 #include "qcodecompletionengine.h"
34 
35 #include "qcodeedit.h"
36 #include "qpanellayout.h"
37 #include "qgotolinedialog.h"
38 #include "qlinemarksinfocenter.h"
39 
40 #include "qreliablefilewatch.h"
41 #include "smallUsefulFunctions.h"
42 #include "latexparser/latexparser.h"
43 #include <QDrag>
44 
45 #include "libqmarkedscrollbar/src/markedscrollbar.h"
46 
47 #include <QPropertyAnimation>
48 
49 #include <QSaveFile>
50 
51 #ifdef Q_OS_MAC
52 #include <QSysInfo>
53 #endif
54 
55 #include <QPrinter>
56 #include <QPrintDialog>
57 //#define Q_GL_EDITOR
58 
59 #ifdef _QMDI_
60 #include "qmdiserver.h"
61 #endif
62 
63 #ifdef _EDYUK_
64 #include "edyukapplication.h"
65 #include "qshortcutmanager.h"
66 
67 #define Q_SHORTCUT(a, s, c) EDYUK_SHORTCUT(a, tr(c), tr(s))
68 #else
69 #define Q_SHORTCUT(a, s, c) a->setShortcut( QKeySequence( tr(s, c) ) )
70 #endif
71 
72 #ifdef Q_GL_EDITOR
73 #include <QGLWidget>
74 #endif
75 
76 #define QCE_ACTION(name, action) { QAction *_a_ = m_actions.value(name); if ( _a_ ) _a_ action; }
77 #define QCE_TR_ACTION(name, label) { QAction *_a_ = m_actions.value(name); if ( _a_ ) _a_->setText(label); }
78 #define QCE_ENABLE_ACTION(name, yes) { QAction *_a_ = m_actions.value(name); if ( _a_ ) _a_->setEnabled(yes); }
79 
80 /*!
81 	\ingroup editor
82 	@{
83 
84 	\class QEditor
85 	\brief A text editing widget
86 
87 	QEditor is the central widget in QCE. It allows user to view and edit a
88 	document.
89 
90 	QEditor has an API similar to that of QTextEdit and it behaves in a very
91 	similar way.
92 
93 	Notable differences are :
94 	<ul>
95 	<li>QEditor can be given an InputBinding which can change the way it
96 	handle user inputs which enables such things as implementing emacs-like
97 	or Vi-like editing (almost) effortlessly.</li>
98 	<li>QEditor has actions instead of hard coded shortcuts and expose them
99 	so that, among other things, they can be easily added to menus/toolbars
100 	and their shortcuts can be changed</li>
101 	<li>QEditor brings the notion of cursor mirrors. Column selection and
102 	column editing are just special use case of cursor mirrors.</li>
103 	<li>QEditor brings the notion of placeholders, snippets-editing is just
104 	as special use case of placeholders.</li>
105 	<li>QEditor allows easy encodings management</li>
106 	</ul>
107 
108 	QEditor can gain features when it is managed by a QCodeEdit class which
109 	is responsible for panels management.
110 */
111 
112 
113 /*!
114 	\enum QEditor::CodecUpdatePolicy
115 	\brief Specify the actions to take when changing the default codec
116 
117 */
118 
119 
120 /*!
121 	\enum QEditor::EditFlag
122 	\brief Flag holding information about the state of an editor
123 
124 	Some of these are public and can be modified freely and some
125 	others are only meant to be used internally though they can
126 	still be read.
127 
128 */
129 
130 /*!
131 	\struct QEditor::PlaceHolder
132 	\brief A small structure holding placeholder data
133 
134 	Placeholders are basically lists of cursors. When several palceholders coexist, it is
135 	possible to navigate among them using the key assigned to that function by the current
136 	input binding (CTRL+arrows by default).
137 
138 	Each placeholder consist of a primary cursor and a list of mirrors (modeling the internals
139 	of QEditor and allowing extended snippet replacements easily).
140 
141 	Additionaly a placeholder can have an Affector which allows the text of the mirrors to be
142 	modified in any imaginable way
143 */
144 
145 /*!
146 	\class QEditor::PlaceHolder::Affector
147 	\brief A small class allowing "placeholder scripting"
148 
149 	The purpose of this class is to affect/process/reformat (whichever word you understand/like
150 	most) the content of placeholder mirrors.
151 
152 	The main placeholder instance (primary cursor) will always contain the text typed by the user,
153 	only mirrors can be affected.
154 
155 	To allow a large panel of processing to be done the affector is passed the following data :
156 	<ul>
157 		<li>A stringlist containing placeholder contents (primary cursor of all placeholders present in the editor)
158 		<li>The index of the current placeholder among these
159 		<li>The key event leading to a modification of the current placeholder
160 		<li>The index of the current mirror
161 		<li>A reference to the text of that placeholder mirror. This text has already been modified according
162 		to the key event. The original text can be retrieved from the first argument (stringlist). This text
163 		can be modified in any way you see fit or left as it is.
164 	</ul>
165 */
166 
167 ////////////////////////////////////////////////////////////////////////
168 //	Bindings handling
169 ////////////////////////////////////////////////////////////////////////
170 
171 QList<QEditor*> QEditor::m_editors;
172 QEditorInputBindingInterface* QEditor::m_defaultBinding = nullptr;
173 QHash<QString, QEditorInputBindingInterface*> QEditor::m_registeredBindings;
174 bool QEditor::m_defaultKeysSet = false;
175 QHash<QString, int> QEditor::m_registeredKeys;
176 QSet<int> QEditor::m_registeredOperations;
177 QHash<QString, int> QEditor::m_registeredDefaultKeys;
178 
179 
180 /*!
181 	\return A list of available input bindings
182 */
registeredInputBindingIds()183 QStringList QEditor::registeredInputBindingIds()
184 {
185 	return m_registeredBindings.keys();
186 }
187 
188 /*!
189 	\return the name of the default input binding
190 
191 	\note The "Default" name (or its translation, obtained via QEditor::tr())
192 	is used to indicate that no default input binding has been set.
193 */
defaultInputBindingId()194 QString QEditor::defaultInputBindingId()
195 {
196 	return m_defaultBinding ? m_defaultBinding->name() : tr("Default");
197 }
198 
199 /*!
200 	\brief Add an input binding to make it available for all editors
201 */
registerInputBinding(QEditorInputBindingInterface * b)202 void QEditor::registerInputBinding(QEditorInputBindingInterface *b)
203 {
204 	m_registeredBindings[b->id()] = b;
205 
206 	foreach ( QEditor *e, m_editors )
207 		e->updateBindingsMenu();
208 
209 }
210 
211 /*!
212 	\brief Remove an input binding from the pool of publicly available ones
213 */
unregisterInputBinding(QEditorInputBindingInterface * b)214 void QEditor::unregisterInputBinding(QEditorInputBindingInterface *b)
215 {
216 	m_registeredBindings.remove(b->id());
217 
218 	foreach ( QEditor *e, m_editors )
219 		e->updateBindingsMenu();
220 
221 }
222 
223 /*!
224 	\brief Set the default input binding
225 
226 	\note This does not change the current input binding of existing editors
227 */
setDefaultInputBinding(QEditorInputBindingInterface * b)228 void QEditor::setDefaultInputBinding(QEditorInputBindingInterface *b)
229 {
230 	m_defaultBinding = b;
231 }
232 
233 /*!
234 	\brief Set the default input binding
235 
236 	\note If no binding of the given name is available the default (null)
237 	binding will be set back as default binding.
238 
239 	\note This does not change the current input binding of existing editors
240 */
setDefaultInputBinding(const QString & b)241 void QEditor::setDefaultInputBinding(const QString& b)
242 {
243 	m_defaultBinding = m_registeredBindings.value(b);
244 }
245 ////////////////////////////////////////////////////////////////////////
246 
247 /*!
248 	\return A pointer to the global "reliable" file monitor used by QEditor to avoid file conflicts
249 
250 	The point of using a custom file watcher is to work around a bug (limitation) of QFileSystemWatcher
251 	which sometimes emit multiple signals for a single file save. It also enables to use a single
252 	object shared by all QEditor instances and reduce memory footprint.
253 */
watcher()254 QReliableFileWatch* QEditor::watcher()
255 {
256 	static QPointer<QReliableFileWatch> _qce_shared;
257 
258 	if ( !_qce_shared )
259 		_qce_shared = new QReliableFileWatch;
260 
261 	return _qce_shared;
262 }
263 
264 ////////////////////////////////////////////////////////////////////////
265 
266 int QEditor::m_defaultFlags = QEditor::MouseWheelZoom | QEditor::AutoIndent | QEditor::AdjustIndent | QEditor::AutoCloseChars | QEditor::ShowPlaceholders;
267 
268 /*!
269 	\return The default flags set to every QEditor upon construction
270 	\note the default flags are a configuration-oriented feature which only expose "user" flags
271 */
defaultFlags()272 int QEditor::defaultFlags()
273 {
274 	return m_defaultFlags;
275 }
276 
277 /*!
278 	\brief Set the default editor flags
279 
280 	Setting editor flags result in them being applied to ALL existing editors
281 	and editors to be created later on.
282 
283 	These can of course be modified on a per-editor basis later on.
284 */
setDefaultFlags(int flags)285 void QEditor::setDefaultFlags(int flags)
286 {
287 	m_defaultFlags = flags & Accessible;
288 
289 	foreach ( QEditor *e, m_editors )
290 	{
291 		bool ontoWrap = (m_defaultFlags & LineWrap) && !(e->m_state & LineWrap);
292 		bool outOfWrap = !(m_defaultFlags & LineWrap) && (e->m_state & LineWrap);
293 
294 		e->m_state &= Internal;
295 		e->m_state |= m_defaultFlags;
296 
297 		if ( ontoWrap )
298 		{
299 			e->document()->setWidthConstraint(e->wrapWidth());
300 		} else if ( outOfWrap ) {
301 			e->document()->clearWidthConstraint();
302 		}
303 
304 		QAction *a = e->m_actions.value("wrap");
305 
306 		if ( a && (a->isChecked() != (bool)(e->m_state & LineWrap)) )
307 			a->setChecked(e->m_state & LineWrap);
308 
309 	}
310 }
311 
312 /*!
313 	\brief ctor
314 
315 	\note Creates builtin menus/actions
316 */
QEditor(QWidget * p)317 QEditor::QEditor(QWidget *p)
318  : QAbstractScrollArea(p),
319     pMenu(nullptr), m_lineEndingsMenu(nullptr), m_lineEndingsActions(nullptr),
320     m_bindingsMenu(nullptr), aDefaultBinding(nullptr), m_bindingsActions(nullptr),
321     m_doc(nullptr), m_definition(nullptr),
322 	m_doubleClickSelectionType(QDocumentCursor::WordOrCommandUnderCursor), m_tripleClickSelectionType(QDocumentCursor::LineUnderCursor),
323 	m_curPlaceHolder(-1), m_placeHolderSynchronizing(false), m_state(defaultFlags()),
324     mDisplayModifyTime(true), m_blockKey(false), m_disableAccentHack(false), m_LineWidth(0), m_wrapAfterNumChars(0), m_scrollAnimation(nullptr)
325 {
326 	m_editors << this;
327 
328 	m_saveState = Undefined;
329 
330 	init();
331 }
332 
333 /*!
334 	\brief ctor
335 	\param actions Whether builtin actions and menus should be created
336 */
QEditor(bool actions,QWidget * p,QDocument * doc)337 QEditor::QEditor(bool actions, QWidget *p,QDocument *doc)
338  : QAbstractScrollArea(p),
339     pMenu(nullptr), m_lineEndingsMenu(nullptr), m_lineEndingsActions(nullptr),
340     m_bindingsMenu(nullptr), aDefaultBinding(nullptr), m_bindingsActions(nullptr),
341     m_doc(nullptr), m_definition(nullptr),
342 	m_doubleClickSelectionType(QDocumentCursor::WordOrCommandUnderCursor), m_tripleClickSelectionType(QDocumentCursor::ParenthesesOuter),
343 	m_curPlaceHolder(-1), m_placeHolderSynchronizing(false), m_state(defaultFlags()),
344     mDisplayModifyTime(true), m_blockKey(false), m_disableAccentHack(false), m_LineWidth(0), m_wrapAfterNumChars(0), m_scrollAnimation(nullptr)
345 {
346 	m_editors << this;
347 
348 	m_saveState = Undefined;
349 
350 	init(actions,doc);
351 }
352 
353 
354 /*!
355 	\brief ctor
356 	\param s file to load
357 
358 	\note Creates builtin menus/actions
359 */
QEditor(const QString & s,QWidget * p)360 QEditor::QEditor(const QString& s, QWidget *p)
361  : QAbstractScrollArea(p),
362     pMenu(nullptr), m_lineEndingsMenu(nullptr), m_lineEndingsActions(nullptr),
363     m_bindingsMenu(nullptr), aDefaultBinding(nullptr), m_bindingsActions(nullptr),
364     m_doc(nullptr), m_definition(nullptr), m_curPlaceHolder(-1), m_placeHolderSynchronizing(false), m_state(defaultFlags()),
365         mDisplayModifyTime(true),m_blockKey(false),m_disableAccentHack(false),m_LineWidth(0),m_scrollAnimation(nullptr)
366 {
367 	m_editors << this;
368 
369 	m_saveState = Undefined;
370 
371 	init();
372 
373 	setText(s, false);
374 }
375 
376 /*!
377 	\brief ctor
378 	\param s file to load
379 	\param actions Whether builtin actions and menus should be created
380 	\note Creates builtin menus/action
381 */
QEditor(const QString & s,bool actions,QWidget * p)382 QEditor::QEditor(const QString& s, bool actions, QWidget *p)
383  : QAbstractScrollArea(p),
384     pMenu(nullptr), m_lineEndingsMenu(nullptr), m_lineEndingsActions(nullptr),
385     m_bindingsMenu(nullptr), aDefaultBinding(nullptr), m_bindingsActions(nullptr),
386     m_doc(nullptr), m_definition(nullptr), m_curPlaceHolder(-1), m_placeHolderSynchronizing(false), m_state(defaultFlags()),
387     mDisplayModifyTime(true), m_useQSaveFile(true), m_blockKey(false), m_disableAccentHack(false), m_LineWidth(0), m_scrollAnimation(nullptr)
388 {
389 	m_editors << this;
390 
391 	m_saveState = Undefined;
392 
393 	init(actions);
394 
395 	setText(s, false);
396 }
397 
398 /*!
399 	\brief dtor
400 */
~QEditor()401 QEditor::~QEditor()
402 {
403 	m_editors.removeAll(this);
404 
405 	if ( m_completionEngine )
406 		delete m_completionEngine;
407 
408 	/* view class should not delete corresponding data
409 	if ( m_doc )
410 		delete m_doc;
411 	*/
412 
413 	if ( m_editors.isEmpty() )
414 	{
415 		delete watcher();
416 	} else {
417 		watcher()->removeWatch(this);
418 	}
419 }
420 
421 /*!
422 	\internal
423 */
init(bool actions,QDocument * doc)424 void QEditor::init(bool actions,QDocument *doc)
425 {
426 	#ifdef Q_GL_EDITOR
427 	setViewport(new QGLWidget);
428 	#endif
429 
430 	viewport()->setCursor(Qt::IBeamCursor);
431 	viewport()->setBackgroundRole(QPalette::Base);
432 	//viewport()->setAttribute(Qt::WA_OpaquePaintEvent, true);
433 	viewport()->setAttribute(Qt::WA_KeyCompression, true);
434 	viewport()->setAttribute(Qt::WA_InputMethodEnabled, true);
435 	viewport()->setAttribute(Qt::WA_AcceptTouchEvents, true);
436 
437 
438     MarkedScrollBar *scrlBar=new MarkedScrollBar();
439     scrlBar->enableClipping(false);
440     scrlBar->setDocument(doc);
441     setVerticalScrollBar(scrlBar);
442     //addMark(5,Qt::red);
443 
444 	verticalScrollBar()->setSingleStep(1);
445 	horizontalScrollBar()->setSingleStep(20);
446 
447 	setAcceptDrops(true);
448 	//setDragEnabled(true);
449 	setFrameStyle(QFrame::NoFrame);
450 	setFrameShadow(QFrame::Plain);
451 	setFocusPolicy(Qt::WheelFocus);
452 	setAttribute(Qt::WA_KeyCompression, true);
453 	setAttribute(Qt::WA_InputMethodEnabled, true);
454 
455 	connect(this							,
456             SIGNAL( markChanged(QString,QDocumentLineHandle*,int,bool) ),
457 			QLineMarksInfoCenter::instance(),
458             SLOT  ( markChanged(QString,QDocumentLineHandle*,int,bool) ) );
459 
460 	if(doc){
461 		// externally created document
462 		m_doc=doc;
463 	}else{
464 		m_doc = new QDocument(this);
465 	}
466 
467     connect(m_doc	, SIGNAL( formatsChange(int,int) ),
468             this	, SLOT  ( repaintContent(int,int) ) );
469 
470     connect(m_doc	, SIGNAL( contentsChange(int,int) ),
471             this	, SLOT  ( updateContent(int,int) ) );
472 
473 	connect(m_doc		, SIGNAL( formatsChanged() ),
474 			viewport()	, SLOT  ( update() ) );
475 
476 	connect(m_doc	, SIGNAL( widthChanged(int) ),
477 			this	, SLOT  ( documentWidthChanged(int) ) );
478 
479 	connect(m_doc	, SIGNAL( heightChanged(int) ),
480 			this	, SLOT  ( documentHeightChanged(int) ) );
481 
482 	connect(m_doc	, SIGNAL( cleanChanged(bool) ),
483 			this	, SLOT  ( setContentClean(bool) ) );
484 
485 	connect(m_doc	, SIGNAL( undoAvailable(bool) ),
486 			this	, SIGNAL( undoAvailable(bool) ) );
487 
488 	connect(m_doc	, SIGNAL( redoAvailable(bool) ),
489 			this	, SIGNAL( redoAvailable(bool) ) );
490 
491 	connect(m_doc	, SIGNAL( markChanged(QDocumentLineHandle*, int, bool) ),
492 			this	, SLOT  ( markChanged(QDocumentLineHandle*, int, bool) ) );
493 
494 	connect(m_doc	, SIGNAL( lineEndingChanged(int) ),
495 			this	, SLOT  ( lineEndingChanged(int) ) );
496 
497 	connect(m_doc, SIGNAL(slowOperationStarted()), SIGNAL(slowOperationStarted()));
498 	connect(m_doc, SIGNAL(slowOperationEnded()), SIGNAL(slowOperationEnded()));
499 
500 	m_cursorLinesFromViewTop=0;
501 	m_lastColumn=-2;
502 	m_lastLine=-2;
503 	m_hoverCount=-2;
504     preEditSet=false;
505     m_preEditFormat=0;
506 
507 	if ( m_defaultBinding )
508 	{
509 		m_bindings << m_defaultBinding;
510 	}
511 
512 	if ( actions )
513 	{
514 		pMenu = new QMenu;
515 
516 		QAction *a, *sep;
517 
518 		a = new QAction(QIcon(":/undo.png"), tr("&Undo"), this);
519 		a->setObjectName("undo");
520 		Q_SHORTCUT(a, "Ctrl+Z", "Edit");
521 		a->setEnabled(false);
522 		connect(this , SIGNAL( undoAvailable(bool) ),
523 				a	, SLOT  ( setEnabled(bool) ) );
524 		connect(a	, SIGNAL( triggered() ),
525 				this , SLOT  ( undo() ) );
526 
527 		addAction(a, "&Edit", "Edit");
528 
529 		a = new QAction(QIcon(":/redo.png"), tr("&Redo"), this);
530 		a->setObjectName("redo");
531 		Q_SHORTCUT(a, "Ctrl+Y", "Edit");
532 		a->setEnabled(false);
533 		connect(this , SIGNAL( redoAvailable(bool) ),
534 				a	, SLOT  ( setEnabled(bool) ) );
535 		connect(a	, SIGNAL( triggered() ),
536 				this , SLOT  ( redo() ) );
537 
538 		addAction(a, "&Edit", "Edit");
539 
540 		sep = new QAction(this);
541 		sep->setSeparator(true);
542 		addAction(sep, "&Edit", "Edit");
543 
544 		a = new QAction(QIcon(":/cut.png"), tr("Cu&t"), this);
545 		a->setObjectName("cut");
546 		Q_SHORTCUT(a, "Ctrl+X", "Edit");
547 		a->setEnabled(false);
548 		connect(this, SIGNAL( copyAvailable(bool) ),
549 				a	, SLOT  ( setEnabled(bool) ) );
550 		connect(a	, SIGNAL( triggered() ),
551 				this, SLOT  ( cut() ) );
552 
553 		addAction(a, "&Edit", "Edit");
554 
555 		a = new QAction(QIcon(":/copy.png"), tr("&Copy"), this);
556 		a->setObjectName("copy");
557 		Q_SHORTCUT(a, "Ctrl+C", "Edit");
558 		a->setEnabled(false);
559 		connect(this , SIGNAL( copyAvailable(bool) ),
560 				a	, SLOT  ( setEnabled(bool) ) );
561 		connect(a	, SIGNAL( triggered() ),
562 				this , SLOT  ( copy() ) );
563 
564 		addAction(a, "&Edit", "Edit");
565 
566 		a = new QAction(QIcon(":/paste.png"), tr("&Paste"), this);
567 		a->setObjectName("paste");
568 		//aPaste->setEnabled(QApplication::clipboard()->text().count());
569 		Q_SHORTCUT(a, "Ctrl+V", "Edit");
570 		connect(QApplication::clipboard()	, SIGNAL( dataChanged() ),
571 				this						, SLOT  ( checkClipboard() ) );
572 
573 		connect(a	, SIGNAL( triggered() ),
574 				this, SLOT  ( paste() ) );
575 
576 		addAction(a, "&Edit", "Edit");
577 
578 		sep = new QAction(this);
579 		sep->setSeparator(true);
580 		addAction(sep, "&Edit", "Edit");
581 
582 		a = new QAction(QIcon(":/indent.png"), tr("&Indent"), this);
583 		a->setObjectName("indent");
584 		Q_SHORTCUT(a, "Ctrl+I", "Edit");
585 		connect(a	, SIGNAL( triggered() ),
586 				this, SLOT  ( indentSelection() ) );
587 
588 		addAction(a, "&Edit", "Edit");
589 
590 		a = new QAction(QIcon(":/unindent.png"), tr("&Unindent"), this);
591 		a->setObjectName("unindent");
592 		Q_SHORTCUT(a, "Ctrl+Shift+I", "Edit");
593 		connect(a	, SIGNAL( triggered() ),
594 				this, SLOT  ( unindentSelection() ) );
595 
596 		addAction(a, "&Edit", "Edit");
597 
598 		sep = new QAction(this);
599 		sep->setSeparator(true);
600 		addAction(sep, "&Edit", "");
601 
602 		a = new QAction(tr("Toggle &Comment"), this);
603 		a->setObjectName("togglecomment");
604 		connect(a	, SIGNAL( triggered() ),
605 				this, SLOT  ( toggleCommentSelection() ) );
606 
607 		a = new QAction(QIcon(":/comment.png"), tr("Co&mment"), this);
608 		a->setObjectName("comment");
609 		Q_SHORTCUT(a, "Ctrl+D", "Edit");
610 		connect(a	, SIGNAL( triggered() ),
611 				this, SLOT  ( commentSelection() ) );
612 
613 		addAction(a, "&Edit", "Edit");
614 
615 		a = new QAction(QIcon(":/uncomment.png"), tr("Unc&omment"), this);
616 		a->setObjectName("uncomment");
617 		Q_SHORTCUT(a, "Ctrl+Shift+D", "Edit");
618 		connect(a	, SIGNAL( triggered() ),
619 				this, SLOT  ( uncommentSelection() ) );
620 
621 		addAction(a, "&Edit", "Edit");
622 
623 		sep = new QAction(this);
624 		sep->setSeparator(true);
625 		addAction(sep, "&Edit", "");
626 
627 		a = new QAction(tr("&Select all"), this);
628 		a->setObjectName("selectAll");
629 		Q_SHORTCUT(a, "Ctrl+A", "Edit");
630 		connect(a	, SIGNAL( triggered() ),
631 				this, SLOT  ( selectAll() ) );
632 
633 		addAction(a, "&Edit", "Edit");
634 
635 		sep = new QAction(this);
636 		sep->setSeparator(true);
637 		addAction(sep, QString());
638 
639 		a = new QAction(QIcon(":/find.png"), tr("&Find"), this);
640 		a->setObjectName("find");
641 		Q_SHORTCUT(a, "Ctrl+F", "Search");
642 		connect(a	, SIGNAL( triggered() ),
643 				this, SLOT  ( find() ) );
644 
645 		addAction(a, "&Search", "Search");
646 
647 		a = new QAction(QIcon(":/next.png"), tr("Fin&d next"), pMenu);
648 		a->setObjectName("findNext");
649 		Q_SHORTCUT(a, "F3", "Search");
650 		connect(a	, SIGNAL( triggered() ),
651 				this, SLOT  ( findNext() ) );
652 
653 		addAction(a, "&Search", "Search");
654 
655 		a = new QAction(QIcon(":/replace.png"), tr("&Replace"), this);
656 		a->setObjectName("replace");
657 		Q_SHORTCUT(a, "Ctrl+R", "Search");
658 		connect(a	, SIGNAL( triggered() ),
659 				this, SLOT  ( replacePanel() ));
660 
661 		addAction(a, "&Search", "Search");
662 
663 		sep = new QAction(this);
664 		sep->setSeparator(true);
665 		addAction(sep, "&Search", "Search");
666 
667 		a = new QAction(QIcon(":/goto.png"), tr("&Goto line..."), this);
668 		a->setObjectName("goto");
669 		Q_SHORTCUT(a, "Ctrl+G", "Search");
670 		connect(a	, SIGNAL( triggered() ),
671 				this, SLOT  ( gotoLine() ) );
672 
673 		addAction(a, "&Search", "Search");
674 
675 		sep = new QAction(this);
676 		sep->setSeparator(true);
677 		addAction(sep, "&Edit", "");
678 
679 		a = new QAction(tr("Dynamic line wrapping"), this);
680 		a->setObjectName("wrap");
681 		a->setCheckable(true);
682 		a->setChecked(flag(LineWrap));
683 
684 		addAction(a, "&Edit", "");
685 
686 		Q_SHORTCUT(a, "F10", "Edit");
687 		connect(a	, SIGNAL( toggled(bool) ),
688 				this, SLOT  ( setLineWrapping(bool) ) );
689 
690 
691 		m_bindingsMenu = new QMenu(tr("Input binding"), this);
692 		m_bindingsActions = new QActionGroup(m_bindingsMenu);
693 		//m_bindingsActions->setExclusive(true);
694 
695 		connect(m_bindingsActions	, SIGNAL( triggered(QAction*) ),
696 				this				, SLOT  ( bindingSelected(QAction*) ) );
697 
698 		aDefaultBinding = new QAction(tr("Default"), m_bindingsMenu);
699 		aDefaultBinding->setCheckable(true);
700 		aDefaultBinding->setData("default");
701 
702 		m_bindingsMenu->addAction(aDefaultBinding);
703 		m_bindingsMenu->addSeparator();
704 		m_bindingsActions->addAction(aDefaultBinding);
705         m_registeredBindings["default"] = nullptr;
706 
707 		updateBindingsMenu();
708 
709 		m_bindingsMenu->menuAction()->setObjectName("bindings");
710 		addAction(m_bindingsMenu->menuAction(), "&Edit", "");
711 
712 		sep = new QAction(this);
713 		sep->setSeparator(true);
714 		addAction(sep, QString());
715 
716 		m_lineEndingsMenu = new QMenu(tr("Line endings"), this);
717 		m_lineEndingsActions = new QActionGroup(m_lineEndingsMenu);
718 		m_lineEndingsActions->setExclusive(true);
719 
720 		connect(m_lineEndingsActions, SIGNAL( triggered(QAction*) ),
721 				this				, SLOT  ( lineEndingSelected(QAction*) ) );
722 
723 		m_lineEndingsActions->addAction(tr("Conservative"))->setData("conservative");
724 		m_lineEndingsActions->addAction(tr("Local"))->setData("local");
725 		m_lineEndingsActions->addAction(tr("Unix/Linux"))->setData("unix");
726 		m_lineEndingsActions->addAction(tr("Dos/Windows"))->setData("dos");
727 		m_lineEndingsActions->addAction(tr("Old Mac"))->setData("mac");
728 
729 		QList<QAction*> lle = m_lineEndingsActions->actions();
730 
731 		foreach ( QAction *a, lle )
732 		{
733 			a->setCheckable(true);
734 			m_lineEndingsMenu->addAction(a);
735 		}
736 
737 		lle.at(0)->setChecked(true);
738 
739 		m_lineEndingsMenu->menuAction()->setObjectName("lineEndings");
740 		addAction(m_lineEndingsMenu->menuAction(), "&Edit", "");
741 
742 		/*
743 		sep = new QAction(this);
744 		sep->setSeparator(true);
745 		addAction(sep, QString());
746 		*/
747 	}
748 
749 	if (!m_defaultKeysSet) getEditOperations();
750 
751 	setWindowTitle("[*]"); //remove warning of setWindowModified
752 
753 	setCursor(QDocumentCursor());
754 }
755 
756 /*!
757 	\return wether the flag \a f is set
758 */
flag(EditFlag f) const759 bool QEditor::flag(EditFlag f) const
760 {
761 	return m_state & f;
762 }
763 
764 /*!
765 	\brief Sets the flag \a f
766 */
setFlag(EditFlag f,bool b)767 void QEditor::setFlag(EditFlag f, bool b)
768 {
769 	bool changed = flag(f) != b;
770 	if ( b )
771 	{
772 		m_state |= f;
773 	} else {
774 		m_state &= ~f;
775 	}
776 
777     if ( f == LineWrap || f == HardLineWrap || f==LineWidthConstraint)
778 	{
779 		m_doc->impl()->setHardLineWrap(flag(HardLineWrap));
780 		m_doc->impl()->setLineWidthConstraint(flag(LineWidthConstraint));
781 
782 
783 		if ( isVisible() ) {
784 		    if ( flag(HardLineWrap) || flag(LineWidthConstraint) )
785 				m_doc->setWidthConstraint( m_LineWidth > 0 ? m_LineWidth : wrapWidth() );
786 			else if ( flag(LineWrap) )
787 				m_doc->setWidthConstraint( wrapWidth() );
788 			else
789 				m_doc->clearWidthConstraint();
790 		}
791 
792 		m_cursor.refreshColumnMemory();
793 
794 		QAction *a = m_actions.value("wrap");
795 
796 		if ( a && !a->isChecked() )
797 			a->setChecked(flag(LineWrap));
798 
799 		// TODO : only update cpos if cursor used to be visible?
800 		ensureCursorVisible();
801 	}
802 	if (changed && f == VerticalOverScroll)
803 		setVerticalScrollBarMaximum();
804 
805 }
806 
807 /*!
808 	\return whether it is possible to call undo()
809 */
canUndo() const810 bool QEditor::canUndo() const
811 {
812 	return m_doc ? m_doc->canUndo() : false;
813 }
814 
815 /*!
816 	\return whether it is possible to call redo()
817 */
canRedo() const818 bool QEditor::canRedo() const
819 {
820 	return m_doc ? m_doc->canRedo() : false;
821 }
822 
823 /*!
824 	\brief Set line wrapping
825 	\param on line wrap on/off
826 
827 	\note the function also enables "cursor movement within wrapped lines"
828 	which can be disabled manually using setFlag(QEditor::CursorJumpPastWrap, false);
829 */
setLineWrapping(bool on)830 void QEditor::setLineWrapping(bool on)
831 {
832 	setFlag(LineWrap, on);
833 	setFlag(CursorJumpPastWrap, on);
834 }
835 
setHardLineWrapping(bool on)836 void QEditor::setHardLineWrapping(bool on)
837 {
838 	setFlag(HardLineWrap, on);
839 }
setSoftLimitedLineWrapping(bool on)840 void QEditor::setSoftLimitedLineWrapping(bool on)
841 {
842 	setFlag(HardLineWrap, false);
843 	setFlag(LineWidthConstraint, on);
844 }
845 
setWrapLineWidth(qreal l)846 void QEditor::setWrapLineWidth(qreal l){
847     m_LineWidth=l;
848     if(flag(HardLineWrap)||flag(LineWidthConstraint))
849 	m_doc->setWidthConstraint(m_LineWidth);
850 }
851 
setWrapAfterNumChars(int numChars)852 void QEditor::setWrapAfterNumChars(int numChars){
853 	if (numChars <= 0) {
854 		m_wrapAfterNumChars = 0;
855 		setWrapLineWidth(0);
856 	}
857 	m_wrapAfterNumChars = qMax(numChars, 20);
858     qreal w=QFontMetricsF(QDocument::font()).averageCharWidth()*(m_wrapAfterNumChars+0.5) + 5; // +5 fixed width on left side, 0.5: 1/2 a char margin on right side
859 	setWrapLineWidth(w);
860 }
861 
862 /*!
863 	\return The whole text being edited
864 */
text() const865 QString QEditor::text() const
866 {
867 	return m_doc ? m_doc->text() : QString();
868 }
869 
870 /*!
871 	\return The text at a given line
872 	\param line text line to extract, using C++ array conventions (start at zero)
873 */
text(int line) const874 QString QEditor::text(int line) const
875 {
876 	return m_doc ? m_doc->line(line).text() : QString();
877 }
878 
879 /*!
880 	\brief Set the text of the underlying document and update display
881 */
setText(const QString & s,bool allowUndo)882 void QEditor::setText(const QString& s, bool allowUndo)
883 {
884 	clearPlaceHolders();
885 
886 	if ( m_doc )
887 		m_doc->setText(s, allowUndo);
888 
889 	if (!allowUndo || !m_cursor.isValid()) setCursor(QDocumentCursor(m_doc));
890 	else setCursor(m_cursor);
891 
892 	documentWidthChanged(m_doc->width());
893 	documentHeightChanged(m_doc->height());
894 	viewport()->update();
895 }
896 
897 /*!
898 	\brief Save the underlying document to a file
899 
900 	\see fileName()
901 */
save()902 void QEditor::save()
903 {
904 	if ( !m_doc )
905 		return;
906 
907 	if ( fileName().isEmpty() )
908 	{
909 		QString fn = QFileDialog::getSaveFileName();
910 
911 		if ( fn.isEmpty() )
912 			return;
913 
914 		setFileName(fn);
915 	} else if ( isInConflict() ) {
916 		QMessageBox msg (QMessageBox::Warning,
917 		             tr("Conflict!"),
918 		             tr(
919 		               "%1\nhas been modified by another application.\n"
920 		               "Press \"Save\" to overwrite the file on disk\n"
921 		               "Press \"Reset\" to reload the file from disk.\n"
922 		               "Press \"Diff\" to show differences in the editor.\n"
923 		               "Press \"Ignore\" to ignore this warning.\n"
924 		               ).arg(fileName()),
925 		             QMessageBox::NoButton,
926 		             this);
927 		msg.addButton(QMessageBox::Save);
928 		msg.addButton(QMessageBox::Reset);
929 		msg.addButton(QMessageBox::Ignore);
930 		QAbstractButton * diffBtn = msg.addButton(tr("Diff"), QMessageBox::ActionRole);
931 		msg.setDefaultButton(msg.addButton(QMessageBox::Cancel));
932 		if (msg.exec() == QMessageBox::NoButton) {
933 			if ( msg.clickedButton() == diffBtn ) {
934 				m_saveState = Undefined;
935 				emit fileInConflictShowDiff();
936 			}
937 			return;
938 		}
939 		QAbstractButton * button = msg.clickedButton();
940 		int ret = msg.standardButton(button);
941 
942 		if ( ret == QMessageBox::Save )
943 		{
944 			m_saveState = Undefined;
945 		} else if ( ret == QMessageBox::Reset ) {
946 			load(fileName(),document()->codec());
947 			m_saveState = Undefined;
948 			return;
949 		} else if ( ret == QMessageBox::Ignore ) {
950 			m_saveState = Undefined;
951 			return;
952 		} else if ( button == diffBtn ){
953 			m_saveState = Undefined;
954 			emit fileInConflictShowDiff(); //guess this branch is unused
955 			return;
956 		} else {
957 			return;
958 		}
959 	}
960 
961 	m_saveState = Saving;
962 
963 	//QTextStream s(&f);
964 	//s << text();
965 	// insert hard line breaks on modified lines (if desired)
966 
967 	//remove all watches (on old and new file name (setfilename above could have create one!) )
968 	Q_ASSERT(watcher());
969 	watcher()->removeWatch(QString(), this);
970 
971 	if (!saveCopy(fileName())) {
972 		m_saveState = Undefined;
973 		reconnectWatcher();
974 
975 		return;
976 	}
977 
978 	m_doc->setClean();
979 
980 	emit saved(this, fileName());
981 	m_saveState = Saved;
982 
983 	QTimer::singleShot(100, this, SLOT( reconnectWatcher() ));
984 
985 	update();
986 }
987 
988 
saveCopy(const QString & filename)989 bool QEditor::saveCopy(const QString& filename){
990 	Q_ASSERT(m_doc);
991 
992 	emit slowOperationStarted();
993 
994 	// insert hard line breaks on modified lines (if desired)
995 	if(flag(HardLineWrap)){
996 		QList<QDocumentLineHandle*> handles = m_doc->impl()->getStatus().keys();
997 		m_doc->applyHardLineWrap(handles);
998 	}
999 
1000 	QString txt = m_doc->text(flag(RemoveTrailing), flag(PreserveTrailingIndent));
1001 	QByteArray data =  m_doc->codec() ? m_doc->codec()->fromUnicode(txt) : txt.toLocal8Bit();
1002 
1003 	if (m_useQSaveFile) {
1004 		QSaveFile file(filename);
1005 		if (file.open(QIODevice::WriteOnly)) {
1006 			file.write(data);
1007 			bool success = file.commit();
1008 			if (!success) {
1009 				QMessageBox::warning(this, tr("Saving failed"),
1010 									 tr("%1\nCould not be written. Error (%2): %3.\n"
1011 										"If the file already existed on disk, it was not modified by this operation.")
1012 										.arg(QDir::toNativeSeparators(filename))
1013 										.arg(file.error())
1014 										.arg(file.errorString()),
1015 									 QMessageBox::Ok);
1016 			}
1017 			return success;
1018 		}
1019 		QMessageBox::warning(this, tr("Saving failed"), tr("Could not get write permissions on file\n%1.\n\nPerhaps it is read-only or opened in another program?").arg(QDir::toNativeSeparators(filename)), QMessageBox::Ok);
1020 		return false;
1021 	} else {
1022 		return writeToFile(filename, data);
1023 	}
1024 }
1025 
1026 /*!
1027  * Securely writes data to a file. If this is not successfull, the original file stays intact.
1028  * This procedure is only necessary for Qt < 5.1.0. More recent versions of Qt provide a standard
1029  * way for this using QSaveFile.
1030  *
1031  * This is our safe saving strategy:
1032  * 1. Prepare: If the file exists, create a copy backupFilename so that it's content is not lost in case of error.
1033  * 2. Save: Write the file.
1034  * 3. Cleanup: In case of error, rename the backupFilename back to the original filename.
1035  *
1036  * \return true if the data were written to the file successfully.
1037  */
writeToFile(const QString & filename,const QByteArray & data)1038 bool QEditor::writeToFile(const QString &filename, const QByteArray &data) {
1039 	bool sucessfullySaved = false;
1040 
1041 	// check available disk space
1042 	quint64 freeBytes;
1043 	while (true) {
1044 		if (!getDiskFreeSpace(QFileInfo(filename).canonicalPath(), freeBytes)) break;
1045 		if (static_cast<quint64>(data.size()) < freeBytes) break;
1046 
1047 		QMessageBox::StandardButton bt;
1048 		bt = QMessageBox::critical(this, tr("Saving failed"),
1049 				tr("There seems to be not enough space to save the file at\n%1\n\n"
1050 				   "File size: %2 kB\n"
1051 				   "Free space: %3 kB\n\n"
1052 				   "You should clean up some space and retry. Alternatively you can\n"
1053 				   "cancel the save operation and save to another location instead.\n"
1054 				   "When ignoring this warning TeXstudio will try save to the specified\n"
1055 				   "location. However if there is really not enough space, this will\n"
1056 				   "result in data loss.\n"
1057 				   ).arg(filename).arg(data.size()/1024).arg(freeBytes/1024L),QMessageBox::Retry|QMessageBox::Ignore|QMessageBox::Cancel, QMessageBox::Retry);
1058 		if (bt == QMessageBox::Cancel) { emit slowOperationEnded(); return false; }
1059 		else if (bt == QMessageBox::Ignore) break;
1060 	}
1061 
1062 	// 1. Prepare
1063 	QString backupFilename;
1064     if (QFileInfo::exists(filename)) {
1065 		int MAX_TRIES = 100;
1066 		for (int i=0; i<MAX_TRIES; i++) {
1067 			QString fn = filename + QString("~txs%1").arg(i);
1068 			if (QFile::copy(filename, fn)) {
1069 				backupFilename = fn;
1070 				break;
1071 			}
1072 		}
1073 		if (backupFilename.isNull()) {
1074 			QMessageBox::warning(this, tr("Warning"),
1075 								 tr("Creating a backup of the file failed. You can still continue saving. "
1076 								    "However, if the save action fails, you may loose the data in the original file. "
1077 								    "Do you wish to continue?"),
1078 								 QMessageBox::Yes|QMessageBox::No, QMessageBox::Yes);
1079 		}
1080 	}
1081 
1082 	// 2. Save
1083 	QFile f(filename);
1084 	if ( !f.open(QFile::WriteOnly) ) {
1085 		QMessageBox::warning(this, tr("Saving failed"), tr("Could not get write permissions on file\n%1.\n\nPerhaps it is read-only or opened in another program?").arg(QDir::toNativeSeparators(filename)), QMessageBox::Ok);
1086 
1087 		// 3. Cleanup
1088 		QFile::remove(backupFilename);  // original was not modified
1089 		sucessfullySaved = false;
1090 	} else {
1091 		int bytesWritten = f.write(data);
1092 		sucessfullySaved = (bytesWritten == data.size());
1093 
1094 		// 3. Cleanup
1095 		if (sucessfullySaved) {
1096 			QFile::remove(backupFilename);
1097 		} else {
1098 			QString message = tr("Writing the document to file\n%1\nfailed.").arg(filename);
1099 			if (!backupFilename.isNull()) {
1100 				QFile::remove(filename);
1101 				bool ok = QFile::rename(backupFilename, filename);  // revert
1102 				if (!ok) {
1103 					message += "\n" + tr("The original file on disk was destroyed during the save operation.\n"
1104 					                     "You'll find a copy at\n%1").arg(backupFilename);
1105 				}
1106 			}
1107 			QMessageBox::critical(this, tr("Saving failed"), message, QMessageBox::Ok);
1108 		}
1109 
1110 		f.close(); //explicite close for watcher (??? is this necessary anymore?)
1111 	}
1112 
1113 	emit slowOperationEnded();
1114 
1115 	return sucessfullySaved;
1116 }
1117 
1118 
1119 /*!
1120 	\brief Save the content of the editor to a file
1121 
1122 	\note This method renames the editor, stop monitoring the old
1123 	file and monitor the new one
1124 */
save(const QString & fn)1125 void QEditor::save(const QString& fn)
1126 {
1127 	if ( fileName().count() ) {
1128 		watcher()->removeWatch(fileName(), this);
1129 	}
1130 
1131 	if ( !saveCopy(fn) ) {
1132 		m_saveState = Undefined;
1133 		reconnectWatcher();
1134 
1135 		return;
1136 	}
1137 
1138 	m_doc->setClean();
1139 
1140 	setFileName(fn);
1141 	emit saved(this, fn);
1142 	m_saveState = Saved;
1143 
1144 	QTimer::singleShot(100, this, SLOT( reconnectWatcher() ));
1145 }
1146 
1147 
1148 /*!
1149   Saves the text to filename without error checking and unmodified (e.g. without performing hard line break)
1150   */
saveEmergencyBackup(const QString & filename)1151 void QEditor::saveEmergencyBackup(const QString& filename){
1152 	Q_ASSERT(m_doc);
1153 
1154 	emit slowOperationStarted();
1155 
1156 	bool sucessfullySaved = QFile::exists(filename);
1157 	do {
1158 		QString txt = m_doc->textLines().join(m_doc->lineEndingString());
1159 		QByteArray data = txt.toUtf8();
1160 
1161 		quint64 freeBytes;
1162 		if (getDiskFreeSpace(QFileInfo(filename).canonicalPath(), freeBytes) && (static_cast<quint64>(data.size()) < freeBytes))
1163 			break;
1164 
1165 		QFile f(filename);
1166 
1167 		if ( !f.open(QFile::WriteOnly) )
1168 			break;
1169 
1170 		sucessfullySaved = f.write(data) == data.size();
1171 		f.flush();
1172 	} while (false);
1173 
1174 	if (!sucessfullySaved)
1175 		QFile::remove(filename);
1176 
1177 	emit slowOperationEnded();
1178 }
1179 
1180 /*!
1181 	\internal
1182 */
checkClipboard()1183 void QEditor::checkClipboard()
1184 {
1185 	// LOOKS LIKE THIS FUNCTION NEVER GETS CALLED DESPITE THE CONNECTION...
1186 
1187 	//const QMimeData *d = QApplication::clipboard()->mimeData();
1188 
1189 	//qDebug("checking clipboard : %s", d);
1190 
1191 	//QCE_ENABLE_ACTION("paste", d && d->hasText())
1192 }
1193 
1194 /*!
1195 	\internal
1196 */
reconnectWatcher()1197 void QEditor::reconnectWatcher()
1198 {
1199 	watcher()->removeWatch(this);
1200 	watcher()->addWatch(fileName(), this);
1201 }
1202 
1203 /*!
1204 	\internal
1205 */
fileChanged(const QString & file)1206 void QEditor::fileChanged(const QString& file)
1207 {
1208 	if ( (file != fileName()) || (m_saveState == Saving) || mIgnoreExternalChanges )
1209 		return;
1210 
1211 	/*
1212 	if ( m_saveState == Saved )
1213 	{
1214 		qApp->processEvents();
1215 
1216 		m_saveState = Undefined;
1217 		return;
1218 	}
1219 	*/
1220 
1221     if ( !QFileInfo::exists(file))
1222 	{
1223 		watcher()->removeWatch(QString(), this); //no duplicated questions
1224 
1225 		if(isHidden() && mSilentReloadOnExternalChanges){ // if hidden, just close the editor
1226 			emit requestClose();
1227 			return;
1228 		}
1229 
1230 		int ret = QMessageBox::warning(this, tr("File deleted"), tr("The file %1 has been deleted on disk.\n"
1231 		                                                            "Should I save the document as it is to restore the file?\n").arg(fileName()), QMessageBox::Save | QMessageBox::Ignore);
1232 		if (ret == QMessageBox::Save) {
1233             if ( QFileInfo::exists(file) ) {
1234 				QMessageBox::warning(this, tr("File deleted"), tr("Well, this is strange: The file %1 is not deleted anymore.\n"
1235 				                                                  "Probably someone else restored it and therefore I'm not going to override the (possible modified) version on the disk.").arg(fileName()), QMessageBox::Ok);
1236 				m_saveState = Conflict;
1237 				reconnectWatcher();
1238 				return;
1239 			} else {
1240 				m_saveState = Undefined;
1241 				save(); //save will reconnect the watcher
1242 				return;
1243 			}
1244 		}
1245 
1246         if ( QFileInfo::exists(file) )
1247 			reconnectWatcher();
1248 
1249 	} else if ( !isContentModified() )
1250 	{
1251 		// silently reload file if the editor contains no modification?
1252 		// -> result in undo/redo history loss, still ask confirmation ?
1253 		bool autoReload = true;
1254 
1255 		if ( (canUndo() || canRedo()) && !mSilentReloadOnExternalChanges )
1256 		{
1257 			watcher()->removeWatch(QString(), this); //no duplicated questions
1258 
1259 			int ret = QMessageBox::warning(
1260 			              this, tr("File changed"),
1261 			              tr("%1\n"
1262 			                 "was changed outside of TeXstudio. Reload from disk?\n\n"
1263 			                 "Notes:\n"
1264 			                 "- Reloading overwrites the editor content with the file from disk. This cannot be undone.\n"
1265 			                 "- You can permanently enable silent reloading in the options."
1266 			              ).arg(fileName()),
1267 			                QMessageBox::Yes | QMessageBox::No
1268 			              );
1269 
1270 			if ( ret == QMessageBox::No )
1271 				autoReload = false;
1272 			reconnectWatcher();
1273 		}
1274 
1275 		if ( autoReload ){
1276 			reload();
1277 			return;
1278 		}
1279 	}
1280 
1281 	// TODO : check for actual modification (using a checksum?)
1282 	// TODO : conflict reversible (checksum again?)
1283 
1284 	//qDebug("conflict!");
1285 	m_saveState = Conflict;
1286 	emit fileInConflict();
1287 }
1288 
1289 /*!
1290 	\return Whether a file conflict has been detected
1291 
1292 	File conflicts happen when the loaded file is modified
1293 	on disk by another application if the text has been
1294 	modified in QCE
1295 */
isInConflict() const1296 bool QEditor::isInConflict() const
1297 {
1298 	return m_saveState == Conflict;
1299 }
1300 
emitNeedUpdatedCompleter()1301 void QEditor::emitNeedUpdatedCompleter(){
1302 	emit needUpdatedCompleter();
1303 }
1304 
getFileCodec() const1305 QTextCodec* QEditor::getFileCodec() const
1306 {
1307  //   if (m_codec==0) QMessageBox::information(0,"abc","def",0);
1308     return m_doc?m_doc->codec():QDocument::defaultCodec();
1309 }
1310 
setFileCodec(QTextCodec * codec)1311 void QEditor::setFileCodec(QTextCodec* codec){
1312     if (!m_doc || !codec || codec==m_doc->codec()) return;
1313     m_doc->setCodec(codec); //standard encoding in memory, file encoding set when saving
1314    // setContentModified(true);
1315 }
viewWithCodec(QTextCodec * codec)1316 void QEditor::viewWithCodec(QTextCodec* codec){
1317     if (!m_doc || !codec) return;
1318     QByteArray dat= m_doc->codec()->fromUnicode(document()->text());
1319     m_doc->setCodec(codec); //standard encoding in memory, file encoding set when saving
1320     document()->setText(m_doc->codec()->toUnicode(dat), false);
1321 }
1322 
1323 /*!
1324 	\brief Print the content of the editor
1325 */
print()1326 void QEditor::print()
1327 {
1328 	if ( !m_doc )
1329 		return;
1330 
1331 	QPrinter printer;
1332 
1333 	// TODO : create a custom print dialog, page range sucks, lines range would be better
1334 	QPrintDialog dialog(&printer, this);
1335 	dialog.setWindowTitle(tr("Print Source Code"));
1336 #if (QT_VERSION<QT_VERSION_CHECK(6,0,0))
1337     dialog.setEnabledOptions(QPrintDialog::PrintToFile | QPrintDialog::PrintPageRange); //TODO Qt6 ??
1338 #endif
1339 
1340 	if ( dialog.exec() == QDialog::Accepted )
1341 	{
1342 		m_doc->print(&printer);
1343 	}
1344 }
1345 
relayPanelCommand(const QString & panel,const QString & command,const QList<QVariant> & args)1346 void QEditor::relayPanelCommand(const QString& panel, const QString& command, const QList<QVariant>& args){
1347 	QCodeEdit *m = QCodeEdit::manager(this);
1348 	if (!m) {
1349 		qDebug("Unmanaged QEditor");
1350 		return;
1351 	}
1352 	if (panel == "Search") m->sendPanelCommand("Goto", "hide");
1353 	m->sendPanelCommand(panel, qPrintable(command), args);
1354 }
1355 
1356 
1357 /*!
1358 	\brief Show the search/replace panel, if any
1359 */
find()1360 void QEditor::find()
1361 {
1362 	relayPanelCommand("Search", "display", QList<QVariant>() << 1 << false);
1363 }
1364 
find(QString text,bool highlight,bool regex,bool word,bool caseSensitive)1365 void QEditor::find(QString text, bool highlight, bool regex, bool word, bool caseSensitive){
1366 	relayPanelCommand("Search", "find", QList<QVariant>() << text << false << highlight << regex << word << caseSensitive);
1367 }
1368 
find(QString text,bool highlight,bool regex,bool word,bool caseSensitive,bool fromCursor,bool selection)1369 void QEditor::find(QString text, bool highlight, bool regex, bool word, bool caseSensitive, bool fromCursor, bool selection){
1370 	relayPanelCommand("Search", "find", QList<QVariant>() << text << false << highlight << regex << word << caseSensitive << fromCursor << selection);
1371 }
1372 
findInSameDir()1373 void QEditor::findInSameDir()
1374 {
1375 	relayPanelCommand("Search", "findNext");
1376 }
findNext()1377 void QEditor::findNext()
1378 {
1379 	relayPanelCommand("Search", "findReplace", QList<QVariant>() << false);
1380 }
findPrev()1381 void QEditor::findPrev()
1382 {
1383 	relayPanelCommand("Search", "findReplace", QList<QVariant>() << true);
1384 }
findCount()1385 void QEditor::findCount()
1386 {
1387 	relayPanelCommand("Search", "findReplace", QList<QVariant>() << false << false << false << true);
1388 }
selectAllMatches()1389 void QEditor::selectAllMatches(){
1390 	relayPanelCommand("Search", "selectAllMatches");
1391 }
1392 
1393 /*!
1394 	\brief Show the search/replace panel, if any
1395 */
replacePanel()1396 void QEditor::replacePanel()
1397 {
1398 	relayPanelCommand("Search", "display", QList<QVariant>() << 1 << true);
1399 }
replaceNext()1400 void QEditor::replaceNext()
1401 {
1402 	relayPanelCommand("Search", "findReplace", QList<QVariant>() << false << true);
1403 }
replacePrev()1404 void QEditor::replacePrev()
1405 {
1406 	relayPanelCommand("Search", "findReplace", QList<QVariant>() << true << true);
1407 }
replaceAll()1408 void QEditor::replaceAll()
1409 {
1410 	relayPanelCommand("Search", "findReplace", QList<QVariant>() << false << true << true);
1411 }
1412 
1413 /*!
1414 	\brief Show a panel or dialog to go to a specific line
1415 */
gotoLine()1416 void QEditor::gotoLine()
1417 {
1418 	QCodeEdit *m = QCodeEdit::manager(this);
1419 
1420 	if ( m && m->hasPanel("Goto") )
1421 	{
1422 		// makes sense hiding this one if present...
1423 		m->sendPanelCommand("Search", "hide");
1424 
1425 		m->sendPanelCommand("Goto", "show");
1426 	} else {
1427 		QGotoLineDialog dlg(this);
1428 
1429 		dlg.exec(this);
1430 	}
1431 }
1432 
1433 /*!
1434 	\brief Run time translation entry point for compat with Edyuk
1435 */
retranslate()1436 void QEditor::retranslate()
1437 {
1438 	QCE_TR_ACTION("undo", tr("&Undo"))
1439 	QCE_TR_ACTION("redo", tr("&Redo"))
1440 
1441 	QCE_TR_ACTION("cut", tr("Cu&t"))
1442 	QCE_TR_ACTION("copy", tr("&Copy"))
1443 	QCE_TR_ACTION("paste", tr("&Paste"))
1444 
1445 	QCE_TR_ACTION("indent", tr("&Indent"))
1446 	QCE_TR_ACTION("unindent", tr("&Unindent"))
1447 	QCE_TR_ACTION("comment", tr("Co&mment"))
1448 	QCE_TR_ACTION("uncomment", tr("Unc&omment"))
1449 
1450 	QCE_TR_ACTION("selectAll", tr("&Select all"))
1451 
1452 	QCE_TR_ACTION("find", tr("&Find"))
1453 	QCE_TR_ACTION("findNext", tr("Fin&d next"))
1454 	QCE_TR_ACTION("replace", tr("&Replace"))
1455 
1456 	QCE_TR_ACTION("goto", tr("&Goto line..."))
1457 
1458 	if ( m_completionEngine )
1459 		m_completionEngine->retranslate();
1460 
1461 	if ( m_bindingsMenu )
1462 		m_bindingsMenu->setTitle(tr("Input binding"));
1463 
1464 	if ( aDefaultBinding )
1465 		aDefaultBinding->setText(tr("Default"));
1466 
1467 	#ifdef _QMDI_
1468 	menus.setTranslation("&Edit", tr("&Edit"));
1469 	menus.setTranslation("&Search", tr("&Search"));
1470 
1471 	toolbars.setTranslation("Edit", tr("Edit"));
1472 	toolbars.setTranslation("Search", tr("Search"));
1473 	#endif
1474 }
1475 
1476 /*!
1477 	\return the action associated with a given name, if the QEditor has been created with actions on
1478 */
action(const QString & s)1479 QAction* QEditor::action(const QString& s)
1480 {
1481 	QHash<QString, QAction*>::const_iterator it = m_actions.constFind(s);
1482 
1483     return it != m_actions.constEnd() ? *it : nullptr;
1484 }
1485 
1486 /*!
1487 	\brief Add an action to the editor
1488 	\param a action to add
1489 	\param menu if not empty (and if QCE is built with qmdilib support) the action will be added to that menu
1490 	\param toolbar similar to \a menu but acts on toolbars
1491 
1492 	\see removeAction()
1493 */
addAction(QAction * a,const QString & menu,const QString & toolbar)1494 void QEditor::addAction(QAction *a, const QString& menu, const QString& toolbar)
1495 {
1496 	if ( !a )
1497 		return;
1498 
1499 	QWidget::addAction(a);
1500 
1501 	m_actions[a->objectName()] = a;
1502 
1503 	if ( pMenu && menu.count() )
1504 	{
1505 		pMenu->addAction(a);
1506 
1507 		#ifdef _QMDI_
1508 		menus[menu]->addAction(a);
1509 		#endif
1510 	}
1511 
1512 	if ( toolbar.count() )
1513 	{
1514 		#ifdef _QMDI_
1515 		toolbars[toolbar]->addAction(a);
1516 		#endif
1517 	}
1518 }
1519 
1520 /*!
1521 	\brief remove an action form the editor
1522 	\param a action to add
1523 	\param menu if not empty (and if QCE is built with qmdilib support) the action will be added to that menu
1524 	\param toolbar similar to \a menu but acts on toolbars
1525 
1526 	\see addAction()
1527 */
removeAction(QAction * a,const QString & menu,const QString & toolbar)1528 void QEditor::removeAction(QAction *a, const QString& menu, const QString& toolbar)
1529 {
1530 	if ( !a )
1531 		return;
1532 
1533 	QWidget::removeAction(a);
1534 
1535 	//m_actions.remove(a->objectName());
1536 
1537 	if ( pMenu )
1538 		pMenu->removeAction(a);
1539 
1540 	#ifdef _QMDI_
1541 	if ( menu.count() )
1542 	{
1543 		menus[menu]->removeAction(a);
1544 	}
1545 
1546 	if ( toolbar.count() )
1547 	{
1548 		toolbars[toolbar]->removeAction(a);
1549 	}
1550 	#else
1551 	Q_UNUSED(menu)
1552 	Q_UNUSED(toolbar)
1553 	#endif
1554 }
1555 
1556 /*!
1557 	\brief load a text file
1558 	\param file file to load
1559 
1560 	If the file cannot be loaded, previous content is cleared.
1561 */
1562 
load(const QString & file,QTextCodec * codec)1563 void QEditor::load(const QString& file, QTextCodec* codec)
1564 {
1565 	clearPlaceHolders();
1566 
1567 	m_doc->load(file,codec);
1568 
1569 	setCursor(QDocumentCursor(m_doc));
1570 
1571 	documentWidthChanged(m_doc->width());
1572 	documentHeightChanged(m_doc->height());
1573 	viewport()->update();
1574 
1575 	//m_codec=codec;
1576 
1577 	//qDebug("checksum = %i", m_lastFileState.checksum);
1578 
1579 	if ( m_lineEndingsActions )
1580 	{
1581 		// TODO : update Conservative to report original line endings
1582         const QRegularExpression rx(" \\[\\w+\\]");
1583 		QAction *a = m_lineEndingsActions->actions().at(0);
1584 
1585 		if ( a )
1586 		{
1587 			QDocument::LineEnding le = m_doc->originalLineEnding();
1588 
1589 			QString txt = a->text();
1590 			txt.remove(rx);
1591 			txt += " [";
1592 
1593 			if ( le == QDocument::Windows )
1594 				txt += tr("Windows");
1595 			else
1596 				txt += tr("Unix");
1597 
1598 			txt += ']';
1599 
1600 			a->setText(txt);
1601 		}
1602 	}
1603 
1604 	setFileName(file);
1605 
1606 	emit loaded(this, file);
1607 
1608 	reconnectWatcher();
1609 }
1610 
reload()1611 void QEditor::reload(){
1612 	emit fileAutoReloading(fileName());
1613 	// save cursor information
1614 	int lineNum = cursor().lineNumber();
1615 	int col = cursor().columnNumber();
1616 	int anchorLineOffset = cursor().anchorLineNumber() - lineNum;
1617 	int anchorCol = cursor().anchorColumnNumber();
1618 	QString lineText = cursor().line().text();
1619 
1620 	load(fileName(),m_doc->codec());
1621 	m_saveState = Undefined;
1622 
1623 	// restore cursor position based on lineText
1624 	int newLineNum = m_doc->findNearLine(lineText, lineNum);
1625 	QDocumentCursor cur(m_doc, newLineNum+anchorLineOffset, anchorCol, newLineNum, col);
1626 	if (newLineNum>=0 && cur.isValid()) {
1627 		setCursor(cur);
1628 	} else {
1629 		// fall back to staying on the same line number
1630 		cur = QDocumentCursor(m_doc, lineNum);
1631 		if (cur.isValid()) {
1632 			setCursor(cur);
1633 		} else {
1634 			// fall back 2: the new document contains fewer lines than the previous cursor position. -> end ist closest to previous position
1635 			setCursor(QDocumentCursor(m_doc, m_doc->lineCount()-1));
1636 		}
1637 	}
1638 
1639 	emit fileReloaded();
1640 	return;
1641 }
1642 
1643 /*!
1644 	\return a pointer to the underlying QDocument object
1645 */
document() const1646 QDocument* QEditor::document() const
1647 {
1648 	return m_doc;
1649 }
1650 
1651 /*!
1652 	\internal
1653 */
setDocument(QDocument * d)1654 void QEditor::setDocument(QDocument *d)
1655 {
1656 	Q_UNUSED(d)
1657 
1658 	qWarning("QEditor::setDocument() is not working yet...");
1659 }
1660 
1661 /*!
1662 	\brief Force a full re-highlighting of the document
1663 */
highlight()1664 void QEditor::highlight()
1665 {
1666 	m_doc->highlight();
1667 	//updateContent(0, m_doc->lines());
1668 }
1669 
1670 /*!
1671 	\return the current InputBinding
1672 */
inputBindings() const1673 QList<QEditorInputBindingInterface*> QEditor::inputBindings() const
1674 {
1675 	return m_bindings;
1676 }
1677 
1678 /*!
1679 	\brief Add an input binding
1680 */
addInputBinding(QEditorInputBindingInterface * b)1681 void QEditor::addInputBinding(QEditorInputBindingInterface *b)
1682 {
1683 	if ( b )
1684 		m_bindings << b;
1685 
1686 	if ( !aDefaultBinding || !m_bindingsActions )
1687 		return;
1688 
1689 	QString id = b ? b->id() : QString();
1690 	aDefaultBinding->setChecked(!b);
1691 
1692 	if ( !b )
1693 		return;
1694 
1695 	QList<QAction*> actions = m_bindingsActions->actions();
1696 
1697 	foreach ( QAction *a, actions )
1698 	{
1699 		if ( a->data().toString() != id )
1700 		a->setChecked(true);
1701 	}
1702 }
1703 
1704 /*!
1705 	\brief Remove an input binding
1706 */
removeInputBinding(QEditorInputBindingInterface * b)1707 void QEditor::removeInputBinding(QEditorInputBindingInterface *b)
1708 {
1709 	int n = m_bindings.removeAll(b);
1710 
1711 	if ( !aDefaultBinding || !m_bindingsActions || !n )
1712 		return;
1713 
1714 	QString id = b ? b->id() : QString();
1715 	aDefaultBinding->setChecked(!b);
1716 
1717 	if ( !b )
1718 		return;
1719 
1720 	QList<QAction*> actions = m_bindingsActions->actions();
1721 
1722 	foreach ( QAction *a, actions )
1723 	{
1724 		if ( a->data().toString() != id )
1725 			a->setChecked(false);
1726 	}
1727 }
1728 
1729 /*!
1730 	\brief Set the current input binding
1731 */
setInputBinding(QEditorInputBindingInterface * b)1732 void QEditor::setInputBinding(QEditorInputBindingInterface *b)
1733 {
1734 	m_bindings.clear();
1735 
1736 	if ( b )
1737 		m_bindings << b;
1738 
1739 	if ( !aDefaultBinding || !m_bindingsActions )
1740 		return;
1741 
1742 	QString id = b ? b->id() : QString();
1743 	aDefaultBinding->setChecked(!b);
1744 
1745 	if ( !b )
1746 		return;
1747 
1748 	QList<QAction*> actions = m_bindingsActions->actions();
1749 
1750 	foreach ( QAction *a, actions )
1751 	{
1752 		if ( a )
1753 			a->setChecked(a->data().toString() != id);
1754 	}
1755 }
1756 
1757 /*!
1758 	\internal
1759 */
updateBindingsMenu()1760 void QEditor::updateBindingsMenu()
1761 {
1762 	if ( !aDefaultBinding || !m_bindingsMenu || !m_bindingsActions )
1763 		return;
1764 
1765 	QStringList bindings = registeredInputBindingIds();
1766 	QList<QAction*> actions = m_bindingsActions->actions();
1767 
1768 	aDefaultBinding->setChecked(m_bindings.contains(m_defaultBinding));
1769 
1770 	foreach ( QAction *a, actions )
1771 	{
1772 		int idx = bindings.indexOf(a->data().toString());
1773 
1774 		if ( idx == -1 )
1775 		{
1776 			m_bindingsMenu->removeAction(a);
1777 			m_bindingsActions->removeAction(a);
1778 			delete a;
1779 		} else {
1780 			bindings.removeAt(idx);
1781 
1782 			foreach ( QEditorInputBindingInterface *b, m_bindings )
1783 				if ( a->data().toString() == b->id() )
1784 				a->setChecked(true);
1785 
1786 		}
1787 	}
1788 
1789 	bindings.removeAll("default");
1790 
1791 	foreach ( QString s, bindings )
1792 	{
1793 		QEditorInputBindingInterface *b = m_registeredBindings.value(s);
1794 
1795 		if ( !b )
1796 			continue;
1797 
1798 		QAction *a = new QAction(b->name(), m_bindingsMenu);
1799 		a->setData(b->id());
1800 		a->setCheckable(true);
1801 
1802 		m_bindingsActions->addAction(a);
1803 		m_bindingsMenu->addAction(a);
1804 	}
1805 }
1806 
1807 /*!
1808 	\internal
1809 */
bindingSelected(QAction * a)1810 void QEditor::bindingSelected(QAction *a)
1811 {
1812 	//a = m_bindingsActions->checkedAction();
1813 
1814 	if ( !a )
1815 		return;
1816 
1817 	QEditorInputBindingInterface *b = m_registeredBindings.value(a->data().toString());
1818 
1819 	if ( a->isChecked() )
1820 		addInputBinding(b);
1821 	else
1822 		removeInputBinding(b);
1823 
1824 	//qDebug("setting binding to %s [0x%x]", qPrintable(a->data().toString()), m_binding);
1825 
1826 	updateMicroFocus();
1827 }
1828 
1829 /*!
1830 	\internal
1831 */
lineEndingSelected(QAction * a)1832 void QEditor::lineEndingSelected(QAction *a)
1833 {
1834 	a = m_lineEndingsActions->checkedAction();
1835 
1836 	if ( !a )
1837 		return;
1838 
1839 	QString le = a->data().toString();
1840 
1841 	if ( le == "conservative" )
1842 		m_doc->setLineEnding(QDocument::Conservative);
1843 	else if ( le == "local" )
1844 		m_doc->setLineEnding(QDocument::Local);
1845 	else if ( le == "unix" )
1846 		m_doc->setLineEnding(QDocument::Unix);
1847 	else if ( le == "dos" )
1848 		m_doc->setLineEnding(QDocument::Windows);
1849 
1850 
1851 	updateMicroFocus();
1852 }
1853 
1854 /*!
1855 	\internal
1856 */
lineEndingChanged(int lineEnding)1857 void QEditor::lineEndingChanged(int lineEnding)
1858 {
1859 	if ( !m_lineEndingsActions )
1860 		return;
1861 
1862 	QAction *a = m_lineEndingsActions->checkedAction(),
1863 			*n = m_lineEndingsActions->actions().at(lineEnding);
1864 
1865 	if ( a != n )
1866 		n->setChecked(true);
1867 
1868 }
1869 
1870 /*!
1871 	\return the current cursor
1872 */
cursor() const1873 QDocumentCursor QEditor::cursor() const
1874 {
1875 	QDocumentCursor copy(m_cursor, false);
1876 	return copy;
1877 }
1878 
1879 /*!
1880 	\return the current cursor handle
1881 */
cursorHandle() const1882 QDocumentCursorHandle* QEditor::cursorHandle() const
1883 {
1884 	return m_cursor.handle();
1885 }
1886 
1887 /*!
1888 	\brief Set the document cursor
1889 	\param moveView: If disabled, no check is made that the new new cursor position position is visible.
1890 		   In this case you take over responsibility to call ensureCursorVisible later on
1891 
1892 */
setCursor(const QDocumentCursor & c,bool moveView)1893 void QEditor::setCursor(const QDocumentCursor& c, bool moveView)
1894 {
1895 	repaintCursor();
1896 
1897 	m_cursor = c.isValid() ? c : QDocumentCursor(m_doc);
1898 	m_cursor.setColumnMemory(true);
1899 	m_cursor.setAutoUpdated(true);
1900 	m_cursor.setAutoErasable(false);
1901 	clearCursorMirrors();
1902 
1903 	if (m_cursor.columnNumber() > m_cursor.line().length())
1904 		m_cursor.setColumnNumber(m_cursor.line().length());
1905 
1906 	if ( m_curPlaceHolder >=0 && m_curPlaceHolder < m_placeHolders.count() )
1907 	{
1908 		const PlaceHolder& ph = m_placeHolders[m_curPlaceHolder];
1909 
1910 		if ( !ph.cursor.isWithinSelection(m_cursor) )
1911 		{
1912 			setPlaceHolder(-1);
1913 			viewport()->update();
1914 		}
1915 	}
1916 
1917 	emitCursorPositionChanged();
1918 
1919 	setFlag(CursorOn, true);
1920 	repaintCursor();
1921 	if (moveView) {
1922 		ensureCursorVisible();
1923 	}
1924 
1925 	updateMicroFocus();
1926 }
1927 
1928 /*!
1929 	\brief Set the cursor
1930 	\param line document line to move the cursor to (start at zero)
1931 	\param index column index of the new cursor (start at zero)
1932 	\param moveView: If disabled, no check is made that the new cursor position is visible.
1933 		   In this case you take over responsibility to call ensureCursorVisible later on
1934 
1935 */
setCursorPosition(int line,int index,bool moveView)1936 void QEditor::setCursorPosition(int line, int index, bool moveView)
1937 {
1938 	setCursor(QDocumentCursor(m_doc, line, index), moveView);
1939 }
1940 
1941 /*!
1942 	\brief Write the current cursor position to to integers
1943 */
getCursorPosition(int & line,int & index)1944 void QEditor::getCursorPosition(int &line, int &index)
1945 {
1946 	line = m_cursor.lineNumber();
1947 	index = m_cursor.columnNumber();
1948 }
1949 
1950 /*!
1951 	\brief Return the position below the cursor
1952 */
getPositionBelowCursor(QPointF & offset,int width,int height)1953 bool QEditor::getPositionBelowCursor(QPointF& offset, int width, int height){
1954 	bool above;
1955 	return getPositionBelowCursor(offset, width, height, above);
1956 }
1957 
1958 /*!
1959  * \brief Calculate an optimal position for a widget of width and height that should be displayed close to the cursor
1960  * \param outOffset: output Position in Editor coordinates
1961  * \param outAbove: indicates whether it's better to place the widget above the cursor than below
1962  * \return false if there is no valid cursor, true otherwise
1963  */
getPositionBelowCursor(QPointF & outOffset,int width,int height,bool & outAbove)1964 bool QEditor::getPositionBelowCursor(QPointF& outOffset, int width, int height, bool& outAbove){
1965 	QDocumentCursor c(m_cursor, false);
1966 	QDocumentLine line = c.line();
1967 	if (!c.line().isValid()) return false;
1968 	if (c.columnNumber() < 0 || c.columnNumber() > line.length()) return false;
1969 
1970 	outOffset = line.cursorToDocumentOffset(c.columnNumber()-1);
1971 	outOffset.setY(outOffset.y() + document()->y(c.lineNumber()) + document()->getLineSpacing());
1972     outOffset = mapFromContents(outOffset.toPoint());
1973     qreal left;
1974     qreal temp;
1975 	getPanelMargins(&left, &temp, &temp, &temp);
1976 	outOffset.setX(outOffset.x() + left);
1977 	if (outOffset.y() + height > this->height()) {
1978 		outOffset.setY(outOffset.y() - document()->getLineSpacing() - height);
1979 		outAbove = true;
1980 	} else {
1981 		outAbove = false;
1982 	}
1983 	if (outOffset.x() + width > this->width()) {
1984 		// box will extend beyond editor width
1985 		// move to left but not further than the left border of the editor widget
1986 		outOffset.setX(qMax(0, this->width() - width));
1987 	}
1988 	return true;
1989 }
1990 
1991 /*!
1992 	\return the number of cursor mirrors currently used
1993 */
cursorMirrorCount() const1994 int QEditor::cursorMirrorCount() const
1995 {
1996 	return m_mirrors.count();
1997 }
1998 
1999 /*!
2000 	\return the cursor mirror at index \a i
2001 
2002 	Index has no extra meaning : you cannot deduce anything about
2003 	the cursor mirror it corresponds to from it. For instance, the
2004 	cursor mirror at index 0 is neither the first mirror added nor
2005 	the one at smallest document position (well : it *might* be but
2006 	that would be a coincidence...)
2007 */
cursorMirror(int i) const2008 QDocumentCursor QEditor::cursorMirror(int i) const
2009 {
2010 	return i >= 0 && i < m_mirrors.count() ? m_mirrors.at(i) : QDocumentCursor();
2011 }
2012 
2013 /*!
2014 	\return the current cursor and all mirrors
2015 
2016 	Not slow, but also not the fastest. Best to avoid in loops
2017 */
cursors() const2018 QList<QDocumentCursor> QEditor::cursors() const{
2019 	QList<QDocumentCursor> res;
2020 	if (m_cursor.isValid()) res << m_cursor;
2021 	res << m_mirrors;
2022 	return res;
2023 }
2024 
2025 /*!
2026 	\brief Clear all placeholders
2027 */
clearPlaceHolders()2028 void QEditor::clearPlaceHolders()
2029 {
2030 	bool updateView = m_curPlaceHolder >= 0 && m_curPlaceHolder < m_placeHolders.count();
2031 
2032 	m_curPlaceHolder = -1;
2033 
2034 	for ( int i = 0; i < m_placeHolders.count(); ++i )
2035 	{
2036 		PlaceHolder& ph = m_placeHolders[i];
2037 
2038 		ph.cursor.setAutoUpdated(false);
2039 
2040 		for ( int j = 0; j < ph.mirrors.count(); ++j )
2041 		{
2042 			ph.mirrors[j].setAutoUpdated(false);
2043 		}
2044 
2045 		ph.mirrors.clear();
2046 	}
2047 
2048 	m_placeHolders.clear();
2049 
2050 	if ( updateView )
2051 		viewport()->update();
2052 
2053 }
2054 
2055 /*!
2056 	\brief Add a placeholder
2057 	\param p placeholder data
2058 	\param autoUpdate whether to force auto updating of all cursors used by the placeholder
2059 
2060 	Auto update is on by default and it is recommended not to disable it unless you know what you are doing.
2061 */
addPlaceHolder(const PlaceHolder & p,bool autoUpdate)2062 void QEditor::addPlaceHolder(const PlaceHolder& p, bool autoUpdate)
2063 {
2064 	m_placeHolders << p;
2065 
2066 	PlaceHolder& ph = m_placeHolders.last();
2067 
2068 	ph.cursor.setAutoUpdated(autoUpdate);
2069 	ph.cursor.setAutoErasable(p.autoRemove);
2070 	ph.cursor.movePosition(ph.length, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
2071 
2072 	for ( int i = 0; i < ph.mirrors.count(); ++i )
2073 	{
2074 		int mirrorLen = ph.length;
2075 		if (ph.affector)
2076             mirrorLen = ph.affector->affect(nullptr, ph.cursor.selectedText(), m_placeHolders.size()-1, i).length();
2077 
2078 
2079 		ph.mirrors[i].setAutoUpdated(autoUpdate);
2080 		ph.mirrors[i].setAutoErasable(p.autoRemove);
2081 		ph.mirrors[i].movePosition(mirrorLen, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
2082 	}
2083 }
2084 /*!
2085   Adds a mirror to the given placeholder
2086   */
addPlaceHolderMirror(int placeHolderId,const QDocumentCursor & c)2087 void QEditor::addPlaceHolderMirror(int placeHolderId, const QDocumentCursor& c){
2088 	if (placeHolderId<0 || placeHolderId>=m_placeHolders.count())
2089 		return;
2090 	PlaceHolder& ph = m_placeHolders[placeHolderId];
2091 	ph.mirrors << c;
2092 	ph.mirrors.last().setAutoUpdated(true);
2093 	ph.mirrors.last().setAutoErasable(true);
2094     int mirrorLen = ph.affector ? ph.affector->affect(nullptr, ph.cursor.selectedText(), placeHolderId, ph.mirrors.size()-1).length() : ph.length;
2095 	ph.mirrors.last().movePosition(mirrorLen, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
2096 }
2097 
2098 /*!
2099 	\brief Remove a placeholder
2100 	\param i placeholder index
2101 
2102 	\note if the current placeholder is removed there will be NO automatic switching to a remaining one.
2103 */
removePlaceHolder(int id)2104 void QEditor::removePlaceHolder(int id)
2105 {
2106 	if ( id == m_curPlaceHolder){
2107 		clearCursorMirrors();
2108 		m_curPlaceHolder = -1;
2109 	}
2110 
2111 	if ( id<0 || id>=m_placeHolders.count() )
2112 		return;
2113 
2114 	PlaceHolder& ph = m_placeHolders[id];
2115 
2116 	for ( int i = 0; i < ph.mirrors.count(); ++i ){
2117 		ph.mirrors[i].setAutoUpdated(false);
2118 		ph.mirrors[i].line().setFlag(QDocumentLine::LayoutDirty,true);
2119 	    }
2120 	ph.mirrors.clear();
2121 	ph.cursor.setAutoUpdated(false);
2122 	m_placeHolders.removeAt(id);
2123 
2124 	if ( id < m_curPlaceHolder )
2125 		--m_curPlaceHolder;
2126 	if ( id < m_lastPlaceHolder )
2127 		--m_lastPlaceHolder;
2128 	else if ( id == m_lastPlaceHolder )
2129 		m_lastPlaceHolder=-1;
2130 
2131 	viewport()->update();
2132 }
2133 
2134 /*!
2135 	\brief Replaces all placeholders with new ones.
2136 
2137 	\note New placeholders will not be initialized
2138 */
replacePlaceHolders(const QList<PlaceHolder> & newPlaceholders)2139 void QEditor::replacePlaceHolders(const QList<PlaceHolder>& newPlaceholders){
2140 	clearPlaceHolders();//is this needed?
2141 	m_placeHolders = newPlaceholders;
2142 	if ( m_curPlaceHolder >= m_placeHolders.size() )
2143 		m_curPlaceHolder = m_placeHolders.size() - 1;
2144 	m_lastPlaceHolder = -1;
2145 	viewport()->update();
2146 }
2147 
2148 /*!
2149 	\return the number of placeholders currently set
2150 */
placeHolderCount() const2151 int QEditor::placeHolderCount() const
2152 {
2153 	return m_placeHolders.count();
2154 }
2155 /*!
2156 	\return the id of the current placeholder
2157 */
currentPlaceHolder() const2158 int QEditor::currentPlaceHolder() const
2159 {
2160 	return m_curPlaceHolder;
2161 }
getPlaceHolder(int i) const2162 const PlaceHolder& QEditor::getPlaceHolder(int i) const{
2163 	return m_placeHolders.at(i);
2164 }
getPlaceHolders()2165 QList<PlaceHolder> QEditor::getPlaceHolders(){
2166 	return m_placeHolders;
2167 }
2168 
2169 /*! Checks if there exists a placeholder that will be auto overridden by inserting string s
2170 */
isAutoOverrideText(const QString & s) const2171 bool QEditor::isAutoOverrideText(const QString& s) const{
2172 	const QDocumentCursor& c= m_cursor;
2173 	if (c.hasSelection() || (flag(Overwrite) && !c.atLineEnd()))
2174 		return false;
2175 	for ( int i = m_placeHolders.size()-1; i >= 0 ; i-- )
2176 		if ( m_placeHolders[i].autoOverride && m_placeHolders[i].cursor.lineNumber() == c.lineNumber() &&
2177 		     m_placeHolders[i].cursor.anchorColumnNumber() == c.anchorColumnNumber() &&
2178 		     m_placeHolders[i].cursor.selectedText().startsWith(s) ) {
2179 		return true;
2180 	}
2181 	return false;
2182 }
2183 
2184 /*! Creates a temporary auto overridden placeholder at position start with length length, and
2185     merges it with a directly following placeholder (equivalent to extending the following placeholder
2186     by length characters to the left)
2187 */
resizeAutoOverridenPlaceholder(const QDocumentCursor & start,int length)2188 void QEditor::resizeAutoOverridenPlaceholder(const QDocumentCursor& start, int length){
2189 	for (int i=0;i<m_placeHolders.size();i++) {
2190 		PlaceHolder& ph = m_placeHolders[i];
2191 		if (ph.autoOverride &&
2192 		    ph.cursor.lineNumber() == start.lineNumber() &&
2193 		    ph.cursor.anchorColumnNumber() == start.columnNumber() + length) {
2194 			ph.cursor.setAnchorColumnNumber(start.columnNumber());
2195 		}
2196 	}
2197 }
2198 /*!
2199 	\brief Set the current placeholder to use
2200 
2201 	This function change the cursor and the cursor mirrors.
2202 */
setPlaceHolder(int i,bool selectCursors)2203 void QEditor::setPlaceHolder(int i, bool selectCursors)
2204 {
2205 	if (i == m_curPlaceHolder && i == -1)
2206 		return;
2207 
2208 	if (m_curPlaceHolder >= 0 && m_curPlaceHolder < m_placeHolders.size() && m_placeHolders[m_curPlaceHolder].autoRemoveIfLeft) {
2209 		if (i > m_curPlaceHolder) i--;
2210 		int toRemove = m_curPlaceHolder;
2211 		m_curPlaceHolder = -1; //prevent endless recursion, if cursor mirrors exist
2212 		removePlaceHolder(toRemove);
2213 	}
2214 
2215 	m_curPlaceHolder = -1;
2216 	clearCursorMirrors();
2217 	if ( i < 0 || i >= m_placeHolders.count() ){
2218 		viewport()->update();
2219 		return;
2220 	}
2221 
2222 	if (m_placeHolderSynchronizing) return;
2223 	m_placeHolderSynchronizing=true; //prevent recursive calls (from updateContent)	TODO: move the synchronizing there
2224 
2225 	QDocumentCursor cc = m_placeHolders[i].cursor;
2226 	selectCursors|=!cc.isWithinSelection(m_cursor);// && cc.hasSelection() && cc.selectionStart()!=cc.selectionEnd();
2227 
2228 	//qDebug("set placeholder: %i, select cursors: %i", i, (int)selectCursors);
2229 
2230 	if (selectCursors)
2231 		setCursor(cc);
2232 	else if (m_cursor.hasColumnMemory())
2233 		m_cursor.setColumnMemory(false);
2234 
2235 	i = -1;
2236 	for (int j=0;j<m_placeHolders.size();j++)
2237 		if (cc.equal(m_placeHolders[j].cursor)) {
2238 			i = j;
2239 			break;
2240 		}
2241 	if (i == -1) return;
2242 
2243 	PlaceHolder& ph = m_placeHolders[i]; //using reference to change the placeholder
2244 	QString base = cc.selectedText();
2245 	for ( int j=0; j< ph.mirrors.size(); j++)
2246 	{
2247 		QDocumentCursor &mc = ph.mirrors[j];
2248         QString mirrored = ph.affector ? ph.affector->affect(nullptr, base, i, j) : base;
2249 		if (mc.selectedText()!=mirrored){
2250 			//qDebug() << "resync placeholder mirror for " << m_curPlaceHolder << " mirror "<<j << " was: " << mc.selectedText() << " should be " << cc.selectedText() << " from " << cc.anchorLineNumber() << ":" << cc.anchorColumnNumber() << "->" << cc.lineNumber() << ":"<<cc.columnNumber()<<"\n";
2251 			//if mirror synchronization is broken => resyncronize
2252 			mc.replaceSelectedText(mirrored);
2253 		}
2254 	}
2255 
2256 	m_placeHolderSynchronizing=false;
2257 
2258 	/*
2259 		ditch cursor mirrors in favor of QDocumentCursor::replaceSelectedText()
2260 
2261 			* workaround mirror limitations re movement (no way to ensure proper
2262 			synchronization when moving up/down)
2263 
2264 			* make it relatively easy to implement affectors
2265 	*/
2266 
2267 	m_curPlaceHolder = i;
2268 
2269 	viewport()->update();
2270 }
2271 
2272 /*!
2273 	\brief Move to next placeholder
2274 
2275 	\see setPlaceHolder
2276 	\return is there actually a next placeholder
2277 */
nextPlaceHolder()2278 bool QEditor::nextPlaceHolder()
2279 {
2280 	if ( m_placeHolders.isEmpty() )
2281 		return false;
2282 
2283 	/*++m_curPlaceHolder;
2284 
2285 	if ( m_curPlaceHolder >= m_placeHolders.count() )
2286 		m_curPlaceHolder = 0;
2287 	*/
2288 	int np=-1;
2289 	for (int i=0; i< m_placeHolders.count();i++){
2290 		if (m_placeHolders[i].cursor.beginBoundaryLarger(m_cursor) &&
2291 			(np==-1 || m_placeHolders[i].cursor<=m_placeHolders[np].cursor) &&
2292 			!m_placeHolders[i].autoOverride)
2293 				np=i;
2294 	}
2295 
2296 	setPlaceHolder(np);
2297 	return np!=-1;
2298 }
2299 
2300 /*!
2301 	\brief Move to previous placeholder
2302 
2303 	\see setPlaceHolder
2304 	\return is there actually a previous placeholder
2305 */
previousPlaceHolder()2306 bool QEditor::previousPlaceHolder()
2307 {
2308 	if ( m_placeHolders.isEmpty() )
2309 		return false;
2310 
2311 	/*if ( m_curPlaceHolder <= 0 )
2312 		m_curPlaceHolder = m_placeHolders.count();
2313 
2314 	--m_curPlaceHolder;*/
2315 	int np=-1;
2316 	for (int i=0; i< m_placeHolders.count();i++){
2317 		if (m_cursor.endBoundaryLarger(m_placeHolders[i].cursor) &&
2318 			(np==-1 || m_placeHolders[i].cursor>=m_placeHolders[np].cursor) &&
2319 			!m_placeHolders[i].autoOverride)
2320 				np=i;
2321 	}
2322 
2323 	setPlaceHolder(np);
2324 	return np!=-1;
2325 }
2326 
2327 /*!
2328 	\return the code completion engine set to this editor, if any
2329 */
completionEngine() const2330 QCodeCompletionEngine* QEditor::completionEngine() const
2331 {
2332 	return m_completionEngine;
2333 }
2334 
2335 /*!
2336 	\brief Set a code completion engine to the editor
2337 
2338 	\warning Most completion engines can only be attached
2339 	to a single editor due to issues in the widget used to
2340 	dispaly matches so you got to clone them and, as a consequence
2341 	QEditor will take ownership of the completion engines
2342 	and delete them.
2343 */
setCompletionEngine(QCodeCompletionEngine * e)2344 void QEditor::setCompletionEngine(QCodeCompletionEngine *e)
2345 {
2346 	if ( m_completionEngine )
2347 	{
2348         m_completionEngine->setEditor(nullptr);
2349 		m_completionEngine->deleteLater();
2350 	}
2351 
2352 	m_completionEngine = e;
2353 
2354 	if ( m_completionEngine )
2355 	{
2356 		m_completionEngine->setEditor(this);
2357 	}
2358 }
2359 
2360 /*!
2361 	\return the language definition set to this editor, if any
2362 */
languageDefinition() const2363 QLanguageDefinition* QEditor::languageDefinition() const
2364 {
2365 	return m_definition;
2366 }
2367 
2368 /*!
2369 	\brief Set a language definition to the editor
2370 */
setLanguageDefinition(QLanguageDefinition * d)2371 void QEditor::setLanguageDefinition(QLanguageDefinition *d)
2372 {
2373 	m_definition = d;
2374 
2375 	if ( m_doc )
2376 		m_doc->setLanguageDefinition(d);
2377 
2378 	if ( m_definition )
2379 	{
2380 		bool cuc = d->singleLineComment().count();
2381 
2382 		QCE_ENABLE_ACTION("comment", cuc)
2383 		QCE_ENABLE_ACTION("uncomment", cuc)
2384 	} else {
2385 		QCE_ENABLE_ACTION("comment", false)
2386 		QCE_ENABLE_ACTION("uncomment", false)
2387 	}
2388 }
2389 
2390 /*!
2391 	\return the line at a given viewport position
2392 */
lineAtPosition(const QPoint & p) const2393 QDocumentLine QEditor::lineAtPosition(const QPoint& p) const
2394 {
2395 	return m_doc ? m_doc->lineAt(p) : QDocumentLine();
2396 }
2397 
2398 /*!
2399 	\return The cursor object nearest to the given viewport position
2400 */
cursorForPosition(const QPoint & p,bool disallowPositionBeyondLine) const2401 QDocumentCursor QEditor::cursorForPosition(const QPoint& p, bool disallowPositionBeyondLine) const
2402 {
2403 	//qDebug("cursor for : (%i, %i)", p.x(), p.y());
2404 
2405     return m_doc ? m_doc->cursorAt(p,disallowPositionBeyondLine) : QDocumentCursor();
2406 }
2407 
2408 /*!
2409 	\brief Set the cursor to that nearest to a given viewport position
2410 	\param moveView: If disabled, no check is made that the cursor is visible.
2411 		   In this case you take over responsibility to call ensureCursorVisible later on
2412 */
setCursorPosition(const QPoint & p,bool moveView)2413 void QEditor::setCursorPosition(const QPoint& p, bool moveView)
2414 {
2415 	//qDebug("cursor for : (%i, %i)", p.x(), p.y());
2416 
2417 	QDocumentCursor c = cursorForPosition(p);
2418 
2419 	if ( c.isValid() )
2420 	{
2421 		setCursor(c, moveView);
2422 	}
2423 }
2424 
2425 /*!
2426 	\brief Emitted whenever the position of the cursor changes
2427 */
emitCursorPositionChanged()2428 void QEditor::emitCursorPositionChanged()
2429 {
2430     m_cursorLinesFromViewTop = qRound(m_cursor.documentPosition().y() / m_doc->getLineSpacing() - verticalScrollBar()->value());
2431 	emit cursorPositionChanged();
2432 	emit copyAvailable(m_cursor.hasSelection());
2433 
2434 	if ( m_definition )
2435 		m_definition->match(m_cursor);
2436 
2437 	if ( m_doc->impl()->hasMarks() )
2438 		QLineMarksInfoCenter::instance()->cursorMoved(this);
2439 
2440 }
2441 
2442 /*!
2443 	\brief Undo the last editing operation, if any on the stack
2444 */
undo()2445 void QEditor::undo()
2446 {
2447 	if ( m_doc )
2448 	{
2449 		if ( m_definition )
2450 			m_definition->clearMatches(m_doc);
2451 
2452 		m_doc->setProposedPosition(QDocumentCursor());
2453 
2454 		m_doc->undo();
2455 
2456 		QDocumentCursor c=m_doc->getProposedPosition();
2457         if(c.isValid() && !m_mirrors.size())
2458             setCursor(c);
2459 
2460 
2461 		ensureCursorVisible();
2462 		setFlag(CursorOn, true);
2463 		emitCursorPositionChanged();
2464 		repaintCursor();
2465 	}
2466 }
2467 
2468 /*!
2469 	\brief Redo the last undone editing operation, if any on the stack
2470 */
redo()2471 void QEditor::redo()
2472 {
2473 	if ( m_doc )
2474 	{
2475 		if ( m_definition )
2476 			m_definition->clearMatches(m_doc);
2477 
2478 		m_doc->redo();
2479 
2480 		ensureCursorVisible();
2481 		setFlag(CursorOn, true);
2482 		emitCursorPositionChanged();
2483 		repaintCursor();
2484 	}
2485 }
2486 
2487 /*!
2488 	\brief Cut the selected text, if any
2489 */
cut()2490 void QEditor::cut()
2491 {
2492 	copy();
2493 
2494     bool macroing = isMirrored();
2495 
2496 	if ( macroing )
2497 		m_doc->beginMacro();
2498 
2499 	m_cursor.removeSelectedText();
2500 
2501 	if ( atPlaceholder() ) // need new evaluation, because remove operation might have changed things
2502 	{
2503 		PlaceHolder& ph = m_placeHolders[m_curPlaceHolder];
2504 		QString baseText = ph.cursor.selectedText();
2505 
2506 		QKeyEvent ev(QEvent::KeyPress, Qt::Key_Paste, Qt::NoModifier); // just a dummy to be able to pass something reasonable to affect() - currently unused
2507 		for ( int phm = 0; phm < ph.mirrors.count(); ++phm )
2508 		{
2509 			QString s = ph.affector ?  ph.affector->affect(&ev, baseText, m_curPlaceHolder, phm) : baseText;
2510 			ph.mirrors[phm].replaceSelectedText(s);
2511 		}
2512 	}
2513 
2514 	cursorMirrorsRemoveSelectedText();
2515 
2516 	if ( macroing )
2517 		m_doc->endMacro();
2518 
2519 	clearCursorMirrors();
2520 
2521 	ensureCursorVisible();
2522 	setFlag(CursorOn, true);
2523 	emitCursorPositionChanged();
2524 	repaintCursor();
2525 }
2526 
2527 /*!
2528 	\brief Copy the selected text, if any
2529 
2530 	\note Column selection may be created but the can only be copied properly in a QCE editor
2531 */
copy()2532 void QEditor::copy()
2533 {
2534 	if ( !m_cursor.hasSelection() )
2535 		return;
2536 
2537 	QMimeData *d = createMimeDataFromSelection();
2538 	QApplication::clipboard()->setMimeData(d);
2539 	//QApplication::clipboard()->setText(m_cursor.selectedText());
2540 }
2541 
2542 /*!
2543 	\brief Paste text from the clipboard to the current cursor position
2544 
2545 	\note May paste column selections from other QCE editors
2546 */
paste()2547 void QEditor::paste()
2548 {
2549 	const QMimeData *d = QApplication::clipboard()->mimeData();
2550 
2551 	if ( d )
2552 		insertFromMimeData(d);
2553 }
2554 
unindent(const QDocumentCursor & cur)2555 static bool unindent(const QDocumentCursor& cur)
2556 {
2557 	QDocumentLine beg(cur.line());
2558 	int r = 0, n = 0, t = QDocument::tabStop();
2559 	QString txt = beg.text().left(beg.firstChar());
2560 
2561 	while ( txt.count() && (n < t) )
2562 	{
2563 		if ( txt.at(txt.length() - 1) == '\t' )
2564 			n += t - (n % t);
2565 		else
2566 			++n;
2567 
2568 		++r;
2569 		txt.chop(1);
2570 	}
2571 
2572 	if ( !r )
2573 		return false;
2574 
2575 	QDocumentCursor c(cur);
2576 	c.setSilent(true);
2577 	c.movePosition(1, QDocumentCursor::StartOfBlock, QDocumentCursor::MoveAnchor);
2578 	c.movePosition(r, QDocumentCursor::Right, QDocumentCursor::KeepAnchor);
2579 	c.removeSelectedText();
2580 
2581 	return true;
2582 }
2583 
insertAtLineStart(const QDocumentCursor & cur,const QString & txt)2584 static void insertAtLineStart(const QDocumentCursor& cur, const QString& txt)
2585 {
2586 	QDocumentCursor c(cur);
2587 	c.setSilent(true);
2588 	c.setColumnNumber(0);
2589 	c.insertText(txt);
2590 }
2591 
removeFromStart(const QDocumentCursor & cur,const QString & txt)2592 static void removeFromStart(const QDocumentCursor& cur, const QString& txt)
2593 {
2594 	QDocumentLine l = cur.line();
2595 	int pos = l.firstChar();
2596 
2597 	if ( l.text().mid(pos, txt.length()) != txt )
2598 		return;
2599 
2600 	QDocumentCursor c(cur.document(), cur.lineNumber(), pos);
2601 	c.setSilent(true);
2602 	c.movePosition(txt.length(),
2603 					QDocumentCursor::NextCharacter,
2604 					QDocumentCursor::KeepAnchor);
2605 	c.removeSelectedText();
2606 }
2607 
2608 /*!
2609  * \brief inserts a tab at given cursor position. Depending on the context and the
2610  * flags ReplaceIndentTabs and ReplaceTextTabs, this inserts spaces up to the next
2611  * tab stop, otherwise '\t' is inserted.
2612  */
insertTab(QDocumentCursor & cur)2613 void QEditor::insertTab(QDocumentCursor &cur)
2614 {
2615 	bool replaceTabs = flag(ReplaceIndentTabs);
2616 	if (flag(ReplaceIndentTabs) != flag(ReplaceTextTabs)) {
2617 		// need to check if cursor is in indent or in text
2618 		QDocumentCursor cur(m_cursor);
2619 		while (!cur.atLineStart()) {
2620 			if (!cur.previousChar().isSpace()) {
2621 				replaceTabs = flag(ReplaceTextTabs);
2622 				break;
2623 			}
2624 			cur.movePosition(1, QDocumentCursor::PreviousCharacter);
2625 		}
2626 	}
2627 
2628 	if (replaceTabs) {
2629 		int tabStop = m_doc->tabStop();
2630 		int spaceCount = tabStop - cur.columnNumber() % tabStop;
2631 		cur.insertText(QString(spaceCount, ' '));
2632 	} else {
2633 		insertText(cur, "\t");
2634 	}
2635 }
2636 
tabOrIndentSelection()2637 void QEditor::tabOrIndentSelection()
2638 {
2639 	if (m_cursor.hasSelection()) {
2640 		indentSelection();
2641 	} else {
2642 		insertTab();
2643 	}
2644 }
2645 
insertTab()2646 void QEditor::insertTab()
2647 {
2648 	bool macroing = m_mirrors.count();
2649 	if (macroing) m_doc->beginMacro();
2650 
2651 	insertTab(m_cursor);
2652 	for ( int i = 0; i < m_mirrors.count(); ++i ) {
2653 		insertTab(m_mirrors[i]);
2654 	}
2655 
2656 	if (macroing) m_doc->endMacro();
2657 }
2658 
2659 /*!
2660 	\brief Indent the selection
2661 */
indentSelection()2662 void QEditor::indentSelection()
2663 {
2664 	// TODO : respect tab stops in case of screwed up indent (correct it?)
2665 
2666 	QString txt = flag(ReplaceIndentTabs) ? QString(m_doc->tabStop(), ' ') : QString("\t");
2667 
2668 	if ( m_mirrors.count() )
2669 	{
2670 		m_doc->beginMacro();
2671 
2672 		if ( !protectedCursor(m_cursor) )
2673 			insertAtLineStart(m_cursor, txt);
2674 
2675 		foreach ( const QDocumentCursor& m, m_mirrors )
2676 			if ( !protectedCursor(m) )
2677 				insertAtLineStart(m, txt);
2678 
2679 		m_doc->endMacro();
2680 
2681 	} else if ( !protectedCursor(m_cursor) ) {
2682 		if ( !m_cursor.hasSelection() )
2683 
2684 			insertAtLineStart(m_cursor, txt);
2685 		else {
2686 			QDocumentSelection s = m_cursor.selection();
2687 			if ( s.end == 0 && s.startLine < s.endLine )
2688 				s.endLine--; //only change last line if there is selected text
2689 			QDocumentCursor c(m_doc, s.startLine);
2690 			c.setSilent(true);
2691 			c.beginEditBlock();
2692 
2693 			while ( c.isValid() && (c.lineNumber() <= s.endLine) )
2694 			{
2695 				c.insertText(txt);
2696 				c.movePosition(1, QDocumentCursor::NextLine, QDocumentCursor::ThroughFolding);
2697 
2698 				if ( c.atEnd() )
2699 					break;
2700 			}
2701 
2702 			c.endEditBlock();
2703 		}
2704 	}
2705 }
2706 
2707 /*!
2708 	\brief Unindent the selection
2709 */
unindentSelection()2710 void QEditor::unindentSelection()
2711 {
2712 	if ( m_mirrors.count() )
2713 	{
2714 		m_doc->beginMacro();
2715 
2716 		if ( !protectedCursor(m_cursor) )
2717 			unindent(m_cursor);
2718 
2719 		foreach ( const QDocumentCursor& m, m_mirrors ){
2720 			if ( !protectedCursor(m) )
2721 				unindent(m);
2722 		}
2723 
2724 		m_doc->endMacro();
2725 
2726 	} else if ( !protectedCursor(m_cursor) ) {
2727 		if ( !m_cursor.hasSelection())
2728 			unindent(m_cursor);
2729 		else {
2730 			QDocumentSelection s = m_cursor.selection();
2731 			if ( s.end == 0 && s.startLine < s.endLine )
2732 				s.endLine--; //only change last line if there is selected text
2733 
2734 			m_doc->beginMacro();
2735 
2736 			for ( int i = s.startLine; i <= s.endLine; ++i )
2737 			{
2738 				unindent(QDocumentCursor(m_doc, i));
2739 			}
2740 
2741 			m_doc->endMacro();
2742 		}
2743 	}
2744 }
2745 
2746 /*!
2747 	\brief Comment the selection (or the current line) using single line comments supported by the language
2748 */
commentSelection()2749 void QEditor::commentSelection()
2750 {
2751 	if ( !m_definition )
2752 		return;
2753 
2754 	QString txt = m_definition->singleLineComment();
2755 
2756 	if ( txt.isEmpty() )
2757 		return;
2758 
2759 	if ( m_mirrors.count() )
2760 	{
2761 		m_doc->beginMacro();
2762 
2763 		m_definition->clearMatches(m_doc);  // Matches are not handled inside comments. We have to remove them. Otherwise they will stay forever in the comment line.
2764 
2765 		if ( !protectedCursor(m_cursor) )
2766 		insertAtLineStart(m_cursor, txt);
2767 
2768 		foreach ( const QDocumentCursor& m, m_mirrors )
2769 			if ( !protectedCursor(m) )
2770 				insertAtLineStart(m, txt);
2771 
2772 		m_doc->endMacro();
2773 
2774 	} else if ( !protectedCursor(m_cursor) ) {
2775 		m_definition->clearMatches(m_doc);  // Matches are not handled inside comments. We have to remove them. Otherwise they will stay forever in the comment line.
2776 		if ( !m_cursor.hasSelection() )
2777 			insertAtLineStart(m_cursor, txt);
2778 		else {
2779 			QDocumentSelection s = m_cursor.selection();
2780             if ( s.end == 0 && s.startLine < s.endLine )
2781                 s.endLine--; //only change last line if there is selected text
2782 			QDocumentCursor c(m_doc, s.startLine);
2783 			c.setSilent(true);
2784 			c.beginEditBlock();
2785 
2786 			while ( c.isValid() && (c.lineNumber() <= s.endLine) )
2787 			{
2788 				c.insertText(txt);
2789 				c.movePosition(1, QDocumentCursor::NextLine, QDocumentCursor::ThroughFolding);
2790 
2791 				if ( c.atEnd() )
2792 					break;
2793 			}
2794 
2795 			c.endEditBlock();
2796 		}
2797 	}
2798 }
2799 
2800 /*!
2801 	\brief Uncomment the selection (or current line), provided it has been commented with single line comments
2802 */
uncommentSelection()2803 void QEditor::uncommentSelection()
2804 {
2805 	if ( !m_definition )
2806 		return;
2807 
2808 	QString txt = m_definition->singleLineComment();
2809 
2810 	if ( txt.isEmpty() )
2811 		return;
2812 
2813 	if ( m_mirrors.count() )
2814 	{
2815 		m_doc->beginMacro();
2816 
2817 		if ( !protectedCursor(m_cursor) )
2818 			removeFromStart(m_cursor, txt);
2819 
2820 		foreach ( const QDocumentCursor& m, m_mirrors ){
2821 			if ( !protectedCursor(m) )
2822 				removeFromStart(m, txt);
2823 		}
2824 
2825 		m_doc->endMacro();
2826 
2827 	} else if ( !protectedCursor(m_cursor) ){
2828 		if ( !m_cursor.hasSelection() )
2829 			removeFromStart(m_cursor, txt);
2830 		else {
2831 			QDocumentSelection s = m_cursor.selection();
2832 			if ( s.end == 0 && s.startLine < s.endLine )
2833 				s.endLine--; //only change last line if there is selected text
2834 			if (s.startLine<0) s.startLine=0;
2835 			if (s.endLine>m_doc->lines()-1) s.endLine=m_doc->lines()-1;
2836 
2837 			m_doc->beginMacro();
2838 
2839 			for ( int i = s.startLine; i <= s.endLine; ++i )
2840 			{
2841 				removeFromStart(QDocumentCursor(m_doc, i), txt);
2842 			}
2843 
2844 			m_doc->endMacro();
2845 		}
2846 	}
2847 }
2848 
2849 /*!
2850 	\brief Toggle comments
2851 	If all lines touched by a cursor or selection are commented remove the comment mark.
2852 	Otherwise insert the comment mark
2853 */
toggleCommentSelection()2854 void QEditor::toggleCommentSelection()
2855 {
2856 	if ( !m_definition )
2857 		return;
2858 
2859 	QString commentMark = m_definition->singleLineComment();
2860 	bool allCommented = true;
2861 
2862 	foreach (const QDocumentCursor &cursor, cursors()) {
2863 		if (cursor.hasSelection()) {
2864 			QDocumentCursor cur = cursor.selectionStart();
2865             for (int i = cursor.startLineNumber(); i <= cursor.endLineNumber() ; ++i) {
2866                 if (!cur.line().startsWith(commentMark) && (i<cursor.endLineNumber() || !cur.line().text().isEmpty())) { //special treatmenat of last line if empty
2867 					allCommented = false;
2868 					break;
2869 				}
2870 				cur.movePosition(1, QDocumentCursor::NextLine);
2871 			}
2872 		} else {
2873 			if (!cursor.line().startsWith(commentMark)) {
2874 				allCommented = false;
2875 			}
2876 		}
2877 		if (!allCommented) break;
2878 	}
2879 
2880 	if (allCommented) {
2881 		uncommentSelection();
2882 	} else {
2883 		commentSelection();
2884 	}
2885 }
2886 
2887 /*!
2888 	\brief Select the whole text
2889 */
selectAll()2890 void QEditor::selectAll()
2891 {
2892 	clearCursorMirrors();
2893 
2894 	m_cursor.movePosition(1, QDocumentCursor::Start);
2895 	m_cursor.movePosition(1, QDocumentCursor::End, QDocumentCursor::KeepAnchor);
2896 
2897 	emitCursorPositionChanged();
2898 
2899 	viewport()->update();
2900 }
2901 
selectNothing()2902 void QEditor::selectNothing(){
2903     QDocumentCursor cur=cursor();
2904     cur.clearSelection();
2905 	setCursor(cur);
2906 }
2907 
selectExpand(QDocumentCursor::SelectionType selectionType)2908 void QEditor::selectExpand(QDocumentCursor::SelectionType selectionType){
2909 	m_cursor.expandSelect(selectionType);
2910 	for (int i=0;i<m_mirrors.size();i++)
2911 		m_mirrors[i].expandSelect(selectionType);
2912 }
2913 
2914 /*!
2915  * \brief searches for the next occurence of the text in the last selection and
2916  * selects this additionally. If there is no selection, the word or command under
2917  * the cursor is selected.
2918  */
selectExpandToNextWord()2919 void QEditor::selectExpandToNextWord()
2920 {
2921 	if (!m_cursor.hasSelection()) {
2922 		m_cursor.select(QDocumentCursor::WordOrCommandUnderCursor);
2923 	} else {
2924 		QDocumentCursor &lastCursor = m_cursor;
2925 		foreach (const QDocumentCursor &cm, m_mirrors) {
2926 			if (cm > lastCursor) lastCursor = cm;
2927 		}
2928 		QString selectedText = lastCursor.selectedText();
2929 		QDocumentCursor searchCursor(lastCursor.document(), lastCursor.endLineNumber(), lastCursor.endColumnNumber());
2930 		while (!searchCursor.atEnd()) {
2931 			int col = searchCursor.line().text().indexOf(selectedText, searchCursor.columnNumber());
2932 			if (col < 0) {
2933 				searchCursor.movePosition(1, QDocumentCursor::EndOfLine);  // ensure searchCursor.atEnd() if no further matches are found
2934 				searchCursor.movePosition(1, QDocumentCursor::NextLine);
2935 			} else {
2936 				QDocumentCursor c(m_cursor);
2937 				m_cursor.setLineNumber(searchCursor.lineNumber());
2938 				m_cursor.setColumnNumber(col);
2939 				m_cursor.movePosition(selectedText.length(), QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
2940 				// need to add the cursor mirror after changing m_cursor (if c == m_cursor, addCursorMirror does nothing)
2941 				addCursorMirror(c);
2942 				break;
2943 			}
2944 		}
2945 	}
2946 
2947 	emitCursorPositionChanged();
2948 	viewport()->update();
2949 }
2950 
2951 /*!
2952  * \brief Expands the selection to include the full line.
2953  * If the selection does already span full lines, the next line will be added.
2954  * If there is no selection, the current line will be selected.
2955  */
selectExpandToNextLine()2956 void QEditor::selectExpandToNextLine()
2957 {
2958 	if (!m_cursor.hasSelection()) {
2959 		m_cursor.movePosition(1, QDocumentCursor::StartOfLine);
2960 		m_cursor.movePosition(1, QDocumentCursor::EndOfLine, QDocumentCursor::KeepAnchor);
2961 		m_cursor.movePosition(1, QDocumentCursor::NextLine, QDocumentCursor::KeepAnchor);
2962 		return;
2963 	}
2964 	if (!m_cursor.isForwardSelection()) {
2965 		m_cursor.flipSelection();
2966 	}
2967 	m_cursor.setAnchorColumnNumber(0);
2968 	m_cursor.movePosition(1, QDocumentCursor::EndOfLine, QDocumentCursor::KeepAnchor);
2969 	m_cursor.movePosition(1, QDocumentCursor::NextLine, QDocumentCursor::KeepAnchor);
2970 
2971 	emitCursorPositionChanged();
2972 	viewport()->update();
2973 }
2974 
2975 /*!
2976  * \brief Select all occurences of the current selection or the word/command under cursor
2977  * Note: if the selection is a word, matches are limited to complete words.
2978  */
selectAllOccurences()2979 void QEditor::selectAllOccurences()
2980 {
2981 	selectOccurence(false, false, true);
2982 }
selectNextOccurence()2983 void QEditor::selectNextOccurence()
2984 {
2985 	selectOccurence(false, false, false);
2986 }
selectPrevOccurence()2987 void QEditor::selectPrevOccurence()
2988 {
2989 	selectOccurence(true, false, false);
2990 }
selectNextOccurenceKeepMirror()2991 void QEditor::selectNextOccurenceKeepMirror()
2992 {
2993 	selectOccurence(false, true, false);
2994 }
selectPrevOccurenceKeepMirror()2995 void QEditor::selectPrevOccurenceKeepMirror()
2996 {
2997 	selectOccurence(true, true, false);
2998 }
selectOccurence(bool backward,bool keepMirrors,bool all)2999 void QEditor::selectOccurence(bool backward, bool keepMirrors, bool all)
3000 {
3001 	//backward and all are exclusive
3002 	if (!m_cursor.hasSelection()) {
3003 		m_cursor.select(QDocumentCursor::WordOrCommandUnderCursor);
3004 	}
3005 	if (!m_cursor.hasSelection()) {
3006 		return;
3007 	}
3008 	QString text = m_cursor.selectedText();
3009 	bool isWord = true;
3010 	foreach (const QChar &c, text) {
3011 		if (!c.isLetterOrNumber()) {
3012 			isWord = false;
3013 			break;
3014 		}
3015 	}
3016 	QDocumentCursor cStart(m_cursor.document(), m_cursor.startLineNumber(), m_cursor.startColumnNumber());
3017 	QDocumentCursor cEnd(m_cursor.document(), m_cursor.endLineNumber(), m_cursor.endColumnNumber());
3018 	bool atBoundaries = (cStart.atLineStart() || !cStart.previousChar().isLetterOrNumber())
3019 	                 && (cEnd.atLineEnd() || !cEnd.nextChar().isLetterOrNumber());
3020 
3021 	QList<QDocumentCursor> cursors;
3022 	if (keepMirrors) cursors = this->cursors();
3023 
3024 	// TODO: this is a quick solution: using the search panel to select all matches
3025 	//       1. initialize the search with the required parameters
3026 	//       2. select all matches
3027 	//       3. close the search panel which was opened as a side effect of 1.
3028 	// It would be better to be able to perform the search and select without interfering
3029 	// with the search panel UI.
3030 	find(text, false, false, isWord && atBoundaries, true);
3031 	if (all) selectAllMatches();
3032 	else if (backward) {
3033 		findPrev();
3034 		findPrev();
3035 	} else {
3036 		//findNext(); find above already searched one
3037 	}
3038 	relayPanelCommand("Search", "closeElement", QList<QVariant>() << true);
3039 
3040 	if (keepMirrors) m_mirrors = cursors;
3041 
3042 	emitCursorPositionChanged();
3043 	viewport()->update();
3044 }
3045 
setVerticalScrollBarMaximum()3046 void QEditor::setVerticalScrollBarMaximum()
3047 {
3048 	if (!m_doc) return;
3049 	const QSize viewportSize = viewport()->size();
3050 	int viewportHeight = viewportSize.height();
3051 	if (flag(VerticalOverScroll))
3052 		viewportHeight /= 2;
3053     const qreal ls = m_doc->getLineSpacing();
3054 	QScrollBar* vsb = verticalScrollBar();
3055     vsb->setMaximum(qMax(0., 1. + (m_doc->height() - viewportHeight) / ls));
3056     vsb->setPageStep(qCeil(1.* viewportSize.height() / ls));
3057 }
3058 
3059 /*!
3060 	\internal
3061 */
event(QEvent * e)3062 bool QEditor::event(QEvent *e)
3063 {
3064 	// preparations for tooltips
3065 
3066 	if (e->type() == QEvent::ToolTip) {
3067 		QHelpEvent *helpEvent = static_cast<QHelpEvent *>(e);
3068 		emit hovered(helpEvent->pos()-QPoint(m_margins.left(),m_margins.top()));
3069 		return true;
3070 	}
3071 
3072 	// qcodedit ...
3073 	bool r = QAbstractScrollArea::event(e);
3074 
3075 	if ( (e->type() == QEvent::Resize || e->type() == QEvent::Show) )
3076 		setVerticalScrollBarMaximum();
3077 
3078     if ( e->type() == QEvent::Resize && flag(LineWrap)  && m_doc)
3079 	{
3080 		//qDebug("resize adjust (1) : wrapping to %i", viewport()->width());
3081 		m_doc->setWidthConstraint(wrapWidth());
3082 		ensureCursorVisible(KeepDistanceFromViewTop);
3083 	}
3084 
3085 	return r;
3086 }
3087 
3088 /*!
3089 	\internal
3090 */
paintEvent(QPaintEvent *)3091 void QEditor::paintEvent(QPaintEvent */*e*/)
3092 {
3093 	if ( !m_doc )
3094 		return;
3095 
3096 	QPainter p(viewport());
3097     const qreal yOffset = verticalOffset();
3098     const qreal xOffset = horizontalOffset();
3099 
3100 	#ifdef Q_GL_EDITOR
3101 	//QRect r(e->rect());
3102 	QRect r(0, 0, viewport()->width(), viewport()->height());
3103 	#else
3104     //QRect r(e->rect());
3105 	#endif
3106 
3107     //qDebug() << r << yOffset;
3108 
3109 	//p.setClipping(false);
3110     p.translate(QPointF(-xOffset, -yOffset));
3111 
3112 	QDocument::PaintContext ctx;
3113 	ctx.xoffset = xOffset;
3114     ctx.yoffset = yOffset;// + ry;//r.y();
3115 	ctx.width = viewport()->width();
3116     ctx.height = viewport()->height();//qMin(r.height(), viewport()->height());
3117 	ctx.palette = palette();
3118 	if (m_cursor.isValid())
3119 		ctx.cursors << m_cursor.handle();
3120 	ctx.fillCursorRect = true;
3121 	ctx.blinkingCursor = flag(CursorOn);
3122 
3123 	if ( m_cursor.hasSelection() )
3124 	{
3125 		//qDebug("atempting to draw selected text");
3126 		QDocumentSelection s = m_cursor.selection();
3127 
3128 		ctx.selections << s;
3129 	}
3130 
3131 	// cursor mirrors :D
3132 	foreach ( const QDocumentCursor& m, m_mirrors )
3133 	{
3134 		if ( ctx.blinkingCursor )
3135 			ctx.extra << m.handle();
3136 
3137 		if ( m.hasSelection() )
3138 		{
3139 			QDocumentSelection s = m.selection();
3140 
3141 			ctx.selections << s;
3142 		}
3143 	}
3144 
3145 	for (int i=0;i<ctx.selections.size();i++) {
3146 		if (ctx.selections[i].start < 0) ctx.selections[i].start = 0;
3147 		if (ctx.selections[i].end < 0) ctx.selections[i].end = 0;
3148 	}
3149 
3150 	if ( m_dragAndDrop.isValid() )
3151 	{
3152 		ctx.extra << m_dragAndDrop.handle();
3153 	}
3154 	if (flag(ShowPlaceholders)) {
3155 		// put placeholder info into paint context
3156 		ctx.placeHolders=m_placeHolders;
3157 		ctx.curPlaceHolder=m_curPlaceHolder;
3158 		ctx.lastPlaceHolder=m_lastPlaceHolder;
3159 	}
3160         //qDebug("elapsed %d ms",tm.elapsed());
3161 
3162 	// fill all the background, including areas on which the document does not draw
3163 	// (e.g. left/right margins and the bottom of the viewport if it is larger than the document)
3164 	QBrush bg = palette().base();
3165 	if ( m_doc->getBackground().isValid() )
3166 		bg.setColor(m_doc->getBackground());
3167     qreal width = qMax(viewport()->width(), qCeil(m_doc->width()));
3168     qreal height = qMax(viewport()->height(), qCeil(m_doc->height() + m_doc->getLineSpacing()));
3169 	// the actual visible height may be up to one line larger than the doc height,
3170 	// because the doc lines is are aligned to the top of the viewport. The viewport
3171 	// then shows n.x lines and when scolled to the very bottom, the .x < 1 line
3172 	// exceeds the document height.
3173     const QRectF rect(0, 0, width, height);
3174     p.fillRect(rect, bg);
3175 
3176 	p.save();
3177 	m_doc->draw(&p, ctx);
3178 	p.restore();
3179         //qDebug("drawn %d ms",tm.elapsed());
3180 
3181 	//TODO: Customizable appearance
3182 	//TODO: documentRegion is too large, isn't correctly redrawn (especially with a non fixed width font)
3183 
3184 
3185 	/*
3186 	debug code for cursor direction:
3187 	p.setPen(QColor(0,128,0));
3188 	for (int i=0; i < m_placeHolders.count(); i++) {
3189 		p.drawConvexPolygon(m_placeHolders[i].cursor.selectionStart().documentRegion());
3190 		foreach (const QDocumentCursor& m, m_placeHolders[i].mirrors )
3191 			p.drawConvexPolygon(m.selectionStart().documentRegion());
3192 	}*/
3193 
3194 }
3195 
3196 /*!
3197 	\internal
3198 */
timerEvent(QTimerEvent * e)3199 void QEditor::timerEvent(QTimerEvent *e)
3200 {
3201 	int id = e->timerId();
3202 
3203 	if ( id == m_blink.timerId() )
3204     {
3205 		bool on = !flag(CursorOn);
3206 
3207 		if ( m_cursor.hasSelection() )
3208 			on &= style()->styleHint(QStyle::SH_BlinkCursorWhenTextSelected,
3209                                     nullptr,
3210 									this) != 0;
3211 
3212 		setFlag(CursorOn, on);
3213 
3214 		repaintCursor();
3215 
3216 		if(m_cursor.lineNumber()==m_lastLine && m_cursor.columnNumber()==m_lastColumn){
3217 		    m_hoverCount++;
3218 		    if(m_hoverCount==2) emit cursorHovered();
3219 		}else{
3220 		    m_lastLine=m_cursor.lineNumber();
3221 		    m_lastColumn=m_cursor.columnNumber();
3222 		    m_hoverCount=0;
3223 		}
3224 
3225 	} else if ( id == m_drag.timerId() ) {
3226 		m_drag.stop();
3227 		//startDrag();
3228 	} else if ( id == m_click.timerId() ) {
3229 		m_click.stop();
3230 	} else if ( id == m_autoScroll.timerId() ) {
3231 		const QPoint cPos = viewport()->mapFromGlobal(QCursor::pos());
3232 		const QPoint mousePos = mapToContents(cPos);
3233 
3234 		QDocumentCursor newCursor = cursorForPosition(mousePos);
3235 
3236 		if ( newCursor.isNull() ) {
3237 			newCursor = QDocumentCursor(m_doc, 0, 0);
3238 			if( mousePos.x() >= 0 ) {
3239 				newCursor.movePosition( 1, QDocumentCursor::End );
3240 			}
3241 		}
3242 
3243 		if (m_multiClickCursor.isValid()) {
3244 			m_cursor.select(m_multiClickCursor.lineNumber(), m_multiClickCursor.columnNumber(),
3245 			                newCursor.lineNumber(), newCursor.columnNumber()
3246 			                );
3247 			m_cursor.expandSelect(m_multiClickCursor.property("isTripleClick").toBool() ? QDocumentCursor::LineUnderCursor : m_doubleClickSelectionType);
3248 		} else {
3249 			m_cursor.setSelectionBoundary(newCursor);
3250 		}
3251 
3252 		ensureCursorVisible();
3253 		//emit clearAutoCloseStack();
3254 		emitCursorPositionChanged();
3255 
3256 		repaintCursor();
3257 	}
3258 }
3259 
max(const QList<QDocumentCursor> & l)3260 static int max(const QList<QDocumentCursor>& l)
3261 {
3262 	int ln = 0;
3263 
3264 	foreach ( const QDocumentCursor& c, l )
3265 		if ( c.lineNumber() > ln )
3266 			ln = c.lineNumber();
3267 
3268 	return ln;
3269 }
3270 
min(const QList<QDocumentCursor> & l)3271 static int min(const QList<QDocumentCursor>& l)
3272 {
3273 	// beware the sign bit...
3274 	int ln = 0x7fffffff;
3275 
3276 	foreach ( const QDocumentCursor& c, l )
3277 		if ( (c.lineNumber() < ln) || (ln < 0) )
3278 			ln = c.lineNumber();
3279 
3280 	return ln;
3281 }
3282 
protectedCursor(const QDocumentCursor & c) const3283 bool QEditor::protectedCursor(const QDocumentCursor& c) const
3284 {
3285         Q_UNUSED(c)
3286 	/*if ( c.hasSelection() )
3287 	{
3288 		int line = qMin(c.lineNumber(), c.anchorLineNumber()), end = qMax(c.lineNumber(), c.anchorLineNumber());
3289 		return document()->linesPartiallyFolded(line,end);
3290 	} else {
3291 		return m_doc->line(c.lineNumber()).hasAnyFlag(QDocumentLine::Hidden | QDocumentLine::CollapsedBlockStart | QDocumentLine::CollapsedBlockEnd);
3292 	}*/
3293 
3294 	/*if (!isProtected) return false;
3295 	if (!c.hasSelection()) return c.line().hasFlag(QDocumentLine::Hidden);
3296 	return c.line().hasFlag(QDocumentLine::Hidden) || c.anchorLine().hasFlag(QDocumentLine::Hidden);*/
3297 	return false;
3298 }
3299 
3300 /*!
3301 	\internal
3302 */
keyPressEvent(QKeyEvent * e)3303 void QEditor::keyPressEvent(QKeyEvent *e)
3304 {
3305         //tm.start();
3306      //qDebug()<<"pressed"<<QTime::currentTime().toString("HH:mm:ss:zzz");
3307 	// reset hover counter
3308 	m_hoverCount=-1;
3309 
3310 	foreach ( QEditorInputBindingInterface *b, m_bindings )
3311 		if ( b->keyPressEvent(e, this) )
3312 			return;
3313 
3314 	EditOperation op = getEditOperation(e->modifiers(), (Qt::Key)e->key());
3315 	bool handled = op != NoOperation;
3316 	if (op >= EnumForCursorStart && op <= EnumForCursorEnd ){
3317 		e->accept();
3318 
3319 		if (m_mirrors.count()) {
3320 			int maxSelect = 0;
3321 			for (int i = -1; i < m_mirrors.count(); i++) {
3322 				QDocumentCursor &cur = (i==-1) ? m_cursor : m_mirrors[i];
3323 				// TODO can it happen with mirrors that the selection is across different lines?
3324 				int len = cur.hasSelection() ? cur.selection().end-cur.selection().start : 0;
3325 				if (len > maxSelect) maxSelect = len;
3326 			}
3327 
3328 			bool leftLine = false;
3329 			for ( int i = -1; !leftLine && (i < m_mirrors.count()); ++i ) {
3330 				QDocumentCursor &cur = (i==-1) ? m_cursor : m_mirrors[i];
3331 				int curLine = cur.lineNumber();
3332 				// handle unequal line lenghts
3333 				if ((op == SelectCursorRight || op == SelectCursorWordRight) && cur.atLineEnd()) continue;
3334 				if (op == SelectCursorLeft) {
3335 					// TODO can it happen with mirrors that the selection is across different lines?
3336 					int len = cur.hasSelection() ? cur.selection().end-cur.selection().start : 0;
3337 					if (len < maxSelect) continue;
3338 				}
3339 				cursorMoveOperation(cur, op);
3340 				leftLine = cur.lineNumber() != curLine;
3341                 if( op == CursorEndOfLine || op == CursorStartOfLine)
3342                     leftLine = true;
3343 			}
3344 
3345 			if ( leftLine || (m_curPlaceHolder >= 0 && m_curPlaceHolder < m_placeHolders.size() && m_placeHolders[m_curPlaceHolder].autoRemoveIfLeft && !m_placeHolders[m_curPlaceHolder].cursor.isWithinSelection(m_cursor)))
3346 				setPlaceHolder(-1);
3347 			if (leftLine) {
3348 				clearCursorMirrors();
3349 				viewport()->update();
3350 			} else {
3351 				repaintCursor();
3352 			}
3353 		} else {
3354 			// normal single cursor movement
3355 			cursorMoveOperation(m_cursor, op);
3356 
3357 			//setFlag(CursorOn, true);
3358 			//ensureCursorVisible();
3359 
3360 			if ( m_curPlaceHolder >= 0 && m_curPlaceHolder < m_placeHolders.size() && m_placeHolders[m_curPlaceHolder].autoRemoveIfLeft && !m_placeHolders[m_curPlaceHolder].cursor.isWithinSelection(m_cursor))
3361 				setPlaceHolder(-1);
3362 				/*if ( m_curPlaceHolder >= 0 && m_curPlaceHolder < m_placeHolders.count() )
3363 				{
3364 					// allow mirror movement out of line while in placeholder
3365 					PlaceHolder& ph = m_placeHolders[m_curPlaceHolder];
3366 					if ( ph.cursor.isWithinSelection(cursor) )
3367 						return true;
3368 					for ( int i = 0; i < ph.mirrors.count(); ++i )
3369 						if ( ph.mirrors.at(i).isWithinSelection(cursor) )
3370 							return true;
3371 					if ( prev == cursor.lineNumber() && m_mirrors.empty()) {
3372 						//mark placeholder as leaved
3373 						m_curPlaceHolder = -1;
3374 						return false;
3375 					}
3376 				}*/
3377 			repaintCursor();
3378 		}
3379 	} else switch (op) {
3380 	case Invalid:
3381 		QApplication::beep();
3382 		break;
3383 	case CreateMirrorUp: case CreateMirrorDown:{
3384 		int ln = - 1;
3385 		QDocumentLine l;
3386 
3387 		if (op == CreateMirrorUp )
3388 		{
3389 			ln = m_cursor.lineNumber();
3390 
3391 			if ( m_mirrors.count() )
3392 				ln = qMin(ln, min(m_mirrors));
3393 
3394 			//qDebug("first %i", ln);
3395 
3396 			l = m_doc->line(--ln);
3397 		} else if ( op == CreateMirrorDown ) {
3398 			ln = m_cursor.lineNumber();
3399 
3400 			if ( m_mirrors.count() )
3401 				ln = qMax(ln, max(m_mirrors));
3402 
3403 			l = m_doc->line(++ln);
3404 		}
3405 
3406 		if ( l.isValid() )
3407 		{
3408 			addCursorMirror(QDocumentCursor(m_doc, ln, m_cursor.anchorColumnNumber()));
3409 			repaintCursor();
3410 			emitCursorPositionChanged();
3411 
3412 			break;
3413 		}
3414 		break;
3415 	}
3416 
3417 	case ChangeOverwrite:
3418 		setFlag(Overwrite, !flag(Overwrite));
3419 		if(m_doc)
3420 		    m_doc->setOverwriteMode(flag(Overwrite));
3421 
3422 		// hack to make sure status panel gets updated...
3423 		// TODO : emit signals on flag change?
3424 		emitCursorPositionChanged();
3425 		break;
3426 
3427 
3428 	case NextPlaceHolder: nextPlaceHolder(); break;
3429 	case PreviousPlaceHolder: previousPlaceHolder(); break;
3430 
3431 	case TabOrIndentSelection: tabOrIndentSelection(); break;
3432 	case InsertTab: insertTab(); break;
3433 	case IndentSelection: indentSelection(); break;
3434 	case UnindentSelection: unindentSelection(); break;
3435 
3436 	case Undo: undo(); break;
3437 	case Redo: redo(); break;
3438 	case Copy: copy(); break;
3439 	case Paste: paste(); break;
3440 	case Cut: cut(); break;
3441 	case Print: print();break;
3442 	case SelectAll: selectAll(); break;
3443 	case Find: find(); break;
3444 	case FindNext: findNext(); break;
3445 	case FindPrevious: findPrev(); break;
3446 	case Replace: replacePanel(); break;
3447 
3448 	case NoOperation:
3449 	case DeleteLeft:
3450 	case DeleteRight:
3451 	case NewLine:
3452 	default: {
3453 		bool bOk = true;
3454 
3455 		if (op == NoOperation) {
3456 			QString text = e->text();
3457 
3458 #if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
3459             if(e->modifiers()&(Qt::MetaModifier|Qt::ControlModifier))
3460                 break;
3461 #endif
3462             if ( text.isEmpty())
3463 				break;
3464             QChar c=text.at(0);
3465             if(!c.isPrint() && !(c.category()==QChar::Other_Format))
3466                 break;
3467             if(c==QChar('\t'))
3468                 break;
3469             if(text.length()>1){
3470                 // assume bug like #1992
3471                 break;
3472             }
3473 		}
3474 
3475 		bool prot = protectedCursor(m_cursor);
3476 
3477 		foreach ( const QDocumentCursor& c, m_mirrors )
3478 			prot |= protectedCursor(c);
3479 
3480 
3481 		if ( prot ) break;
3482 
3483 		handled = true;
3484 
3485 		// clear matches to avoid offsetting and subsequent remanence of matches
3486 		if ( m_definition )
3487 			m_definition->clearMatches(m_doc);
3488 
3489 		bool macroing = isMirrored() || m_mirrors.count() > 0;
3490 
3491 		if ( macroing )
3492 			m_doc->beginMacro();
3493 
3494 		//TODO: blocked key
3495 		if(!m_blockKey)
3496 			processEditOperation(m_cursor, e, op);
3497 		else
3498 			m_blockKey=false;
3499 
3500 		if ( atPlaceholder() ) // need new evaluation, because edit operation might have changed things
3501 		{
3502 			PlaceHolder& ph = m_placeHolders[m_curPlaceHolder];
3503 
3504 			QString baseText = ph.cursor.selectedText();
3505 
3506 			for ( int phm = 0; phm < ph.mirrors.count(); ++phm )
3507 			{
3508 				QString s = ph.affector ?  ph.affector->affect(e, baseText, m_curPlaceHolder, phm) : baseText;
3509 
3510 				ph.mirrors[phm].replaceSelectedText(s);
3511 			}
3512 		}
3513 
3514 		if ( m_mirrors.isEmpty() )
3515 		{
3516 			// this signal is NOT emitted when cursor mirrors are used ON PURPOSE
3517 			// as it is the "standard" entry point for code completion, which cannot
3518 			// work properly with cursor mirrors (art least not always and not simply)
3519 			if ( handled )
3520 				emit textEdited(e);
3521 
3522 		} else {
3523 
3524 			for ( int i = 0; bOk && (i < m_mirrors.count()); ++i )
3525 				processEditOperation(m_mirrors[i], e, op);
3526 
3527 		}
3528 
3529 		if ( macroing )
3530 			m_doc->endMacro();
3531 
3532 	}
3533 
3534 	}
3535 
3536 
3537 
3538 	if ( !handled)
3539 	{
3540         QAbstractScrollArea::keyPressEvent(e);
3541 
3542 		foreach ( QEditorInputBindingInterface *b, m_bindings )
3543 			b->postKeyPressEvent(e, this);
3544 		return;
3545 	}
3546 
3547 	e->accept();
3548 	emitCursorPositionChanged();
3549 	setFlag(CursorOn, true);
3550 	ensureCursorVisible();
3551 #ifdef  Q_OS_MAC
3552 	//repaintCursor(); // squeeze for a little speed
3553 	if (QApplication::cursorFlashTime() > 0) {
3554 		m_blink.start(QApplication::cursorFlashTime() / 2, this);
3555 	}
3556 #else
3557 	repaintCursor();
3558 #endif
3559 
3560 	foreach ( QEditorInputBindingInterface *b, m_bindings )
3561 		b->postKeyPressEvent(e, this);
3562 }
3563 
3564 /*!
3565 	\internal
3566 */
keyReleaseEvent(QKeyEvent * e)3567 void QEditor::keyReleaseEvent(QKeyEvent *e)
3568 {
3569 	foreach ( QEditorInputBindingInterface *b, m_bindings )
3570 		if ( b->keyReleaseEvent(e, this) )
3571 			return;
3572 
3573 	// currently the editor is doing nothing on KeyRelease
3574 
3575 	foreach ( QEditorInputBindingInterface *b, m_bindings )
3576 		b->postKeyReleaseEvent(e, this);
3577 }
3578 
3579 /*!
3580 	\internal
3581 */
inputMethodEvent(QInputMethodEvent * e)3582 void QEditor::inputMethodEvent(QInputMethodEvent* e)
3583 {
3584     foreach ( QEditorInputBindingInterface *b, m_bindings )
3585 		if ( b->inputMethodEvent(e, this) )
3586 			return;
3587 
3588 	/*
3589 	if ( m_doc->readOnly() )
3590 	{
3591 		e->ignore();
3592 		return;
3593 	}
3594 	*/
3595 //#ifdef Q_OS_MAC
3596     QString preEdit=e->preeditString();
3597     if( !preEdit.isEmpty()){
3598         int i=-1;
3599         m_cursor.beginEditBlock();
3600         if(preEditSet){
3601             i=preEditColumnNumber;
3602             m_cursor.movePosition(preEditLength,QDocumentCursor::Left,QDocumentCursor::KeepAnchor);
3603         }else{
3604             i=qMin(m_cursor.columnNumber(), m_cursor.anchorColumnNumber());
3605         }
3606         m_cursor.insertText(preEdit);
3607         m_cursor.line().addOverlay(QFormatRange(i,preEdit.length(),m_preEditFormat));
3608         m_cursor.endEditBlock();
3609         preEditSet=true;
3610         preEditColumnNumber=i;
3611         preEditLength=preEdit.length();
3612         preEditLineNumber=m_cursor.lineNumber();
3613         updateMicroFocus();
3614     }
3615 //#endif
3616 
3617 	if ( e->commitString().count() ) {
3618 		m_cursor.beginEditBlock();
3619         if(preEditSet){
3620             preEditSet=false;
3621             if(m_cursor.lineNumber()==preEditLineNumber &&
3622                m_cursor.columnNumber()==preEditColumnNumber+preEditLength
3623             ){
3624                 m_cursor.movePosition(preEditLength,QDocumentCursor::Left,QDocumentCursor::KeepAnchor);
3625                 m_cursor.removeSelectedText();
3626             }
3627         }else{
3628 #ifdef Q_OS_MAC
3629             // work-around for bug 1723
3630             // needs to handle chinese punctuation here, so filter only special, non-printable chars
3631             // see bug 1770
3632 
3633             if(e->commitString().count()==1){
3634                 ushort code=e->commitString().at(0).unicode();
3635                 if(code<32)
3636                     return;
3637             }
3638 #endif
3639         }
3640 
3641         m_cursor.insertText(e->commitString());
3642 
3643 		m_cursor.endEditBlock();
3644 	}
3645 
3646     if( preEdit.isEmpty() && e->commitString().isEmpty() && preEditSet) {
3647         m_cursor.beginEditBlock();
3648         m_cursor.movePosition(preEditLength, QDocumentCursor::Left, QDocumentCursor::KeepAnchor);
3649         m_cursor.removeSelectedText();
3650         m_cursor.endEditBlock();
3651         preEditSet = false;
3652         preEditLength = 0;
3653     }
3654 
3655 	foreach ( QEditorInputBindingInterface *b, m_bindings )
3656 		b->postInputMethodEvent(e, this);
3657 
3658     e->accept();
3659 }
3660 
3661 /*!
3662 	\internal
3663 */
mouseMoveEvent(QMouseEvent * e)3664 void QEditor::mouseMoveEvent(QMouseEvent *e)
3665 {
3666 	foreach ( QEditorInputBindingInterface *b, m_bindings )
3667 		if ( b->mouseMoveEvent(e, this) )
3668 			return;
3669 
3670 	forever
3671 	{
3672 		if ( !(e->buttons() & Qt::LeftButton) )
3673 			break;
3674 
3675 		if ( !( flag(MousePressed) || m_multiClickCursor.hasSelection() ) )
3676 			break;
3677 
3678 		if ( flag(MaybeDrag) )
3679 		{
3680 			m_drag.stop();
3681 
3682 			if (	(e->globalPos() - m_dragPoint).manhattanLength() >
3683 					QApplication::startDragDistance()
3684 				)
3685 				startDrag();
3686 
3687 			//emit clearAutoCloseStack();
3688 			break;
3689 		}
3690 
3691 		repaintCursor();
3692 
3693 		if( !(viewport()->rect().contains(e->pos())) ) {
3694 			//don't accelerate scrolling just because the mouse is moving
3695 			if( m_autoScroll.isActive() ) break;
3696 			m_autoScroll.start(33, this);
3697 		} else {
3698 			m_autoScroll.stop();
3699 		}
3700 
3701 		const QPoint mousePos = mapToContents(e->pos());
3702 
3703 		QDocumentCursor newCursor = cursorForPosition(mousePos);
3704 
3705 		if ( newCursor.isNull() )
3706 			break;
3707 
3708 		if ( e->modifiers() & Qt::ControlModifier )
3709 		{
3710 			selectCursorMirrorBlock(newCursor, e->modifiers() & Qt::ShiftModifier);
3711 		} else {
3712 			if (m_multiClickCursor.isValid()) {
3713 				m_cursor.select(m_multiClickCursor.lineNumber(), m_multiClickCursor.columnNumber(),
3714 				                newCursor.lineNumber(), newCursor.columnNumber()
3715 				                );
3716 				m_cursor.expandSelect(m_multiClickCursor.property("isTripleClick").toBool() ? QDocumentCursor::LineUnderCursor : m_doubleClickSelectionType);
3717 			} else {
3718 				m_cursor.setSelectionBoundary(newCursor);
3719 			}
3720 		}
3721 
3722 		ensureCursorVisible();
3723 		//emit clearAutoCloseStack();
3724 		emitCursorPositionChanged();
3725 
3726 		repaintCursor();
3727 		break;
3728 	}
3729 
3730 	foreach ( QEditorInputBindingInterface *b, m_bindings )
3731 		b->postMouseMoveEvent(e, this);
3732 
3733 }
3734 
3735 /*!
3736 	\internal
3737 */
mousePressEvent(QMouseEvent * e)3738 void QEditor::mousePressEvent(QMouseEvent *e)
3739 {
3740 	foreach ( QEditorInputBindingInterface *b, m_bindings )
3741 		if ( b->mousePressEvent(e, this) )
3742 			return;
3743 
3744 	forever
3745 	{
3746 		if ( !(e->buttons() & Qt::LeftButton) )
3747 			break;
3748 
3749 		QPoint p = mapToContents(e->pos());
3750 
3751 		setFlag(MousePressed, true);
3752 		setFlag(MaybeDrag, false);
3753 
3754 		repaintCursor();
3755 
3756 		if ( m_click.isActive() &&
3757 			(( e->globalPos() - m_clickPoint).manhattanLength() <
3758 				QApplication::startDragDistance() ))
3759 		{
3760 			m_multiClickCursor = m_cursor;
3761 			m_multiClickCursor.clearSelection();  // just store the click position
3762 			m_multiClickCursor.setProperty("isTripleClick", true);
3763 			m_cursor.select(m_tripleClickSelectionType);
3764 			m_click.stop();
3765 		} else {
3766 			QDocumentCursor cursor = cursorForPosition(p);
3767 
3768 			if (!cursor.isValid() && !cursor.line().isValid() && cursor.lineNumber() > 0) {
3769 				// clicked beyond end of doc: lineNumber is last line + 1
3770 				// move cursor to end of doc.
3771 				cursor.setLineNumber(cursor.lineNumber() - 1);
3772 				cursor.movePosition(1, QDocumentCursor::EndOfLine);
3773 			}
3774 
3775 			if ( cursor.isNull() )
3776 				break;
3777 
3778 			if ( e->modifiers() == Qt::ShiftModifier )
3779 			{
3780 				clearCursorMirrors();
3781 				m_cursor.setSelectionBoundary(cursor);
3782 			} else if ( e->modifiers() & Qt::ControlModifier && ((e->modifiers() & Qt::ShiftModifier) || (e->modifiers() & Qt::AltModifier)) ) {
3783 				//m_mirrors << cursor;
3784 				if ( e->modifiers() & Qt::ShiftModifier )
3785 				{
3786 					selectCursorMirrorBlock(cursor, true);
3787 				} else if ( (e->modifiers() & Qt::AltModifier) )
3788 				{
3789 					//remove existing mirrors if one is at the same position
3790 					if ( m_cursor.isWithinSelection(cursor) || m_cursor.equal(cursor) )
3791 					{
3792 						if ( m_mirrors.size() )
3793 						{
3794 							m_cursor = m_mirrors.takeFirst();
3795 							if (m_cursorMirrorBlockAnchor >= 0)
3796 								m_cursorMirrorBlockAnchor--;
3797 						} else
3798 							setCursor(cursor);
3799 						break;
3800 					} else {
3801 						bool removedExisting = false;
3802 						for ( int i = 0; i < m_mirrors.size(); i++ )
3803 							if ( m_mirrors[i].isWithinSelection(cursor) || m_mirrors[i].equal(cursor) )
3804 							{
3805 								m_mirrors.removeAt(i);
3806 								removedExisting = true;
3807 								if (m_cursorMirrorBlockAnchor >= i)
3808 									m_cursorMirrorBlockAnchor--;
3809 								break;
3810 							}
3811 						if ( removedExisting ) break;
3812 					}
3813 					m_cursorMirrorBlockAnchor = m_mirrors.size();
3814 					addCursorMirror(cursor);
3815 				}
3816 			} else {
3817 
3818 				const QDocumentCursor& cur = m_cursor;
3819 
3820 				if ( m_cursor.hasSelection() )
3821 				{
3822 					bool inSel = cur.isWithinSelection(cursor);
3823 
3824 					if ( !inSel )
3825 					{
3826 						foreach ( const QDocumentCursor& m, m_mirrors )
3827 						{
3828 							inSel = m.isWithinSelection(cursor);
3829 
3830 							if ( inSel )
3831 								break;
3832 						}
3833 					}
3834 
3835 					if ( inSel && flag(AllowDragAndDrop) )
3836 					{
3837 						setFlag(MaybeDrag, true);
3838 
3839 						m_dragPoint = e->globalPos();
3840 						m_drag.start(QApplication::startDragTime(), this);
3841 
3842 						break;
3843 					}
3844 				}
3845 
3846 				m_multiClickCursor = QDocumentCursor();
3847 				cutBuffer.clear();
3848 				setCursor(cursor);
3849 				break;
3850 			}
3851 		}
3852 
3853 		ensureCursorVisible();
3854 		//emit clearAutoCloseStack();
3855 		cutBuffer.clear();
3856 		emitCursorPositionChanged();
3857 		repaintCursor();
3858 		break;
3859 	}
3860 
3861 	foreach ( QEditorInputBindingInterface *b, m_bindings )
3862 		b->postMousePressEvent(e, this);
3863 
3864 }
3865 
3866 /*!
3867 	\internal
3868 */
mouseReleaseEvent(QMouseEvent * e)3869 void QEditor::mouseReleaseEvent(QMouseEvent *e)
3870 {
3871 	foreach ( QEditorInputBindingInterface *b, m_bindings )
3872 		if ( b->mouseReleaseEvent(e, this) )
3873 			return;
3874 
3875 	m_autoScroll.stop( );
3876 	repaintCursor();
3877 
3878 	if ( flag(MaybeDrag) )
3879 	{
3880 		setFlag(MousePressed, false);
3881 		setCursorPosition(mapToContents(e->pos()));
3882 
3883 			m_cursor.clearSelection();
3884 	}
3885 
3886 	if ( flag(MousePressed) )
3887 	{
3888 		setFlag(MousePressed, false);
3889 
3890 		setClipboardSelection();
3891     } else if (	e->button() == Qt::MiddleButton
3892 				&& QApplication::clipboard()->supportsSelection()) {
3893 		setCursorPosition(mapToContents(e->pos()));
3894 		//setCursorPosition(viewport()->mapFromGlobal(e->globalPos()));
3895 
3896 		const QMimeData *md = QApplication::clipboard()
3897 								->mimeData(QClipboard::Selection);
3898 
3899 		if ( md )
3900 			insertFromMimeData(md);
3901 	}
3902 
3903 	repaintCursor();
3904 
3905 	if ( m_drag.isActive() )
3906 		m_drag.stop();
3907 
3908 	foreach ( QEditorInputBindingInterface *b, m_bindings )
3909 		b->postMouseReleaseEvent(e, this);
3910 
3911 }
3912 
3913 /*!
3914 	\internal
3915 */
mouseDoubleClickEvent(QMouseEvent * e)3916 void QEditor::mouseDoubleClickEvent(QMouseEvent *e)
3917 {
3918 	foreach ( QEditorInputBindingInterface *b, m_bindings )
3919 		if ( b->mouseDoubleClickEvent(e, this) )
3920 			return;
3921 
3922 	forever
3923 	{
3924 		if ( e->button() != Qt::LeftButton )
3925 		{
3926 			e->ignore();
3927 			break;
3928 		}
3929 
3930 		setFlag(MousePressed, true);
3931 		setFlag(MaybeDrag, false);
3932 
3933 		repaintCursor();
3934 		clearCursorMirrors();
3935 		setCursorPosition(mapToContents(e->pos()));
3936 
3937 		m_multiClickCursor = m_cursor;
3938 		m_multiClickCursor.clearSelection();  // just store the click position
3939 		m_multiClickCursor.setProperty("isTripleClick", false);
3940 
3941 		if ( m_cursor.isValid() )
3942 		{
3943 			m_cursor.select(m_doubleClickSelectionType);
3944 
3945 			setClipboardSelection();
3946 			//emit clearAutoCloseStack();
3947 			emitCursorPositionChanged();
3948             emit emitWordDoubleClicked();
3949 
3950 			repaintCursor();
3951 		} else {
3952 			//qDebug("invalid cursor");
3953 		}
3954 
3955 		m_clickPoint = e->globalPos();
3956 		m_click.start(qApp->doubleClickInterval(), this);
3957 		break;
3958 	}
3959 
3960 	foreach ( QEditorInputBindingInterface *b, m_bindings )
3961 		b->postMouseDoubleClickEvent(e, this);
3962 
3963 }
3964 
3965 /*!
3966 	\internal
3967 */
dragEnterEvent(QDragEnterEvent * e)3968 void QEditor::dragEnterEvent(QDragEnterEvent *e)
3969 {
3970 	if (
3971 			e
3972 		&&
3973 			e->mimeData()
3974 		&&
3975 			(
3976 				e->mimeData()->hasFormat("text/plain")
3977 			||
3978 				e->mimeData()->hasFormat("text/html")
3979 			)
3980 		&&
3981 			!e->mimeData()->hasFormat("text/uri-list")
3982 		)
3983 		e->acceptProposedAction();
3984 	else
3985 		return;
3986 
3987 	m_dragAndDrop = QDocumentCursor();
3988 }
3989 
3990 /*!
3991 	\internal
3992 */
dragLeaveEvent(QDragLeaveEvent *)3993 void QEditor::dragLeaveEvent(QDragLeaveEvent *)
3994 {
3995     const QRectF crect = cursorRect(m_dragAndDrop);
3996 	m_dragAndDrop = QDocumentCursor();
3997 
3998 	if ( crect.isValid() )
3999         viewport()->update(crect.toRect());
4000 
4001 }
4002 
4003 /*!
4004 	\internal
4005 */
dragMoveEvent(QDragMoveEvent * e)4006 void QEditor::dragMoveEvent(QDragMoveEvent *e)
4007 {
4008 	if (
4009 			e
4010 		&&
4011 			e->mimeData()
4012 		&&
4013 			(
4014 				e->mimeData()->hasFormat("text/plain")
4015 			||
4016 				e->mimeData()->hasFormat("text/html")
4017 			)
4018 		&&
4019 			!e->mimeData()->hasFormat("text/uri-list")
4020 		)
4021 		e->acceptProposedAction();
4022 	else
4023 		return;
4024 
4025     QDocumentCursor c = cursorForPosition(mapToContents(e->pos()));
4026 
4027 	if ( c.isValid() )
4028 	{
4029         QRectF crect = cursorRect(m_dragAndDrop);
4030 
4031 		if ( crect.isValid() )
4032             viewport()->update(crect.toRect());
4033 
4034 		m_dragAndDrop = c;
4035 
4036 		// workaround for dragging
4037 		// TODO 1: Exchange the use of KeepSurrounding for a check
4038 		//         int lineSpacing = this->document()->getLineSpacing();
4039 		//         if (e->pos().y() < lineSpacing || e->pos().y() > (height() - 2*lineSpacing))
4040 		// TODO 2: The event is only fired when the mouse is moved, but scrolling should continue
4041 		//         while dragging if the mouse is close to the border and not moved further
4042 		//         (maybe even at different speeds depending on the distance to the border
4043 		int backup = m_cursorSurroundingLines;
4044 		m_cursorSurroundingLines = 1;  // restricting to 1 & KeepSurrounding is almost like scrolling
4045 		ensureCursorVisible(m_dragAndDrop, KeepSurrounding);
4046 		m_cursorSurroundingLines = backup;
4047 
4048 		crect = cursorRect(m_dragAndDrop);
4049         viewport()->update(crect.toRect());
4050 	}
4051 
4052 	//e->acceptProposedAction();
4053 }
4054 
4055 /*!
4056 	\internal
4057 */
dropEvent(QDropEvent * e)4058 void QEditor::dropEvent(QDropEvent *e)
4059 {
4060 	m_dragAndDrop = QDocumentCursor();
4061 
4062 	QDocumentCursor c(cursorForPosition(mapToContents(e->pos())));
4063 
4064     if ( (e->source() == this) && (m_cursor.isWithinSelection(c)) ){
4065         e->setDropAction(Qt::CopyAction);
4066         e->accept();
4067 		return;
4068     }
4069 
4070 	if (
4071 			e
4072 		&&
4073 			e->mimeData()
4074 		&&
4075 			(
4076 				e->mimeData()->hasFormat("text/plain")
4077 			||
4078 				e->mimeData()->hasFormat("text/html")
4079 			)
4080 		&&
4081 			!e->mimeData()->hasFormat("text/uri-list")
4082 		)
4083 	{
4084 		e->acceptProposedAction();
4085 	} else {
4086 		return;
4087 	}
4088 
4089 	//repaintSelection();
4090 
4091 	//m_doc->beginMacro();
4092 	//m_cursor.beginEditBlock();
4093     QDocumentCursor insertCursor = cursorForPosition(mapToContents(e->pos()));
4094 
4095 	if (
4096 			(e->dropAction() == Qt::MoveAction)
4097 		&&
4098 			(
4099 				(e->source() == this)
4100 			||
4101 				(e->source() == viewport())
4102 			)
4103 		)
4104 	{
4105 		insertCursor.setAutoUpdated(true);
4106 		m_doc->beginMacro();
4107 
4108 		m_cursor.removeSelectedText();
4109 		cursorMirrorsRemoveSelectedText();
4110 
4111 		clearCursorMirrors();
4112 		m_cursor=insertCursor;//.moveTo(cursorForPosition(mapToContents(e->pos())));
4113 		insertFromMimeData(e->mimeData());
4114 
4115 		m_doc->endMacro();
4116 		insertCursor.setAutoUpdated(false);
4117 	} else {
4118 		//qDebug("action : %i", e->dropAction());
4119 		m_cursor.clearSelection();
4120 		clearCursorMirrors();
4121 		m_cursor=insertCursor;//.moveTo(cursorForPosition(mapToContents(e->pos())));
4122 		insertFromMimeData(e->mimeData());
4123 	}
4124 
4125 	//m_cursor.endEditBlock();
4126 
4127 	//m_doc->endMacro();
4128 }
4129 
4130 /*!
4131 	\internal
4132 */
changeEvent(QEvent * e)4133 void QEditor::changeEvent(QEvent *e)
4134 {
4135 	QAbstractScrollArea::changeEvent(e);
4136 
4137 	if (
4138 			e->type() == QEvent::ApplicationFontChange
4139 		||
4140 			e->type() == QEvent::FontChange
4141 		)
4142 	{
4143 		if ( !m_doc )
4144 			return;
4145 
4146         m_doc->setBaseFont(font());
4147 		//setTabStop(iTab);
4148 
4149 	}
4150 }
4151 
4152 /*!
4153 	\internal
4154 */
showEvent(QShowEvent * e)4155 void QEditor::showEvent(QShowEvent *e)
4156 {
4157 	QCodeEdit *ce = QCodeEdit::manager(this);
4158 
4159 	if ( ce )
4160 		ce->panelLayout()->update();
4161 
4162 	QAbstractScrollArea::showEvent(e);
4163 
4164 	if ( flag(HardLineWrap)||flag(LineWidthConstraint) )
4165 		m_doc->setWidthConstraint( m_LineWidth > 0 ? m_LineWidth : wrapWidth() );
4166 	else if ( flag(LineWrap) )
4167 		m_doc->setWidthConstraint( wrapWidth() );
4168 	else
4169 		m_doc->clearWidthConstraint();
4170 
4171 	if ( flag(EnsureVisible) )
4172 		ensureCursorVisible();
4173 
4174 }
4175 
4176 /*!
4177 	\internal
4178 	\brief Zoom in/out upon ctrl+wheel
4179 */
wheelEvent(QWheelEvent * e)4180 void QEditor::wheelEvent(QWheelEvent *e)
4181 {
4182 	if ( e->modifiers() & Qt::ControlModifier && flag(MouseWheelZoom))
4183 	{
4184         const int delta = e->angleDelta().y();
4185 
4186 		if ( delta > 0 )
4187 			zoom(1);
4188 		else if ( delta < 0 )
4189 			zoom(-1);
4190 
4191 		//viewport()->update();
4192 
4193 		return;
4194 	}
4195 
4196 	QAbstractScrollArea::wheelEvent(e);
4197 	updateMicroFocus();
4198 	//viewport()->update();
4199 }
4200 
4201 /*!
4202 	\internal
4203 */
resizeEvent(QResizeEvent *)4204 void QEditor::resizeEvent(QResizeEvent *)
4205 {
4206 	const QSize viewportSize = viewport()->size();
4207 
4208 	if ( flag(HardLineWrap)||flag(LineWidthConstraint) ){
4209 	    horizontalScrollBar()->setMaximum(qMax(0, m_LineWidth - viewportSize.width()));
4210 	    horizontalScrollBar()->setPageStep(viewportSize.width());
4211 	} else if ( flag(LineWrap) )
4212 	{
4213 	    //qDebug("resize t (2) : wrapping to %i", viewport()->width());
4214 
4215 	    m_doc->setWidthConstraint(wrapWidth());
4216 	} else {
4217         horizontalScrollBar()->setMaximum(qMax(0., m_doc->width() - viewportSize.width()));
4218 	    horizontalScrollBar()->setPageStep(viewportSize.width());
4219 	}
4220 
4221 	setVerticalScrollBarMaximum();
4222 
4223 	emit visibleLinesChanged();
4224 	//qDebug("page step : %i", viewportSize.height() / ls);
4225 
4226 	//if ( isCursorVisible() && flag(LineWrap) )
4227 	//	ensureCursorVisible();
4228 }
4229 
4230 /*!
4231 	\internal
4232 */
focusInEvent(QFocusEvent * e)4233 void QEditor::focusInEvent(QFocusEvent *e)
4234 {
4235 	setFlag(CursorOn, true);
4236 	if (QApplication::cursorFlashTime() > 0) {
4237 		m_blink.start(QApplication::cursorFlashTime() / 2, this);
4238 	}
4239 	//ensureCursorVisible();
4240 	emit focusReceived();
4241 
4242 	QAbstractScrollArea::focusInEvent(e);
4243 }
4244 
4245 /*!
4246 	\internal
4247 */
focusOutEvent(QFocusEvent * e)4248 void QEditor::focusOutEvent(QFocusEvent *e)
4249 {
4250 	setFlag(CursorOn, false);
4251 	m_blink.stop();
4252 
4253 	QAbstractScrollArea::focusOutEvent(e);
4254 }
4255 
4256 /*!
4257 	\brief Context menu event
4258 
4259 	All the (managed) actions added to the editor are showed in it by default.
4260 */
contextMenuEvent(QContextMenuEvent * e)4261 void QEditor::contextMenuEvent(QContextMenuEvent *e)
4262 {
4263 	foreach ( QEditorInputBindingInterface *b, m_bindings )
4264 		if ( b->contextMenuEvent(e, this) )
4265 			return;
4266 
4267 	if ( !pMenu )
4268 	{
4269 		e->ignore();
4270 		return;
4271 	}
4272 
4273 	e->accept();
4274 
4275 	pMenu->exec(e->globalPos());
4276 }
4277 
4278 /*!
4279 	\brief Close event
4280 
4281 	When build with qmdilib support (e.g in Edyuk) this check for
4282 	modifications and a dialog pops up to offer various options
4283 	(like saving, discarding or canceling)
4284 */
closeEvent(QCloseEvent * e)4285 void QEditor::closeEvent(QCloseEvent *e)
4286 {
4287 	#ifdef _QMDI_
4288 	bool bOK = true;
4289 
4290 	if ( isContentModified() )
4291 		bOK = server()->maybeSave(this);
4292 
4293 	if ( bOK )
4294 	{
4295 		e->accept();
4296 		notifyDeletion();
4297 	} else {
4298 		e->ignore();
4299 	}
4300 	#else
4301 	QAbstractScrollArea::closeEvent(e);
4302 	#endif
4303 }
4304 
4305 #ifndef _QMDI_
4306 /*!
4307 	\return Whether the document has been modified.
4308 */
isContentModified() const4309 bool QEditor::isContentModified() const
4310 {
4311 	return m_doc ? !m_doc->isClean() : false;
4312 }
4313 #endif
4314 
4315 /*!
4316 	\brief Notify that the content is clean (modifications undone or document saved)
4317 
4318 	\note Don't mess with this. The document knows better.
4319 */
setContentClean(bool y)4320 void QEditor::setContentClean(bool y)
4321 {
4322 	setContentModified(!y);
4323 }
4324 
4325 /*!
4326 	\brief Notify that the content has been modified
4327 
4328 	\note Don't mess with this. The document knows better.
4329 */
setContentModified(bool y)4330 void QEditor::setContentModified(bool y)
4331 {
4332 	#ifdef _QMDI_
4333 	qmdiClient::setContentModified(y);
4334 	#endif
4335 
4336 	setWindowModified(y);
4337 	emit contentModified(y);
4338 }
4339 
4340 /*!
4341 	\brief Changes the file name
4342 
4343 	This method does not affect files on disk (no save/load/move occurs)
4344 */
setFileName(const QString & f)4345 void QEditor::setFileName(const QString& f)
4346 {
4347 	REQUIRE(m_doc);
4348 
4349 	QString prev = fileName();
4350 
4351 	if ( f == prev )
4352 		return;
4353 
4354 	watcher()->removeWatch(QString(), this);
4355 
4356 	m_doc->setFileName_DONOTCALLTHIS(f);
4357 
4358 	if ( fileName().count() )
4359 		watcher()->addWatch(fileName(), this);
4360 
4361 	setTitle(name().count() ? name() : "untitled");
4362 }
4363 
4364 /*!
4365 	\brief Set the title of the widget
4366 
4367 	Take care of adding a "[*]" prefix so that document changes are visible
4368 	on title bars.
4369 */
setTitle(const QString & title)4370 void QEditor::setTitle(const QString& title)
4371 {
4372 	QString s(title);
4373 
4374 	if ( !s.contains("[*]") )
4375 		s.prepend("[*]");
4376 
4377 	setWindowTitle(s);
4378 	emit titleChanged(title);
4379 }
4380 
4381 /*!
4382 	\return The name of the file being edited (without its path)
4383 */
name() const4384 QString QEditor::name() const
4385 {
4386 	REQUIRE_RET(m_doc,"");
4387 	return m_doc->getName();
4388 }
4389 
4390 /*!
4391 	\return The full filename of the file being edited
4392 */
fileName() const4393 QString QEditor::fileName() const
4394 {
4395 	REQUIRE_RET(m_doc,"");
4396 	return m_doc->getFileName();
4397 }
fileInfo() const4398 QFileInfo QEditor::fileInfo() const{
4399 	REQUIRE_RET(m_doc,QFileInfo());
4400 	return m_doc->getFileInfo();
4401 }
4402 
isReadOnly() const4403 bool QEditor::isReadOnly() const
4404 {
4405 	REQUIRE_RET(m_doc, true);
4406 	return m_doc->isReadOnly();
4407 }
4408 
setReadOnly(bool b)4409 void QEditor::setReadOnly(bool b)
4410 {
4411 	REQUIRE(m_doc);
4412 	m_doc->setReadOnly(b);
4413 	emit readOnlyChanged(b);
4414 }
4415 
4416 
4417 /*!
4418 	\brief Prevent tab key press to be considered as widget navigation
4419 */
focusNextPrevChild(bool)4420 bool QEditor::focusNextPrevChild(bool)
4421 {
4422 	// make sure we catch tabs :)
4423 
4424 	return false;
4425 }
4426 
registerEditOperation(const EditOperation & op)4427 void QEditor::registerEditOperation(const EditOperation& op){
4428 	m_registeredOperations << op;
4429 }
4430 
addEditOperation(const EditOperation & op,const Qt::KeyboardModifiers & modifiers,const Qt::Key & key)4431 void QEditor::addEditOperation(const EditOperation& op, const Qt::KeyboardModifiers& modifiers, const Qt::Key& key){
4432     QKeySequence qkey=QKeySequence((modifiers&~Qt::KeypadModifier) | key); // filter out keypadmodifier which is sent with cursor on OSX
4433     m_registeredKeys.insert(qkey.toString(), op);
4434 	m_registeredOperations << op;
4435 }
4436 
addEditOperation(const EditOperation & op,const QKeySequence::StandardKey & key)4437 void QEditor::addEditOperation(const EditOperation& op, const QKeySequence::StandardKey& key){
4438 	QList<QKeySequence> sc = QKeySequence::keyBindings(key);
4439 	foreach (const QKeySequence& seq, sc){
4440 		if (!seq.count()) continue;
4441         m_registeredKeys.insert(seq.toString(), op);
4442         m_registeredOperations << op;
4443 	}
4444 }
4445 
getEditOperation(const Qt::KeyboardModifiers & modifiers,const Qt::Key & key)4446 QEditor::EditOperation QEditor::getEditOperation(const Qt::KeyboardModifiers& modifiers, const Qt::Key& key){
4447     QKeySequence qkey=QKeySequence((modifiers&~Qt::KeypadModifier) | key); // filter out keypadmodifier which is sent with cursor on OSX
4448     EditOperation op = static_cast<EditOperation>(m_registeredKeys.value(qkey.toString() , NoOperation));
4449 	static const int MAX_JUMP_TO_PLACEHOLDER = 5;
4450 	switch (op){
4451 	case IndentSelection: case UnindentSelection:
4452         //if (!m_cursor.hasSelection()) op = NoOperation; // works also if the cursor has no selection !
4453 		break;
4454 	case NextPlaceHolder: case PreviousPlaceHolder:
4455 		if (m_placeHolders.isEmpty()) op = NoOperation;
4456 		break;
4457 	case NextPlaceHolderOrWord:
4458 		op = CursorWordRight;
4459 		foreach (const PlaceHolder& ph, m_placeHolders)
4460 			if (ph.cursor.selectionStart() > m_cursor.selectionEnd() && !ph.autoOverride &&
4461 			    ph.cursor.selectionStart().lineNumber() - m_cursor.selectionEnd().lineNumber() <= MAX_JUMP_TO_PLACEHOLDER  ){
4462 				op = NextPlaceHolder;
4463 				break;
4464 			}
4465 		break;
4466 	case PreviousPlaceHolderOrWord:
4467 		op = CursorWordLeft;
4468 		foreach (const PlaceHolder& ph, m_placeHolders)
4469 			if (ph.cursor.selectionEnd() < m_cursor.selectionStart() && !ph.autoOverride &&
4470 			    m_cursor.selectionStart().lineNumber() - ph.cursor.selectionEnd().lineNumber() <= MAX_JUMP_TO_PLACEHOLDER ){
4471 				op = PreviousPlaceHolder;
4472 				break;
4473 			}
4474 		break;
4475 	case NextPlaceHolderOrChar:
4476 		op = CursorRight;
4477 		foreach (const PlaceHolder& ph, m_placeHolders)
4478 			if (ph.cursor.selectionStart() > m_cursor.selectionEnd() && !ph.autoOverride &&
4479 				ph.cursor.selectionStart().lineNumber() - m_cursor.selectionEnd().lineNumber() <= MAX_JUMP_TO_PLACEHOLDER  ){
4480 				op = NextPlaceHolder;
4481 				break;
4482 			}
4483 		break;
4484 	case PreviousPlaceHolderOrChar:
4485 		op = CursorLeft;
4486 		foreach (const PlaceHolder& ph, m_placeHolders)
4487 			if (ph.cursor.selectionEnd() < m_cursor.selectionStart() && !ph.autoOverride &&
4488 				m_cursor.selectionStart().lineNumber() - ph.cursor.selectionEnd().lineNumber() <= MAX_JUMP_TO_PLACEHOLDER ){
4489 				op = PreviousPlaceHolder;
4490 				break;
4491 			}
4492 		break;
4493 	default:;
4494 	}
4495 	return op;
4496 }
4497 
setEditOperations(const QHash<QString,int> & newOptions,bool mergeWithDefault)4498 void QEditor::setEditOperations(const QHash<QString, int> &newOptions, bool mergeWithDefault){
4499 	if (!mergeWithDefault) {
4500 		m_registeredKeys = newOptions;
4501 		return;
4502 	}
4503 	m_registeredKeys = m_registeredDefaultKeys;
4504     QHash<QString, int>::const_iterator i = newOptions.begin();
4505 	while (i != newOptions.constEnd()) {
4506 		m_registeredKeys.insert(i.key(),i.value());
4507 		++i;
4508 	}
4509 }
4510 
getAvailableOperations()4511 QSet<int> QEditor::getAvailableOperations(){
4512 	return m_registeredOperations;
4513 }
4514 
getEditOperations(bool excludeDefault)4515 QHash<QString, int> QEditor::getEditOperations(bool excludeDefault){
4516 	if (!m_defaultKeysSet) { //todo: thread safe lock
4517 		m_defaultKeysSet = true;
4518 
4519     addEditOperation(CursorUp, QKeySequence::MoveToPreviousLine);
4520     addEditOperation(CursorDown, QKeySequence::MoveToNextLine);
4521     addEditOperation(SelectCursorUp, QKeySequence::SelectPreviousLine);
4522     addEditOperation(SelectCursorDown, QKeySequence::SelectNextLine);
4523 
4524     addEditOperation(CursorLeft, QKeySequence::MoveToPreviousChar);
4525     addEditOperation(CursorRight, QKeySequence::MoveToNextChar);
4526     addEditOperation(SelectCursorLeft, QKeySequence::SelectPreviousChar);
4527     addEditOperation(SelectCursorRight, QKeySequence::SelectNextChar);
4528 
4529     addEditOperation(CursorStartOfLine, QKeySequence::MoveToStartOfLine);
4530     addEditOperation(CursorEndOfLine, QKeySequence::MoveToEndOfLine);
4531     addEditOperation(SelectCursorStartOfLine, QKeySequence::SelectStartOfLine);
4532     addEditOperation(SelectCursorEndOfLine, QKeySequence::SelectEndOfLine);
4533 
4534     addEditOperation(CursorStartOfDocument, QKeySequence::MoveToStartOfDocument);
4535     addEditOperation(CursorEndOfDocument, QKeySequence::MoveToEndOfDocument);
4536     addEditOperation(SelectCursorStartOfDocument, QKeySequence::SelectStartOfDocument);
4537     addEditOperation(SelectCursorEndOfDocument, QKeySequence::SelectEndOfDocument);
4538 
4539     #ifndef Q_OS_MAC  // Use the default Windows bindings.
4540 		addEditOperation(CursorWordLeft, Qt::ControlModifier, Qt::Key_Left);
4541 		addEditOperation(CursorWordRight, Qt::ControlModifier, Qt::Key_Right);
4542 		addEditOperation(SelectCursorWordLeft, Qt::ControlModifier | Qt::ShiftModifier, Qt::Key_Left);
4543 		addEditOperation(SelectCursorWordRight, Qt::ControlModifier | Qt::ShiftModifier, Qt::Key_Right);
4544 
4545 		addEditOperation(CursorStartOfDocument, Qt::ControlModifier, Qt::Key_Home);
4546 		addEditOperation(CursorEndOfDocument, Qt::ControlModifier, Qt::Key_End);
4547 		addEditOperation(SelectCursorStartOfDocument, Qt::ControlModifier | Qt::ShiftModifier, Qt::Key_Home);
4548 		addEditOperation(SelectCursorEndOfDocument, Qt::ControlModifier | Qt::ShiftModifier, Qt::Key_End);
4549 	#else
4550 	/*
4551 		Except for pageup and pagedown, Mac OS X has very different behavior, we
4552 		don't do it all, but here's the breakdown:
4553 
4554 		Shift still works as an anchor, but only one of the other keys can be dow
4555 		Ctrl (Command), Alt (Option), or Meta (Control).
4556 
4557 		Command/Control + Left/Right -- Move to left or right of the line
4558 						+ Up/Down -- Move to top bottom of the file.
4559 						(Control doesn't move the cursor)
4560 
4561 		Option	+ Left/Right -- Move one word Left/right.
4562 				+ Up/Down  -- Begin/End of Paragraph.
4563 
4564 		Home/End Top/Bottom of file. (usually don't move the cursor, but will select)
4565 
4566 		There can be only one modifier (+ shift), but we also need to make sure
4567 		that we have a "move key" pressed before we reject it.
4568 	*/
4569         /* whyever fill the list with "invalid" commands ?????
4570 		QList<Qt::KeyboardModifiers> modifierPairs;
4571 		modifierPairs <<  (Qt::ControlModifier | Qt::AltModifier) << (Qt::ControlModifier | Qt::MetaModifier) << (Qt::AltModifier | Qt::MetaModifier);
4572 		QList<Qt::Key> movementKeys;
4573 		movementKeys << Qt::Key_Up << Qt::Key_Down << Qt::Key_Left << Qt::Key_Right;
4574 		foreach (Qt::KeyboardModifiers mod, modifierPairs)
4575 		    foreach (Qt::Key key, movementKeys)
4576 			addEditOperation(Invalid, mod , key);
4577 		modifierPairs <<  Qt::ControlModifier << Qt::AltModifier << Qt::MetaModifier;
4578 		modifierPairs <<  (Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier);
4579 		movementKeys = QList<Qt::Key>() << Qt::Key_Home << Qt::Key_End;
4580 		foreach (Qt::KeyboardModifiers mod, modifierPairs)
4581 		    foreach (Qt::Key key, movementKeys)
4582 			addEditOperation(Invalid, mod , key);
4583        */
4584 
4585 		addEditOperation(CursorStartOfLine, Qt::AltModifier, Qt::Key_Up);
4586 		addEditOperation(CursorEndOfLine, Qt::AltModifier, Qt::Key_Down);
4587 		addEditOperation(SelectCursorStartOfLine, Qt::ShiftModifier | Qt::AltModifier, Qt::Key_Up);
4588 		addEditOperation(SelectCursorEndOfLine, Qt::ShiftModifier | Qt::AltModifier, Qt::Key_Down);
4589 
4590 		addEditOperation(CursorStartOfLine, Qt::ControlModifier, Qt::Key_Left);
4591 		addEditOperation(CursorEndOfLine, Qt::ControlModifier, Qt::Key_Right);
4592 		addEditOperation(SelectCursorStartOfLine, Qt::ShiftModifier | Qt::ControlModifier, Qt::Key_Left);
4593 		addEditOperation(SelectCursorEndOfLine, Qt::ShiftModifier | Qt::ControlModifier, Qt::Key_Right);
4594 
4595 		addEditOperation(CursorStartOfLine, Qt::MetaModifier, Qt::Key_Left);
4596 		addEditOperation(CursorEndOfLine, Qt::MetaModifier, Qt::Key_Right);
4597 		addEditOperation(SelectCursorStartOfLine, Qt::ShiftModifier | Qt::MetaModifier, Qt::Key_Left);
4598 		addEditOperation(SelectCursorEndOfLine, Qt::ShiftModifier | Qt::MetaModifier, Qt::Key_Right);
4599 
4600 		addEditOperation(CursorWordLeft, Qt::AltModifier, Qt::Key_Left);
4601 		addEditOperation(CursorWordRight, Qt::AltModifier, Qt::Key_Right);
4602 		addEditOperation(SelectCursorWordLeft, Qt::ShiftModifier | Qt::AltModifier, Qt::Key_Left);
4603 		addEditOperation(SelectCursorWordRight, Qt::ShiftModifier | Qt::AltModifier, Qt::Key_Right);
4604 	#endif
4605 
4606 		addEditOperation(CursorPageUp, Qt::NoModifier, Qt::Key_PageUp);
4607 		addEditOperation(SelectPageUp, Qt::ShiftModifier, Qt::Key_PageUp);
4608 		addEditOperation(CursorPageDown, Qt::NoModifier, Qt::Key_PageDown);
4609 		addEditOperation(SelectPageDown, Qt::ShiftModifier, Qt::Key_PageDown);
4610 
4611 		addEditOperation(DeleteLeft, Qt::NoModifier, Qt::Key_Backspace);
4612         addEditOperation(DeleteRight, QKeySequence::Delete);
4613     #ifdef Q_OS_MAC
4614         addEditOperation(DeleteRight, Qt::ShiftModifier, Qt::Key_Backspace);
4615     #else
4616         addEditOperation(DeleteLeft, Qt::ShiftModifier, Qt::Key_Backspace);
4617     #endif
4618 
4619     #ifdef Q_OS_MAC
4620 		addEditOperation(DeleteLeftWord, Qt::AltModifier, Qt::Key_Backspace);
4621 		addEditOperation(DeleteRightWord, Qt::AltModifier, Qt::Key_Delete);
4622 	#else
4623 		addEditOperation(DeleteLeftWord, Qt::ControlModifier, Qt::Key_Backspace);
4624 		addEditOperation(DeleteRightWord, Qt::ControlModifier, Qt::Key_Delete);
4625 	#endif
4626 
4627 		addEditOperation(NewLine, Qt::NoModifier, Qt::Key_Enter);
4628 		addEditOperation(NewLine, Qt::NoModifier, Qt::Key_Return);
4629 
4630 		addEditOperation(ChangeOverwrite, Qt::NoModifier, Qt::Key_Insert);
4631 
4632 		addEditOperation(CreateMirrorUp, Qt::AltModifier | Qt::ControlModifier, Qt::Key_Up);
4633 		addEditOperation(CreateMirrorDown, Qt::AltModifier | Qt::ControlModifier, Qt::Key_Down);
4634 
4635 		registerEditOperation(NextPlaceHolder);
4636 		registerEditOperation(PreviousPlaceHolder);
4637 	#ifdef Q_OS_MAC
4638 		registerEditOperation(NextPlaceHolderOrWord);
4639 		registerEditOperation(PreviousPlaceHolderOrWord);
4640 	#else
4641 		addEditOperation(NextPlaceHolderOrWord, Qt::ControlModifier, Qt::Key_Right);
4642 		addEditOperation(PreviousPlaceHolderOrWord, Qt::ControlModifier, Qt::Key_Left);
4643 	#endif
4644 		registerEditOperation(NextPlaceHolderOrChar);
4645 		registerEditOperation(PreviousPlaceHolderOrChar);
4646 
4647 		addEditOperation(TabOrIndentSelection, Qt::NoModifier, Qt::Key_Tab);
4648 		addEditOperation(UnindentSelection, Qt::ShiftModifier, Qt::Key_Backtab);
4649 
4650 		addEditOperation(Undo, QKeySequence::Undo);
4651 		addEditOperation(Redo, QKeySequence::Redo);
4652 		addEditOperation(Copy, QKeySequence::Copy);
4653 		addEditOperation(Paste, QKeySequence::Paste);
4654 		addEditOperation(Cut, QKeySequence::Cut);
4655 		addEditOperation(Print, QKeySequence::Print);
4656 		addEditOperation(SelectAll, QKeySequence::SelectAll);
4657 		addEditOperation(Find, QKeySequence::Find);
4658 		addEditOperation(FindNext, QKeySequence::FindNext);
4659 		addEditOperation(FindPrevious, QKeySequence::FindPrevious);
4660 		addEditOperation(Replace, QKeySequence::Replace);
4661 
4662 		m_registeredDefaultKeys = m_registeredKeys;
4663 	}
4664 	if (!excludeDefault) return m_registeredKeys;
4665 	else {
4666         QHash<QString, int> result = m_registeredKeys;
4667         foreach(QString key,m_registeredDefaultKeys.keys()){
4668                 if(result.contains(key)){
4669                     if(result.value(key)==m_registeredDefaultKeys.value(key)){
4670                         result.remove(key);
4671                     }
4672                 }else{
4673                     // add for removal
4674                     result.insert("#"+key,m_registeredDefaultKeys.value(key));
4675                 }
4676         }
4677         return result;
4678 	}
4679 }
4680 
translateEditOperation(const EditOperation & op)4681 QString QEditor::translateEditOperation(const EditOperation& op){
4682 	switch (op){
4683 	case NoOperation: return tr("None");
4684 	case Invalid: return tr("Invalid");
4685 
4686 	case EnumForCursorStart: return tr("Internal");
4687 
4688 	case CursorUp: return tr("Move cursor up");
4689 	case CursorDown: return tr("Move cursor down");
4690 	case CursorLeft: return tr("Move cursor left (1 character)");
4691 	case CursorRight: return tr("Move cursor right (1 character)");
4692 	case CursorWordLeft: return tr("Move cursor left (1 word)");
4693 	case CursorWordRight: return tr("Move cursor right (1 word)");
4694 	case CursorStartOfLine: return tr("Move cursor to line start");
4695 	case CursorEndOfLine: return tr("Move cursor to line end");
4696 	case CursorStartOfDocument: return tr("Move cursor to document start");
4697 	case CursorEndOfDocument: return tr("Move cursor to document end");
4698 
4699 	case CursorPageUp: return tr("Move cursor one page up");
4700 	case CursorPageDown: return tr("Move cursor one page down");
4701 
4702 	case EnumForSelectionStart: return tr("Internal");
4703 
4704 	case SelectCursorUp: return tr("Select up");
4705 	case SelectCursorDown: return tr("Select down");
4706 	case SelectCursorLeft: return tr("Select left (1 character)");
4707 	case SelectCursorRight: return tr("Select right (1 character)");
4708 	case SelectCursorWordLeft: return tr("Select left (1 word)");
4709 	case SelectCursorWordRight: return tr("Select right (1 word)");
4710 	case SelectCursorStartOfLine: return tr("Select to line start");
4711 	case SelectCursorEndOfLine: return tr("Select to line end");
4712 	case SelectCursorStartOfDocument: return tr("Select to document start");
4713 	case SelectCursorEndOfDocument: return tr("Select to document end");
4714 
4715 	case SelectPageUp: return tr("Select page up");
4716 	case SelectPageDown: return tr("Select page down");
4717 
4718 	case EnumForCursorEnd: return tr("Internal");
4719 
4720 	case DeleteLeft: return tr("Delete left character");
4721 	case DeleteRight: return tr("Delete right character");
4722 	case DeleteLeftWord: return tr("Delete left word");
4723 	case DeleteRightWord: return tr("Delete right word");
4724 	case NewLine: return tr("New line");
4725 
4726 	case ChangeOverwrite: return tr("Change overwrite mode");
4727 	case Undo: return tr("Undo");
4728 	case Redo: return tr("Redo");
4729 	case Copy: return tr("Copy");
4730 	case Paste: return tr("Paste");
4731 	case Cut: return tr("Cut");
4732 	case Print: return tr("Print");
4733 	case SelectAll: return tr("Select all");
4734 	case Find: return tr("Find");
4735 	case FindNext: return tr("Find next");
4736 	case FindPrevious: return tr("Find previous");
4737 	case Replace: return tr("Replace");
4738 
4739 	case CreateMirrorUp: return tr("Create cursor mirror up");
4740 	case CreateMirrorDown: return tr("Create cursor mirror down");
4741 	case NextPlaceHolder: return tr("Next placeholder");
4742 	case PreviousPlaceHolder: return tr("Previous placeholder");
4743 	case NextPlaceHolderOrWord: return tr("Next placeholder or one word right");
4744 	case PreviousPlaceHolderOrWord: return tr("Previous placeholder or one word left");
4745 	case NextPlaceHolderOrChar: return tr("Next placeholder or character");
4746 	case PreviousPlaceHolderOrChar: return tr("Previous placeholder or character");
4747 	case TabOrIndentSelection: return tr("Tab or Indent selection");
4748 	case InsertTab: return tr("Insert tab");
4749 	case IndentSelection: return tr("Indent selection");
4750 	case UnindentSelection: return tr("Unindent selection");
4751 
4752 	}
4753 	return tr("Unknown");
4754 }
4755 
4756 /*!
4757 	\brief Start a drag and drop operation using the current selection
4758 */
startDrag()4759 void QEditor::startDrag()
4760 {
4761 	setFlag(MousePressed, false);
4762 	QMimeData *data = createMimeDataFromSelection();
4763 
4764 	QDrag *drag = new QDrag(this);
4765 	drag->setMimeData(data);
4766 
4767 	Qt::DropActions actions = Qt::CopyAction | Qt::MoveAction;
4768 	Qt::DropAction action = drag->exec(actions, Qt::MoveAction);
4769 
4770 	if ( (action == Qt::MoveAction) && (drag->target() != this))
4771 	{
4772 		m_cursor.removeSelectedText();
4773 		cursorMirrorsRemoveSelectedText();
4774 	}
4775 }
4776 
4777 /*!
4778 	\brief Handle cursor movements upon key event
4779 */
4780 
cursorMoveOperation(QDocumentCursor & cursor,EditOperation eop)4781 void QEditor::cursorMoveOperation(QDocumentCursor &cursor, EditOperation eop){
4782 	QDocumentCursor::MoveMode mode = eop >= EnumForSelectionStart ? QDocumentCursor::KeepAnchor : QDocumentCursor::MoveAnchor;
4783 	if ( flag(LineWrap) && flag(CursorJumpPastWrap) )
4784 		mode |= QDocumentCursor::ThroughWrap;
4785 	QDocumentCursor::MoveOperation op = QDocumentCursor::NoMove;
4786 	switch (eop){
4787 	case CursorUp: case SelectCursorUp:
4788 		op = QDocumentCursor::Up;
4789 		cutBuffer.clear();
4790 		break;
4791 	case CursorDown: case SelectCursorDown:
4792 		op = QDocumentCursor::Down;
4793 		cutBuffer.clear();
4794 		break;
4795 	case CursorLeft: case SelectCursorLeft:
4796 		if (flag(BidiVisualColumnMode)) op = QDocumentCursor::Left;
4797 		else op = QDocumentCursor::PreviousCharacter;
4798 		cutBuffer.clear();
4799 		break;
4800 	case CursorRight: case SelectCursorRight:
4801 		if (flag(BidiVisualColumnMode)) op = QDocumentCursor::Right;
4802 		else op = QDocumentCursor::NextCharacter;
4803 		cutBuffer.clear();
4804 		break;
4805 	case CursorWordLeft: case SelectCursorWordLeft:
4806 		if (flag(BidiVisualColumnMode)) op = QDocumentCursor::WordLeft;
4807 		else op = QDocumentCursor::PreviousWord;
4808 		break;
4809 	case CursorWordRight: case SelectCursorWordRight:
4810 		if (flag(BidiVisualColumnMode)) op = QDocumentCursor::WordRight;
4811 		else op = QDocumentCursor::NextWord;
4812 		break;
4813 	case CursorStartOfLine: case SelectCursorStartOfLine:
4814 		op = QDocumentCursor::StartOfLine;
4815 		break;
4816 	case CursorEndOfLine: case SelectCursorEndOfLine:
4817 		op = QDocumentCursor::EndOfLine;
4818 		break;
4819 	case CursorStartOfDocument: case SelectCursorStartOfDocument:
4820 		cutBuffer.clear();
4821 		op = QDocumentCursor::Start;
4822 		break;
4823 	case CursorEndOfDocument: case SelectCursorEndOfDocument:
4824 		cutBuffer.clear();
4825 		op = QDocumentCursor::End;
4826 		break;
4827 
4828 	case CursorPageUp: case SelectPageUp:
4829 		cutBuffer.clear();
4830 		pageUp(mode);
4831 		return;
4832 	case CursorPageDown: case SelectPageDown:
4833 		cutBuffer.clear();
4834 		pageDown(mode);
4835 		return;
4836 	default:
4837 		return;
4838 	}
4839 
4840 	cursor.movePosition(1, op, mode);
4841 }
4842 
4843 
4844 
4845 /*!
4846 	\brief Go up by one page
4847 
4848 	\note This method clears all cursor mirrors and suspend placeholder edition.
4849 */
pageUp(QDocumentCursor::MoveMode moveMode)4850 void QEditor::pageUp(QDocumentCursor::MoveMode moveMode)
4851 {
4852 	setPlaceHolder(-1);
4853 	clearCursorMirrors();
4854 
4855 	if ( m_cursor.atStart() )
4856 		return;
4857 
4858     int n = qFloor(1. * viewport()->height() / QDocument::getLineSpacing());
4859 
4860 	repaintCursor();
4861 	m_cursor.movePosition(n, QDocumentCursor::Up, moveMode);
4862 
4863 	ensureCursorVisible();
4864 	emitCursorPositionChanged();
4865 	//updateMicroFocus();
4866 }
4867 
4868 /*!
4869 	\brief Go down by one page
4870 
4871 	\note This method clears all cursor mirrors.
4872 */
pageDown(QDocumentCursor::MoveMode moveMode)4873 void QEditor::pageDown(QDocumentCursor::MoveMode moveMode)
4874 {
4875 	clearCursorMirrors();
4876 	setPlaceHolder(-1);
4877 
4878 	if ( m_cursor.atEnd() )
4879 		return;
4880 
4881     int n = qFloor(1. * viewport()->height() / document()->getLineSpacing());
4882 
4883 	repaintCursor();
4884 	m_cursor.movePosition(n, QDocumentCursor::Down, moveMode);
4885 
4886 	ensureCursorVisible();
4887 	emitCursorPositionChanged();
4888 }
4889 
4890 /*!
4891 	\internal
4892 	\brief Process a key event for a given cursor
4893 
4894 	This method only take care of editing operations, not movements.
4895 */
processEditOperation(QDocumentCursor & c,const QKeyEvent * e,EditOperation op)4896 void QEditor::processEditOperation(QDocumentCursor& c, const QKeyEvent* e, EditOperation op)
4897 {
4898 	bool hasSelection = c.hasSelection();
4899 
4900 	if ( hasSelection ) {
4901 	    cutBuffer=c.selectedText();
4902 	    c.removeSelectedText();
4903 	}
4904 
4905 	switch ( op )
4906 	{
4907 	case DeleteLeft :
4908 		if(!hasSelection) c.deletePreviousChar();
4909 		cutBuffer.clear();
4910 		break;
4911 	case DeleteRight :
4912 		if(!hasSelection) c.deleteChar();
4913 		cutBuffer.clear();
4914 		break;
4915 
4916 	case DeleteLeftWord :
4917 		c.movePosition(1,QDocumentCursor::PreviousWord,QDocumentCursor::KeepAnchor);
4918 		c.removeSelectedText();
4919 		cutBuffer.clear();
4920 		break;
4921 
4922 	case DeleteRightWord :
4923 		c.movePosition(1,QDocumentCursor::NextWord,QDocumentCursor::KeepAnchor);
4924 		c.removeSelectedText();
4925 		cutBuffer.clear();
4926 		break;
4927 
4928 	case NewLine:
4929 		insertText(c, "\n");
4930 		cutBuffer.clear();
4931 		break;
4932 
4933 	default :
4934 	{
4935 		QString text = e->text();
4936 
4937 		if ( flag(ReplaceIndentTabs) )
4938 		{
4939 			text.replace("\t", QString(m_doc->tabStop(), ' '));
4940 		}
4941 
4942 		insertText(c, text);
4943 
4944 		break;
4945 	}
4946 	}
4947 }
4948 
selectCursorMirrorBlock(const QDocumentCursor & cursor,bool horizontalSelect)4949 void QEditor::selectCursorMirrorBlock(const QDocumentCursor &cursor, bool horizontalSelect)
4950 {
4951 	QDocumentCursorHandle *blockAnchor;
4952 	if ( m_cursorMirrorBlockAnchor >= 0 && m_cursorMirrorBlockAnchor < m_mirrors.size() ) {
4953 		blockAnchor = m_mirrors[m_cursorMirrorBlockAnchor].handle();
4954 		while ( m_mirrors.size() > m_cursorMirrorBlockAnchor + 1) //remove all after m_cursorMirrorBlockAnchor
4955 			m_mirrors.removeLast();
4956 	} else {
4957 		clearCursorMirrors();
4958 		blockAnchor = m_cursor.handle();
4959 	}
4960 	if (!blockAnchor) return;
4961 	// get column number for column selection
4962 	int org = blockAnchor->anchorColumnNumber();
4963 	int dst = cursor.columnNumber();
4964 	// TODO : fix and adapt to line wrapping...
4965 
4966 	int min = qMin(blockAnchor->lineNumber(), cursor.lineNumber());
4967 	int max = qMax(blockAnchor->lineNumber(), cursor.lineNumber());
4968 
4969 	if ( min != max )
4970 	{
4971 		for ( int l = min; l <= max; ++l )
4972 		{
4973 			if ( l != blockAnchor->lineNumber() )
4974 				addCursorMirror(QDocumentCursor(m_doc, l, org));
4975 
4976 		}
4977 
4978 		if ( horizontalSelect )
4979 		{
4980 			blockAnchor->setColumnNumber(dst, QDocumentCursor::KeepAnchor);
4981 
4982 			for ( int i = qMax(0, m_cursorMirrorBlockAnchor); i < m_mirrors.count(); ++i )
4983 				m_mirrors[i].setColumnNumber(dst, QDocumentCursor::KeepAnchor);
4984 		}
4985 	} else {
4986 		blockAnchor->setSelectionBoundary(cursor);
4987 	}
4988 }
4989 
preInsertUnindent(QDocumentCursor & c,const QString & s,int additionalUnindent)4990 void QEditor::preInsertUnindent(QDocumentCursor& c, const QString& s, int additionalUnindent)
4991 {
4992 	if ( !flag(AutoIndent) || flag(WeakIndent) ) return;
4993 	if ( m_curPlaceHolder != -1 ) return;
4994 	if ( !c.columnNumber() ) return;
4995 
4996 	int firstNS = 0;
4997 	QString txt = c.line().text();
4998 
4999 	while ( (firstNS < txt.length()) && txt.at(firstNS).isSpace() )
5000 		++firstNS;
5001 
5002 	if ( !firstNS || firstNS < c.columnNumber() )
5003 		return;
5004 
5005 	if ( m_definition && m_definition->unindent(c, s) ) additionalUnindent++;
5006 
5007 	if ( additionalUnindent <= 0 ) return;
5008 
5009 
5010 	const int off = c.columnNumber() - firstNS;
5011 
5012 	if ( off > 0 )
5013 		c.movePosition(off, QDocumentCursor::PreviousCharacter);
5014 
5015 	//TODO: fix unindent with tab/space mix by using c.previousChar() instead of txt.at(firstNS - 1) or calculate firstNS -= off.
5016 	//      (example: "___|->", _ means space, -> tab, | cursor, write } )
5017 
5018 	/*
5019 		It might be possible to improve that part to have a more natural/smarter unindenting
5020 		by trying to guess the scheme used by the user...
5021 	*/
5022 	for (;additionalUnindent > 0 && firstNS > 0; additionalUnindent--) {
5023 		if ( txt.at(firstNS - 1) == '\t' )
5024 		{
5025 			c.movePosition(1, QDocumentCursor::Left, QDocumentCursor::KeepAnchor);
5026 			firstNS--;
5027 		} else {
5028 			const int ts = m_doc->tabStop();
5029 
5030 			if (txt.contains(' ') && txt.contains('\t') && c.previousChar()=='\t') {
5031 				--firstNS;
5032 				c.movePosition(1, QDocumentCursor::Left, QDocumentCursor::KeepAnchor);
5033 			} else
5034 				do
5035 				{
5036 					--firstNS;
5037 					c.movePosition(1, QDocumentCursor::Left, QDocumentCursor::KeepAnchor);
5038 				} while ( QDocument::screenColumn(txt.constData(), firstNS, ts) % ts );
5039 		}
5040 	}
5041 	c.removeSelectedText();
5042 
5043 	if ( off > 0 )
5044 		c.movePosition(off, QDocumentCursor::NextCharacter);
5045 }
5046 
5047 /*!
5048 	\brief Insert some text at a given cursor position
5049 
5050 	This function is provided to keep indenting/outdenting working when editing
5051 */
insertText(QDocumentCursor & c,const QString & text)5052 void QEditor::insertText(QDocumentCursor& c, const QString& text)
5053 {
5054     if ( protectedCursor(c) || text.isEmpty())
5055         return;
5056 #if (QT_VERSION>=QT_VERSION_CHECK(5,14,0))
5057     QStringList lines = text.split('\n', Qt::KeepEmptyParts);
5058 #else
5059     QStringList lines = text.split('\n', QString::KeepEmptyParts);
5060 #endif
5061 
5062     bool hasSelection = c.hasSelection();
5063     if (hasSelection && c.selectedText() == text) {
5064         if (!c.isForwardSelection())
5065             c.flipSelection();
5066         c.clearSelection();
5067         return;  // replacing a selection with itself -> nothing to do. (It's more safe to directly stop here, because the below indentation correction does not get all cases right).
5068     }
5069 
5070     bool beginNewMacro = !m_doc->hasMacros() && (hasSelection || flag(Overwrite) || lines.size()>1);
5071     if (beginNewMacro)
5072         m_doc->beginMacro();
5073 
5074     bool autoOverridePlaceHolder = false;
5075     if ( !hasSelection && flag(Overwrite) && !c.atLineEnd() )
5076         c.deleteChar();
5077     else {
5078         if ( hasSelection ){
5079             cutBuffer=c.selectedText();
5080             c.removeSelectedText();
5081         }
5082 
5083         //see isAutoOverrideText()
5084         for ( int i = m_placeHolders.size()-1; i >= 0 ; i-- )
5085             if ( m_placeHolders[i].autoOverride && m_placeHolders[i].cursor.lineNumber() == c.lineNumber() &&
5086                  m_placeHolders[i].cursor.anchorColumnNumber() == c.anchorColumnNumber() &&
5087                  (m_placeHolders[i].cursor.selectedText().startsWith(text)))
5088             {
5089                 autoOverridePlaceHolder = true;
5090                 if (m_placeHolders[i].cursor.selectedText() == text) removePlaceHolder(i);
5091             }
5092         if (autoOverridePlaceHolder) {
5093             for (int i=0; i<text.length(); i++)
5094                 c.deleteChar();
5095         }
5096         QChar text0 = text.at(0);
5097         if (text0 == c.nextChar()) {
5098             // special functions for overwriting existing text
5099             if (flag(OverwriteClosingBracketFollowingPlaceholder) && (text0=='}' || text0==']')) {
5100                 // remove placeholder when typing closing bracket at the end of a placeholder
5101                 // e.g. \textbf{[ph]|} and typing '}'
5102                 for ( int i = m_placeHolders.size()-1; i >= 0 ; i-- )
5103                     if (m_placeHolders[i].cursor.lineNumber() == c.lineNumber() &&
5104                             m_placeHolders[i].cursor.columnNumber() == c.columnNumber()
5105                             )
5106                     {
5107                         QChar openBracket = (text0=='}') ? '{' : '[';
5108                         if (findOpeningBracket(c.line().text(), c.columnNumber()-1, openBracket, text0) < m_placeHolders[i].cursor.anchorColumnNumber()) {
5109                             removePlaceHolder(i);
5110                             c.deleteChar();
5111                         }
5112                     }
5113             } else if (flag(OverwriteOpeningBracketFollowedByPlaceholder) && (text0=='{' || text0=='[')) {
5114                 // skip over opening bracket if followed by a placeholder
5115                 for ( int i = m_placeHolders.size()-1; i >= 0 ; i-- )
5116                     if (m_placeHolders[i].cursor.anchorLineNumber() == c.lineNumber() &&
5117                             m_placeHolders[i].cursor.anchorColumnNumber() == c.columnNumber() + 1
5118                             )  // anchor == start of placeholder
5119                     {
5120                         setPlaceHolder(i);
5121                         if (text.length() == 1) {
5122                             return; // don't insert the bracket because we've just jumped over it
5123                         } else {
5124                             QString remainder(text);
5125                             remainder.remove(0,1);
5126                             insertText(c, remainder);
5127                             return;
5128                         }
5129                     }
5130             }
5131         }
5132     }
5133 
5134     //prepare for auto bracket insertion
5135     QString writtenBracket;
5136     QString autoBracket;
5137     bool autoComplete = false;
5138     if (flag(AutoCloseChars) && !autoOverridePlaceHolder
5139             && (m_curPlaceHolder<0 || m_curPlaceHolder>=m_placeHolders.size() || m_placeHolders[m_curPlaceHolder].mirrors.isEmpty())
5140             && languageDefinition() && languageDefinition()->possibleEndingOfOpeningParenthesis(text)){
5141         autoComplete = true;
5142         foreach (const QString& s, languageDefinition()->openingParenthesis())
5143             if (s == text){
5144                 writtenBracket = s;
5145                 autoBracket = languageDefinition()->getClosingParenthesis(s);
5146                 break;
5147             }
5148         if (autoBracket == writtenBracket)
5149             autoComplete = false; //don't things like "" or $$ (assuming only single letter self closing brackets exists)
5150 
5151         // no idea what the following code is supposed to do, it is probably erroneous
5152         // e.g {abc} abc |   , insert "{" at | will give a false match to the previous closing brace
5153         // check what would be the matching element if we inserted it
5154         if(autoComplete){
5155             c.insertText(text);
5156             // check if we are handling a multi-chrachter parenthesis, e.g. \[
5157             QString newAutoBracket;
5158             const QString& lineText = c.line().text().mid(0, c.columnNumber());
5159             foreach (const QString& s, languageDefinition()->openingParenthesis()){
5160                 if (s.length() >= text.length() &&  //don't complete bracket of pasted text or codesnippets
5161                         lineText.endsWith(s)){
5162                     newAutoBracket = languageDefinition()->getClosingParenthesis(s);
5163                     writtenBracket = s;
5164                     break;
5165                 }
5166             }
5167             if (newAutoBracket != autoBracket) {
5168                 autoBracket=newAutoBracket;
5169             }
5170 
5171             QDocumentCursor prevc(c);
5172             QList<QList<QDocumentCursor> > matches = languageDefinition()->getMatches(prevc);
5173             bool found=false;
5174             for (int i=0; i < matches.size(); i++) {
5175                 if (matches[i][0].anchorColumnNumber() == c.anchorColumnNumber()-writtenBracket.size()) {
5176                     if(matches[i][1].selectedText()==autoBracket){
5177                         prevc=matches[i][1];
5178                         found=true;
5179                         break;
5180                     }
5181                 } else if (matches[i][1].anchorColumnNumber()==c.anchorColumnNumber()-writtenBracket.size()) {
5182                     if(matches[i][0].selectedText()==autoBracket){
5183                         prevc=matches[i][0];
5184                         found=true;
5185                         break;
5186                     }
5187                 }
5188             }
5189             for(int k=0;k<text.size();k++){
5190                 c.deletePreviousChar();
5191             }
5192             if(found){
5193                 // check whether the found element has a matching element without our insertion
5194                 prevc.flipSelection();
5195                 matches = languageDefinition()->getMatches(prevc);
5196                 if(matches.isEmpty()){
5197                     // no opening element without our insertion, so our insertion should *not* be autoclosed
5198                     autoComplete=false;
5199                 }
5200             }
5201         }
5202     }
5203 
5204     //insert
5205     if ( (lines.count() == 1) || !flag(AdjustIndent)  || !flag(AutoIndent)) //|| flag(WeakIndent) || !flag(AdjustIndent)  || !flag(AutoIndent))
5206     {
5207         preInsertUnindent(c, lines.first(), 0);
5208 
5209         if ( flag(ReplaceIndentTabs) )
5210         {
5211             // TODO : replace tabs by spaces properly
5212         }
5213 
5214         c.insertText(text);
5215     } else {
5216         bool originallyAtLineStart = c.atLineStart();
5217         preInsertUnindent(c, lines.first(), 0);
5218 
5219         QDocumentCursor cc(c,false);
5220         if(!flag(WeakIndent)){
5221             // remove all prepending spaces as it is done later
5222             QString newText=lines.first().trimmed();
5223 
5224             for(int i=1;i<lines.count();i++){
5225                 newText.append('\n');
5226                 newText.append(lines.at(i).trimmed());
5227             }
5228             c.insertText(newText,true); // leads to insertion of text twice which runs through all documentPatched steps. TODO: find a way to insert text only once
5229         }
5230 
5231         // FIXME ? work on strings to make sure command grouping does not interfere with cursor state...
5232         int firstChar = cc.line().firstChar();
5233         if (firstChar == -1) firstChar = cc.line().length(); //line contains only spaces
5234         QString constIndent = cc.line().text().left(qMax(0, qMin(firstChar, cc.columnNumber())));
5235         if ( flag(ReplaceIndentTabs) )
5236             constIndent.replace("\t", QString(m_doc->tabStop(), ' '));
5237         QString indent;
5238         int indentCount = 0;
5239         int delayedDeltaIndentCount=0; // this indent change is applied on the next line (e.g. unindentation is inhibited by prior characters like xy})
5240 
5241         QString newText=lines.takeFirst();
5242         cc.movePosition(1,QDocumentCursor::EndOfLine);
5243         QDocumentLine dl=cc.line();
5244         foreach (QParenthesis p, dl.parentheses()) {
5245             if ( !(p.role & QParenthesis::Indent) )
5246                 continue;
5247 
5248             if ( p.role & QParenthesis::Open )
5249             {
5250                 ++indentCount;
5251             }
5252             if ( p.role & QParenthesis::Close )
5253             {
5254                 --indentCount;
5255             }
5256         }
5257 
5258         for (int i=0; i<lines.length(); i++)
5259         {
5260             QString l = lines[i];
5261             indentCount+=delayedDeltaIndentCount;
5262             delayedDeltaIndentCount=0;
5263 
5264             if(!flag(WeakIndent)){
5265                 int n = 0;
5266 
5267                 while ( n < l.count() && l.at(n).isSpace() )
5268                     ++n;
5269 
5270                 l.remove(0, n);
5271 
5272                 if ( m_definition )
5273                 {
5274                     // TODO: FIXME ? work on strings to make sure command grouping does not interfere with cursor state...
5275                     //             (then the indentCount can be removed and the function should return the unindented indent,
5276                     //              but still needs to check for an unindent caused by a } at the beginning of the line)
5277 
5278                     //int deltaIndentCount=0;
5279                     //m_definition->indent(cc, &deltaIndentCount);
5280 
5281 
5282 
5283                     cc.movePosition(1,QDocumentCursor::Down);
5284                     cc.movePosition(1,QDocumentCursor::EndOfLine);
5285 
5286                     dl=cc.line();
5287                     QString dummyText=dl.text();
5288                     foreach (QParenthesis p, dl.parentheses()) {
5289                         if ( !(p.role & QParenthesis::Indent) )
5290                             continue;
5291 
5292                         if ( p.role & QParenthesis::Open )
5293                         {
5294                             ++delayedDeltaIndentCount;
5295                         }
5296                         if ( p.role & QParenthesis::Close )
5297                         {
5298                             if(delayedDeltaIndentCount){
5299                                 --delayedDeltaIndentCount;
5300                             }else{
5301                                 if(dummyText.left(p.offset).trimmed().isEmpty()){
5302                                     --indentCount;
5303                                     dummyText.replace(p.offset,p.length,QString(p.length,QChar(' ')));
5304                                 }else{
5305                                     --delayedDeltaIndentCount;
5306                                 }
5307                             }
5308                         }
5309                     }
5310 
5311                     if (indentCount >= 0){
5312                         indent=QString(indentCount,'\t');
5313                     }else{
5314                         //remove indent from constIndent
5315                         indent.clear();
5316                         if(!constIndent.isEmpty()){
5317                             int p=constIndent.indexOf('\t');
5318                             if(p>-1){
5319                                 // remove one tab
5320                                 constIndent.remove(p,1);
5321                             }else{
5322                                 // remove tabStop spaces
5323                                 constIndent.remove(0,m_doc->tabStop());
5324                             }
5325                         }
5326                     }
5327 
5328                     if ( flag(ReplaceIndentTabs) )
5329                         indent.replace("\t", QString(m_doc->tabStop(), ' '));
5330                 }
5331             }
5332             //c.insertLine();
5333             newText.append("\n");
5334             if (i<lines.length()-1 || !l.isEmpty() || !originallyAtLineStart)
5335                 // always indent line except last line if it is empty and the cursor was at line start
5336                 // in that case, the original indentation is still present from the first line
5337                 // example:
5338                 // >....a
5339                 // >|...c (and insert '....b\n'
5340             {
5341                 //c.insertText(indent);
5342                 //if(i>0 || flag(WeakIndent))
5343                 newText.append(constIndent);  // indent is taken from previous line
5344                 newText.append(indent);
5345             }
5346 
5347             //preInsertUnindent(c, l, additionalUnindent);
5348 
5349             //c.insertText(l);
5350             newText.append(l);
5351         }
5352         c.insertText(newText); // avoid inserting many single lines as it slows down txs considerably (contentschanged,patchStructure etc)
5353     }
5354 
5355     //bracket auto insertion
5356     if (autoComplete) {
5357         if (!cutBuffer.isEmpty()) {
5358             c.insertText(cutBuffer+autoBracket);
5359             c.movePosition(cutBuffer.length()+autoBracket.length(), QDocumentCursor::PreviousCharacter, QDocumentCursor::MoveAnchor);
5360             c.movePosition(cutBuffer.length(), QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
5361         }
5362 
5363         if (flag(QEditor::AutoInsertLRM) && c.isRTL() && autoBracket == "}")
5364             autoBracket = "}" + QString(QChar(LRM));
5365 
5366         QDocumentCursor copiedCursor = c.selectionEnd();
5367         PlaceHolder ph(autoBracket.length(),copiedCursor);
5368         ph.autoOverride = true;
5369         ph.cursor.handle()->setFlag(QDocumentCursorHandle::AutoUpdateKeepBegin);
5370         ph.cursor.handle()->setFlag(QDocumentCursorHandle::AutoUpdateKeepEnd);
5371 
5372         if (!cutBuffer.isEmpty()) {
5373             addPlaceHolder(ph);
5374             cutBuffer.clear();
5375         } else {
5376             copiedCursor.insertText(autoBracket);
5377             if(!autoBracket.startsWith('\\')){ // don't set placeholder for commands e.g. \} or \left as it pretty much inhibits enterring normal commands
5378                 addPlaceHolder(ph);
5379             }
5380             c.movePosition(autoBracket.length(), QDocumentCursor::PreviousCharacter, QDocumentCursor::MoveAnchor);
5381         }
5382     }
5383 
5384 
5385     if (beginNewMacro)
5386         m_doc->endMacro();
5387 }
5388 
insertText(const QString & text)5389 void QEditor::insertText(const QString& text){
5390 	insertText(m_cursor, text);
5391 }
5392 
5393 /*!
5394 	\brief Write some text at the current cursor position
5395 
5396 	This function is provided to make editing operations easier
5397 	from the outside and to keep them compatible with cursor
5398 	mirrors.
5399 */
write(const QString & s)5400 void QEditor::write(const QString& s)
5401 {
5402 	document()->clearLanguageMatches();
5403 
5404 	if (!m_mirrors.empty())
5405 		m_doc->beginMacro();
5406 
5407 	insertText(m_cursor, s);
5408 
5409 	for ( int i = 0; i < m_mirrors.count(); ++i )
5410 		insertText(m_mirrors[i], s);
5411 
5412 	if (!m_mirrors.empty())
5413 		m_doc->endMacro();
5414 
5415 	emitCursorPositionChanged();
5416 	setFlag(CursorOn, true);
5417 	ensureCursorVisible();
5418 	repaintCursor();
5419 }
5420 
zoomIn()5421 void QEditor::zoomIn()
5422 {
5423 	zoom(1);
5424 }
5425 
zoomOut()5426 void QEditor::zoomOut()
5427 {
5428 	zoom(-1);
5429 }
5430 
resetZoom()5431 void QEditor::resetZoom()
5432 {
5433 	zoom(0);
5434 }
5435 
5436 /*!
5437 	\brief Zoom
5438 	\param n relative zoom factor
5439 
5440 	Zooming is achieved by changing the point size of the font as follows:
5441 
5442 	fontPointSize += \a n
5443 
5444 	n == 0 is used to reset the zoom
5445 */
zoom(int n)5446 void QEditor::zoom(int n)
5447 {
5448 	if ( !m_doc )
5449 		return;
5450 
5451 	if ( n == 0 )
5452 		m_doc->setFontSizeModifier(0);
5453 	else
5454 		m_doc->setFontSizeModifier(m_doc->fontSizeModifier() + n);
5455 
5456 	if (m_wrapAfterNumChars)
5457 		setWrapAfterNumChars(m_wrapAfterNumChars); // updates the width for the new font
5458 }
5459 
5460 /*!
5461 	\brief Obtain the value of panel margins
5462 	\param l left margin
5463 	\param t top margin
5464 	\param r right margin
5465 	\param b bottom margin
5466 */
getPanelMargins(qreal * l,qreal * t,qreal * r,qreal * b) const5467 void QEditor::getPanelMargins(qreal *l, qreal *t, qreal *r, qreal *b) const
5468 {
5469 	m_margins.getCoords(l, t, r, b);
5470 }
5471 
5472 /*!
5473 	\brief Change the viewport margins to make room for panels
5474 	\param l left margin
5475 	\param t top margin
5476 	\param r right margin
5477 	\param b bottom margin
5478 */
setPanelMargins(qreal l,qreal t,qreal r,qreal b)5479 void QEditor::setPanelMargins(qreal l, qreal t, qreal r, qreal b)
5480 {
5481 	m_margins.setCoords(l, t, r, b);
5482 
5483 	setViewportMargins(l, t, r, b);
5484 
5485 	if ( flag(LineWrap) )
5486 	{
5487 		//qDebug("panel adjust : wrapping to %i", viewport()->width());
5488 		m_doc->setWidthConstraint(wrapWidth());
5489 	}
5490 }
5491 
5492 
5493 /*!
5494 	\brief Request repaint (using QWidget::update()) for the region occupied by the cursor
5495 */
repaintCursor()5496 void QEditor::repaintCursor()
5497 {
5498 	if ( m_mirrors.count() ){
5499 		viewport()->update();
5500 		return;
5501 	}
5502 	if (m_cursor.isNull())
5503 		return;
5504 	//check whether Format/Layout needs update
5505 	bool updateAll=false;
5506 	for(int i=getFirstVisibleLine();i<=getLastVisibleLine();i++){
5507 	    if(m_doc->line(i).hasFlag(QDocumentLine::LayoutDirty)||!m_doc->line(i).hasFlag(QDocumentLine::FormatsApplied))
5508 		updateAll=true;
5509 	}
5510     if(updateAll){
5511 	    viewport()->update();
5512 	    return;
5513     }
5514 
5515     QRectF r = cursorRect();
5516 
5517 	if ( m_crect != r )
5518 	{
5519         viewport()->update(m_crect.translated(horizontalOffset(), 0).toRect());
5520 		m_crect = r;
5521         viewport()->update(m_crect.translated(horizontalOffset(), 0).toRect());
5522 	} else {
5523         viewport()->update(m_crect.translated(horizontalOffset(), 0).toRect());
5524 	}
5525 }
5526 
5527 /*!
5528 	\return whether the cursor is currently visible
5529 */
isCursorVisible() const5530 bool QEditor::isCursorVisible() const
5531 {
5532     QPointF pos = m_cursor.documentPosition();
5533 
5534     const QRectF display(horizontalOffset(), verticalOffset(), viewport()->width(), viewport()->height());
5535 
5536 	//qDebug() << pos << " belongs to " << display << " ?";
5537 
5538     return display.contains(pos);
5539 }
5540 
5541 /*!
5542 	\brief Ensure that the current cursor is visible
5543 */
ensureCursorVisible(const QDocumentCursor & cursor,MoveFlags mflags)5544 void QEditor::ensureCursorVisible(const QDocumentCursor& cursor, MoveFlags mflags){
5545     QPointF pos = cursor.documentPosition();
5546 
5547 	if (mflags & KeepDistanceFromViewTop && cursor == m_cursor) {
5548 		int linesFromDocStart = pos.y() / m_doc->getLineSpacing();
5549 		verticalScrollBar()->setValue(linesFromDocStart - m_cursorLinesFromViewTop);
5550 	}
5551 
5552 	int surrounding = (mflags&KeepSurrounding) ? m_cursorSurroundingLines : 0;
5553 
5554     const qreal ls = document()->getLineSpacing();
5555 
5556     qreal surroundingHeight = ls * surrounding;
5557 
5558     qreal ypos = pos.y(),
5559 		yval = verticalScrollBar()->value() * ls, //verticalOffset(),
5560 		ylen = viewport()->height(),
5561 		yend = ypos + ls;
5562 	int ytarget = -1;
5563 
5564      if ( ypos - surroundingHeight < yval ) {// cursor above
5565         ytarget = qFloor(ypos / ls);
5566      } else if ( yend + surroundingHeight > (yval + ylen ) ) {// cursor below
5567           if (ypos > (yval + ylen) && (mflags & AllowScrollToTop)) { // cursor off screen: maximal move (cursor at topmost pos + surrounding - like in cursor above)
5568             ytarget = qFloor(ypos / ls);
5569           } else { // cursor still on screen: minimal move (cursor at bottommost pos - surrounding)
5570             ytarget = qFloor(1. + (yend - ylen) / ls + surrounding + surrounding);
5571           }
5572      }
5573 
5574     if(ytarget>=0){
5575         ytarget-=surrounding;
5576         if(ytarget<0)
5577             ytarget=0;
5578 		int absDeltaY = qAbs(ytarget - verticalScrollBar()->value());
5579 		if (flag(QEditor::SmoothScrolling) && mflags&Animated) {
5580 			if (!m_scrollAnimation) {
5581 				m_scrollAnimation = new QPropertyAnimation(this);
5582 			}
5583 			if (m_scrollAnimation->state() == QAbstractAnimation::Running) {
5584 				m_scrollAnimation->stop();
5585 			}
5586 			m_scrollAnimation->setStartValue(verticalScrollBar()->value());
5587 			m_scrollAnimation->setEndValue(ytarget);
5588 			m_scrollAnimation->setTargetObject(verticalScrollBar());
5589 			m_scrollAnimation->setPropertyName("value");
5590 			m_scrollAnimation->setDuration(absDeltaY > 20 ? 300 : (absDeltaY * 300) / 20);
5591 			if (absDeltaY > 40)
5592 				m_scrollAnimation->setEasingCurve(QEasingCurve::InOutQuart);
5593 			else if (absDeltaY > 20)
5594 				m_scrollAnimation->setEasingCurve(QEasingCurve::InOutQuad);
5595 			else
5596 				m_scrollAnimation->setEasingCurve(QEasingCurve::Linear);
5597 			m_scrollAnimation->start();
5598 		} else {
5599 			verticalScrollBar()->setValue(ytarget);
5600 		}
5601     }
5602 
5603     qreal xval = horizontalOffset(),
5604 		xlen = viewport()->width(),
5605 		xpos = pos.x();
5606 
5607 	if ( xpos < xval )
5608 	{
5609 		//qDebug("scroll leftward");
5610         horizontalScrollBar()->setValue(qMax(0., xpos - 4));
5611 	} else if ( xpos > (xval + xlen - 4) ) {
5612 		//qDebug("scroll rightward : %i", xpos - xlen + 4);
5613 		horizontalScrollBar()
5614             ->setValue(qMax(1.*horizontalScrollBar()->value(), xpos - xlen + 4));
5615 	}
5616 
5617 	if ((mflags&ExpandFold) && m_cursor.line().isHidden())
5618 		document()->expandParents(m_cursor.lineNumber());
5619 }
5620 
5621 /*!
5622 	\brief Ensure that the current cursor is visible
5623 */
ensureCursorVisible(MoveFlags mflags)5624 void QEditor::ensureCursorVisible(MoveFlags mflags)
5625 {
5626 	if ( !isVisible() )
5627 	{
5628 		setFlag(EnsureVisible, true);
5629 		return;
5630 	}
5631 
5632 	ensureCursorVisible(m_cursor, mflags);
5633 
5634 	setFlag(EnsureVisible, false);
5635 }
5636 
5637 
5638 /*!
5639 	\brief ensure that a given line is visible by updating scrollbars if needed
5640 */
ensureVisible(int line)5641 void QEditor::ensureVisible(int line)
5642 {
5643 	if ( !m_doc )
5644 		return;
5645 
5646     const qreal ls = document()->getLineSpacing();
5647     qreal ypos = m_doc->y(line),
5648 		yval = verticalScrollBar()->value() * ls, //verticalOffset(),
5649 		ylen = viewport()->height(),
5650 		yend = ypos + ls;
5651 
5652 	if ( ypos < yval )
5653         verticalScrollBar()->setValue(qFloor(ypos / ls));
5654 	else if ( yend > (yval + ylen) )
5655         verticalScrollBar()->setValue(qFloor(1. + (yend - ylen) / ls));
5656 
5657 }
5658 
5659 /*!
5660 	\brief Ensure that a given rect is visible by updating scrollbars if needed
5661 */
ensureVisible(const QRectF & rect)5662 void QEditor::ensureVisible(const QRectF &rect)
5663 {
5664 	if ( !m_doc )
5665 		return;
5666 
5667     const qreal ls = document()->getLineSpacing();
5668     qreal ypos = rect.y(),
5669 		yval = verticalOffset(),
5670 		ylen = viewport()->height(),
5671 		yend = ypos + rect.height();
5672 
5673 	if ( ypos < yval )
5674         verticalScrollBar()->setValue(qFloor(ypos / ls));
5675 	else if ( yend > (yval + ylen) )
5676         verticalScrollBar()->setValue(qFloor(1. + (yend - ylen) / ls));
5677 
5678 	//verticalScrollBar()->setValue(rect.y());
5679 }
5680 
5681 /*!
5682 	\return the rectangle occupied by the current cursor
5683 
5684 	This will either return a cursorRect for the current cursor or
5685 	the selectionRect() if the cursor has a selection.
5686 
5687 	The cursor position, which would be the top left corner of the actual
5688 	rectangle occupied by the cursor can be obtained using QDocumentCursor::documentPosition()
5689 
5690 	The behavior of this method may surprise newcomers but it is actually quite sensible
5691 	as this rectangle is mainly used to specify the update rect of the widget and the whole
5692 	line needs to be updated to properly update the line background whenever the cursor move
5693 	from a line to another.
5694 */
cursorRect() const5695 QRectF QEditor::cursorRect() const
5696 {
5697 	return m_cursor.hasSelection() ? selectionRect() : cursorRect(m_cursor);
5698 }
5699 
getFirstVisibleLine()5700 int QEditor::getFirstVisibleLine(){
5701 	if (!document()) return 0;
5702 	return document()->lineNumber(verticalOffset());
5703 }
5704 
getLastVisibleLine()5705 int QEditor::getLastVisibleLine(){
5706 	if (!document()) return 0;
5707 	return qMin(document()->lines()-1, document()->lineNumber(verticalOffset() + viewport()->height()) + 1);
5708 }
5709 
scrollToFirstLine(int l)5710 void QEditor::scrollToFirstLine(int l){
5711 
5712     const qreal ls = document()->getLineSpacing();
5713     const qreal ypos = m_doc->y(l-1);
5714     const qreal yval = verticalOffset();
5715     const int ylen = viewport()->height();
5716     const qreal yend = ypos + ylen;
5717 
5718 	if ( ypos < yval )
5719         verticalScrollBar()->setValue(qFloor(ypos / ls));
5720 	else if ( yend > (yval + ylen) )
5721         verticalScrollBar()->setValue(qFloor(1. + (yend - ylen) / ls));
5722 
5723 }
5724 
setCursorSurroundingLines(int s)5725 void QEditor::setCursorSurroundingLines(int s){
5726 	m_cursorSurroundingLines = s;
5727 }
5728 
5729 /*!
5730 	\return the rectangle occupied by the selection in viewport coordinates
5731 
5732 	If the current cursor does not have a selection, its cursorRect() is returned.
5733 
5734 	The returned rectangle will always be bigger than the actual selection has
5735 	it is actually the union of all the rectangles occupied by all lines the selection
5736 	spans over.
5737 */
selectionRect() const5738 QRectF QEditor::selectionRect() const
5739 {
5740 	if ( !m_cursor.hasSelection() )
5741 		return cursorRect(m_cursor);
5742 
5743 	QDocumentSelection s = m_cursor.selection();
5744 
5745 	if ( s.startLine == s.endLine )
5746 		return cursorRect(m_cursor);
5747 
5748     qreal y = m_doc->y(s.startLine);
5749     QRectF r = m_doc->lineRect(s.endLine);
5750     qreal height = r.y() + r.height() - y;
5751 
5752     r = QRectF(0, y, viewport()->width(), height);
5753 	r.translate(-horizontalOffset(), -verticalOffset());
5754 	return r;
5755 }
5756 
5757 /*!
5758 	\return the rectangle occupied by the given line, in viewport coordinates
5759 
5760 	The width of the returned rectangle will always be the viewport width.
5761 */
lineRect(int line) const5762 QRectF QEditor::lineRect(int line) const
5763 {
5764 	if ( !m_doc )
5765         return QRectF();
5766 
5767     QRectF r = m_doc->lineRect(line);
5768 	r.setWidth(viewport()->width());
5769 	r.translate(-horizontalOffset(), -verticalOffset());
5770 
5771 	return r;
5772 }
5773 
5774 /*!
5775 	\return The line rect of the given cursor
5776 */
cursorRect(const QDocumentCursor & c) const5777 QRectF QEditor::cursorRect(const QDocumentCursor& c) const
5778 {
5779     return lineRect(c.lineNumber());
5780 }
5781 
5782 /*!
5783 	\return the area covered by the cursor (in widget coordinates)
5784 */
cursorMircoFocusRect() const5785 QRectF QEditor::cursorMircoFocusRect() const
5786 {
5787 	QDocumentCursor c(m_cursor, false);
5788 	QDocumentLine line=c.line();
5789 	if (!c.isValid()) return QRect();
5790     if (c.columnNumber()<0 || c.columnNumber()>line.length()) return QRectF();
5791 
5792     qreal left;
5793     qreal top;
5794     qreal temp;
5795     getPanelMargins(&left,&top,&temp,&temp);
5796 
5797 	top += lineRect(m_cursor.lineNumber()).top();
5798     QPointF p = line.cursorToDocumentOffset(c.columnNumber());
5799 	left += p.x();
5800 	top += p.y();
5801 
5802     const qreal width = 1; // TODO adapt for bold cursor and overwrite cursor
5803     const qreal height = document()->getLineSpacing();
5804     return QRectF(left, top, width, height);
5805 }
5806 
5807 /*!
5808 	\brief creates a valid QMimeData object depending on the selection
5809 */
createMimeDataFromSelection() const5810 QMimeData* QEditor::createMimeDataFromSelection() const
5811 {
5812 	QMimeData *d = new QMimeData;
5813 
5814 	if ( !m_cursor.hasSelection() )
5815 	{
5816 		qWarning("Generated empty MIME data");
5817 		return d;
5818 	}
5819 
5820 	if ( m_mirrors.isEmpty() )
5821 	{
5822 		d->setText(m_cursor.selectedText());
5823 	} else {
5824 		// Multiple cursors. Use QMap to have the texts are ordered by line number.
5825 		// Ordering by m_mirrors, would be selection order, which may be unexpected.
5826 		// Also, selection order would require special handling of m_cursor insert
5827 		// position depending on selection order (see bug 2315).
5828 		// So line number order seems the more clear way to handle this.
5829 		QMap<int, QString> texts;
5830 		texts.insert(m_cursor.lineNumber(), m_cursor.selectedText());
5831 
5832 		foreach ( const QDocumentCursor& m, m_mirrors )
5833 		{
5834 			texts.insert(m.lineNumber(), m.selectedText());
5835 		}
5836 
5837 		QString serialized = QStringList(texts.values()).join("\n");
5838 		d->setText(serialized);
5839 		d->setData("text/column-selection", serialized.toLocal8Bit());
5840 	}
5841 
5842 	//qDebug("generated selection from : \"%s\"", qPrintable(d->text()));
5843 
5844 	return d;
5845 }
5846 
5847 /*!
5848 	\brief Inserts the content of a QMimeData object at the cursor position
5849 
5850 	\note Only plain text is supported... \see QMimeData::hasText()
5851 */
insertFromMimeData(const QMimeData * d)5852 void QEditor::insertFromMimeData(const QMimeData *d)
5853 {
5854 	if ( d && m_cursor.isValid() && !isReadOnly() )
5855 	{
5856 
5857 		if ( d->hasFormat("text/column-selection") )
5858 		{
5859 			QStringList columns = QString::fromLocal8Bit(
5860 										d->data("text/column-selection")
5861 									).split('\n');
5862 
5863 			m_doc->beginMacro();
5864 
5865 			if ( m_cursor.hasSelection() )
5866 				m_cursor.removeSelectedText();
5867 			cursorMirrorsRemoveSelectedText();
5868 			clearCursorMirrors();
5869 
5870 			int col = m_cursor.columnNumber();
5871 			//m_cursor.insertText(columns.takeFirst());
5872 			insertText(m_cursor, columns.takeFirst());
5873 			QDocumentCursor c = m_cursor;
5874 
5875 			while ( columns.count() )
5876 			{
5877 				// check for end of doc and add line if needed...
5878 				c.setColumnNumber(c.line().length());
5879 
5880 				if ( c.atEnd() )
5881 					c.insertText("\n");
5882 				else
5883 					c.movePosition(1, QDocumentCursor::NextCharacter);
5884 
5885 				// align
5886 				c.setColumnNumber(qMin(col, c.line().length()));
5887 
5888 				// copy content of clipboard
5889 				//c.insertText(columns.takeFirst());
5890 				insertText(c, columns.takeFirst());
5891 				addCursorMirror(c);
5892 			}
5893 
5894 			m_doc->endMacro();
5895 
5896 		} else {
5897 			QString txt;
5898 
5899 			if ( d->hasFormat("text/plain") )
5900 				txt = d->text();
5901 			else if ( d->hasFormat("text/html") )
5902 				txt = d->html();
5903 
5904 			if (txt.isEmpty())
5905 				return;
5906 
5907             // filter \r (bug #1919)
5908             txt.remove('\r');
5909 
5910 			bool slow = txt.size() > 5*1024;
5911 			if (slow) emit slowOperationStarted();
5912 
5913             bool macroing = true; //isMirrored() || m_mirrors.size();
5914 
5915 			if ( macroing )
5916 				m_doc->beginMacro();
5917 
5918 			//if ( s )
5919 			//{
5920 			//	m_cursor.removeSelectedText();
5921 			//}
5922 
5923 			if ( !macroing && m_doc->lineCount() == 1 && m_doc->line(0).length() == 0)
5924 				setText(txt, true);
5925 			else {
5926 				insertText(m_cursor, txt);
5927 
5928 				if ( atPlaceholder() ) // need new evaluation, because insert operation might have changed things
5929 				{
5930 					PlaceHolder& ph = m_placeHolders[m_curPlaceHolder];
5931 					QString baseText = ph.cursor.selectedText();
5932 
5933 					QKeyEvent ev(QEvent::KeyPress, Qt::Key_Paste, Qt::NoModifier); // just a dummy to be able to pass something reasonable to affect() - currently unused
5934 					for ( int phm = 0; phm < ph.mirrors.count(); ++phm )
5935 					{
5936 						QString s = ph.affector ?  ph.affector->affect(&ev, baseText, m_curPlaceHolder, phm) : baseText;
5937 						ph.mirrors[phm].replaceSelectedText(s);
5938 					}
5939 				}
5940 
5941 				for ( int i = 0; i < m_mirrors.count(); ++i )
5942 				{
5943 					insertText(m_mirrors[i], txt);
5944 				}
5945 			}
5946 
5947 			if ( macroing )
5948 				m_doc->endMacro();
5949 
5950 			if (slow) emit slowOperationEnded();
5951 		}
5952 
5953 		ensureCursorVisible(KeepSurrounding);
5954 		setFlag(CursorOn, true);
5955 
5956 		emitCursorPositionChanged();
5957 	}
5958 }
5959 
5960 /*!
5961 	\brief Removes all cursor mirrors
5962 */
clearCursorMirrors()5963 void QEditor::clearCursorMirrors()
5964 {
5965 	if ( m_mirrors.isEmpty() )
5966 		return;
5967 
5968 	setPlaceHolder(-1);
5969 	repaintCursor();
5970 
5971 	for ( int i = 0; i < m_mirrors.count(); ++i )
5972 	{
5973 		m_mirrors[i].setAutoUpdated(false);
5974 	}
5975 
5976 	m_mirrors.clear();
5977 	m_cursorMirrorBlockAnchor = -1;
5978 
5979 	viewport()->update();
5980 }
5981 
5982 /*!
5983 	\brief Add a cursor mirror
5984 */
addCursorMirror(const QDocumentCursor & c)5985 void QEditor::addCursorMirror(const QDocumentCursor& c)
5986 {
5987 	if ( c.isNull() || (c == m_cursor) || m_mirrors.contains(c) )
5988 		return;
5989 
5990 	m_mirrors << c;
5991 
5992 	// necessary for smooth mirroring
5993 	m_mirrors.last().setSilent(true);
5994 	m_mirrors.last().setAutoUpdated(true);
5995 	m_mirrors.last().setAutoErasable(false);
5996 }
5997 
cursorMirrorsRemoveSelectedText()5998 void QEditor::cursorMirrorsRemoveSelectedText()
5999 {
6000 	for ( int i = 0; i < m_mirrors.count(); ++i )
6001 	{
6002 		m_mirrors[i].removeSelectedText();
6003 	}
6004 }
6005 
setCursorBold(bool bold)6006 void QEditor::setCursorBold(bool bold)
6007 {
6008 	if (m_doc)
6009 		m_doc->impl()->setCursorBold(bold);
6010 }
6011 
6012 /*!
6013 	\internal
6014 	\brief Copy the selection to the clipboard
6015 */
setClipboardSelection()6016 void QEditor::setClipboardSelection()
6017 {
6018 	QClipboard *clipboard = QApplication::clipboard();
6019 
6020 	if ( !clipboard->supportsSelection() || !m_cursor.hasSelection() )
6021 		return;
6022 
6023 	QMimeData *data = createMimeDataFromSelection();
6024 
6025 	clipboard->setMimeData(data, QClipboard::Selection);
6026 }
6027 
6028 /*!
6029 	\internal
6030 	\brief Scroll contents
6031 
6032 	Refer to QAbstractScrollArea doc for more info.
6033 */
scrollContentsBy(int dx,int dy)6034 void QEditor::scrollContentsBy(int dx, int dy)
6035 {
6036 	#ifdef Q_GL_EDITOR
6037 	viewport()->update();
6038 	#else
6039 #if QT_VERSION<QT_VERSION_CHECK(6,0,0)
6040     const qreal ls = document()->getLineSpacing();
6041     viewport()->scroll(dx, qFloor(dy * ls));
6042 #else
6043     Q_UNUSED(dx)
6044     viewport()->update();
6045 #endif
6046 	#endif
6047 
6048 	if (dy != 0)
6049 		emit visibleLinesChanged();
6050 }
6051 
inputMethodQuery(Qt::InputMethodQuery property) const6052 QVariant QEditor::inputMethodQuery(Qt::InputMethodQuery property) const {
6053 	switch(property) {
6054     case Qt::ImCursorRectangle:
6055         return cursorMircoFocusRect();
6056 	case Qt::ImFont:
6057 		// TODO find out correct value: qtextcontol uses the following
6058 		//return QVariant(d->cursor.charFormat().font());
6059 		return QVariant();
6060 	case Qt::ImCursorPosition:
6061 		// TODO find out correct value: qtextcontol uses the following
6062 		//return QVariant(d->cursor.position() - block.position());
6063 		return QVariant();
6064 	case Qt::ImSurroundingText:
6065 		return QVariant(cursor().line().text());
6066 	case Qt::ImCurrentSelection:
6067 		return QVariant(cursor().selectedText());
6068 	case Qt::ImMaximumTextLength:
6069 		return QVariant(); // No limit.
6070 	case Qt::ImAnchorPosition:
6071 		// TODO find out correct value: qtextcontol uses the following
6072 		//return QVariant(qBound(0, d->cursor.anchor() - block.position(), block.length()));
6073 		return QVariant();
6074 	default:
6075 		return QVariant();
6076 	}
6077 }
6078 
6079 /*!
6080 	\internal
6081 	\brief Workaround inconsistent width determination of viewport width
6082 	accross platfroms when scrollbars are visible...
6083 */
wrapWidth() const6084 int QEditor::wrapWidth() const
6085 {
6086     #ifdef Q_OS_WIN32
6087 	//if ( verticalScrollBar()->isVisible() )
6088 	//	return viewport()->width() - verticalScrollBar()->width();
6089 	#endif
6090 	return (flag(HardLineWrap)||flag(LineWidthConstraint))&&m_LineWidth>0 ? m_LineWidth : viewport()->width();
6091 }
6092 
6093 /*!
6094 	\internal
6095 	\brief Slot called whenever document width changes
6096 
6097 	Horizontal scrollbar is updated here.
6098 
6099 	\note ensureCursorVisible() is NOT called.
6100 */
documentWidthChanged(int newWidth)6101 void QEditor::documentWidthChanged(int newWidth)
6102 {
6103 	if ( flag(LineWrap)&&!flag(HardLineWrap)&&!flag(LineWidthConstraint) )
6104 	{
6105 		horizontalScrollBar()->setMaximum(0);
6106 		return;
6107 	}
6108 
6109 	int nv = qMax(0, newWidth - wrapWidth());
6110 
6111 	if ( flag(HardLineWrap)||flag(LineWidthConstraint) ){
6112 	    const QSize viewportSize = viewport()->size();
6113 	    nv=(qMax(0, m_LineWidth - viewportSize.width()));
6114 	}
6115 
6116 	horizontalScrollBar()->setMaximum(nv);
6117 
6118 	//ensureCursorVisible();
6119 }
6120 
6121 /*!
6122 	\internal
6123 	\brief Slot called whenever document height changes
6124 
6125 	Vertical scrollbar is updated here (maximum is changed
6126 	and value is modified if needed to ensure that the cursor is visible)
6127 */
documentHeightChanged(int)6128 void QEditor::documentHeightChanged(int)
6129 {
6130 
6131 	if ( flag(LineWrap) )
6132 	{
6133 		m_doc->setWidthConstraint(wrapWidth());
6134 	}
6135 	setVerticalScrollBarMaximum();
6136 	//ensureCursorVisible();
6137 }
6138 
6139 /*!
6140 	\internal
6141 	\brief Request paint event upon modification
6142 	\param i first modified line
6143 	\param n number of modified lines
6144 */
repaintContent(int i,int n)6145 void QEditor::repaintContent(int i, int n)
6146 {
6147 	if ( !m_doc )
6148 		return;
6149 
6150 	#ifdef Q_GL_EDITOR
6151 	viewport()->update();
6152 	#else
6153 	if ( n <= 0 )
6154 	{
6155 		viewport()->update();
6156 	}
6157 
6158     QRectF frect = m_doc->lineRect(i);
6159 
6160     const qreal yoff = verticalOffset() + viewport()->height();
6161 
6162 	if ( frect.y() > yoff )
6163 		return;
6164 
6165 	if ( n == 1 )
6166 	{
6167 		frect.translate(0, -verticalOffset());
6168 		//qDebug() << frect;
6169         viewport()->update(frect.toRect());
6170 		return;
6171 	}
6172 
6173     QRectF lrect = m_doc->lineRect(i + n - 1);
6174 
6175 	if ( (n > 0) && (lrect.y() + lrect.height()) < verticalOffset() )
6176 		return;
6177 
6178 	//qDebug("repainting %i lines starting from %ith one", n, i);
6179 
6180 	//rect.setWidth(viewport()->width());
6181 	//rect.setHeight(qMin(viewport()->height(), rect.height() * n));
6182 
6183     const qreal paintOffset = frect.y() - verticalOffset();
6184     const qreal paintHeight = lrect.y() + lrect.height() - frect.y();
6185     const qreal maxPaintHeight = viewport()->height() - paintOffset;
6186 
6187     QRectF rect = QRectF(
6188 				frect.x(),
6189 				paintOffset,
6190 				viewport()->width(),
6191 					(n <= 0)
6192 				?
6193 					maxPaintHeight
6194 				:
6195 					qMin(maxPaintHeight, paintHeight)
6196 			);
6197 
6198 	//qDebug() << rect;
6199 
6200     viewport()->update(rect.toRect());
6201 	#endif
6202 }
6203 
6204 /*!
6205 	\internal
6206 	\brief Update function called upon editing action
6207 	\param i First modified line
6208 	\param n Number of modified lines
6209 
6210 	If more than one line has been modified this function
6211 	causes a repaint from the first visible line to the end
6212 	of the viewport due to the way QAbstractScrollArea
6213 	handles scrolling.
6214 
6215 	\note This function used to update formatting but
6216 	the highlighting has been moved to QDocument recently
6217 */
updateContent(int i,int n)6218 void QEditor::updateContent (int i, int n)
6219 {
6220 	if ( !m_doc )
6221 		return;
6222 
6223 	//qDebug("updating %i, %i", i, n);
6224 
6225 	if (m_placeHolders.count()>0 &&
6226         !m_placeHolderSynchronizing) { //no recursion, if updateContent is called due to changes made by setPlaceHolder
6227 		//look which placeholder has been modified
6228 		if (m_mirrors.count()==0){
6229 			for (int i=0;i<m_placeHolders.count();i++){
6230 				if (m_placeHolders[i].autoOverride) continue;
6231 				bool found=false;
6232 				if (m_placeHolders[i].cursor.isWithinSelection(m_cursor))
6233 					found=true;
6234 				else foreach (const QDocumentCursor &c, m_placeHolders[i].mirrors)
6235 					if (c.isWithinSelection(m_cursor)) {
6236 						found=true;
6237 						break;
6238 					}
6239 				if (found) {
6240 					if (m_curPlaceHolder!=i)
6241 						setPlaceHolder(i,false);
6242 					break;
6243 				}
6244 			}
6245 		}
6246 		//remove placeholders
6247 		//if another has been modified
6248         for(int i=0;i<m_curPlaceHolder && i< m_placeHolders.count();++i){
6249             const PlaceHolder& current_ph = m_placeHolders.at(m_curPlaceHolder);
6250             const PlaceHolder& ph = m_placeHolders.at(i);
6251             if(ph.cursor.lineNumber()!=current_ph.cursor.lineNumber()){
6252                 //remove old placeholders
6253                 removePlaceHolder(i);
6254                 --i;
6255             }
6256         }
6257         /*if (m_curPlaceHolder!=m_lastPlaceHolder &&
6258 			m_lastPlaceHolder>=0 &&  m_lastPlaceHolder < m_placeHolders.count())
6259 			if (m_placeHolders[m_lastPlaceHolder].autoRemove){
6260 				removePlaceHolder(m_lastPlaceHolder);
6261 				m_lastPlaceHolder=m_curPlaceHolder;
6262             }*/
6263 		//if someone pressed enter
6264 		if (m_curPlaceHolder>=0 && m_curPlaceHolder < m_placeHolders.count())
6265 			if (m_placeHolders[m_curPlaceHolder].autoRemove && m_placeHolders[m_curPlaceHolder].cursor.lineNumber() != m_placeHolders[m_curPlaceHolder].cursor.anchorLineNumber())
6266 				removePlaceHolder(m_curPlaceHolder);
6267 		//empty ones (which are not currently used)
6268 		for (int i=m_placeHolders.count()-1;i>=0;i--) {
6269 			const PlaceHolder& ph = m_placeHolders.at(i);
6270 			if (i != m_curPlaceHolder && i!=m_lastPlaceHolder && ph.autoRemove &&
6271 				ph.cursor.lineNumber()==ph.cursor.anchorLineNumber() &&
6272 				ph.cursor.columnNumber()==ph.cursor.anchorColumnNumber())
6273 					removePlaceHolder(i);
6274 		}
6275 		//invalid used ones
6276 		if (m_lastPlaceHolder>=0 &&  m_lastPlaceHolder < m_placeHolders.count() &&
6277 			m_placeHolders[m_lastPlaceHolder].cursor.lineNumber()==-1) {
6278 			removePlaceHolder(m_lastPlaceHolder);
6279 		}
6280 		if (m_curPlaceHolder>=0 &&  m_curPlaceHolder < m_placeHolders.count() &&
6281 			m_placeHolders[m_curPlaceHolder].cursor.lineNumber()==-1) {
6282 			removePlaceHolder(m_curPlaceHolder);
6283 		}
6284 
6285 
6286 
6287 		//stupid mirrors, resyncronize them if necessary
6288 		if (m_mirrors.count()>0 && m_curPlaceHolder>=0 && m_curPlaceHolder < m_placeHolders.count()){
6289 			const PlaceHolder &ph = m_placeHolders[m_curPlaceHolder];
6290 			foreach (const QDocumentCursor &mc, ph.mirrors)
6291 				if (mc.selectedText()!=ph.cursor.selectedText()){
6292 					setPlaceHolder(m_curPlaceHolder,false);
6293 					break;
6294 				}
6295 		}
6296 	}
6297 	m_lastPlaceHolder=m_curPlaceHolder;
6298 
6299 	repaintContent(i, n>1 ? -1 : n);
6300 }
6301 
6302 /*!
6303 	\internal
6304 */
markChanged(QDocumentLineHandle * l,int mark,bool on)6305 void QEditor::markChanged(QDocumentLineHandle *l, int mark, bool on)
6306 {
6307 	emit markChanged(fileName(), l, mark, on);
6308 }
6309 
displayModifyTime() const6310 bool QEditor::displayModifyTime() const
6311 {
6312     return mDisplayModifyTime;
6313 }
6314 
isMirrored()6315 bool QEditor::isMirrored(){
6316     bool macroing=atPlaceholder();
6317     if(macroing){
6318         if(m_placeHolders[m_curPlaceHolder].mirrors.count()<1)
6319             macroing=false;
6320     }
6321     return macroing;
6322 }
6323 
addMark(int pos,QColor color,QString type)6324 void QEditor::addMark(int pos, QColor color, QString type){
6325     MarkedScrollBar *scrlBar=qobject_cast<MarkedScrollBar*>(verticalScrollBar());
6326     scrlBar->addMark(pos,color,type);
6327     scrlBar->repaint();
6328 }
addMarkDelayed(int pos,QColor color,QString type)6329 void QEditor::addMarkDelayed(int pos, QColor color, QString type){
6330     MarkedScrollBar *scrlBar=qobject_cast<MarkedScrollBar*>(verticalScrollBar());
6331     scrlBar->addMark(pos,color,type);
6332     //scrlBar->repaint();
6333 }
paintMarks()6334 void QEditor::paintMarks(){
6335     MarkedScrollBar *scrlBar=qobject_cast<MarkedScrollBar*>(verticalScrollBar());
6336     scrlBar->repaint();
6337 }
6338 
addMark(QDocumentLineHandle * dlh,QColor color,QString type)6339 void QEditor::addMark(QDocumentLineHandle *dlh, QColor color, QString type){
6340     if(dlh==nullptr)
6341         return;
6342     MarkedScrollBar *scrlBar=qobject_cast<MarkedScrollBar*>(verticalScrollBar());
6343     scrlBar->addMark(dlh,color,type);
6344     scrlBar->repaint();
6345 }
6346 
removeMark(int pos,QString type)6347 void QEditor::removeMark(int pos,QString type){
6348     MarkedScrollBar *scrlBar=qobject_cast<MarkedScrollBar*>(verticalScrollBar());
6349     scrlBar->removeMark(pos,type);
6350     scrlBar->repaint();
6351 }
6352 
removeMark(QDocumentLineHandle * dlh,QString type)6353 void QEditor::removeMark(QDocumentLineHandle *dlh,QString type){
6354     MarkedScrollBar *scrlBar=qobject_cast<MarkedScrollBar*>(verticalScrollBar());
6355     scrlBar->removeMark(dlh,type);
6356     scrlBar->repaint();
6357 }
6358 
removeMark(QString type)6359 void QEditor::removeMark(QString type){
6360     MarkedScrollBar *scrlBar=qobject_cast<MarkedScrollBar*>(verticalScrollBar());
6361     scrlBar->removeMark(type);
6362     scrlBar->repaint();
6363 }
6364 
removeAllMarks()6365 void QEditor::removeAllMarks(){
6366     MarkedScrollBar *scrlBar=qobject_cast<MarkedScrollBar*>(verticalScrollBar());
6367     scrlBar->removeAllMarks();
6368     scrlBar->removeAllShades();
6369 }
6370 
addMarkRange(int start,int end,QColor color,QString type)6371 void QEditor::addMarkRange(int start, int end, QColor color, QString type){
6372     MarkedScrollBar *scrlBar=qobject_cast<MarkedScrollBar*>(verticalScrollBar());
6373     scrlBar->addShade(start,end,color,type);
6374 }
6375 
6376 /*! @} */
6377