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