diff --git a/autotests/src/kateview_test.cpp b/autotests/src/kateview_test.cpp index b34a2211..a0880f81 100644 --- a/autotests/src/kateview_test.cpp +++ b/autotests/src/kateview_test.cpp @@ -1,395 +1,396 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kateview_test.h" #include "moc_kateview_test.cpp" #include #include #include #include #include #include #include #include #include using namespace KTextEditor; QTEST_MAIN(KateViewTest) KateViewTest::KateViewTest() : QObject() { KTextEditor::EditorPrivate::enableUnitTestMode(); } KateViewTest::~KateViewTest() { } void KateViewTest::testCoordinatesToCursor() { KTextEditor::DocumentPrivate doc(false, false); doc.setText("Hi World!\nHi\n"); KTextEditor::View* view1 = static_cast(doc.createView(nullptr)); + view1->resize(400, 300); view1->show(); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 2))), KTextEditor::Cursor(0, 2)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 1))), KTextEditor::Cursor(1, 1)); // behind end of line should give an invalid cursor QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 5))), KTextEditor::Cursor::invalid()); QCOMPARE(view1->cursorToCoordinate(KTextEditor::Cursor(3, 1)), QPoint(-1, -1)); // check consistency between cursorToCoordinate(view->cursorPosition() and cursorPositionCoordinates() // random position view1->setCursorPosition(KTextEditor::Cursor(0, 3)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(view1->cursorPosition())), KTextEditor::Cursor(0, 3)); QCOMPARE(view1->coordinatesToCursor(view1->cursorPositionCoordinates()), KTextEditor::Cursor(0, 3)); // end of line view1->setCursorPosition(KTextEditor::Cursor(0, 9)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 9))), KTextEditor::Cursor(0, 9)); QCOMPARE(view1->coordinatesToCursor(view1->cursorPositionCoordinates()), KTextEditor::Cursor(0, 9)); // empty line view1->setCursorPosition(KTextEditor::Cursor(2, 0)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(2, 0))), KTextEditor::Cursor(2, 0)); QCOMPARE(view1->coordinatesToCursor(view1->cursorPositionCoordinates()), KTextEditor::Cursor(2, 0)); // same test again, but with message widget on top visible KTextEditor::Message *message = new KTextEditor::Message("Jo World!", KTextEditor::Message::Information); doc.postMessage(message); // wait 500ms until show animation is finished, so the message widget is visible QTest::qWait(500); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 2))), KTextEditor::Cursor(0, 2)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 1))), KTextEditor::Cursor(1, 1)); // behind end of line should give an invalid cursor QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 5))), KTextEditor::Cursor::invalid()); QCOMPARE(view1->cursorToCoordinate(KTextEditor::Cursor(3, 1)), QPoint(-1, -1)); } void KateViewTest::testCursorToCoordinates() { KTextEditor::DocumentPrivate doc(false, false); doc.setText("int a;"); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->config()->setDynWordWrap(true); view->show(); // don't crash, see https://bugs.kde.org/show_bug.cgi?id=337863 view->cursorToCoordinate(Cursor(0, 0)); view->cursorToCoordinate(Cursor(1, 0)); view->cursorToCoordinate(Cursor(-1, 0)); } void KateViewTest::testReloadMultipleViews() { QTemporaryFile file("XXXXXX.cpp"); file.open(); QTextStream stream(&file); const QString line = "const char* foo = \"asdf\"\n"; for (int i = 0; i < 200; ++i) { stream << line; } stream << flush; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); QCOMPARE(doc.highlightingMode(), QString("C++")); KTextEditor::ViewPrivate *view1 = new KTextEditor::ViewPrivate(&doc, nullptr); KTextEditor::ViewPrivate *view2 = new KTextEditor::ViewPrivate(&doc, nullptr); view1->show(); view2->show(); QCOMPARE(doc.views().count(), 2); QVERIFY(doc.documentReload()); } void KateViewTest::testTabCursorOnReload() { // testcase for https://bugs.kde.org/show_bug.cgi?id=258480 QTemporaryFile file("XXXXXX.cpp"); file.open(); QTextStream stream(&file); stream << "\tfoo\n"; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); const KTextEditor::Cursor cursor(0, 4); view->setCursorPosition(cursor); QCOMPARE(view->cursorPosition(), cursor); QVERIFY(doc.documentReload()); QCOMPARE(view->cursorPosition(), cursor); } void KateViewTest::testLowerCaseBlockSelection() { // testcase for https://bugs.kde.org/show_bug.cgi?id=258480 KTextEditor::DocumentPrivate doc; doc.setText("nY\nnYY\n"); KTextEditor::ViewPrivate *view1 = new KTextEditor::ViewPrivate(&doc, nullptr); view1->setBlockSelection(true); view1->setSelection(Range(0, 1, 1, 3)); view1->lowercase(); QCOMPARE(doc.text(), QString("ny\nnyy\n")); } void KateViewTest::testSelection() { // see also: https://bugs.kde.org/show_bug.cgi?id=277422 // wrong behavior before: // Open file with text // click at end of some line (A) and drag to right, i.e. without selecting anything // click somewhere else (B) // shift click to another place (C) // => expected: selection from B to C // => actual: selection from A to C QTemporaryFile file("XXXXXX.txt"); file.open(); QTextStream stream(&file); stream << "A\n" << "B\n" << "C"; stream << flush; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->resize(100, 200); view->show(); QObject *internalView = nullptr; foreach (QObject* child, view->children()) { if (child->metaObject()->className() == QByteArrayLiteral("KateViewInternal")) { internalView = child; break; } } QVERIFY(internalView); const QPoint afterA = view->cursorToCoordinate(Cursor(0, 1)); const QPoint afterB = view->cursorToCoordinate(Cursor(1, 1)); const QPoint afterC = view->cursorToCoordinate(Cursor(2, 1)); // click after A QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterA, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterA, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCOMPARE(view->cursorPosition(), Cursor(0, 1)); // drag to right QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterA, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseMove, afterA + QPoint(50, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterA + QPoint(50, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCOMPARE(view->cursorPosition(), Cursor(0, 1)); QVERIFY(!view->selection()); // click after C QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterC, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterC, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCOMPARE(view->cursorPosition(), Cursor(2, 1)); // shift+click after B QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterB, Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterB, Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier)); QCOMPARE(view->cursorPosition(), Cursor(1, 1)); QCOMPARE(view->selectionRange(), Range(1, 1, 2, 1)); } void KateViewTest::testKillline() { KTextEditor::DocumentPrivate doc; doc.insertLines(0, { "foo", "bar", "baz" }); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->setCursorPositionInternal(KTextEditor::Cursor(1, 2)); view->killLine(); QCOMPARE(doc.text(), QLatin1String("foo\nbaz\n")); doc.clear(); QVERIFY(doc.isEmpty()); doc.insertLines(0, { "foo", "bar", "baz", "xxx" }); view->setCursorPositionInternal(KTextEditor::Cursor(1, 2)); view->shiftDown(); view->killLine(); QCOMPARE(doc.text(), QLatin1String("foo\nxxx\n")); } void KateViewTest::testFoldFirstLine() { QTemporaryFile file("XXXXXX.cpp"); file.open(); QTextStream stream(&file); stream << "/**\n" << " * foo\n" << " */\n" << "\n" << "int main() {}\n"; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); QCOMPARE(doc.highlightingMode(), QString("C++")); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->config()->setFoldFirstLine(false); view->setCursorPosition({4, 0}); // initially, nothing is folded QVERIFY(view->textFolding().isLineVisible(1)); // now change the config, and expect the header to be folded view->config()->setFoldFirstLine(true); qint64 foldedRangeId = 0; QVERIFY(!view->textFolding().isLineVisible(1, &foldedRangeId)); // now unfold the range QVERIFY(view->textFolding().unfoldRange(foldedRangeId)); QVERIFY(view->textFolding().isLineVisible(1)); // and save the file, we do not expect the folding to change then doc.setModified(true); doc.saveFile(); QVERIFY(view->textFolding().isLineVisible(1)); // now reload the document, nothing should change doc.setModified(false); QVERIFY(doc.documentReload()); QVERIFY(view->textFolding().isLineVisible(1)); } // test for bug https://bugs.kde.org/374163 void KateViewTest::testDragAndDrop() { KTextEditor::DocumentPrivate doc(false, false); doc.setText("line0\n" "line1\n" "line2\n" "\n" "line4"); KTextEditor::View* view = static_cast(doc.createView(nullptr)); view->show(); view->resize(400, 300); QWidget *internalView = nullptr; foreach (QObject* child, view->children()) { if (child->metaObject()->className() == QByteArrayLiteral("KateViewInternal")) { internalView = qobject_cast(child); break; } } QVERIFY(internalView); // select "line1\n" view->setSelection(Range(1, 0, 2, 0)); QCOMPARE(view->selectionRange(), Range(1, 0, 2, 0)); QVERIFY(QTest::qWaitForWindowExposed(view)); const QPoint startDragPos = internalView->mapFrom(view, view->cursorToCoordinate(KTextEditor::Cursor(1, 2))); const QPoint endDragPos = internalView->mapFrom(view, view->cursorToCoordinate(KTextEditor::Cursor(3, 0))); const QPoint gStartDragPos = internalView->mapToGlobal(startDragPos); const QPoint gEndDragPos = internalView->mapToGlobal(endDragPos); // now drag and drop selected text to Cursor(3, 0) QMouseEvent pressEvent(QEvent::MouseButtonPress, startDragPos, gStartDragPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QCoreApplication::sendEvent(internalView, &pressEvent); // ugly workaround: Drag & Drop has own blocking event queue. Therefore, we need a single-shot timer to // break out of the blocking event queue, see (*) QTimer::singleShot(50, [&](){ QMouseEvent moveEvent(QEvent::MouseMove, endDragPos + QPoint(5, 0), gEndDragPos + QPoint(5, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, endDragPos, gEndDragPos, Qt::LeftButton, Qt::NoButton, Qt::NoModifier); QCoreApplication::sendEvent(internalView, &moveEvent); QCoreApplication::sendEvent(internalView, &releaseEvent); }); // (*) this somehow blocks... QMouseEvent moveEvent1(QEvent::MouseMove, endDragPos + QPoint(10, 0), gEndDragPos + QPoint(10, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QCoreApplication::sendEvent(internalView, &moveEvent1); QTest::qWait(100); // final tests of dragged text QCOMPARE(doc.text(), QString("line0\n" "line2\n" "line1\n" "\n" "line4")); QCOMPARE(view->cursorPosition(), KTextEditor::Cursor(3, 0)); QCOMPARE(view->selectionRange(), Range(2, 0, 3, 0)); } // kate: indent-mode cstyle; indent-width 4; replace-tabs on; diff --git a/src/include/ktexteditor/message.h b/src/include/ktexteditor/message.h index 6289e144..95cde103 100644 --- a/src/include/ktexteditor/message.h +++ b/src/include/ktexteditor/message.h @@ -1,377 +1,384 @@ /* This file is part of the KDE project * * Copyright (C) 2012-2013 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KTEXTEDITOR_MESSAGE_H #define KTEXTEDITOR_MESSAGE_H #include #include #include #include #include namespace KTextEditor { class View; class Document; /** * @brief This class holds a Message to display in View%s. * * @section message_intro Introduction * * The Message class holds the data used to display interactive message widgets * in the editor. Use the Document::postMessage() to post a message as follows: * * @code * // always use a QPointer go guard your Message, if you keep a pointer * // after calling postMessage() * QPointer message = * new KTextEditor::Message("text", KTextEditor::Message::Information); * message->setWordWrap(true); * message->addAction(...); // add your actions... * document->postMessage(message); * @endcode * * A Message is deleted automatically if the Message gets closed, meaning that * you usually can forget the pointer. If you really need to delete a message * before the user processed it, always guard it with a QPointer! * * @section message_creation Message Creation and Deletion * * To create a new Message, use code like this: * @code * QPointer message = * new KTextEditor::Message("My information text", KTextEditor::Message::Information); * message->setWordWrap(true); * // ... * @endcode * * Although discouraged in general, the text of the Message can be changed * on the fly when it is already visible with setText(). * * Once you posted the Message through Document::postMessage(), the * lifetime depends on the user interaction. The Message gets automatically * deleted either if the user clicks a closing action in the message, or for * instance if the document is reloaded. * * If you posted a message but want to remove it yourself again, just delete * the message. But beware of the following warning! * * @warning Always use QPointer\ to guard the message pointer from * getting invalid, if you need to access the Message after you posted * it. * * @section message_positioning Positioning * * By default, the Message appears right above of the View. However, if desired, * the position can be changed through setPosition(). For instance, the * search-and-replace code in Kate Part shows the number of performed replacements * in a message floating in the view. For further information, have a look at * the enum MessagePosition. * * @section message_hiding Autohiding Messages * * Message%s can be shown for only a short amount of time by using the autohide * feature. With setAutoHide() a timeout in milliseconds can be set after which * the Message is automatically hidden. Further, use setAutoHideMode() to either * trigger the autohide timer as soon as the widget is shown (AutoHideMode::Immediate), * or only after user interaction with the view (AutoHideMode::AfterUserInteraction). * * The default autohide mode is set to AutoHideMode::AfterUserInteraction. * This way, it is unlikely the user misses a notification. * * @author Dominik Haumann \ * @since 4.11 */ class KTEXTEDITOR_EXPORT Message : public QObject { Q_OBJECT // // public data types // public: /** * Message types used as visual indicator. * The message types match exactly the behavior of KMessageWidget::MessageType. * For simple notifications either use Positive or Information. */ enum MessageType { Positive = 0, ///< positive information message Information, ///< information message type Warning, ///< warning message type Error ///< error message type }; /** * Message position used to place the message either above or below of the * KTextEditor::View. */ enum MessagePosition { - AboveView = 0, ///< show message above view - BelowView, ///< show message below view - TopInView, ///< show message as view overlay in the top right corner - BottomInView ///< show message as view overlay om the bottom right corner + /// show message above view. + AboveView = 0, + /// show message below view. + BelowView, + /// show message as view overlay in the top right corner. + TopInView, + /// show message as view overlay in the bottom right corner. + BottomInView, + /// show message as view overlay in the center of the view. + /// @since 5.42 + CenterInView }; /** * The AutoHideMode determines when to trigger the autoHide timer. * @see setAutoHide(), autoHide() */ enum AutoHideMode { Immediate = 0, ///< auto-hide is triggered as soon as the message is shown AfterUserInteraction ///< auto-hide is triggered only after the user interacted with the view }; public: /** * Constructor for new messages. * @param type the message type, e.g. MessageType::Information * @param richtext text to be displayed */ Message(const QString &richtext, MessageType type = Message::Information); /** * Destructor. */ virtual ~Message(); /** * Returns the text set in the constructor. */ QString text() const; /** * Returns the icon of this message. * If the message has no icon set, a null icon is returned. */ QIcon icon() const; /** * Returns the message type set in the constructor. */ MessageType messageType() const; /** * Adds an action to the message. * * By default (@p closeOnTrigger = true), the action closes the message * displayed in all View%s. If @p closeOnTrigger is @e false, the message * is stays open. * * The actions will be displayed in the order you added the actions. * * To connect to an action, use the following code: * @code * connect(action, SIGNAL(triggered()), receiver, SLOT(slotActionTriggered())); * @endcode * * @param action action to be added * @param closeOnTrigger when triggered, the message widget is closed * * @warning The added actions are deleted automatically. * So do \em not delete the added actions yourself. */ void addAction(QAction *action, bool closeOnTrigger = true); /** * Accessor to all actions, mainly used in the internal implementation * to add the actions into the gui. */ QList actions() const; /** * Set the auto hide timer to @p autoHideTimer milliseconds. * If @p autoHideTimer < 0, auto hide is disabled. * If @p autoHideTimer = 0, auto hide is enabled and set to a sane default * value of several seconds. * * By default, auto hide is disabled. * * @see autoHide(), setAutoHideMode() */ void setAutoHide(int autoHideTimer = 0); /** * Returns the auto hide time in milliseconds. * Please refer to setAutoHide() for an explanation of the return value. * * @see setAutoHide(), autoHideMode() */ int autoHide() const; /** * Sets the autoHide mode to @p mode. * The default mode is set to AutoHideMode::AfterUserInteraction. * @param mode autoHide mode * @see autoHideMode(), setAutoHide() */ void setAutoHideMode(KTextEditor::Message::AutoHideMode mode); /** * Get the Message's autoHide mode. * The default mode is set to AutoHideMode::AfterUserInteraction. * @see setAutoHideMode(), autoHide() */ KTextEditor::Message::AutoHideMode autoHideMode() const; /** * Enabled word wrap according to @p wordWrap. * By default, auto wrap is disabled. * * Word wrap is enabled automatically, if the Message's width is larger than * the parent widget's width to avoid breaking the gui layout. * * @see wordWrap() */ void setWordWrap(bool wordWrap); /** * Check, whether word wrap is enabled or not. * * @see setWordWrap() */ bool wordWrap() const; /** * Set the priority of this message to @p priority. * Messages with higher priority are shown first. * The default priority is 0. * * @see priority() */ void setPriority(int priority); /** * Returns the priority of the message. Default is 0. * * @see setPriority() */ int priority() const; /** * Set the associated view of the message. * If @p view is 0, the message is shown in all View%s of the Document. * If @p view is given, i.e. non-zero, the message is shown only in this view. * @param view the associated view the message should be displayed in */ void setView(KTextEditor::View *view); /** * This function returns the view you set by setView(). If setView() was * not called, the return value is 0. */ KTextEditor::View *view() const; /** * Set the document pointer to @p document. * This is called by the implementation, as soon as you post a message * through Document::postMessage(), so that you do not have to * call this yourself. * @see document() */ void setDocument(KTextEditor::Document *document); /** * Returns the document pointer this message was posted in. * This pointer is 0 as long as the message was not posted. */ KTextEditor::Document *document() const; /** * Sets the @p position either to AboveView or BelowView. * By default, the position is set to MessagePosition::AboveView. * @see MessagePosition */ void setPosition(MessagePosition position); /** * Returns the desired message position of this message. */ MessagePosition position() const; public Q_SLOTS: /** * Sets the notification contents to @p text. * If the message was already sent through Document::postMessage(), * the displayed text changes on the fly. * @note Change text on the fly with care, since changing the text may * resize the notification widget, which may result in a distracting * user experience. * @param richtext new notification text (rich text supported) * @see textChanged() */ void setText(const QString &richtext); /** * Optionally set an icon for this notification. * The icon is shown next to the message text. * @param icon the icon to be displayed */ void setIcon(const QIcon &icon); Q_SIGNALS: /** * This signal is emitted before the message is deleted. Afterwards, this * pointer is invalid. * * Use the function document() to access the associated Document. * * @param message closed/processed message */ void closed(KTextEditor::Message *message); /** * This signal is emitted whenever setText() is called. * If the message was already sent through Document::postMessage(), * the displayed text changes on the fly. * @note Change text on the fly with care, since changing the text may * resize the notification widget, which may result in a distracting * user experience. * * @param text new notification text (rich text supported) * @see setText() */ void textChanged(const QString &text); /** * This signal is emitted whenever setIcon() is called. * If the message was already sent through Document::postMessage(), * the displayed icon changes on the fly. * @note Change the icon on the fly with care, since changing the text may * resize the notification widget, which may result in a distracting * user experience. * * @param icon new notification icon * @see setIcon() */ void iconChanged(const QIcon &icon); private: class MessagePrivate *const d; }; } #endif diff --git a/src/view/kateview.cpp b/src/view/kateview.cpp index 897cee5d..2d710927 100644 --- a/src/view/kateview.cpp +++ b/src/view/kateview.cpp @@ -1,3687 +1,3681 @@ /* This file is part of the KDE libraries Copyright (C) 2009 Michel Ludwig Copyright (C) 2007 Mirko Stocker Copyright (C) 2003 Hamish Rodda Copyright (C) 2002 John Firebaugh Copyright (C) 2001-2004 Christoph Cullmann Copyright (C) 2001-2010 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //BEGIN includes #include "kateview.h" #include "kateviewinternal.h" #include "kateviewhelpers.h" #include "katerenderer.h" #include "katedocument.h" #include "kateundomanager.h" #include "kateglobal.h" #include "katehighlight.h" #include "katehighlightmenu.h" #include "katedialogs.h" #include "katetextline.h" #include "kateschema.h" #include "katebookmarks.h" #include "kateconfig.h" #include "katemodemenu.h" #include "kateautoindent.h" #include "katecompletionwidget.h" #include "katewordcompletion.h" #include "katekeywordcompletion.h" #include "katelayoutcache.h" #include "spellcheck/spellcheck.h" #include "spellcheck/spellcheckdialog.h" #include "spellcheck/spellingmenu.h" #include "katebuffer.h" #include "script/katescriptmanager.h" #include "script/katescriptaction.h" #include "export/exporter.h" #include "katemessagewidget.h" #include "katetemplatehandler.h" #include "katepartdebug.h" #include "printing/kateprinter.h" #include "katestatusbar.h" #include "kateabstractinputmode.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define VIEW_RANGE_DEBUG //END includes namespace { bool hasCommentInFirstLine(KTextEditor::DocumentPrivate* doc) { const Kate::TextLine& line = doc->kateTextLine(0); Q_ASSERT(line); return doc->isComment(0, line->firstChar()); } } void KTextEditor::ViewPrivate::blockFix(KTextEditor::Range &range) { if (range.start().column() > range.end().column()) { int tmp = range.start().column(); range.setStart(KTextEditor::Cursor(range.start().line(), range.end().column())); range.setEnd(KTextEditor::Cursor(range.end().line(), tmp)); } } KTextEditor::ViewPrivate::ViewPrivate(KTextEditor::DocumentPrivate *doc, QWidget *parent, KTextEditor::MainWindow *mainWindow) : KTextEditor::View (this, parent) , m_completionWidget(nullptr) , m_annotationModel(nullptr) , m_hasWrap(false) , m_doc(doc) , m_textFolding(doc->buffer()) , m_config(new KateViewConfig(this)) , m_renderer(new KateRenderer(doc, m_textFolding, this)) , m_viewInternal(new KateViewInternal(this)) , m_spell(new KateSpellCheckDialog(this)) , m_bookmarks(new KateBookmarks(this)) , m_topSpacer(new QSpacerItem(0,0)) , m_leftSpacer(new QSpacerItem(0,0)) , m_rightSpacer(new QSpacerItem(0,0)) , m_bottomSpacer(new QSpacerItem(0,0)) , m_startingUp(true) , m_updatingDocumentConfig(false) , m_selection(m_doc->buffer(), KTextEditor::Range::invalid(), Kate::TextRange::ExpandLeft, Kate::TextRange::AllowEmpty) , blockSelect(false) , m_bottomViewBar(nullptr) , m_gotoBar(nullptr) , m_dictionaryBar(nullptr) , m_spellingMenu(new KateSpellingMenu(this)) , m_userContextMenuSet(false) , m_delayedUpdateTriggered(false) , m_lineToUpdateMin(-1) , m_lineToUpdateMax(-1) - , m_floatTopMessageWidget(nullptr) - , m_floatBottomMessageWidget(nullptr) , m_mainWindow(mainWindow ? mainWindow : KTextEditor::EditorPrivate::self()->dummyMainWindow()) // use dummy window if no window there! , m_statusBar(nullptr) , m_temporaryAutomaticInvocationDisabled(false) , m_autoFoldedFirstLine(false) { // queued connect to collapse view updates for range changes, INIT THIS EARLY ENOUGH! connect(this, SIGNAL(delayedUpdateOfView()), this, SLOT(slotDelayedUpdateOfView()), Qt::QueuedConnection); KXMLGUIClient::setComponentName(KTextEditor::EditorPrivate::self()->aboutData().componentName(), KTextEditor::EditorPrivate::self()->aboutData().displayName()); // selection if for this view only and will invalidate if becoming empty m_selection.setView(this); // use z depth defined in moving ranges interface m_selection.setZDepth(-100000.0); KTextEditor::EditorPrivate::self()->registerView(this); /** * try to let the main window, if any, create a view bar for this view */ QWidget *bottomBarParent = m_mainWindow->createViewBar(this); m_bottomViewBar = new KateViewBar(bottomBarParent != nullptr, bottomBarParent ? bottomBarParent : this, this); // ugly workaround: // Force the layout to be left-to-right even on RTL deskstop, as discussed // on the mailing list. This will cause the lines and icons panel to be on // the left, even for Arabic/Hebrew/Farsi/whatever users. setLayoutDirection(Qt::LeftToRight); m_bottomViewBar->installEventFilter(m_viewInternal); // add KateMessageWidget for KTE::MessageInterface immediately above view - m_topMessageWidget = new KateMessageWidget(this); - m_topMessageWidget->hide(); + m_messageWidgets[KTextEditor::Message::AboveView] = new KateMessageWidget(this); + m_messageWidgets[KTextEditor::Message::AboveView]->hide(); // add KateMessageWidget for KTE::MessageInterface immediately above view - m_bottomMessageWidget = new KateMessageWidget(this); - m_bottomMessageWidget->hide(); + m_messageWidgets[KTextEditor::Message::BelowView] = new KateMessageWidget(this); + m_messageWidgets[KTextEditor::Message::BelowView]->hide(); // add bottom viewbar... if (bottomBarParent) { m_mainWindow->addWidgetToViewBar(this, m_bottomViewBar); } // add layout for floating message widgets to KateViewInternal - m_notificationLayout = new QVBoxLayout(m_viewInternal); + m_notificationLayout = new KateMessageLayout(m_viewInternal); m_notificationLayout->setContentsMargins(20, 20, 20, 20); m_viewInternal->setLayout(m_notificationLayout); // this really is needed :) m_viewInternal->updateView(); doc->addView(this); setFocusProxy(m_viewInternal); setFocusPolicy(Qt::StrongFocus); setXMLFile(QStringLiteral("katepart5ui.rc")); setupConnections(); setupActions(); // auto word completion new KateWordCompletionView(this, actionCollection()); // update the enabled state of the undo/redo actions... slotUpdateUndo(); /** * create the status bar of this view * do this after action creation, we use some of them! */ toggleStatusBar(); m_startingUp = false; updateConfig(); slotHlChanged(); KCursor::setAutoHideCursor(m_viewInternal, true); - // user interaction (scrolling) starts notification auto-hide timer - connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), m_topMessageWidget, SLOT(startAutoHideTimer())); - connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), m_bottomMessageWidget, SLOT(startAutoHideTimer())); + for (auto messageWidget : m_messageWidgets) { + if (messageWidget) { + // user interaction (scrolling) starts notification auto-hide timer + connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), messageWidget, SLOT(startAutoHideTimer())); - // user interaction (cursor navigation) starts notification auto-hide timer - connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), m_topMessageWidget, SLOT(startAutoHideTimer())); - connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), m_bottomMessageWidget, SLOT(startAutoHideTimer())); + // user interaction (cursor navigation) starts notification auto-hide timer + connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), messageWidget, SLOT(startAutoHideTimer())); + } + } // folding restoration on reload connect(m_doc, SIGNAL(aboutToReload(KTextEditor::Document*)), SLOT(saveFoldingState())); connect(m_doc, SIGNAL(reloaded(KTextEditor::Document*)), SLOT(applyFoldingState())); // update highlights on scrolling and co connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), this, SLOT(createHighlights())); // clear highlights on reload connect(m_doc, SIGNAL(aboutToReload(KTextEditor::Document*)), SLOT(clearHighlights())); // setup layout setupLayout(); } KTextEditor::ViewPrivate::~ViewPrivate() { // invalidate update signal m_delayedUpdateTriggered = false; // remove from xmlgui factory, to be safe if (factory()) { factory()->removeClient(this); } // delete internal view before view bar! delete m_viewInternal; /** * remove view bar again, if needed */ m_mainWindow->deleteViewBar(this); m_bottomViewBar = nullptr; m_doc->removeView(this); delete m_renderer; delete m_config; KTextEditor::EditorPrivate::self()->deregisterView(this); } void KTextEditor::ViewPrivate::toggleStatusBar() { /** * if there, delete it */ if (m_statusBar) { bottomViewBar()->removePermanentBarWidget(m_statusBar); delete m_statusBar; m_statusBar = nullptr; emit statusBarEnabledChanged(this, false); return; } /** * else: create it */ m_statusBar = new KateStatusBar(this); bottomViewBar()->addPermanentBarWidget(m_statusBar); emit statusBarEnabledChanged(this, true); } void KTextEditor::ViewPrivate::setupLayout() { // delete old layout if any if (layout()) { delete layout(); /** * need to recreate spacers because they are deleted with * their belonging layout */ m_topSpacer = new QSpacerItem(0,0); m_leftSpacer = new QSpacerItem(0,0); m_rightSpacer = new QSpacerItem(0,0); m_bottomSpacer = new QSpacerItem(0,0); } // set margins QStyleOptionFrame opt; opt.initFrom(this); opt.frameShape = QFrame::StyledPanel; opt.state |= QStyle::State_Sunken; const int margin = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this); m_topSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); m_leftSpacer->changeSize(margin, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); m_rightSpacer->changeSize(margin, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); m_bottomSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); // define layout QGridLayout *layout=new QGridLayout(this); layout->setMargin(0); layout->setSpacing(0); const bool frameAroundContents = style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &opt, this); if (frameAroundContents) { // top message widget - layout->addWidget(m_topMessageWidget, 0, 0, 1, 5); + layout->addWidget(m_messageWidgets[KTextEditor::Message::AboveView], 0, 0, 1, 5); // top spacer layout->addItem(m_topSpacer, 1, 0, 1, 4); // left spacer layout->addItem(m_leftSpacer, 2, 0, 1, 1); // left border layout->addWidget(m_viewInternal->m_leftBorder, 2, 1, 1, 1); // view layout->addWidget(m_viewInternal, 2, 2, 1, 1); // right spacer layout->addItem(m_rightSpacer, 2, 3, 1, 1); // bottom spacer layout->addItem(m_bottomSpacer, 3, 0, 1, 4); // vertical scrollbar layout->addWidget(m_viewInternal->m_lineScroll, 1, 4, 3, 1); // horizontal scrollbar layout->addWidget(m_viewInternal->m_columnScroll, 4, 0, 1, 4); // dummy layout->addWidget(m_viewInternal->m_dummy, 4, 4, 1, 1); // bottom message - layout->addWidget(m_bottomMessageWidget, 5, 0, 1, 5); + layout->addWidget(m_messageWidgets[KTextEditor::Message::BelowView], 5, 0, 1, 5); // bottom viewbar if (m_bottomViewBar->parentWidget() == this) { layout->addWidget(m_bottomViewBar, 6, 0, 1, 5); } // stretch layout->setColumnStretch(2, 1); layout->setRowStretch(2, 1); // adjust scrollbar background m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Window); m_viewInternal->m_lineScroll->setAutoFillBackground(false); m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Window); m_viewInternal->m_columnScroll->setAutoFillBackground(false); } else { // top message widget - layout->addWidget(m_topMessageWidget, 0, 0, 1, 5); + layout->addWidget(m_messageWidgets[KTextEditor::Message::AboveView], 0, 0, 1, 5); // top spacer layout->addItem(m_topSpacer, 1, 0, 1, 5); // left spacer layout->addItem(m_leftSpacer, 2, 0, 1, 1); // left border layout->addWidget(m_viewInternal->m_leftBorder, 2, 1, 1, 1); // view layout->addWidget(m_viewInternal, 2, 2, 1, 1); // vertical scrollbar layout->addWidget(m_viewInternal->m_lineScroll, 2, 3, 1, 1); // right spacer layout->addItem(m_rightSpacer, 2, 4, 1, 1); // horizontal scrollbar layout->addWidget(m_viewInternal->m_columnScroll, 3, 1, 1, 2); // dummy layout->addWidget(m_viewInternal->m_dummy, 3, 3, 1, 1); // bottom spacer layout->addItem(m_bottomSpacer, 4, 0, 1, 5); // bottom message - layout->addWidget(m_bottomMessageWidget, 5, 0, 1, 5); + layout->addWidget(m_messageWidgets[KTextEditor::Message::BelowView], 5, 0, 1, 5); // bottom viewbar if (m_bottomViewBar->parentWidget() == this) { layout->addWidget(m_bottomViewBar, 6, 0, 1, 5); } // stretch layout->setColumnStretch(2, 1); layout->setRowStretch(2, 1); // adjust scrollbar background m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Base); m_viewInternal->m_lineScroll->setAutoFillBackground(true); m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Base); m_viewInternal->m_columnScroll->setAutoFillBackground(true); } } void KTextEditor::ViewPrivate::setupConnections() { connect(m_doc, SIGNAL(undoChanged()), this, SLOT(slotUpdateUndo())); connect(m_doc, SIGNAL(highlightingModeChanged(KTextEditor::Document*)), this, SLOT(slotHlChanged())); connect(m_doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); connect(m_viewInternal, SIGNAL(dropEventPass(QDropEvent*)), this, SIGNAL(dropEventPass(QDropEvent*))); connect(m_doc, SIGNAL(annotationModelChanged(KTextEditor::AnnotationModel*,KTextEditor::AnnotationModel*)), m_viewInternal->m_leftBorder, SLOT(annotationModelChanged(KTextEditor::AnnotationModel*,KTextEditor::AnnotationModel*))); } void KTextEditor::ViewPrivate::goToPreviousEditingPosition() { auto c = doc()->lastEditingPosition(KTextEditor::DocumentPrivate::Previous, cursorPosition()); if(c.isValid()){ setCursorPosition(c); } } void KTextEditor::ViewPrivate::goToNextEditingPosition() { auto c = doc()->lastEditingPosition(KTextEditor::DocumentPrivate::Next, cursorPosition()); if(c.isValid()){ setCursorPosition(c); } } void KTextEditor::ViewPrivate::setupActions() { KActionCollection *ac = actionCollection(); QAction *a; m_toggleWriteLock = nullptr; m_cut = a = ac->addAction(KStandardAction::Cut, this, SLOT(cut())); a->setWhatsThis(i18n("Cut the selected text and move it to the clipboard")); m_paste = a = ac->addAction(KStandardAction::Paste, this, SLOT(paste())); a->setWhatsThis(i18n("Paste previously copied or cut clipboard contents")); m_copy = a = ac->addAction(KStandardAction::Copy, this, SLOT(copy())); a->setWhatsThis(i18n("Use this command to copy the currently selected text to the system clipboard.")); m_pasteMenu = ac->addAction(QStringLiteral("edit_paste_menu"), new KatePasteMenu(i18n("Clipboard &History"), this)); connect(KTextEditor::EditorPrivate::self(), SIGNAL(clipboardHistoryChanged()), this, SLOT(slotClipboardHistoryChanged())); if (!m_doc->readOnly()) { a = ac->addAction(KStandardAction::Save, m_doc, SLOT(documentSave())); a->setWhatsThis(i18n("Save the current document")); a = m_editUndo = ac->addAction(KStandardAction::Undo, m_doc, SLOT(undo())); a->setWhatsThis(i18n("Revert the most recent editing actions")); a = m_editRedo = ac->addAction(KStandardAction::Redo, m_doc, SLOT(redo())); a->setWhatsThis(i18n("Revert the most recent undo operation")); // Tools > Scripts // stored inside scoped pointer to ensure we destruct it early enough as it does internal cleanups of other child objects m_scriptActionMenu.reset(new KateScriptActionMenu(this, i18n("&Scripts"))); ac->addAction(QStringLiteral("tools_scripts"), m_scriptActionMenu.data()); a = ac->addAction(QStringLiteral("tools_apply_wordwrap")); a->setText(i18n("Apply &Word Wrap")); a->setWhatsThis(i18n("Use this command to wrap all lines of the current document which are longer than the width of the" " current view, to fit into this view.

This is a static word wrap, meaning it is not updated" " when the view is resized.")); connect(a, SIGNAL(triggered(bool)), SLOT(applyWordWrap())); a = ac->addAction(QStringLiteral("tools_cleanIndent")); a->setText(i18n("&Clean Indentation")); a->setWhatsThis(i18n("Use this to clean the indentation of a selected block of text (only tabs/only spaces).

" "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog.")); connect(a, SIGNAL(triggered(bool)), SLOT(cleanIndent())); a = ac->addAction(QStringLiteral("tools_align")); a->setText(i18n("&Align")); a->setWhatsThis(i18n("Use this to align the current line or block of text to its proper indent level.")); connect(a, SIGNAL(triggered(bool)), SLOT(align())); a = ac->addAction(QStringLiteral("tools_comment")); a->setText(i18n("C&omment")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_D)); a->setWhatsThis(i18n("This command comments out the current line or a selected block of text.

" "The characters for single/multiple line comments are defined within the language's highlighting.")); connect(a, SIGNAL(triggered(bool)), SLOT(comment())); a = ac->addAction(QStringLiteral("Previous Editing Line")); a->setText(i18n("Go to previous editing line")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_E)); connect(a, SIGNAL(triggered(bool)), SLOT(goToPreviousEditingPosition())); a = ac->addAction(QStringLiteral("Next Editing Line")); a->setText(i18n("Go to next editing line")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_E)); connect(a, SIGNAL(triggered(bool)), SLOT(goToNextEditingPosition())); a = ac->addAction(QStringLiteral("tools_uncomment")); a->setText(i18n("Unco&mment")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_D)); a->setWhatsThis(i18n("This command removes comments from the current line or a selected block of text.

" "The characters for single/multiple line comments are defined within the language's highlighting.")); connect(a, SIGNAL(triggered(bool)), SLOT(uncomment())); a = ac->addAction(QStringLiteral("tools_toggle_comment")); a->setText(i18n("Toggle Comment")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleComment())); a = m_toggleWriteLock = new KToggleAction(i18n("&Read Only Mode"), this); a->setWhatsThis(i18n("Lock/unlock the document for writing")); a->setChecked(!m_doc->isReadWrite()); connect(a, SIGNAL(triggered(bool)), SLOT(toggleWriteLock())); ac->addAction(QStringLiteral("tools_toggle_write_lock"), a); a = ac->addAction(QStringLiteral("tools_uppercase")); a->setText(i18n("Uppercase")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_U)); a->setWhatsThis(i18n("Convert the selection to uppercase, or the character to the " "right of the cursor if no text is selected.")); connect(a, SIGNAL(triggered(bool)), SLOT(uppercase())); a = ac->addAction(QStringLiteral("tools_lowercase")); a->setText(i18n("Lowercase")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_U)); a->setWhatsThis(i18n("Convert the selection to lowercase, or the character to the " "right of the cursor if no text is selected.")); connect(a, SIGNAL(triggered(bool)), SLOT(lowercase())); a = ac->addAction(QStringLiteral("tools_capitalize")); a->setText(i18n("Capitalize")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_U)); a->setWhatsThis(i18n("Capitalize the selection, or the word under the " "cursor if no text is selected.")); connect(a, SIGNAL(triggered(bool)), SLOT(capitalize())); a = ac->addAction(QStringLiteral("tools_join_lines")); a->setText(i18n("Join Lines")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_J)); connect(a, SIGNAL(triggered(bool)), SLOT(joinLines())); a = ac->addAction(QStringLiteral("tools_invoke_code_completion")); a->setText(i18n("Invoke Code Completion")); a->setWhatsThis(i18n("Manually invoke command completion, usually by using a shortcut bound to this action.")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_Space)); connect(a, SIGNAL(triggered(bool)), SLOT(userInvokedCompletion())); } else { m_cut->setEnabled(false); m_paste->setEnabled(false); m_pasteMenu->setEnabled(false); m_editUndo = nullptr; m_editRedo = nullptr; } a = ac->addAction(KStandardAction::Print, this, SLOT(print())); a->setWhatsThis(i18n("Print the current document.")); a = ac->addAction(KStandardAction::PrintPreview, this, SLOT(printPreview())); a->setWhatsThis(i18n("Show print preview of current document")); a = ac->addAction(QStringLiteral("file_reload")); a->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); a->setText(i18n("Reloa&d")); ac->setDefaultShortcuts(a, KStandardShortcut::reload()); a->setWhatsThis(i18n("Reload the current document from disk.")); connect(a, SIGNAL(triggered(bool)), SLOT(reloadFile())); a = ac->addAction(KStandardAction::SaveAs, m_doc, SLOT(documentSaveAs())); a->setWhatsThis(i18n("Save the current document to disk, with a name of your choice.")); a = new KateViewEncodingAction(m_doc, this, i18n("Save As with Encoding..."), this, true /* special mode for save as */); a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); ac->addAction(QStringLiteral("file_save_as_with_encoding"), a); a = ac->addAction(QStringLiteral("file_save_copy_as")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); a->setText(i18n("Save &Copy As...")); a->setWhatsThis(i18n("Save a copy of the current document to disk.")); connect(a, SIGNAL(triggered(bool)), m_doc, SLOT(documentSaveCopyAs())); a = ac->addAction(KStandardAction::GotoLine, this, SLOT(gotoLine())); a->setWhatsThis(i18n("This command opens a dialog and lets you choose a line that you want the cursor to move to.")); a = ac->addAction(QStringLiteral("modified_line_up")); a->setText(i18n("Move to Previous Modified Line")); a->setWhatsThis(i18n("Move upwards to the previous modified line.")); connect(a, SIGNAL(triggered(bool)), SLOT(toPrevModifiedLine())); a = ac->addAction(QStringLiteral("modified_line_down")); a->setText(i18n("Move to Next Modified Line")); a->setWhatsThis(i18n("Move downwards to the next modified line.")); connect(a, SIGNAL(triggered(bool)), SLOT(toNextModifiedLine())); a = ac->addAction(QStringLiteral("set_confdlg")); a->setText(i18n("&Configure Editor...")); a->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other"))); a->setWhatsThis(i18n("Configure various aspects of this editor.")); connect(a, SIGNAL(triggered(bool)), SLOT(slotConfigDialog())); m_modeAction = new KateModeMenu(i18n("&Mode"), this); ac->addAction(QStringLiteral("tools_mode"), m_modeAction); m_modeAction->setWhatsThis(i18n("Here you can choose which mode should be used for the current document. This will influence the highlighting and folding being used, for example.")); m_modeAction->updateMenu(m_doc); KateHighlightingMenu *menu = new KateHighlightingMenu(i18n("&Highlighting"), this); ac->addAction(QStringLiteral("tools_highlighting"), menu); menu->setWhatsThis(i18n("Here you can choose how the current document should be highlighted.")); menu->updateMenu(m_doc); KateViewSchemaAction *schemaMenu = new KateViewSchemaAction(i18n("&Schema"), this); ac->addAction(QStringLiteral("view_schemas"), schemaMenu); schemaMenu->updateMenu(this); // indentation menu KateViewIndentationAction *indentMenu = new KateViewIndentationAction(m_doc, i18n("&Indentation"), this); ac->addAction(QStringLiteral("tools_indentation"), indentMenu); m_selectAll = a = ac->addAction(KStandardAction::SelectAll, this, SLOT(selectAll())); a->setWhatsThis(i18n("Select the entire text of the current document.")); m_deSelect = a = ac->addAction(KStandardAction::Deselect, this, SLOT(clearSelection())); a->setWhatsThis(i18n("If you have selected something within the current document, this will no longer be selected.")); a = ac->addAction(QStringLiteral("view_inc_font_sizes")); a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in"))); a->setText(i18n("Enlarge Font")); ac->setDefaultShortcuts(a, KStandardShortcut::zoomIn()); a->setWhatsThis(i18n("This increases the display font size.")); connect(a, SIGNAL(triggered(bool)), m_viewInternal, SLOT(slotIncFontSizes())); a = ac->addAction(QStringLiteral("view_dec_font_sizes")); a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out"))); a->setText(i18n("Shrink Font")); ac->setDefaultShortcuts(a, KStandardShortcut::zoomOut()); a->setWhatsThis(i18n("This decreases the display font size.")); connect(a, SIGNAL(triggered(bool)), m_viewInternal, SLOT(slotDecFontSizes())); a = m_toggleBlockSelection = new KToggleAction(i18n("Bl&ock Selection Mode"), this); ac->addAction(QStringLiteral("set_verticalSelect"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_B)); a->setWhatsThis(i18n("This command allows switching between the normal (line based) selection mode and the block selection mode.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleBlockSelection())); a = ac->addAction(QStringLiteral("switch_next_input_mode")); a->setText(i18n("Switch to Next Input Mode")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_V)); a->setWhatsThis(i18n("Switch to the next input mode.")); connect(a, SIGNAL(triggered(bool)), SLOT(cycleInputMode())); a = m_toggleInsert = new KToggleAction(i18n("Overwr&ite Mode"), this); ac->addAction(QStringLiteral("set_insert"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Insert)); a->setWhatsThis(i18n("Choose whether you want the text you type to be inserted or to overwrite existing text.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleInsert())); KToggleAction *toggleAction; a = m_toggleDynWrap = toggleAction = new KToggleAction(i18n("&Dynamic Word Wrap"), this); ac->addAction(QStringLiteral("view_dynamic_word_wrap"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F10)); a->setWhatsThis(i18n("If this option is checked, the text lines will be wrapped at the view border on the screen.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleDynWordWrap())); a = m_setDynWrapIndicators = new KSelectAction(i18n("Dynamic Word Wrap Indicators"), this); ac->addAction(QStringLiteral("dynamic_word_wrap_indicators"), a); a->setWhatsThis(i18n("Choose when the Dynamic Word Wrap Indicators should be displayed")); connect(m_setDynWrapIndicators, SIGNAL(triggered(int)), this, SLOT(setDynWrapIndicators(int))); const QStringList list2{ i18n("&Off"), i18n("Follow &Line Numbers"), i18n("&Always On") }; m_setDynWrapIndicators->setItems(list2); m_setDynWrapIndicators->setEnabled(m_toggleDynWrap->isChecked()); // only synced on real change, later a = toggleAction = m_toggleFoldingMarkers = new KToggleAction(i18n("Show Folding &Markers"), this); ac->addAction(QStringLiteral("view_folding_markers"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F9)); a->setWhatsThis(i18n("You can choose if the codefolding marks should be shown, if codefolding is possible.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleFoldingMarkers())); a = m_toggleIconBar = toggleAction = new KToggleAction(i18n("Show &Icon Border"), this); ac->addAction(QStringLiteral("view_border"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F6)); a->setWhatsThis(i18n("Show/hide the icon border.

The icon border shows bookmark symbols, for instance.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleIconBorder())); a = toggleAction = m_toggleLineNumbers = new KToggleAction(i18n("Show &Line Numbers"), this); ac->addAction(QStringLiteral("view_line_numbers"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F11)); a->setWhatsThis(i18n("Show/hide the line numbers on the left hand side of the view.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleLineNumbersOn())); a = m_toggleScrollBarMarks = toggleAction = new KToggleAction(i18n("Show Scroll&bar Marks"), this); ac->addAction(QStringLiteral("view_scrollbar_marks"), a); a->setWhatsThis(i18n("Show/hide the marks on the vertical scrollbar.

The marks show bookmarks, for instance.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleScrollBarMarks())); a = m_toggleScrollBarMiniMap = toggleAction = new KToggleAction(i18n("Show Scrollbar Mini-Map"), this); ac->addAction(QStringLiteral("view_scrollbar_minimap"), a); a->setWhatsThis(i18n("Show/hide the mini-map on the vertical scrollbar.

The mini-map shows an overview of the whole document.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleScrollBarMiniMap())); // a = m_toggleScrollBarMiniMapAll = toggleAction = new KToggleAction(i18n("Show the whole document in the Mini-Map"), this); // ac->addAction(QLatin1String("view_scrollbar_minimap_all"), a); // a->setWhatsThis(i18n("Display the whole document in the mini-map.

With this option set the whole document will be visible in the mini-map.")); // connect(a, SIGNAL(triggered(bool)), SLOT(toggleScrollBarMiniMapAll())); // connect(m_toggleScrollBarMiniMap, SIGNAL(triggered(bool)), m_toggleScrollBarMiniMapAll, SLOT(setEnabled(bool))); a = toggleAction = m_toggleWWMarker = new KToggleAction(i18n("Show Static &Word Wrap Marker"), this); ac->addAction(QStringLiteral("view_word_wrap_marker"), a); a->setWhatsThis(i18n( "Show/hide the Word Wrap Marker, a vertical line drawn at the word " "wrap column as defined in the editing properties")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleWWMarker())); a = m_toggleNPSpaces = new KToggleAction(i18n("Show Non-Printable Spaces"), this); ac->addAction(QStringLiteral("view_non_printable_spaces"), a); a->setWhatsThis(i18n("Show/hide bounding box around non-printable spaces")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleNPSpaces())); a = m_toggleWordCount = new KToggleAction(i18n("Show Word Count"), this); a->setChecked(false); ac->addAction(QStringLiteral("view_word_count"), a); a->setWhatsThis(i18n("Show/hide word count in status bar")); connect(a, &QAction::triggered, this, &ViewPrivate::toggleWordCount); a = m_switchCmdLine = ac->addAction(QStringLiteral("switch_to_cmd_line")); a->setText(i18n("Switch to Command Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F7)); a->setWhatsThis(i18n("Show/hide the command line on the bottom of the view.")); connect(a, SIGNAL(triggered(bool)), SLOT(switchToCmdLine())); KActionMenu *am = new KActionMenu(i18n("Input Modes"), this); m_inputModeActions = new QActionGroup(am); ac->addAction(QStringLiteral("view_input_modes"), am); Q_FOREACH(KateAbstractInputMode *mode, m_viewInternal->m_inputModes) { a = new QAction(mode->viewInputModeHuman(), m_inputModeActions); am->addAction(a); a->setWhatsThis(i18n("Activate/deactivate %1", mode->viewInputModeHuman())); const InputMode im = mode->viewInputMode(); a->setData(static_cast(im)); a->setCheckable(true); if (im == m_config->inputMode()) a->setChecked(true); connect(a, SIGNAL(triggered()), SLOT(toggleInputMode())); } a = m_setEndOfLine = new KSelectAction(i18n("&End of Line"), this); ac->addAction(QStringLiteral("set_eol"), a); a->setWhatsThis(i18n("Choose which line endings should be used, when you save the document")); const QStringList list { i18nc("@item:inmenu End of Line", "&UNIX") , i18nc("@item:inmenu End of Line", "&Windows/DOS") , i18nc("@item:inmenu End of Line", "&Macintosh") }; m_setEndOfLine->setItems(list); m_setEndOfLine->setCurrentItem(m_doc->config()->eol()); connect(m_setEndOfLine, SIGNAL(triggered(int)), this, SLOT(setEol(int))); a = m_addBom = new KToggleAction(i18n("Add &Byte Order Mark (BOM)"), this); m_addBom->setChecked(m_doc->config()->bom()); ac->addAction(QStringLiteral("add_bom"), a); a->setWhatsThis(i18n("Enable/disable adding of byte order marks for UTF-8/UTF-16 encoded files while saving")); connect(m_addBom, SIGNAL(triggered(bool)), this, SLOT(setAddBom(bool))); // encoding menu m_encodingAction = new KateViewEncodingAction(m_doc, this, i18n("E&ncoding"), this); ac->addAction(QStringLiteral("set_encoding"), m_encodingAction); a = ac->addAction(KStandardAction::Find, this, SLOT(find())); a->setWhatsThis(i18n("Look up the first occurrence of a piece of text or regular expression.")); addAction(a); a = ac->addAction(QStringLiteral("edit_find_selected")); a->setText(i18n("Find Selected")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_H)); a->setWhatsThis(i18n("Finds next occurrence of selected text.")); connect(a, SIGNAL(triggered(bool)), SLOT(findSelectedForwards())); a = ac->addAction(QStringLiteral("edit_find_selected_backwards")); a->setText(i18n("Find Selected Backwards")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_H)); a->setWhatsThis(i18n("Finds previous occurrence of selected text.")); connect(a, SIGNAL(triggered(bool)), SLOT(findSelectedBackwards())); a = ac->addAction(KStandardAction::FindNext, this, SLOT(findNext())); a->setWhatsThis(i18n("Look up the next occurrence of the search phrase.")); addAction(a); a = ac->addAction(KStandardAction::FindPrev, QStringLiteral("edit_find_prev"), this, SLOT(findPrevious())); a->setWhatsThis(i18n("Look up the previous occurrence of the search phrase.")); addAction(a); a = ac->addAction(KStandardAction::Replace, this, SLOT(replace())); a->setWhatsThis(i18n("Look up a piece of text or regular expression and replace the result with some given text.")); m_spell->createActions(ac); m_toggleOnTheFlySpellCheck = new KToggleAction(i18n("Automatic Spell Checking"), this); m_toggleOnTheFlySpellCheck->setWhatsThis(i18n("Enable/disable automatic spell checking")); connect(m_toggleOnTheFlySpellCheck, SIGNAL(triggered(bool)), SLOT(toggleOnTheFlySpellCheck(bool))); ac->addAction(QStringLiteral("tools_toggle_automatic_spell_checking"), m_toggleOnTheFlySpellCheck); ac->setDefaultShortcut(m_toggleOnTheFlySpellCheck, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_O)); a = ac->addAction(QStringLiteral("tools_change_dictionary")); a->setText(i18n("Change Dictionary...")); a->setWhatsThis(i18n("Change the dictionary that is used for spell checking.")); connect(a, SIGNAL(triggered()), SLOT(changeDictionary())); a = ac->addAction(QStringLiteral("tools_clear_dictionary_ranges")); a->setText(i18n("Clear Dictionary Ranges")); a->setVisible(false); a->setWhatsThis(i18n("Remove all the separate dictionary ranges that were set for spell checking.")); connect(a, SIGNAL(triggered()), m_doc, SLOT(clearDictionaryRanges())); connect(m_doc, SIGNAL(dictionaryRangesPresent(bool)), a, SLOT(setVisible(bool))); m_copyHtmlAction = ac->addAction(QStringLiteral("edit_copy_html"), this, SLOT(exportHtmlToClipboard())); m_copyHtmlAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); m_copyHtmlAction->setText(i18n("Copy as &HTML")); m_copyHtmlAction->setWhatsThis(i18n("Use this command to copy the currently selected text as HTML to the system clipboard.")); a = ac->addAction(QStringLiteral("file_export_html"), this, SLOT(exportHtmlToFile())); a->setText(i18n("E&xport as HTML...")); a->setWhatsThis(i18n("This command allows you to export the current document" " with all highlighting information into a HTML document.")); m_spellingMenu->createActions(ac); m_bookmarks->createActions(ac); slotSelectionChanged(); //Now setup the editing actions before adding the associated //widget and setting the shortcut context setupEditActions(); setupCodeFolding(); slotClipboardHistoryChanged(); ac->addAssociatedWidget(m_viewInternal); foreach (QAction *action, ac->actions()) { action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } connect(this, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(slotSelectionChanged())); } void KTextEditor::ViewPrivate::slotConfigDialog() { // invoke config dialog, will auto-save configuration to katepartrc KTextEditor::EditorPrivate::self()->configDialog(this); } void KTextEditor::ViewPrivate::setupEditActions() { //If you add an editing action to this //function make sure to include the line //m_editActions << a after creating the action KActionCollection *ac = actionCollection(); QAction *a = ac->addAction(QStringLiteral("word_left")); a->setText(i18n("Move Word Left")); ac->setDefaultShortcuts(a, KStandardShortcut::backwardWord()); connect(a, SIGNAL(triggered(bool)), SLOT(wordLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("select_char_left")); a->setText(i18n("Select Character Left")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Left)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftCursorLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("select_word_left")); a->setText(i18n("Select Word Left")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_Left)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftWordLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("word_right")); a->setText(i18n("Move Word Right")); ac->setDefaultShortcuts(a, KStandardShortcut::forwardWord()); connect(a, SIGNAL(triggered(bool)), SLOT(wordRight())); m_editActions << a; a = ac->addAction(QStringLiteral("select_char_right")); a->setText(i18n("Select Character Right")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Right)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftCursorRight())); m_editActions << a; a = ac->addAction(QStringLiteral("select_word_right")); a->setText(i18n("Select Word Right")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_Right)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftWordRight())); m_editActions << a; a = ac->addAction(QStringLiteral("beginning_of_line")); a->setText(i18n("Move to Beginning of Line")); ac->setDefaultShortcuts(a, KStandardShortcut::beginningOfLine()); connect(a, SIGNAL(triggered(bool)), SLOT(home())); m_editActions << a; a = ac->addAction(QStringLiteral("beginning_of_document")); a->setText(i18n("Move to Beginning of Document")); ac->setDefaultShortcuts(a, KStandardShortcut::begin()); connect(a, SIGNAL(triggered(bool)), SLOT(top())); m_editActions << a; a = ac->addAction(QStringLiteral("select_beginning_of_line")); a->setText(i18n("Select to Beginning of Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Home)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftHome())); m_editActions << a; a = ac->addAction(QStringLiteral("select_beginning_of_document")); a->setText(i18n("Select to Beginning of Document")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_Home)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftTop())); m_editActions << a; a = ac->addAction(QStringLiteral("end_of_line")); a->setText(i18n("Move to End of Line")); ac->setDefaultShortcuts(a, KStandardShortcut::endOfLine()); connect(a, SIGNAL(triggered(bool)), SLOT(end())); m_editActions << a; a = ac->addAction(QStringLiteral("end_of_document")); a->setText(i18n("Move to End of Document")); ac->setDefaultShortcuts(a, KStandardShortcut::end()); connect(a, SIGNAL(triggered(bool)), SLOT(bottom())); m_editActions << a; a = ac->addAction(QStringLiteral("select_end_of_line")); a->setText(i18n("Select to End of Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_End)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftEnd())); m_editActions << a; a = ac->addAction(QStringLiteral("select_end_of_document")); a->setText(i18n("Select to End of Document")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_End)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftBottom())); m_editActions << a; a = ac->addAction(QStringLiteral("select_line_up")); a->setText(i18n("Select to Previous Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Up)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftUp())); m_editActions << a; a = ac->addAction(QStringLiteral("scroll_line_up")); a->setText(i18n("Scroll Line Up")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_Up)); connect(a, SIGNAL(triggered(bool)), SLOT(scrollUp())); m_editActions << a; a = ac->addAction(QStringLiteral("move_line_down")); a->setText(i18n("Move to Next Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Down)); connect(a, SIGNAL(triggered(bool)), SLOT(down())); m_editActions << a; a = ac->addAction(QStringLiteral("move_line_up")); a->setText(i18n("Move to Previous Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Up)); connect(a, SIGNAL(triggered(bool)), SLOT(up())); m_editActions << a; a = ac->addAction(QStringLiteral("move_cursor_right")); a->setText(i18n("Move Cursor Right")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Right)); connect(a, SIGNAL(triggered(bool)), SLOT(cursorRight())); m_editActions << a; a = ac->addAction(QStringLiteral("move_cusor_left")); a->setText(i18n("Move Cursor Left")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Left)); connect(a, SIGNAL(triggered(bool)), SLOT(cursorLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("select_line_down")); a->setText(i18n("Select to Next Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Down)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftDown())); m_editActions << a; a = ac->addAction(QStringLiteral("scroll_line_down")); a->setText(i18n("Scroll Line Down")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_Down)); connect(a, SIGNAL(triggered(bool)), SLOT(scrollDown())); m_editActions << a; a = ac->addAction(QStringLiteral("scroll_page_up")); a->setText(i18n("Scroll Page Up")); ac->setDefaultShortcuts(a, KStandardShortcut::prior()); connect(a, SIGNAL(triggered(bool)), SLOT(pageUp())); m_editActions << a; a = ac->addAction(QStringLiteral("select_page_up")); a->setText(i18n("Select Page Up")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_PageUp)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftPageUp())); m_editActions << a; a = ac->addAction(QStringLiteral("move_top_of_view")); a->setText(i18n("Move to Top of View")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_PageUp)); connect(a, SIGNAL(triggered(bool)), SLOT(topOfView())); m_editActions << a; a = ac->addAction(QStringLiteral("select_top_of_view")); a->setText(i18n("Select to Top of View")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_PageUp)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftTopOfView())); m_editActions << a; a = ac->addAction(QStringLiteral("scroll_page_down")); a->setText(i18n("Scroll Page Down")); ac->setDefaultShortcuts(a, KStandardShortcut::next()); connect(a, SIGNAL(triggered(bool)), SLOT(pageDown())); m_editActions << a; a = ac->addAction(QStringLiteral("select_page_down")); a->setText(i18n("Select Page Down")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_PageDown)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftPageDown())); m_editActions << a; a = ac->addAction(QStringLiteral("move_bottom_of_view")); a->setText(i18n("Move to Bottom of View")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_PageDown)); connect(a, SIGNAL(triggered(bool)), SLOT(bottomOfView())); m_editActions << a; a = ac->addAction(QStringLiteral("select_bottom_of_view")); a->setText(i18n("Select to Bottom of View")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_PageDown)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftBottomOfView())); m_editActions << a; a = ac->addAction(QStringLiteral("to_matching_bracket")); a->setText(i18n("Move to Matching Bracket")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_6)); connect(a, SIGNAL(triggered(bool)), SLOT(toMatchingBracket())); //m_editActions << a; a = ac->addAction(QStringLiteral("select_matching_bracket")); a->setText(i18n("Select to Matching Bracket")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_6)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftToMatchingBracket())); //m_editActions << a; // anders: shortcuts doing any changes should not be created in read-only mode if (!m_doc->readOnly()) { a = ac->addAction(QStringLiteral("transpose_char")); a->setText(i18n("Transpose Characters")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_T)); connect(a, SIGNAL(triggered(bool)), SLOT(transpose())); m_editActions << a; a = ac->addAction(QStringLiteral("delete_line")); a->setText(i18n("Delete Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_K)); connect(a, SIGNAL(triggered(bool)), SLOT(killLine())); m_editActions << a; a = ac->addAction(QStringLiteral("delete_word_left")); a->setText(i18n("Delete Word Left")); ac->setDefaultShortcuts(a, KStandardShortcut::deleteWordBack()); connect(a, SIGNAL(triggered(bool)), SLOT(deleteWordLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("delete_word_right")); a->setText(i18n("Delete Word Right")); ac->setDefaultShortcuts(a, KStandardShortcut::deleteWordForward()); connect(a, SIGNAL(triggered(bool)), SLOT(deleteWordRight())); m_editActions << a; a = ac->addAction(QStringLiteral("delete_next_character")); a->setText(i18n("Delete Next Character")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Delete)); connect(a, SIGNAL(triggered(bool)), SLOT(keyDelete())); m_editActions << a; a = ac->addAction(QStringLiteral("backspace")); a->setText(i18n("Backspace")); QList scuts; scuts << QKeySequence(Qt::Key_Backspace) << QKeySequence(Qt::SHIFT + Qt::Key_Backspace); ac->setDefaultShortcuts(a, scuts); connect(a, SIGNAL(triggered(bool)), SLOT(backspace())); m_editActions << a; a = ac->addAction(QStringLiteral("insert_tabulator")); a->setText(i18n("Insert Tab")); connect(a, SIGNAL(triggered(bool)), SLOT(insertTab())); m_editActions << a; a = ac->addAction(QStringLiteral("smart_newline")); a->setText(i18n("Insert Smart Newline")); a->setWhatsThis(i18n("Insert newline including leading characters of the current line which are not letters or numbers.")); scuts.clear(); scuts << QKeySequence(Qt::SHIFT + Qt::Key_Return) << QKeySequence(Qt::SHIFT + Qt::Key_Enter); ac->setDefaultShortcuts(a, scuts); connect(a, SIGNAL(triggered(bool)), SLOT(smartNewline())); m_editActions << a; a = ac->addAction(QStringLiteral("tools_indent")); a->setIcon(QIcon::fromTheme(QStringLiteral("format-indent-more"))); a->setText(i18n("&Indent")); a->setWhatsThis(i18n("Use this to indent a selected block of text.

" "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog.")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_I)); connect(a, SIGNAL(triggered(bool)), SLOT(indent())); a = ac->addAction(QStringLiteral("tools_unindent")); a->setIcon(QIcon::fromTheme(QStringLiteral("format-indent-less"))); a->setText(i18n("&Unindent")); a->setWhatsThis(i18n("Use this to unindent a selected block of text.")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_I)); connect(a, SIGNAL(triggered(bool)), SLOT(unIndent())); } if (hasFocus()) { slotGotFocus(); } else { slotLostFocus(); } } void KTextEditor::ViewPrivate::setupCodeFolding() { KActionCollection *ac = this->actionCollection(); QAction *a; a = ac->addAction(QStringLiteral("folding_toplevel")); a->setText(i18n("Fold Toplevel Nodes")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Minus)); connect(a, SIGNAL(triggered(bool)), SLOT(slotFoldToplevelNodes())); a = ac->addAction(QLatin1String("folding_expandtoplevel")); a->setText(i18n("Unfold Toplevel Nodes")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Plus)); connect(a, SIGNAL(triggered(bool)), SLOT(slotExpandToplevelNodes())); /*a = ac->addAction(QLatin1String("folding_expandall")); a->setText(i18n("Unfold All Nodes")); connect(a, SIGNAL(triggered(bool)), m_doc->foldingTree(), SLOT(expandAll())); a = ac->addAction(QLatin1String("folding_collapse_dsComment")); a->setText(i18n("Fold Multiline Comments")); connect(a, SIGNAL(triggered(bool)), m_doc->foldingTree(), SLOT(collapseAll_dsComments())); */ a = ac->addAction(QStringLiteral("folding_collapselocal")); a->setText(i18n("Fold Current Node")); connect(a, SIGNAL(triggered(bool)), SLOT(slotCollapseLocal())); a = ac->addAction(QStringLiteral("folding_expandlocal")); a->setText(i18n("Unfold Current Node")); connect(a, SIGNAL(triggered(bool)), SLOT(slotExpandLocal())); } void KTextEditor::ViewPrivate::slotFoldToplevelNodes() { for (int line = 0; line < doc()->lines(); ++line) { if (textFolding().isLineVisible(line)) { foldLine(line); } } } void KTextEditor::ViewPrivate::slotExpandToplevelNodes() { const auto topLevelRanges(textFolding().foldingRangesForParentRange()); for (const auto &range : topLevelRanges) { textFolding().unfoldRange(range.first); } } void KTextEditor::ViewPrivate::slotCollapseLocal() { foldLine(cursorPosition().line()); } void KTextEditor::ViewPrivate::slotExpandLocal() { unfoldLine(cursorPosition().line()); } void KTextEditor::ViewPrivate::foldLine(int startLine) { // only for valid lines if (startLine < 0 || startLine >= doc()->buffer().lines()) { return; } // try to fold all known ranges QVector > startingRanges = textFolding().foldingRangesStartingOnLine(startLine); for (int i = 0; i < startingRanges.size(); ++i) { textFolding().foldRange(startingRanges[i].first); } // try if the highlighting can help us and create a fold textFolding().newFoldingRange(doc()->buffer().computeFoldingRangeForStartLine(startLine), Kate::TextFolding::Folded); } void KTextEditor::ViewPrivate::unfoldLine(int startLine) { // only for valid lines if (startLine < 0 || startLine >= doc()->buffer().lines()) { return; } // try to unfold all known ranges QVector > startingRanges = textFolding().foldingRangesStartingOnLine(startLine); for (int i = 0; i < startingRanges.size(); ++i) { textFolding().unfoldRange(startingRanges[i].first); } } KTextEditor::View::ViewMode KTextEditor::ViewPrivate::viewMode() const { return currentInputMode()->viewMode(); } QString KTextEditor::ViewPrivate::viewModeHuman() const { QString currentMode = currentInputMode()->viewModeHuman(); /** * append read-only if needed */ if (!m_doc->isReadWrite()) { currentMode = i18n("(R/O) %1", currentMode); } /** * return full mode */ return currentMode; } KTextEditor::View::InputMode KTextEditor::ViewPrivate::viewInputMode() const { return currentInputMode()->viewInputMode(); } QString KTextEditor::ViewPrivate::viewInputModeHuman() const { return currentInputMode()->viewInputModeHuman(); } void KTextEditor::ViewPrivate::setInputMode(KTextEditor::View::InputMode mode) { if (currentInputMode()->viewInputMode() == mode) { return; } if (!m_viewInternal->m_inputModes.contains(mode)) { return; } m_viewInternal->m_currentInputMode->deactivate(); m_viewInternal->m_currentInputMode = m_viewInternal->m_inputModes[mode]; m_viewInternal->m_currentInputMode->activate(); config()->setInputMode(mode); // TODO: this could be called from read config procedure, so it's not a good idea to set a specific view mode here /* small duplication, but need to do this if not toggled by action */ Q_FOREACH(QAction *action, m_inputModeActions->actions()) { if (static_cast(action->data().toInt()) == mode) { action->setChecked(true); break; } } /* inform the rest of the system about the change */ emit viewInputModeChanged(this, mode); emit viewModeChanged(this, viewMode()); } void KTextEditor::ViewPrivate::slotGotFocus() { //qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotGotFocus"; currentInputMode()->gotFocus(); /** * update current view and scrollbars * it is needed for styles that implement different frame and scrollbar * rendering when focused */ update(); if (m_viewInternal->m_lineScroll->isVisible()) { m_viewInternal->m_lineScroll->update(); } if (m_viewInternal->m_columnScroll->isVisible()) { m_viewInternal->m_columnScroll->update(); } emit focusIn(this); } void KTextEditor::ViewPrivate::slotLostFocus() { //qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotLostFocus"; currentInputMode()->lostFocus(); /** * update current view and scrollbars * it is needed for styles that implement different frame and scrollbar * rendering when focused */ update(); if (m_viewInternal->m_lineScroll->isVisible()) { m_viewInternal->m_lineScroll->update(); } if (m_viewInternal->m_columnScroll->isVisible()) { m_viewInternal->m_columnScroll->update(); } emit focusOut(this); } void KTextEditor::ViewPrivate::setDynWrapIndicators(int mode) { config()->setDynWordWrapIndicators(mode); } bool KTextEditor::ViewPrivate::isOverwriteMode() const { return m_doc->config()->ovr(); } void KTextEditor::ViewPrivate::reloadFile() { // bookmarks and cursor positions are temporarily saved by the document m_doc->documentReload(); } void KTextEditor::ViewPrivate::slotReadWriteChanged() { if (m_toggleWriteLock) { m_toggleWriteLock->setChecked(! m_doc->isReadWrite()); } m_cut->setEnabled(m_doc->isReadWrite() && (selection() || m_config->smartCopyCut())); m_paste->setEnabled(m_doc->isReadWrite()); m_pasteMenu->setEnabled(m_doc->isReadWrite() && !KTextEditor::EditorPrivate::self()->clipboardHistory().isEmpty()); m_setEndOfLine->setEnabled(m_doc->isReadWrite()); static const QStringList l { QStringLiteral("edit_replace") , QStringLiteral("tools_spelling") , QStringLiteral("tools_indent") , QStringLiteral("tools_unindent") , QStringLiteral("tools_cleanIndent") , QStringLiteral("tools_align") , QStringLiteral("tools_comment") , QStringLiteral("tools_uncomment") , QStringLiteral("tools_toggle_comment") , QStringLiteral("tools_uppercase") , QStringLiteral("tools_lowercase") , QStringLiteral("tools_capitalize") , QStringLiteral("tools_join_lines") , QStringLiteral("tools_apply_wordwrap") , QStringLiteral("tools_spelling_from_cursor") , QStringLiteral("tools_spelling_selection") }; foreach (const QString &action, l) { QAction *a = actionCollection()->action(action); if (a) { a->setEnabled(m_doc->isReadWrite()); } } slotUpdateUndo(); currentInputMode()->readWriteChanged(m_doc->isReadWrite()); // => view mode changed emit viewModeChanged(this, viewMode()); emit viewInputModeChanged(this, viewInputMode()); } void KTextEditor::ViewPrivate::slotClipboardHistoryChanged() { m_pasteMenu->setEnabled(m_doc->isReadWrite() && !KTextEditor::EditorPrivate::self()->clipboardHistory().isEmpty()); } void KTextEditor::ViewPrivate::slotUpdateUndo() { if (m_doc->readOnly()) { return; } m_editUndo->setEnabled(m_doc->isReadWrite() && m_doc->undoCount() > 0); m_editRedo->setEnabled(m_doc->isReadWrite() && m_doc->redoCount() > 0); } bool KTextEditor::ViewPrivate::setCursorPositionInternal(const KTextEditor::Cursor &position, uint tabwidth, bool calledExternally) { Kate::TextLine l = m_doc->kateTextLine(position.line()); if (!l) { return false; } QString line_str = m_doc->line(position.line()); int x = 0; int z = 0; for (; z < line_str.length() && z < position.column(); z++) { if (line_str[z] == QLatin1Char('\t')) { x += tabwidth - (x % tabwidth); } else { x++; } } if (blockSelection()) if (z < position.column()) { x += position.column() - z; } m_viewInternal->updateCursor(KTextEditor::Cursor(position.line(), x), false, true, calledExternally); return true; } void KTextEditor::ViewPrivate::toggleInsert() { m_doc->config()->setOvr(!m_doc->config()->ovr()); m_toggleInsert->setChecked(isOverwriteMode()); emit viewModeChanged(this, viewMode()); emit viewInputModeChanged(this, viewInputMode()); } void KTextEditor::ViewPrivate::slotSaveCanceled(const QString &error) { if (!error.isEmpty()) { // happens when canceling a job KMessageBox::error(this, error); } } void KTextEditor::ViewPrivate::gotoLine() { gotoBar()->updateData(); bottomViewBar()->showBarWidget(gotoBar()); } void KTextEditor::ViewPrivate::changeDictionary() { dictionaryBar()->updateData(); bottomViewBar()->showBarWidget(dictionaryBar()); } void KTextEditor::ViewPrivate::joinLines() { int first = selectionRange().start().line(); int last = selectionRange().end().line(); //int left = m_doc->line( last ).length() - m_doc->selEndCol(); if (first == last) { first = cursorPosition().line(); last = first + 1; } m_doc->joinLines(first, last); } void KTextEditor::ViewPrivate::readSessionConfig(const KConfigGroup &config, const QSet &flags) { Q_UNUSED(flags) // cursor position setCursorPositionInternal(KTextEditor::Cursor(config.readEntry("CursorLine", 0), config.readEntry("CursorColumn", 0))); // restore dyn word wrap if set for this view if (config.hasKey("Dynamic Word Wrap")) { m_config->setDynWordWrap(config.readEntry("Dynamic Word Wrap", false)); } // restore text folding m_savedFoldingState = QJsonDocument::fromJson(config.readEntry("TextFolding", QByteArray())); applyFoldingState(); Q_FOREACH(KateAbstractInputMode *mode, m_viewInternal->m_inputModes) { mode->readSessionConfig(config); } } void KTextEditor::ViewPrivate::writeSessionConfig(KConfigGroup &config, const QSet &flags) { Q_UNUSED(flags) // cursor position config.writeEntry("CursorLine", m_viewInternal->m_cursor.line()); config.writeEntry("CursorColumn", m_viewInternal->m_cursor.column()); // save dyn word wrap if set for this view if (m_config->dynWordWrapSet()) { config.writeEntry("Dynamic Word Wrap", m_config->dynWordWrap()); } // save text folding state saveFoldingState(); config.writeEntry("TextFolding", m_savedFoldingState.toJson(QJsonDocument::Compact)); m_savedFoldingState = QJsonDocument(); Q_FOREACH(KateAbstractInputMode *mode, m_viewInternal->m_inputModes) { mode->writeSessionConfig(config); } } int KTextEditor::ViewPrivate::getEol() const { return m_doc->config()->eol(); } void KTextEditor::ViewPrivate::setEol(int eol) { if (!doc()->isReadWrite()) { return; } if (m_updatingDocumentConfig) { return; } if (eol != m_doc->config()->eol()) { m_doc->setModified(true); // mark modified (bug #143120) m_doc->config()->setEol(eol); } } void KTextEditor::ViewPrivate::setAddBom(bool enabled) { if (!doc()->isReadWrite()) { return; } if (m_updatingDocumentConfig) { return; } m_doc->config()->setBom(enabled); m_doc->bomSetByUser(); } void KTextEditor::ViewPrivate::setIconBorder(bool enable) { config()->setIconBar(enable); } void KTextEditor::ViewPrivate::toggleIconBorder() { config()->setIconBar(!config()->iconBar()); } void KTextEditor::ViewPrivate::setLineNumbersOn(bool enable) { config()->setLineNumbers(enable); } void KTextEditor::ViewPrivate::toggleLineNumbersOn() { config()->setLineNumbers(!config()->lineNumbers()); } void KTextEditor::ViewPrivate::setScrollBarMarks(bool enable) { config()->setScrollBarMarks(enable); } void KTextEditor::ViewPrivate::toggleScrollBarMarks() { config()->setScrollBarMarks(!config()->scrollBarMarks()); } void KTextEditor::ViewPrivate::setScrollBarMiniMap(bool enable) { config()->setScrollBarMiniMap(enable); } void KTextEditor::ViewPrivate::toggleScrollBarMiniMap() { config()->setScrollBarMiniMap(!config()->scrollBarMiniMap()); } void KTextEditor::ViewPrivate::setScrollBarMiniMapAll(bool enable) { config()->setScrollBarMiniMapAll(enable); } void KTextEditor::ViewPrivate::toggleScrollBarMiniMapAll() { config()->setScrollBarMiniMapAll(!config()->scrollBarMiniMapAll()); } void KTextEditor::ViewPrivate::setScrollBarMiniMapWidth(int width) { config()->setScrollBarMiniMapWidth(width); } void KTextEditor::ViewPrivate::toggleDynWordWrap() { config()->setDynWordWrap(!config()->dynWordWrap()); } void KTextEditor::ViewPrivate::toggleWWMarker() { m_renderer->config()->setWordWrapMarker(!m_renderer->config()->wordWrapMarker()); } void KTextEditor::ViewPrivate::toggleNPSpaces() { m_renderer->setShowNonPrintableSpaces(!m_renderer->showNonPrintableSpaces()); m_viewInternal->update(); // force redraw } void KTextEditor::ViewPrivate::toggleWordCount(bool on) { config()->setShowWordCount(on); } void KTextEditor::ViewPrivate::setFoldingMarkersOn(bool enable) { config()->setFoldingBar(enable); } void KTextEditor::ViewPrivate::toggleFoldingMarkers() { config()->setFoldingBar(!config()->foldingBar()); } bool KTextEditor::ViewPrivate::iconBorder() { return m_viewInternal->m_leftBorder->iconBorderOn(); } bool KTextEditor::ViewPrivate::lineNumbersOn() { return m_viewInternal->m_leftBorder->lineNumbersOn(); } bool KTextEditor::ViewPrivate::scrollBarMarks() { return m_viewInternal->m_lineScroll->showMarks(); } bool KTextEditor::ViewPrivate::scrollBarMiniMap() { return m_viewInternal->m_lineScroll->showMiniMap(); } int KTextEditor::ViewPrivate::dynWrapIndicators() { return m_viewInternal->m_leftBorder->dynWrapIndicators(); } bool KTextEditor::ViewPrivate::foldingMarkersOn() { return m_viewInternal->m_leftBorder->foldingMarkersOn(); } void KTextEditor::ViewPrivate::toggleWriteLock() { m_doc->setReadWrite(! m_doc->isReadWrite()); } void KTextEditor::ViewPrivate::registerTextHintProvider(KTextEditor::TextHintProvider *provider) { m_viewInternal->registerTextHintProvider(provider); } void KTextEditor::ViewPrivate::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider) { m_viewInternal->unregisterTextHintProvider(provider); } void KTextEditor::ViewPrivate::setTextHintDelay(int delay) { m_viewInternal->setTextHintDelay(delay); } int KTextEditor::ViewPrivate::textHintDelay() const { return m_viewInternal->textHintDelay(); } void KTextEditor::ViewPrivate::find() { currentInputMode()->find(); } void KTextEditor::ViewPrivate::findSelectedForwards() { currentInputMode()->findSelectedForwards(); } void KTextEditor::ViewPrivate::findSelectedBackwards() { currentInputMode()->findSelectedBackwards(); } void KTextEditor::ViewPrivate::replace() { currentInputMode()->findReplace(); } void KTextEditor::ViewPrivate::findNext() { currentInputMode()->findNext(); } void KTextEditor::ViewPrivate::findPrevious() { currentInputMode()->findPrevious(); } void KTextEditor::ViewPrivate::slotSelectionChanged() { m_copy->setEnabled(selection() || m_config->smartCopyCut()); m_deSelect->setEnabled(selection()); m_copyHtmlAction->setEnabled (selection()); // update highlighting of current selected word selectionChangedForHighlights (); if (m_doc->readOnly()) { return; } m_cut->setEnabled(selection() || m_config->smartCopyCut()); m_spell->updateActions(); } void KTextEditor::ViewPrivate::switchToCmdLine() { currentInputMode()->activateCommandLine(); } KateRenderer *KTextEditor::ViewPrivate::renderer() { return m_renderer; } void KTextEditor::ViewPrivate::updateConfig() { if (m_startingUp) { return; } // dyn. word wrap & markers if (m_hasWrap != config()->dynWordWrap()) { m_viewInternal->prepareForDynWrapChange(); m_hasWrap = config()->dynWordWrap(); m_viewInternal->dynWrapChanged(); m_setDynWrapIndicators->setEnabled(config()->dynWordWrap()); m_toggleDynWrap->setChecked(config()->dynWordWrap()); } m_viewInternal->m_leftBorder->setDynWrapIndicators(config()->dynWordWrapIndicators()); m_setDynWrapIndicators->setCurrentItem(config()->dynWordWrapIndicators()); // line numbers m_viewInternal->m_leftBorder->setLineNumbersOn(config()->lineNumbers()); m_toggleLineNumbers->setChecked(config()->lineNumbers()); // icon bar m_viewInternal->m_leftBorder->setIconBorderOn(config()->iconBar()); m_toggleIconBar->setChecked(config()->iconBar()); // scrollbar marks m_viewInternal->m_lineScroll->setShowMarks(config()->scrollBarMarks()); m_toggleScrollBarMarks->setChecked(config()->scrollBarMarks()); // scrollbar mini-map m_viewInternal->m_lineScroll->setShowMiniMap(config()->scrollBarMiniMap()); m_toggleScrollBarMiniMap->setChecked(config()->scrollBarMiniMap()); // scrollbar mini-map - (whole document) m_viewInternal->m_lineScroll->setMiniMapAll(config()->scrollBarMiniMapAll()); //m_toggleScrollBarMiniMapAll->setChecked( config()->scrollBarMiniMapAll() ); // scrollbar mini-map.width m_viewInternal->m_lineScroll->setMiniMapWidth(config()->scrollBarMiniMapWidth()); // misc edit m_toggleBlockSelection->setChecked(blockSelection()); m_toggleInsert->setChecked(isOverwriteMode()); updateFoldingConfig(); // bookmark m_bookmarks->setSorting((KateBookmarks::Sorting) config()->bookmarkSort()); m_viewInternal->setAutoCenterLines(config()->autoCenterLines()); Q_FOREACH(KateAbstractInputMode *input, m_viewInternal->m_inputModes) { input->updateConfig(); } setInputMode(config()->inputMode()); reflectOnTheFlySpellCheckStatus(m_doc->isOnTheFlySpellCheckingEnabled()); // register/unregister word completion... bool wc = config()->wordCompletion(); if (wc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->wordCompletionModel())) { if (wc) registerCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); else unregisterCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); } bool kc = config()->keywordCompletion(); if (kc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->keywordCompletionModel())) { if (kc) registerCompletionModel(KTextEditor::EditorPrivate::self()->keywordCompletionModel()); else unregisterCompletionModel (KTextEditor::EditorPrivate::self()->keywordCompletionModel()); } m_cut->setEnabled(m_doc->isReadWrite() && (selection() || m_config->smartCopyCut())); m_copy->setEnabled(selection() || m_config->smartCopyCut()); // now redraw... m_viewInternal->cache()->clear(); tagAll(); updateView(true); emit configChanged(); } void KTextEditor::ViewPrivate::updateDocumentConfig() { if (m_startingUp) { return; } m_updatingDocumentConfig = true; m_setEndOfLine->setCurrentItem(m_doc->config()->eol()); m_addBom->setChecked(m_doc->config()->bom()); m_updatingDocumentConfig = false; // maybe block selection or wrap-cursor mode changed ensureCursorColumnValid(); // first change this m_renderer->setTabWidth(m_doc->config()->tabWidth()); m_renderer->setIndentWidth(m_doc->config()->indentationWidth()); // now redraw... m_viewInternal->cache()->clear(); tagAll(); updateView(true); } void KTextEditor::ViewPrivate::updateRendererConfig() { if (m_startingUp) { return; } m_toggleWWMarker->setChecked(m_renderer->config()->wordWrapMarker()); m_viewInternal->updateBracketMarkAttributes(); m_viewInternal->updateBracketMarks(); // now redraw... m_viewInternal->cache()->clear(); tagAll(); m_viewInternal->updateView(true); // update the left border right, for example linenumbers m_viewInternal->m_leftBorder->updateFont(); m_viewInternal->m_leftBorder->repaint(); m_viewInternal->m_lineScroll->queuePixmapUpdate(); currentInputMode()->updateRendererConfig(); // @@ showIndentLines is not cached anymore. // m_renderer->setShowIndentLines (m_renderer->config()->showIndentationLines()); emit configChanged(); } void KTextEditor::ViewPrivate::updateFoldingConfig() { // folding bar m_viewInternal->m_leftBorder->setFoldingMarkersOn(config()->foldingBar()); m_toggleFoldingMarkers->setChecked(config()->foldingBar()); if (hasCommentInFirstLine(m_doc)) { if (config()->foldFirstLine() && !m_autoFoldedFirstLine) { foldLine(0); m_autoFoldedFirstLine = true; } else if (!config()->foldFirstLine() && m_autoFoldedFirstLine) { unfoldLine(0); m_autoFoldedFirstLine = false; } } else { m_autoFoldedFirstLine = false; } #if 0 // FIXME: FOLDING const QStringList l { QStringLiteral("folding_toplevel") , QStringLiteral("folding_expandtoplevel") , QStringLiteral("folding_collapselocal") , QStringLiteral("folding_expandlocal") }; QAction *a = 0; for (int z = 0; z < l.size(); z++) if ((a = actionCollection()->action(l[z].toAscii().constData()))) { a->setEnabled(m_doc->highlight() && m_doc->highlight()->allowsFolding()); } #endif } void KTextEditor::ViewPrivate::ensureCursorColumnValid() { KTextEditor::Cursor c = m_viewInternal->getCursor(); // make sure the cursor is valid: // - in block selection mode or if wrap cursor is off, the column is arbitrary // - otherwise: it's bounded by the line length if (!blockSelection() && wrapCursor() && (!c.isValid() || c.column() > m_doc->lineLength(c.line()))) { c.setColumn(m_doc->kateTextLine(cursorPosition().line())->length()); setCursorPosition(c); } } //BEGIN EDIT STUFF void KTextEditor::ViewPrivate::editStart() { m_viewInternal->editStart(); } void KTextEditor::ViewPrivate::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom) { m_viewInternal->editEnd(editTagLineStart, editTagLineEnd, tagFrom); } void KTextEditor::ViewPrivate::editSetCursor(const KTextEditor::Cursor &cursor) { m_viewInternal->editSetCursor(cursor); } //END //BEGIN TAG & CLEAR bool KTextEditor::ViewPrivate::tagLine(const KTextEditor::Cursor &virtualCursor) { return m_viewInternal->tagLine(virtualCursor); } bool KTextEditor::ViewPrivate::tagRange(const KTextEditor::Range &range, bool realLines) { return m_viewInternal->tagRange(range, realLines); } bool KTextEditor::ViewPrivate::tagLines(int start, int end, bool realLines) { return m_viewInternal->tagLines(start, end, realLines); } bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors) { return m_viewInternal->tagLines(start, end, realCursors); } void KTextEditor::ViewPrivate::tagAll() { m_viewInternal->tagAll(); } void KTextEditor::ViewPrivate::clear() { m_viewInternal->clear(); } void KTextEditor::ViewPrivate::repaintText(bool paintOnlyDirty) { if (paintOnlyDirty) { m_viewInternal->updateDirty(); } else { m_viewInternal->update(); } } void KTextEditor::ViewPrivate::updateView(bool changed) { //qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::updateView"; m_viewInternal->updateView(changed); m_viewInternal->m_leftBorder->update(); } //END void KTextEditor::ViewPrivate::slotHlChanged() { KateHighlighting *hl = m_doc->highlight(); bool ok(!hl->getCommentStart(0).isEmpty() || !hl->getCommentSingleLineStart(0).isEmpty()); if (actionCollection()->action(QStringLiteral("tools_comment"))) { actionCollection()->action(QStringLiteral("tools_comment"))->setEnabled(ok); } if (actionCollection()->action(QStringLiteral("tools_uncomment"))) { actionCollection()->action(QStringLiteral("tools_uncomment"))->setEnabled(ok); } if (actionCollection()->action(QStringLiteral("tools_toggle_comment"))) { actionCollection()->action(QStringLiteral("tools_toggle_comment"))->setEnabled(ok); } // show folding bar if "view defaults" says so, otherwise enable/disable only the menu entry updateFoldingConfig(); } int KTextEditor::ViewPrivate::virtualCursorColumn() const { return m_doc->toVirtualColumn(m_viewInternal->getCursor()); } void KTextEditor::ViewPrivate::notifyMousePositionChanged(const KTextEditor::Cursor &newPosition) { emit mousePositionChanged(this, newPosition); } //BEGIN KTextEditor::SelectionInterface stuff bool KTextEditor::ViewPrivate::setSelection(const KTextEditor::Range &selection) { /** * anything to do? */ if (selection == m_selection) { return true; } /** * backup old range */ KTextEditor::Range oldSelection = m_selection; /** * set new range */ m_selection.setRange(selection.isEmpty() ? KTextEditor::Range::invalid() : selection); /** * trigger update of correct area */ tagSelection(oldSelection); repaintText(true); /** * emit holy signal */ emit selectionChanged(this); /** * be done */ return true; } bool KTextEditor::ViewPrivate::clearSelection() { return clearSelection(true); } bool KTextEditor::ViewPrivate::clearSelection(bool redraw, bool finishedChangingSelection) { /** * no selection, nothing to do... */ if (!selection()) { return false; } /** * backup old range */ KTextEditor::Range oldSelection = m_selection; /** * invalidate current selection */ m_selection.setRange(KTextEditor::Range::invalid()); /** * trigger update of correct area */ tagSelection(oldSelection); if (redraw) { repaintText(true); } /** * emit holy signal */ if (finishedChangingSelection) { emit selectionChanged(this); } /** * be done */ return true; } bool KTextEditor::ViewPrivate::selection() const { if (!wrapCursor()) { return m_selection != KTextEditor::Range::invalid(); } else { return m_selection.toRange().isValid(); } } QString KTextEditor::ViewPrivate::selectionText() const { return m_doc->text(m_selection, blockSelect); } bool KTextEditor::ViewPrivate::removeSelectedText() { if (!selection()) { return false; } m_doc->editStart(); // Optimization: clear selection before removing text KTextEditor::Range selection = m_selection; m_doc->removeText(selection, blockSelect); // don't redraw the cleared selection - that's done in editEnd(). if (blockSelect) { int selectionColumn = qMin(m_doc->toVirtualColumn(selection.start()), m_doc->toVirtualColumn(selection.end())); KTextEditor::Range newSelection = selection; newSelection.setStart(KTextEditor::Cursor(newSelection.start().line(), m_doc->fromVirtualColumn(newSelection.start().line(), selectionColumn))); newSelection.setEnd(KTextEditor::Cursor(newSelection.end().line(), m_doc->fromVirtualColumn(newSelection.end().line(), selectionColumn))); setSelection(newSelection); setCursorPositionInternal(newSelection.start()); } else { clearSelection(false); } m_doc->editEnd(); return true; } bool KTextEditor::ViewPrivate::selectAll() { setBlockSelection(false); top(); shiftBottom(); return true; } bool KTextEditor::ViewPrivate::cursorSelected(const KTextEditor::Cursor &cursor) { KTextEditor::Cursor ret = cursor; if ((!blockSelect) && (ret.column() < 0)) { ret.setColumn(0); } if (blockSelect) return cursor.line() >= m_selection.start().line() && ret.line() <= m_selection.end().line() && ret.column() >= m_selection.start().column() && ret.column() <= m_selection.end().column(); else { return m_selection.toRange().contains(cursor) || m_selection.end() == cursor; } } bool KTextEditor::ViewPrivate::lineSelected(int line) { return !blockSelect && m_selection.toRange().containsLine(line); } bool KTextEditor::ViewPrivate::lineEndSelected(const KTextEditor::Cursor &lineEndPos) { return (!blockSelect) && (lineEndPos.line() > m_selection.start().line() || (lineEndPos.line() == m_selection.start().line() && (m_selection.start().column() < lineEndPos.column() || lineEndPos.column() == -1))) && (lineEndPos.line() < m_selection.end().line() || (lineEndPos.line() == m_selection.end().line() && (lineEndPos.column() <= m_selection.end().column() && lineEndPos.column() != -1))); } bool KTextEditor::ViewPrivate::lineHasSelected(int line) { return selection() && m_selection.toRange().containsLine(line); } bool KTextEditor::ViewPrivate::lineIsSelection(int line) { return (line == m_selection.start().line() && line == m_selection.end().line()); } void KTextEditor::ViewPrivate::tagSelection(const KTextEditor::Range &oldSelection) { if (selection()) { if (oldSelection.start().line() == -1) { // We have to tag the whole lot if // 1) we have a selection, and: // a) it's new; or tagLines(m_selection, true); } else if (blockSelection() && (oldSelection.start().column() != m_selection.start().column() || oldSelection.end().column() != m_selection.end().column())) { // b) we're in block selection mode and the columns have changed tagLines(m_selection, true); tagLines(oldSelection, true); } else { if (oldSelection.start() != m_selection.start()) { if (oldSelection.start() < m_selection.start()) { tagLines(oldSelection.start(), m_selection.start(), true); } else { tagLines(m_selection.start(), oldSelection.start(), true); } } if (oldSelection.end() != m_selection.end()) { if (oldSelection.end() < m_selection.end()) { tagLines(oldSelection.end(), m_selection.end(), true); } else { tagLines(m_selection.end(), oldSelection.end(), true); } } } } else { // No more selection, clean up tagLines(oldSelection, true); } } void KTextEditor::ViewPrivate::selectWord(const KTextEditor::Cursor &cursor) { setSelection(m_doc->wordRangeAt(cursor)); } void KTextEditor::ViewPrivate::selectLine(const KTextEditor::Cursor &cursor) { int line = cursor.line(); if (line + 1 >= m_doc->lines()) { setSelection(KTextEditor::Range(line, 0, line, m_doc->lineLength(line))); } else { setSelection(KTextEditor::Range(line, 0, line + 1, 0)); } } void KTextEditor::ViewPrivate::cut() { if (!selection() && !m_config->smartCopyCut()) { return; } copy(); if (!selection()) { selectLine(m_viewInternal->m_cursor); } removeSelectedText(); } void KTextEditor::ViewPrivate::copy() const { QString text = selectionText(); if (!selection()) { if (!m_config->smartCopyCut()) { return; } text = m_doc->line(m_viewInternal->m_cursor.line()) + QLatin1Char('\n'); m_viewInternal->moveEdge(KateViewInternal::left, false); } // copy to clipboard and our history! KTextEditor::EditorPrivate::self()->copyToClipboard(text); } void KTextEditor::ViewPrivate::applyWordWrap() { if (selection()) { m_doc->wrapText(selectionRange().start().line(), selectionRange().end().line()); } else { m_doc->wrapText(0, m_doc->lastLine()); } } //END //BEGIN KTextEditor::BlockSelectionInterface stuff bool KTextEditor::ViewPrivate::blockSelection() const { return blockSelect; } bool KTextEditor::ViewPrivate::setBlockSelection(bool on) { if (on != blockSelect) { blockSelect = on; KTextEditor::Range oldSelection = m_selection; const bool hadSelection = clearSelection(false, false); setSelection(oldSelection); m_toggleBlockSelection->setChecked(blockSelection()); // when leaving block selection mode, if cursor is at an invalid position or past the end of the // line, move the cursor to the last column of the current line unless cursor wrapping is off ensureCursorColumnValid(); if (!hadSelection) { // emit selectionChanged() according to the KTextEditor::View api // documentation also if there is no selection around. This is needed, // as e.g. the Kate App status bar uses this signal to update the state // of the selection mode (block selection, line based selection) emit selectionChanged(this); } } return true; } bool KTextEditor::ViewPrivate::toggleBlockSelection() { m_toggleBlockSelection->setChecked(!blockSelect); return setBlockSelection(!blockSelect); } bool KTextEditor::ViewPrivate::wrapCursor() const { return !blockSelection(); } //END void KTextEditor::ViewPrivate::slotTextInserted(KTextEditor::View *view, const KTextEditor::Cursor &position, const QString &text) { emit textInserted(view, position, text); } bool KTextEditor::ViewPrivate::insertTemplateInternal(const KTextEditor::Cursor& c, const QString& templateString, const QString& script) { /** * no empty templates */ if (templateString.isEmpty()) { return false; } /** * not for read-only docs */ if (!m_doc->isReadWrite()) { return false; } /** * only one handler maybe active at a time; store it in the document. * Clear it first to make sure at no time two handlers are active at once */ doc()->setActiveTemplateHandler(nullptr); doc()->setActiveTemplateHandler(new KateTemplateHandler(this, c, templateString, script, m_doc->undoManager())); return true; } bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Range range, bool realRange) { return tagLines(range.start(), range.end(), realRange); } void KTextEditor::ViewPrivate::deactivateEditActions() { foreach (QAction *action, m_editActions) { action->setEnabled(false); } } void KTextEditor::ViewPrivate::activateEditActions() { foreach (QAction *action, m_editActions) { action->setEnabled(true); } } bool KTextEditor::ViewPrivate::mouseTrackingEnabled() const { // FIXME support return true; } bool KTextEditor::ViewPrivate::setMouseTrackingEnabled(bool) { // FIXME support return true; } bool KTextEditor::ViewPrivate::isCompletionActive() const { return completionWidget()->isCompletionActive(); } KateCompletionWidget *KTextEditor::ViewPrivate::completionWidget() const { if (!m_completionWidget) { m_completionWidget = new KateCompletionWidget(const_cast(this)); } return m_completionWidget; } void KTextEditor::ViewPrivate::startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model) { completionWidget()->startCompletion(word, model); } void KTextEditor::ViewPrivate::abortCompletion() { completionWidget()->abortCompletion(); } void KTextEditor::ViewPrivate::forceCompletion() { completionWidget()->execute(); } void KTextEditor::ViewPrivate::registerCompletionModel(KTextEditor::CodeCompletionModel *model) { completionWidget()->registerCompletionModel(model); } void KTextEditor::ViewPrivate::unregisterCompletionModel(KTextEditor::CodeCompletionModel *model) { completionWidget()->unregisterCompletionModel(model); } bool KTextEditor::ViewPrivate::isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const { return completionWidget()->isCompletionModelRegistered(model); } bool KTextEditor::ViewPrivate::isAutomaticInvocationEnabled() const { return !m_temporaryAutomaticInvocationDisabled && m_config->automaticCompletionInvocation(); } void KTextEditor::ViewPrivate::setAutomaticInvocationEnabled(bool enabled) { config()->setAutomaticCompletionInvocation(enabled); } void KTextEditor::ViewPrivate::sendCompletionExecuted(const KTextEditor::Cursor &position, KTextEditor::CodeCompletionModel *model, const QModelIndex &index) { emit completionExecuted(this, position, model, index); } void KTextEditor::ViewPrivate::sendCompletionAborted() { emit completionAborted(this); } void KTextEditor::ViewPrivate::paste(const QString *textToPaste) { m_temporaryAutomaticInvocationDisabled = true; m_doc->paste(this, textToPaste ? *textToPaste : QApplication::clipboard()->text(QClipboard::Clipboard)); m_temporaryAutomaticInvocationDisabled = false; } bool KTextEditor::ViewPrivate::setCursorPosition(KTextEditor::Cursor position) { return setCursorPositionInternal(position, 1, true); } KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPosition() const { return m_viewInternal->getCursor(); } KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPositionVirtual() const { return KTextEditor::Cursor(m_viewInternal->getCursor().line(), virtualCursorColumn()); } QPoint KTextEditor::ViewPrivate::cursorToCoordinate(const KTextEditor::Cursor &cursor) const { // map from ViewInternal to View coordinates const QPoint pt = m_viewInternal->cursorToCoordinate(cursor, true, false); return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt); } KTextEditor::Cursor KTextEditor::ViewPrivate::coordinatesToCursor(const QPoint &coords) const { // map from View to ViewInternal coordinates return m_viewInternal->coordinatesToCursor(m_viewInternal->mapFromParent(coords), false); } QPoint KTextEditor::ViewPrivate::cursorPositionCoordinates() const { // map from ViewInternal to View coordinates const QPoint pt = m_viewInternal->cursorCoordinates(false); return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt); } void KTextEditor::ViewPrivate::setScrollPositionInternal(KTextEditor::Cursor &cursor) { m_viewInternal->scrollPos(cursor, false, true, false); } void KTextEditor::ViewPrivate::setHorizontalScrollPositionInternal(int x) { m_viewInternal->scrollColumns(x); } KTextEditor::Cursor KTextEditor::ViewPrivate::maxScrollPositionInternal() const { return m_viewInternal->maxStartPos(true); } int KTextEditor::ViewPrivate::firstDisplayedLineInternal(LineType lineType) const { if (lineType == RealLine) { return m_textFolding.visibleLineToLine(m_viewInternal->startLine()); } else { return m_viewInternal->startLine(); } } int KTextEditor::ViewPrivate::lastDisplayedLineInternal(LineType lineType) const { if (lineType == RealLine) { return m_textFolding.visibleLineToLine(m_viewInternal->endLine()); } else { return m_viewInternal->endLine(); } } QRect KTextEditor::ViewPrivate::textAreaRectInternal() const { const auto sourceRect = m_viewInternal->rect(); const auto topLeft = m_viewInternal->mapTo(this, sourceRect.topLeft()); const auto bottomRight = m_viewInternal->mapTo(this, sourceRect.bottomRight()); return {topLeft, bottomRight}; } bool KTextEditor::ViewPrivate::setCursorPositionVisual(const KTextEditor::Cursor &position) { return setCursorPositionInternal(position, m_doc->config()->tabWidth(), true); } QString KTextEditor::ViewPrivate::currentTextLine() { return m_doc->line(cursorPosition().line()); } QTextLayout * KTextEditor::ViewPrivate::textLayout(int line) const { KateLineLayoutPtr thisLine = m_viewInternal->cache()->line(line); return thisLine->isValid() ? thisLine->layout() : nullptr; } QTextLayout * KTextEditor::ViewPrivate::textLayout(const KTextEditor::Cursor &pos) const { KateLineLayoutPtr thisLine = m_viewInternal->cache()->line(pos); return thisLine->isValid() ? thisLine->layout() : nullptr; } void KTextEditor::ViewPrivate::indent() { KTextEditor::Cursor c(cursorPosition().line(), 0); KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); m_doc->indent(r, 1); } void KTextEditor::ViewPrivate::unIndent() { KTextEditor::Cursor c(cursorPosition().line(), 0); KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); m_doc->indent(r, -1); } void KTextEditor::ViewPrivate::cleanIndent() { KTextEditor::Cursor c(cursorPosition().line(), 0); KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); m_doc->indent(r, 0); } void KTextEditor::ViewPrivate::align() { // no selection: align current line; selection: use selection range const int line = cursorPosition().line(); KTextEditor::Range alignRange(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, 0)); if (selection()) { alignRange = selectionRange(); } m_doc->align(this, alignRange); } void KTextEditor::ViewPrivate::comment() { m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight); m_doc->comment(this, cursorPosition().line(), cursorPosition().column(), 1); m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight); } void KTextEditor::ViewPrivate::uncomment() { m_doc->comment(this, cursorPosition().line(), cursorPosition().column(), -1); } void KTextEditor::ViewPrivate::toggleComment() { m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight); m_doc->comment(this, cursorPosition().line(), cursorPosition().column(), 0); m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight); } void KTextEditor::ViewPrivate::uppercase() { m_doc->transform(this, m_viewInternal->m_cursor, KTextEditor::DocumentPrivate::Uppercase); } void KTextEditor::ViewPrivate::killLine() { if (m_selection.isEmpty()) { m_doc->removeLine(cursorPosition().line()); } else { m_doc->editStart(); // cache endline, else that moves and we might delete complete document if last line is selected! for (int line = m_selection.end().line(), endLine = m_selection.start().line(); line >= endLine; line--) { m_doc->removeLine(line); } m_doc->editEnd(); } } void KTextEditor::ViewPrivate::lowercase() { m_doc->transform(this, m_viewInternal->m_cursor, KTextEditor::DocumentPrivate::Lowercase); } void KTextEditor::ViewPrivate::capitalize() { m_doc->editStart(); m_doc->transform(this, m_viewInternal->m_cursor, KTextEditor::DocumentPrivate::Lowercase); m_doc->transform(this, m_viewInternal->m_cursor, KTextEditor::DocumentPrivate::Capitalize); m_doc->editEnd(); } void KTextEditor::ViewPrivate::keyReturn() { m_viewInternal->doReturn(); } void KTextEditor::ViewPrivate::smartNewline() { m_viewInternal->doSmartNewline(); } void KTextEditor::ViewPrivate::backspace() { m_viewInternal->doBackspace(); } void KTextEditor::ViewPrivate::insertTab() { m_viewInternal->doTabulator(); } void KTextEditor::ViewPrivate::deleteWordLeft() { m_viewInternal->doDeletePrevWord(); } void KTextEditor::ViewPrivate::keyDelete() { m_viewInternal->doDelete(); } void KTextEditor::ViewPrivate::deleteWordRight() { m_viewInternal->doDeleteNextWord(); } void KTextEditor::ViewPrivate::transpose() { m_viewInternal->doTranspose(); } void KTextEditor::ViewPrivate::cursorLeft() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->cursorNextChar(); } else { m_viewInternal->cursorPrevChar(); } } void KTextEditor::ViewPrivate::shiftCursorLeft() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->cursorNextChar(true); } else { m_viewInternal->cursorPrevChar(true); } } void KTextEditor::ViewPrivate::cursorRight() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->cursorPrevChar(); } else { m_viewInternal->cursorNextChar(); } } void KTextEditor::ViewPrivate::shiftCursorRight() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->cursorPrevChar(true); } else { m_viewInternal->cursorNextChar(true); } } void KTextEditor::ViewPrivate::wordLeft() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->wordNext(); } else { m_viewInternal->wordPrev(); } } void KTextEditor::ViewPrivate::shiftWordLeft() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->wordNext(true); } else { m_viewInternal->wordPrev(true); } } void KTextEditor::ViewPrivate::wordRight() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->wordPrev(); } else { m_viewInternal->wordNext(); } } void KTextEditor::ViewPrivate::shiftWordRight() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->wordPrev(true); } else { m_viewInternal->wordNext(true); } } void KTextEditor::ViewPrivate::home() { m_viewInternal->home(); } void KTextEditor::ViewPrivate::shiftHome() { m_viewInternal->home(true); } void KTextEditor::ViewPrivate::end() { m_viewInternal->end(); } void KTextEditor::ViewPrivate::shiftEnd() { m_viewInternal->end(true); } void KTextEditor::ViewPrivate::up() { m_viewInternal->cursorUp(); } void KTextEditor::ViewPrivate::shiftUp() { m_viewInternal->cursorUp(true); } void KTextEditor::ViewPrivate::down() { m_viewInternal->cursorDown(); } void KTextEditor::ViewPrivate::shiftDown() { m_viewInternal->cursorDown(true); } void KTextEditor::ViewPrivate::scrollUp() { m_viewInternal->scrollUp(); } void KTextEditor::ViewPrivate::scrollDown() { m_viewInternal->scrollDown(); } void KTextEditor::ViewPrivate::topOfView() { m_viewInternal->topOfView(); } void KTextEditor::ViewPrivate::shiftTopOfView() { m_viewInternal->topOfView(true); } void KTextEditor::ViewPrivate::bottomOfView() { m_viewInternal->bottomOfView(); } void KTextEditor::ViewPrivate::shiftBottomOfView() { m_viewInternal->bottomOfView(true); } void KTextEditor::ViewPrivate::pageUp() { m_viewInternal->pageUp(); } void KTextEditor::ViewPrivate::shiftPageUp() { m_viewInternal->pageUp(true); } void KTextEditor::ViewPrivate::pageDown() { m_viewInternal->pageDown(); } void KTextEditor::ViewPrivate::shiftPageDown() { m_viewInternal->pageDown(true); } void KTextEditor::ViewPrivate::top() { m_viewInternal->top_home(); } void KTextEditor::ViewPrivate::shiftTop() { m_viewInternal->top_home(true); } void KTextEditor::ViewPrivate::bottom() { m_viewInternal->bottom_end(); } void KTextEditor::ViewPrivate::shiftBottom() { m_viewInternal->bottom_end(true); } void KTextEditor::ViewPrivate::toMatchingBracket() { m_viewInternal->cursorToMatchingBracket(); } void KTextEditor::ViewPrivate::shiftToMatchingBracket() { m_viewInternal->cursorToMatchingBracket(true); } void KTextEditor::ViewPrivate::toPrevModifiedLine() { const int startLine = m_viewInternal->m_cursor.line() - 1; const int line = m_doc->findTouchedLine(startLine, false); if (line >= 0) { KTextEditor::Cursor c(line, 0); m_viewInternal->updateSelection(c, false); m_viewInternal->updateCursor(c); } } void KTextEditor::ViewPrivate::toNextModifiedLine() { const int startLine = m_viewInternal->m_cursor.line() + 1; const int line = m_doc->findTouchedLine(startLine, true); if (line >= 0) { KTextEditor::Cursor c(line, 0); m_viewInternal->updateSelection(c, false); m_viewInternal->updateCursor(c); } } KTextEditor::Range KTextEditor::ViewPrivate::selectionRange() const { return m_selection; } KTextEditor::Document *KTextEditor::ViewPrivate::document() const { return m_doc; } void KTextEditor::ViewPrivate::setContextMenu(QMenu *menu) { if (m_contextMenu) { disconnect(m_contextMenu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); disconnect(m_contextMenu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); } m_contextMenu = menu; m_userContextMenuSet = true; if (m_contextMenu) { connect(m_contextMenu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); connect(m_contextMenu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); } } QMenu *KTextEditor::ViewPrivate::contextMenu() const { if (m_userContextMenuSet) { return m_contextMenu; } else { KXMLGUIClient *client = const_cast(this); while (client->parentClient()) { client = client->parentClient(); } //qCDebug(LOG_KTE) << "looking up all menu containers"; if (client->factory()) { QList conts = client->factory()->containers(QStringLiteral("menu")); foreach (QWidget *w, conts) { if (w->objectName() == QLatin1String("ktexteditor_popup")) { //perhaps optimize this block QMenu *menu = (QMenu *)w; disconnect(menu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); disconnect(menu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); connect(menu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); connect(menu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); return menu; } } } } return nullptr; } QMenu *KTextEditor::ViewPrivate::defaultContextMenu(QMenu *menu) const { if (!menu) { menu = new QMenu(const_cast(this)); } menu->addAction(m_editUndo); menu->addAction(m_editRedo); menu->addSeparator(); menu->addAction(m_cut); menu->addAction(m_copy); menu->addAction(m_paste); menu->addSeparator(); menu->addAction(m_selectAll); menu->addAction(m_deSelect); if (QAction *spellingSuggestions = actionCollection()->action(QStringLiteral("spelling_suggestions"))) { menu->addSeparator(); menu->addAction(spellingSuggestions); } if (QAction *bookmark = actionCollection()->action(QStringLiteral("bookmarks"))) { menu->addSeparator(); menu->addAction(bookmark); } return menu; } void KTextEditor::ViewPrivate::aboutToShowContextMenu() { QMenu *menu = qobject_cast(sender()); if (menu) { emit contextMenuAboutToShow(this, menu); } } void KTextEditor::ViewPrivate::aboutToHideContextMenu() { m_spellingMenu->setUseMouseForMisspelledRange(false); } // BEGIN ConfigInterface stff QStringList KTextEditor::ViewPrivate::configKeys() const { static const QStringList keys = { QStringLiteral("icon-bar"), QStringLiteral("line-numbers"), QStringLiteral("dynamic-word-wrap"), QStringLiteral("background-color"), QStringLiteral("selection-color"), QStringLiteral("search-highlight-color"), QStringLiteral("replace-highlight-color"), QStringLiteral("default-mark-type"), QStringLiteral("allow-mark-menu"), QStringLiteral("folding-bar"), QStringLiteral("folding-preview"), QStringLiteral("icon-border-color"), QStringLiteral("folding-marker-color"), QStringLiteral("line-number-color"), QStringLiteral("current-line-number-color"), QStringLiteral("modification-markers"), QStringLiteral("keyword-completion"), QStringLiteral("word-count"), QStringLiteral("scrollbar-minimap"), QStringLiteral("scrollbar-preview"), QStringLiteral("font") }; return keys; } QVariant KTextEditor::ViewPrivate::configValue(const QString &key) { if (key == QLatin1String("icon-bar")) { return config()->iconBar(); } else if (key == QLatin1String("line-numbers")) { return config()->lineNumbers(); } else if (key == QLatin1String("dynamic-word-wrap")) { return config()->dynWordWrap(); } else if (key == QLatin1String("background-color")) { return renderer()->config()->backgroundColor(); } else if (key == QLatin1String("selection-color")) { return renderer()->config()->selectionColor(); } else if (key == QLatin1String("search-highlight-color")) { return renderer()->config()->searchHighlightColor(); } else if (key == QLatin1String("replace-highlight-color")) { return renderer()->config()->replaceHighlightColor(); } else if (key == QLatin1String("default-mark-type")) { return config()->defaultMarkType(); } else if (key == QLatin1String("allow-mark-menu")) { return config()->allowMarkMenu(); } else if (key == QLatin1String("folding-bar")) { return config()->foldingBar(); } else if (key == QLatin1String("folding-preview")) { return config()->foldingPreview(); } else if (key == QLatin1String("icon-border-color")) { return renderer()->config()->iconBarColor(); } else if (key == QLatin1String("folding-marker-color")) { return renderer()->config()->foldingColor(); } else if (key == QLatin1String("line-number-color")) { return renderer()->config()->lineNumberColor(); } else if (key == QLatin1String("current-line-number-color")) { return renderer()->config()->currentLineNumberColor(); } else if (key == QLatin1String("modification-markers")) { return config()->lineModification(); } else if (key == QLatin1String("keyword-completion")) { return config()->keywordCompletion(); } else if (key == QLatin1String("word-count")) { return config()->showWordCount(); } else if (key == QLatin1String("scrollbar-minimap")) { return config()->scrollBarMiniMap(); } else if (key == QLatin1String("scrollbar-preview")) { return config()->scrollBarPreview(); } else if (key == QLatin1String("font")) { return renderer()->config()->font(); } // return invalid variant return QVariant(); } void KTextEditor::ViewPrivate::setConfigValue(const QString &key, const QVariant &value) { if (value.canConvert(QVariant::Color)) { if (key == QLatin1String("background-color")) { renderer()->config()->setBackgroundColor(value.value()); } else if (key == QLatin1String("selection-color")) { renderer()->config()->setSelectionColor(value.value()); } else if (key == QLatin1String("search-highlight-color")) { renderer()->config()->setSearchHighlightColor(value.value()); } else if (key == QLatin1String("replace-highlight-color")) { renderer()->config()->setReplaceHighlightColor(value.value()); } else if (key == QLatin1String("icon-border-color")) { renderer()->config()->setIconBarColor(value.value()); } else if (key == QLatin1String("folding-marker-color")) { renderer()->config()->setFoldingColor(value.value()); } else if (key == QLatin1String("line-number-color")) { renderer()->config()->setLineNumberColor(value.value()); } else if (key == QLatin1String("current-line-number-color")) { renderer()->config()->setCurrentLineNumberColor(value.value()); } } else if (value.type() == QVariant::Bool) { // Note explicit type check above. If we used canConvert, then // values of type UInt will be trapped here. if (key == QLatin1String("icon-bar")) { config()->setIconBar(value.toBool()); } else if (key == QLatin1String("line-numbers")) { config()->setLineNumbers(value.toBool()); } else if (key == QLatin1String("dynamic-word-wrap")) { config()->setDynWordWrap(value.toBool()); } else if (key == QLatin1String("allow-mark-menu")) { config()->setAllowMarkMenu(value.toBool()); } else if (key == QLatin1String("folding-bar")) { config()->setFoldingBar(value.toBool()); } else if (key == QLatin1String("folding-preview")) { config()->setFoldingPreview(value.toBool()); } else if (key == QLatin1String("modification-markers")) { config()->setLineModification(value.toBool()); } else if (key == QLatin1String("keyword-completion")) { config()->setKeywordCompletion(value.toBool()); } else if (key == QLatin1String("word-count")) { config()->setShowWordCount(value.toBool()); } else if (key == QLatin1String("scrollbar-minimap")) { config()->setScrollBarMiniMap(value.toBool()); } else if (key == QLatin1String("scrollbar-preview")) { config()->setScrollBarPreview(value.toBool()); } } else if (value.canConvert(QVariant::UInt)) { if (key == QLatin1String("default-mark-type")) { config()->setDefaultMarkType(value.toUInt()); } } else if (value.canConvert(QVariant::Font)) { if (key == QLatin1String("font")) { renderer()->config()->setFont(value.value()); } } } // END ConfigInterface void KTextEditor::ViewPrivate::userInvokedCompletion() { completionWidget()->userInvokedCompletion(); } KateViewBar *KTextEditor::ViewPrivate::bottomViewBar() const { return m_bottomViewBar; } KateGotoBar *KTextEditor::ViewPrivate::gotoBar() { if (!m_gotoBar) { m_gotoBar = new KateGotoBar(this); bottomViewBar()->addBarWidget(m_gotoBar); } return m_gotoBar; } KateDictionaryBar *KTextEditor::ViewPrivate::dictionaryBar() { if (!m_dictionaryBar) { m_dictionaryBar = new KateDictionaryBar(this); bottomViewBar()->addBarWidget(m_dictionaryBar); } return m_dictionaryBar; } void KTextEditor::ViewPrivate::setAnnotationModel(KTextEditor::AnnotationModel *model) { KTextEditor::AnnotationModel *oldmodel = m_annotationModel; m_annotationModel = model; m_viewInternal->m_leftBorder->annotationModelChanged(oldmodel, m_annotationModel); } KTextEditor::AnnotationModel *KTextEditor::ViewPrivate::annotationModel() const { return m_annotationModel; } void KTextEditor::ViewPrivate::setAnnotationBorderVisible(bool visible) { m_viewInternal->m_leftBorder->setAnnotationBorderOn(visible); if ( !visible ) { // make sure the tooltip is hidden QToolTip::hideText(); } } bool KTextEditor::ViewPrivate::isAnnotationBorderVisible() const { return m_viewInternal->m_leftBorder->annotationBorderOn(); } KTextEditor::Range KTextEditor::ViewPrivate::visibleRange() { //ensure that the view is up-to-date, otherwise 'endPos()' might fail! m_viewInternal->updateView(); return KTextEditor::Range(m_viewInternal->toRealCursor(m_viewInternal->startPos()), m_viewInternal->toRealCursor(m_viewInternal->endPos())); } bool KTextEditor::ViewPrivate::event(QEvent *e) { switch (e->type()) { case QEvent::StyleChange: setupLayout(); return true; default: return KTextEditor::View::event(e); } } void KTextEditor::ViewPrivate::paintEvent(QPaintEvent *e) { //base class KTextEditor::View::paintEvent(e); const QRect contentsRect = m_topSpacer->geometry()| m_bottomSpacer->geometry()| m_leftSpacer->geometry()| m_rightSpacer->geometry(); if (contentsRect.isValid()) { QStyleOptionFrame opt; opt.initFrom(this); opt.frameShape = QFrame::StyledPanel; opt.state |= QStyle::State_Sunken; // clear mouseOver and focus state // update from relevant widgets opt.state &= ~(QStyle::State_HasFocus|QStyle::State_MouseOver); const QList widgets = QList() << m_viewInternal << m_viewInternal->m_leftBorder << m_viewInternal->m_lineScroll << m_viewInternal->m_columnScroll; foreach (const QWidget *w, widgets) { if (w->hasFocus()) opt.state |= QStyle::State_HasFocus; if (w->underMouse()) opt.state |= QStyle::State_MouseOver; } // update rect opt.rect=contentsRect; // render QPainter paint(this); paint.setClipRegion(e->region()); paint.setRenderHints(QPainter::Antialiasing); style()->drawControl(QStyle::CE_ShapedFrame, &opt, &paint, this); } } void KTextEditor::ViewPrivate::toggleOnTheFlySpellCheck(bool b) { m_doc->onTheFlySpellCheckingEnabled(b); } void KTextEditor::ViewPrivate::reflectOnTheFlySpellCheckStatus(bool enabled) { m_spellingMenu->setVisible(enabled); m_toggleOnTheFlySpellCheck->setChecked(enabled); } KateSpellingMenu *KTextEditor::ViewPrivate::spellingMenu() { return m_spellingMenu; } void KTextEditor::ViewPrivate::notifyAboutRangeChange(int startLine, int endLine, bool rangeWithAttribute) { #ifdef VIEW_RANGE_DEBUG // output args qCDebug(LOG_KTE) << "trigger attribute changed from" << startLine << "to" << endLine << "rangeWithAttribute" << rangeWithAttribute; #endif // first call: if (!m_delayedUpdateTriggered) { m_delayedUpdateTriggered = true; m_lineToUpdateMin = -1; m_lineToUpdateMax = -1; // only set initial line range, if range with attribute! if (rangeWithAttribute) { m_lineToUpdateMin = startLine; m_lineToUpdateMax = endLine; } // emit queued signal and be done emit delayedUpdateOfView(); return; } // ignore lines if no attribute if (!rangeWithAttribute) { return; } // update line range if (startLine != -1 && (m_lineToUpdateMin == -1 || startLine < m_lineToUpdateMin)) { m_lineToUpdateMin = startLine; } if (endLine != -1 && endLine > m_lineToUpdateMax) { m_lineToUpdateMax = endLine; } } void KTextEditor::ViewPrivate::slotDelayedUpdateOfView() { if (!m_delayedUpdateTriggered) { return; } #ifdef VIEW_RANGE_DEBUG // output args qCDebug(LOG_KTE) << "delayed attribute changed from" << m_lineToUpdateMin << "to" << m_lineToUpdateMax; #endif // update ranges in updateRangesIn(KTextEditor::Attribute::ActivateMouseIn); updateRangesIn(KTextEditor::Attribute::ActivateCaretIn); // update view, if valid line range, else only feedback update wanted anyway if (m_lineToUpdateMin != -1 && m_lineToUpdateMax != -1) { tagLines(m_lineToUpdateMin, m_lineToUpdateMax, true); updateView(true); } // reset flags m_delayedUpdateTriggered = false; m_lineToUpdateMin = -1; m_lineToUpdateMax = -1; } void KTextEditor::ViewPrivate::updateRangesIn(KTextEditor::Attribute::ActivationType activationType) { // new ranges with cursor in, default none QSet newRangesIn; // on which range set we work? QSet &oldSet = (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_rangesMouseIn : m_rangesCaretIn; // which cursor position to honor? KTextEditor::Cursor currentCursor = (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_viewInternal->getMouse() : m_viewInternal->getCursor(); // first: validate the remembered ranges QSet validRanges; foreach (Kate::TextRange *range, oldSet) if (m_doc->buffer().rangePointerValid(range)) { validRanges.insert(range); } // cursor valid? else no new ranges can be found if (currentCursor.isValid() && currentCursor.line() < m_doc->buffer().lines()) { // now: get current ranges for the line of cursor with an attribute QList rangesForCurrentCursor = m_doc->buffer().rangesForLine(currentCursor.line(), this, false); // match which ranges really fit the given cursor foreach (Kate::TextRange *range, rangesForCurrentCursor) { // range has no dynamic attribute of right type and no feedback object if ((!range->attribute() || !range->attribute()->dynamicAttribute(activationType)) && !range->feedback()) { continue; } // range doesn't contain cursor, not interesting if ((range->start().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (currentCursor < range->start().toCursor()) : (currentCursor <= range->start().toCursor())) { continue; } if ((range->end().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (range->end().toCursor() <= currentCursor) : (range->end().toCursor() < currentCursor)) { continue; } // range contains cursor, was it already in old set? if (validRanges.contains(range)) { // insert in new, remove from old, be done with it newRangesIn.insert(range); validRanges.remove(range); continue; } // oh, new range, trigger update and insert into new set newRangesIn.insert(range); if (range->attribute() && range->attribute()->dynamicAttribute(activationType)) { notifyAboutRangeChange(range->start().line(), range->end().line(), true); } // feedback if (range->feedback()) { if (activationType == KTextEditor::Attribute::ActivateMouseIn) { range->feedback()->mouseEnteredRange(range, this); } else { range->feedback()->caretEnteredRange(range, this); } } #ifdef VIEW_RANGE_DEBUG // found new range for activation qCDebug(LOG_KTE) << "activated new range" << range << "by" << activationType; #endif } } // now: notify for left ranges! foreach (Kate::TextRange *range, validRanges) { // range valid + right dynamic attribute, trigger update if (range->toRange().isValid() && range->attribute() && range->attribute()->dynamicAttribute(activationType)) { notifyAboutRangeChange(range->start().line(), range->end().line(), true); } // feedback if (range->feedback()) { if (activationType == KTextEditor::Attribute::ActivateMouseIn) { range->feedback()->mouseExitedRange(range, this); } else { range->feedback()->caretExitedRange(range, this); } } } // set new ranges oldSet = newRangesIn; } void KTextEditor::ViewPrivate::postMessage(KTextEditor::Message *message, QList > actions) { // just forward to KateMessageWidget :-) - if (message->position() == KTextEditor::Message::AboveView) { - m_topMessageWidget->postMessage(message, actions); - } else if (message->position() == KTextEditor::Message::BelowView) { - m_bottomMessageWidget->postMessage(message, actions); - } else if (message->position() == KTextEditor::Message::TopInView) { - if (!m_floatTopMessageWidget) { - m_floatTopMessageWidget = new KateMessageWidget(m_viewInternal, true); - m_notificationLayout->insertWidget(0, m_floatTopMessageWidget, 0, Qt::Alignment(Qt::AlignTop | Qt::AlignRight)); - connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), m_floatTopMessageWidget, SLOT(startAutoHideTimer())); - connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), m_floatTopMessageWidget, SLOT(startAutoHideTimer())); - } - m_floatTopMessageWidget->postMessage(message, actions); - } else if (message->position() == KTextEditor::Message::BottomInView) { - if (!m_floatBottomMessageWidget) { - m_floatBottomMessageWidget = new KateMessageWidget(m_viewInternal, true); - m_notificationLayout->addWidget(m_floatBottomMessageWidget, 0, Qt::Alignment(Qt::AlignBottom | Qt::AlignRight)); - connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), m_floatBottomMessageWidget, SLOT(startAutoHideTimer())); - connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), m_floatBottomMessageWidget, SLOT(startAutoHideTimer())); - } - m_floatBottomMessageWidget->postMessage(message, actions); + auto messageWidget = m_messageWidgets[message->position()]; + if (!messageWidget) { + // this branch is used for: TopInView, CenterInView, and BottomInView + messageWidget = new KateMessageWidget(m_viewInternal, true); + m_messageWidgets[message->position()] = messageWidget; + m_notificationLayout->addWidget(messageWidget, message->position()); + connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), messageWidget, SLOT(startAutoHideTimer())); + connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), messageWidget, SLOT(startAutoHideTimer())); } + messageWidget->postMessage(message, actions); +} + +KateMessageWidget *KTextEditor::ViewPrivate::messageWidget() +{ + return m_messageWidgets[KTextEditor::Message::TopInView]; } void KTextEditor::ViewPrivate::saveFoldingState() { m_savedFoldingState = m_textFolding.exportFoldingRanges(); } void KTextEditor::ViewPrivate::applyFoldingState() { m_textFolding.importFoldingRanges(m_savedFoldingState); m_savedFoldingState = QJsonDocument(); } void KTextEditor::ViewPrivate::exportHtmlToFile(const QString &file) { KateExporter(this).exportToFile(file); } void KTextEditor::ViewPrivate::exportHtmlToClipboard () { KateExporter(this).exportToClipboard(); } void KTextEditor::ViewPrivate::exportHtmlToFile () { const QString file = QFileDialog::getSaveFileName(this, i18n("Export File as HTML"), m_doc->documentName()); if (!file.isEmpty()) { KateExporter(this).exportToFile(file); } } void KTextEditor::ViewPrivate::clearHighlights() { qDeleteAll(m_rangesForHighlights); m_rangesForHighlights.clear(); m_currentTextForHighlights.clear(); } void KTextEditor::ViewPrivate::selectionChangedForHighlights() { QString text; // if text of selection is still the same, abort if (selection() && selectionRange().onSingleLine()) { text = selectionText(); if (text == m_currentTextForHighlights) return; } // text changed: remove all highlights + create new ones // (do not call clearHighlights(), since this also resets the m_currentTextForHighlights qDeleteAll(m_rangesForHighlights); m_rangesForHighlights.clear(); // do not highlight strings with leading and trailing spaces if (!text.isEmpty() && (text.at(0).isSpace() || text.at(text.length()-1).isSpace())) return; // trigger creation of ranges for current view range m_currentTextForHighlights = text; createHighlights(); } void KTextEditor::ViewPrivate::createHighlights() { // do nothing if no text to highlight if (m_currentTextForHighlights.isEmpty()) { return; } KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); attr->setBackground(Qt::yellow); // set correct highlight color from Kate's color schema QColor fgColor = defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); QColor bgColor = renderer()->config()->searchHighlightColor(); attr->setForeground(fgColor); attr->setBackground(bgColor); KTextEditor::Cursor start(visibleRange().start()); KTextEditor::Range searchRange; /** * only add word boundary if we can find the text then * fixes $lala hl */ QString regex = QRegExp::escape (m_currentTextForHighlights); if (QRegExp (QStringLiteral("\\b%1").arg(regex)).indexIn (QStringLiteral(" %1 ").arg(m_currentTextForHighlights)) != -1) regex = QStringLiteral("\\b%1").arg(regex); if (QRegExp (QStringLiteral("%1\\b").arg(regex)).indexIn (QStringLiteral(" %1 ").arg(m_currentTextForHighlights)) != -1) regex = QStringLiteral("%1\\b").arg(regex); QVector matches; do { searchRange.setRange(start, visibleRange().end()); matches = m_doc->searchText(searchRange, regex, KTextEditor::Regex); if (matches.first().isValid()) { KTextEditor::MovingRange* mr = m_doc->newMovingRange(matches.first()); mr->setAttribute(attr); mr->setView(this); mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection mr->setAttributeOnlyForViews(true); m_rangesForHighlights.append(mr); start = matches.first().end(); } } while (matches.first().isValid()); } KateAbstractInputMode *KTextEditor::ViewPrivate::currentInputMode() const { return m_viewInternal->m_currentInputMode; } void KTextEditor::ViewPrivate::toggleInputMode() { if (QAction *a = dynamic_cast(sender())) { setInputMode(static_cast(a->data().toInt())); } } void KTextEditor::ViewPrivate::cycleInputMode() { InputMode current = currentInputMode()->viewInputMode(); InputMode to = (current == KTextEditor::View::NormalInputMode) ? KTextEditor::View::ViInputMode : KTextEditor::View::NormalInputMode; setInputMode(to); } //BEGIN KTextEditor::PrintInterface stuff bool KTextEditor::ViewPrivate::print() { return KatePrinter::print(this); } void KTextEditor::ViewPrivate::printPreview() { KatePrinter::printPreview(this); } //END KTextEditor::Attribute::Ptr KTextEditor::ViewPrivate::defaultStyleAttribute(KTextEditor::DefaultStyle defaultStyle) const { KateRendererConfig * renderConfig = const_cast(this)->renderer()->config(); KTextEditor::Attribute::Ptr style = m_doc->highlight()->attributes(renderConfig->schema()).at(defaultStyle); if (!style->hasProperty(QTextFormat::BackgroundBrush)) { // make sure the returned style has the default background color set style = new KTextEditor::Attribute(*style); style->setBackground(QBrush(renderConfig->backgroundColor())); } return style; } QList KTextEditor::ViewPrivate::lineAttributes(int line) { QList attribs; if (line < 0 || line >= m_doc->lines()) return attribs; Kate::TextLine kateLine = m_doc->kateTextLine(line); if (!kateLine) { return attribs; } const QVector &intAttrs = kateLine->attributesList(); for (int i = 0; i < intAttrs.size(); ++i) { if (intAttrs[i].length > 0 && intAttrs[i].attributeValue > 0) { attribs << KTextEditor::AttributeBlock( intAttrs.at(i).offset, intAttrs.at(i).length, renderer()->attribute(intAttrs.at(i).attributeValue) ); } } return attribs; } diff --git a/src/view/kateview.h b/src/view/kateview.h index e0a83038..e0c172c4 100644 --- a/src/view/kateview.h +++ b/src/view/kateview.h @@ -1,1002 +1,995 @@ /* This file is part of the KDE libraries Copyright (C) 2002 John Firebaugh Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001-2010 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef kate_view_h #define kate_view_h #include #include #include #include #include #include #include #include #include #include #include #include #include "katetextrange.h" #include "katetextfolding.h" #include "katerenderer.h" namespace KTextEditor { class AnnotationModel; class Message; } namespace KTextEditor { class DocumentPrivate; } class KateBookmarks; class KateViewConfig; class KateRenderer; class KateSpellCheckDialog; class KateCompletionWidget; class KateViewInternal; class KateViewBar; class KateTextPreview; class KateGotoBar; class KateDictionaryBar; class KateSpellingMenu; class KateMessageWidget; class KateIconBorder; class KateStatusBar; class KateViewEncodingAction; class KateModeMenu; class KateAbstractInputMode; class KateScriptActionMenu; +class KateMessageLayout; class KToggleAction; class KSelectAction; class QAction; -class QGridLayout; -class QVBoxLayout; namespace KTextEditor { // // Kate KTextEditor::View class ;) // class KTEXTEDITOR_EXPORT ViewPrivate : public KTextEditor::View, public KTextEditor::TextHintInterface, public KTextEditor::CodeCompletionInterface, public KTextEditor::ConfigInterface, public KTextEditor::AnnotationViewInterface { Q_OBJECT Q_INTERFACES(KTextEditor::TextHintInterface) Q_INTERFACES(KTextEditor::ConfigInterface) Q_INTERFACES(KTextEditor::CodeCompletionInterface) Q_INTERFACES(KTextEditor::AnnotationViewInterface) friend class KTextEditor::View; friend class ::KateViewInternal; friend class ::KateIconBorder; friend class ::KateTextPreview; public: ViewPrivate (KTextEditor::DocumentPrivate *doc, QWidget *parent, KTextEditor::MainWindow *mainWindow = nullptr); ~ViewPrivate (); /** * Get the view's main window, if any * \return the view's main window */ KTextEditor::MainWindow *mainWindow() const Q_DECL_OVERRIDE { return m_mainWindow; } KTextEditor::Document *document() const Q_DECL_OVERRIDE; ViewMode viewMode() const Q_DECL_OVERRIDE; QString viewModeHuman() const Q_DECL_OVERRIDE; InputMode viewInputMode() const Q_DECL_OVERRIDE; QString viewInputModeHuman() const Q_DECL_OVERRIDE; void setInputMode(InputMode mode); // // KTextEditor::ClipboardInterface // public Q_SLOTS: void paste(const QString *textToPaste = nullptr); void cut(); void copy() const; private Q_SLOTS: /** * internal use, apply word wrap */ void applyWordWrap(); // // KTextEditor::PopupMenuInterface // public: void setContextMenu(QMenu *menu) Q_DECL_OVERRIDE; QMenu *contextMenu() const Q_DECL_OVERRIDE; QMenu *defaultContextMenu(QMenu *menu = nullptr) const Q_DECL_OVERRIDE; private Q_SLOTS: void aboutToShowContextMenu(); void aboutToHideContextMenu(); private: QPointer m_contextMenu; // // KTextEditor::ViewCursorInterface // public: bool setCursorPosition(KTextEditor::Cursor position) Q_DECL_OVERRIDE; KTextEditor::Cursor cursorPosition() const Q_DECL_OVERRIDE; KTextEditor::Cursor cursorPositionVirtual() const Q_DECL_OVERRIDE; QPoint cursorToCoordinate(const KTextEditor::Cursor &cursor) const Q_DECL_OVERRIDE; KTextEditor::Cursor coordinatesToCursor(const QPoint &coord) const Q_DECL_OVERRIDE; QPoint cursorPositionCoordinates() const Q_DECL_OVERRIDE; bool setCursorPositionVisual(const KTextEditor::Cursor &position); /** * Return the virtual cursor column, each tab is expanded into the * document's tabWidth characters. If word wrap is off, the cursor may be * behind the line's length. */ int virtualCursorColumn() const; bool mouseTrackingEnabled() const Q_DECL_OVERRIDE; bool setMouseTrackingEnabled(bool enable) Q_DECL_OVERRIDE; private: void notifyMousePositionChanged(const KTextEditor::Cursor &newPosition); // Internal public: bool setCursorPositionInternal(const KTextEditor::Cursor &position, uint tabwidth = 1, bool calledExternally = false); // // KTextEditor::ConfigInterface // public: QStringList configKeys() const Q_DECL_OVERRIDE; QVariant configValue(const QString &key) Q_DECL_OVERRIDE; void setConfigValue(const QString &key, const QVariant &value) Q_DECL_OVERRIDE; Q_SIGNALS: void configChanged(); public: /** * Try to fold starting at the given line. * This will both try to fold existing folding ranges of this line and to query the highlighting what to fold. * @param startLine start line to fold at */ void foldLine(int startLine); /** * Try to unfold all foldings starting at the given line. * @param startLine start line to unfold at */ void unfoldLine(int startLine); // // KTextEditor::CodeCompletionInterface2 // public: bool isCompletionActive() const Q_DECL_OVERRIDE; void startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model) Q_DECL_OVERRIDE; void abortCompletion() Q_DECL_OVERRIDE; void forceCompletion() Q_DECL_OVERRIDE; void registerCompletionModel(KTextEditor::CodeCompletionModel *model) Q_DECL_OVERRIDE; void unregisterCompletionModel(KTextEditor::CodeCompletionModel *model) Q_DECL_OVERRIDE; bool isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const; bool isAutomaticInvocationEnabled() const Q_DECL_OVERRIDE; void setAutomaticInvocationEnabled(bool enabled = true) Q_DECL_OVERRIDE; Q_SIGNALS: void completionExecuted(KTextEditor::View *view, const KTextEditor::Cursor &position, KTextEditor::CodeCompletionModel *model, const QModelIndex &); void completionAborted(KTextEditor::View *view); public Q_SLOTS: void userInvokedCompletion(); public: KateCompletionWidget *completionWidget() const; mutable KateCompletionWidget *m_completionWidget; void sendCompletionExecuted(const KTextEditor::Cursor &position, KTextEditor::CodeCompletionModel *model, const QModelIndex &index); void sendCompletionAborted(); // // KTextEditor::TextHintInterface // public: void registerTextHintProvider(KTextEditor::TextHintProvider *provider) Q_DECL_OVERRIDE; void unregisterTextHintProvider(KTextEditor::TextHintProvider *provider) Q_DECL_OVERRIDE; void setTextHintDelay(int delay) Q_DECL_OVERRIDE; int textHintDelay() const Q_DECL_OVERRIDE; public: bool dynWordWrap() const { return m_hasWrap; } // // KTextEditor::SelectionInterface stuff // public Q_SLOTS: bool setSelection(const KTextEditor::Range &selection) Q_DECL_OVERRIDE; bool removeSelection() Q_DECL_OVERRIDE { return clearSelection(); } bool removeSelectionText() Q_DECL_OVERRIDE { return removeSelectedText(); } bool setBlockSelection(bool on) Q_DECL_OVERRIDE; bool toggleBlockSelection(); bool clearSelection(); bool clearSelection(bool redraw, bool finishedChangingSelection = true); bool removeSelectedText(); bool selectAll(); public: bool selection() const Q_DECL_OVERRIDE; QString selectionText() const Q_DECL_OVERRIDE; bool blockSelection() const Q_DECL_OVERRIDE; KTextEditor::Range selectionRange() const Q_DECL_OVERRIDE; static void blockFix(KTextEditor::Range &range); // // Arbitrary Syntax HL + Action extensions // public: // Action association extension void deactivateEditActions(); void activateEditActions(); // // internal helper stuff, for katerenderer and so on // public: // should cursor be wrapped ? take config + blockselection state in account bool wrapCursor() const; // some internal functions to get selection state of a line/col bool cursorSelected(const KTextEditor::Cursor &cursor); bool lineSelected(int line); bool lineEndSelected(const KTextEditor::Cursor &lineEndPos); bool lineHasSelected(int line); bool lineIsSelection(int line); void ensureCursorColumnValid(); void tagSelection(const KTextEditor::Range &oldSelection); void selectWord(const KTextEditor::Cursor &cursor); void selectLine(const KTextEditor::Cursor &cursor); //BEGIN EDIT STUFF public: void editStart(); void editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom); void editSetCursor(const KTextEditor::Cursor &cursor); //END //BEGIN TAG & CLEAR public: bool tagLine(const KTextEditor::Cursor &virtualCursor); bool tagRange(const KTextEditor::Range &range, bool realLines = false); bool tagLines(int start, int end, bool realLines = false); bool tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors = false); bool tagLines(KTextEditor::Range range, bool realRange = false); void tagAll(); void clear(); void repaintText(bool paintOnlyDirty = false); void updateView(bool changed = false); //END // // KTextEditor::AnnotationView // public: void setAnnotationModel(KTextEditor::AnnotationModel *model) Q_DECL_OVERRIDE; KTextEditor::AnnotationModel *annotationModel() const Q_DECL_OVERRIDE; void setAnnotationBorderVisible(bool visible) Q_DECL_OVERRIDE; bool isAnnotationBorderVisible() const Q_DECL_OVERRIDE; Q_SIGNALS: void annotationContextMenuAboutToShow(KTextEditor::View *view, QMenu *menu, int line) Q_DECL_OVERRIDE; void annotationActivated(KTextEditor::View *view, int line) Q_DECL_OVERRIDE; // KF6: fix View -> KTextEditor::View void annotationBorderVisibilityChanged(View *view, bool visible) Q_DECL_OVERRIDE; void navigateLeft(); void navigateRight(); void navigateUp(); void navigateDown(); void navigateAccept(); void navigateBack(); private: KTextEditor::AnnotationModel *m_annotationModel; // // KTextEditor::View // public: void emitNavigateLeft() { emit navigateLeft(); } void emitNavigateRight() { emit navigateRight(); } void emitNavigateUp() { emit navigateUp(); } void emitNavigateDown() { emit navigateDown(); } void emitNavigateAccept() { emit navigateAccept(); } void emitNavigateBack() { emit navigateBack(); } /** Return values for "save" related commands. */ bool isOverwriteMode() const; QString currentTextLine(); QTextLayout * textLayout(int line) const; QTextLayout * textLayout(const KTextEditor::Cursor &pos) const; public Q_SLOTS: void indent(); void unIndent(); void cleanIndent(); void align(); void comment(); void uncomment(); void toggleComment(); void killLine(); /** * Sets the cursor to the previous editing position in this document */ void goToPreviousEditingPosition(); /** * Sets the cursor to the next editing position in this document */ void goToNextEditingPosition(); /** Uppercases selected text, or an alphabetic character next to the cursor. */ void uppercase(); /** Lowercases selected text, or an alphabetic character next to the cursor. */ void lowercase(); /** Capitalizes the selection (makes each word start with an uppercase) or the word under the cursor. */ void capitalize(); /** Joins lines touched by the selection */ void joinLines(); // Note - the following functions simply forward to KateViewInternal void keyReturn(); void smartNewline(); void backspace(); void deleteWordLeft(); void keyDelete(); void deleteWordRight(); void transpose(); void cursorLeft(); void shiftCursorLeft(); void cursorRight(); void shiftCursorRight(); void wordLeft(); void shiftWordLeft(); void wordRight(); void shiftWordRight(); void home(); void shiftHome(); void end(); void shiftEnd(); void up(); void shiftUp(); void down(); void shiftDown(); void scrollUp(); void scrollDown(); void topOfView(); void shiftTopOfView(); void bottomOfView(); void shiftBottomOfView(); void pageUp(); void shiftPageUp(); void pageDown(); void shiftPageDown(); void top(); void shiftTop(); void bottom(); void shiftBottom(); void toMatchingBracket(); void shiftToMatchingBracket(); void toPrevModifiedLine(); void toNextModifiedLine(); void insertTab(); void gotoLine(); // config file / session management functions public: /** * Read session settings from the given \p config. * * Known flags: * "SkipUrl" => don't save/restore the file * "SkipMode" => don't save/restore the mode * "SkipHighlighting" => don't save/restore the highlighting * "SkipEncoding" => don't save/restore the encoding * * \param config read the session settings from this KConfigGroup * \param flags additional flags * \see writeSessionConfig() */ void readSessionConfig(const KConfigGroup &config, const QSet &flags = QSet()) Q_DECL_OVERRIDE; /** * Write session settings to the \p config. * See readSessionConfig() for more details. * * \param config write the session settings to this KConfigGroup * \param flags additional flags * \see readSessionConfig() */ void writeSessionConfig(KConfigGroup &config, const QSet &flags = QSet()) Q_DECL_OVERRIDE; public Q_SLOTS: void setEol(int eol); void setAddBom(bool enabled); void find(); void findSelectedForwards(); void findSelectedBackwards(); void replace(); void findNext(); void findPrevious(); void setFoldingMarkersOn(bool enable); // Not in KTextEditor::View, but should be void setIconBorder(bool enable); void setLineNumbersOn(bool enable); void setScrollBarMarks(bool enable); void setScrollBarMiniMap(bool enable); void setScrollBarMiniMapAll(bool enable); void setScrollBarMiniMapWidth(int width); void toggleFoldingMarkers(); void toggleIconBorder(); void toggleLineNumbersOn(); void toggleScrollBarMarks(); void toggleScrollBarMiniMap(); void toggleScrollBarMiniMapAll(); void toggleDynWordWrap(); void setDynWrapIndicators(int mode); public: int getEol() const; public: KateRenderer *renderer(); bool iconBorder(); bool lineNumbersOn(); bool scrollBarMarks(); bool scrollBarMiniMap(); bool scrollBarMiniMapAll(); int dynWrapIndicators(); bool foldingMarkersOn(); private Q_SLOTS: /** * used to update actions after selection changed */ void slotSelectionChanged(); void toggleInputMode(); void cycleInputMode(); public: /** * accessor to katedocument pointer * @return pointer to document */ KTextEditor::DocumentPrivate *doc() { return m_doc; } const KTextEditor::DocumentPrivate *doc() const { return m_doc; } public Q_SLOTS: void slotUpdateUndo(); void toggleInsert(); void reloadFile(); void toggleWWMarker(); void toggleNPSpaces(); void toggleWordCount(bool on); void toggleWriteLock(); void switchToCmdLine(); void slotReadWriteChanged(); void slotClipboardHistoryChanged(); Q_SIGNALS: void dropEventPass(QDropEvent *); public: /** * Folding handler for this view. * @return folding handler */ Kate::TextFolding &textFolding() { return m_textFolding; } public: void slotTextInserted(KTextEditor::View *view, const KTextEditor::Cursor &position, const QString &text); void exportHtmlToFile(const QString &file); private Q_SLOTS: void slotGotFocus(); void slotLostFocus(); void slotSaveCanceled(const QString &error); void slotConfigDialog(); void exportHtmlToClipboard (); void exportHtmlToFile (); public Q_SLOTS: void slotFoldToplevelNodes(); void slotExpandToplevelNodes(); void slotCollapseLocal(); void slotExpandLocal(); private: void setupLayout(); void setupConnections(); void setupActions(); void setupEditActions(); void setupCodeFolding(); QList m_editActions; QAction *m_editUndo; QAction *m_editRedo; QAction *m_pasteMenu; KToggleAction *m_toggleFoldingMarkers; KToggleAction *m_toggleIconBar; KToggleAction *m_toggleLineNumbers; KToggleAction *m_toggleScrollBarMarks; KToggleAction *m_toggleScrollBarMiniMap; KToggleAction *m_toggleScrollBarMiniMapAll; KToggleAction *m_toggleDynWrap; KSelectAction *m_setDynWrapIndicators; KToggleAction *m_toggleWWMarker; KToggleAction *m_toggleNPSpaces; KToggleAction *m_toggleWordCount; QAction *m_switchCmdLine; KToggleAction *m_viInputModeAction; KSelectAction *m_setEndOfLine; KToggleAction *m_addBom; QAction *m_cut; QAction *m_copy; QAction *m_copyHtmlAction; QAction *m_paste; QAction *m_selectAll; QAction *m_deSelect; QActionGroup *m_inputModeActions; KToggleAction *m_toggleBlockSelection; KToggleAction *m_toggleInsert; KToggleAction *m_toggleWriteLock; bool m_hasWrap; KTextEditor::DocumentPrivate *const m_doc; Kate::TextFolding m_textFolding; KateViewConfig *const m_config; KateRenderer *const m_renderer; KateViewInternal *const m_viewInternal; KateSpellCheckDialog *m_spell; KateBookmarks *const m_bookmarks; //* margins QSpacerItem *m_topSpacer; QSpacerItem *m_leftSpacer; QSpacerItem *m_rightSpacer; QSpacerItem *m_bottomSpacer; private Q_SLOTS: void slotHlChanged(); /** * Configuration */ public: inline KateViewConfig *config() { return m_config; } void updateConfig(); void updateDocumentConfig(); void updateRendererConfig(); private Q_SLOTS: void updateFoldingConfig(); private: bool m_startingUp; bool m_updatingDocumentConfig; // stores the current selection Kate::TextRange m_selection; // do we select normal or blockwise ? bool blockSelect; // templates public: bool insertTemplateInternal(const KTextEditor::Cursor& insertPosition, const QString& templateString, const QString& script = QString()); /** * Accessors to the bars... */ public: KateViewBar *bottomViewBar() const; KateDictionaryBar *dictionaryBar(); private: KateGotoBar *gotoBar(); /** * viewbar + its widgets * they are created on demand... */ private: // created in constructor of the view KateViewBar *m_bottomViewBar; // created on demand..., only access them through the above accessors.... KateGotoBar *m_gotoBar; KateDictionaryBar *m_dictionaryBar; // input modes public: KateAbstractInputMode *currentInputMode() const; public: KTextEditor::Range visibleRange(); Q_SIGNALS: void displayRangeChanged(KTextEditor::ViewPrivate *view); protected: bool event(QEvent *e) Q_DECL_OVERRIDE; void paintEvent(QPaintEvent *e) Q_DECL_OVERRIDE; KToggleAction *m_toggleOnTheFlySpellCheck; KateSpellingMenu *m_spellingMenu; protected Q_SLOTS: void toggleOnTheFlySpellCheck(bool b); public Q_SLOTS: void changeDictionary(); void reflectOnTheFlySpellCheckStatus(bool enabled); public: KateSpellingMenu *spellingMenu(); private: bool m_userContextMenuSet; private Q_SLOTS: /** * save folding state before document reload */ void saveFoldingState(); /** * restore folding state after document reload */ void applyFoldingState(); void clearHighlights(); void createHighlights(); private: void selectionChangedForHighlights(); /** * saved folding state */ QJsonDocument m_savedFoldingState; QString m_currentTextForHighlights; QList m_rangesForHighlights; public: /** * Attribute of a range changed or range with attribute changed in given line range. * @param startLine start line of change * @param endLine end line of change * @param rangeWithAttribute attribute changed or is active, this will perhaps lead to repaints */ void notifyAboutRangeChange(int startLine, int endLine, bool rangeWithAttribute); private Q_SLOTS: /** * Delayed update for view after text ranges changed */ void slotDelayedUpdateOfView(); Q_SIGNALS: /** * Delayed update for view after text ranges changed */ void delayedUpdateOfView(); public: /** * set of ranges which had the mouse inside last time, used for rendering * @return set of ranges which had the mouse inside last time checked */ const QSet *rangesMouseIn() const { return &m_rangesMouseIn; } /** * set of ranges which had the caret inside last time, used for rendering * @return set of ranges which had the caret inside last time checked */ const QSet *rangesCaretIn() const { return &m_rangesCaretIn; } /** * check if ranges changed for mouse in and caret in * @param activationType type of activation to check */ void updateRangesIn(KTextEditor::Attribute::ActivationType activationType); // // helpers for delayed view update after ranges changes // private: /** * update already inited? */ bool m_delayedUpdateTriggered; /** * minimal line to update */ int m_lineToUpdateMin; /** * maximal line to update */ int m_lineToUpdateMax; /** * set of ranges which had the mouse inside last time */ QSet m_rangesMouseIn; /** * set of ranges which had the caret inside last time */ QSet m_rangesCaretIn; // // forward impl for KTextEditor::MessageInterface // public: /** * Used by Document::postMessage(). */ void postMessage(KTextEditor::Message *message, QList > actions); private: - /** Message widget showing KTextEditor::Messages above the View. */ - KateMessageWidget *m_topMessageWidget; - /** Message widget showing KTextEditor::Messages below the View. */ - KateMessageWidget *m_bottomMessageWidget; - /** Message widget showing KTextEditor::Messages as view overlay in top right corner. */ - KateMessageWidget *m_floatTopMessageWidget; - /** Message widget showing KTextEditor::Messages as view overlay in bottom left corner. */ - KateMessageWidget *m_floatBottomMessageWidget; + /** + * Message widgets showing KTextEditor::Messages. + * The index of the array maps to the enum KTextEditor::Message::MessagePosition. + */ + std::array m_messageWidgets{{nullptr}}; /** Layout for floating notifications */ - QVBoxLayout *m_notificationLayout; + KateMessageLayout *m_notificationLayout = nullptr; // for unit test 'tests/messagetest.cpp' public: - KateMessageWidget *messageWidget() - { - return m_floatTopMessageWidget; - } + KateMessageWidget *messageWidget(); private: /** * The main window responsible for this view, if any */ QPointer m_mainWindow; // // KTextEditor::PrintInterface // public Q_SLOTS: bool print() Q_DECL_OVERRIDE; void printPreview() Q_DECL_OVERRIDE; public: /** * Get the view status bar * @return status bar, in enabled */ KateStatusBar *statusBar () const { return m_statusBar; } /** * Toogle status bar, e.g. create or remove it */ void toggleStatusBar (); /** * Get the encoding menu * @return the encoding menu */ KateViewEncodingAction *encodingAction () const { return m_encodingAction; } /** * Get the mode menu * @return the mode menu */ KateModeMenu *modeAction () const { return m_modeAction; } private: /** * the status bar of this view */ KateStatusBar *m_statusBar; /** * the encoding selection menu, used by view + status bar */ KateViewEncodingAction *m_encodingAction; /** * the mode selection menu, used by view + status bar */ KateModeMenu *m_modeAction; /** * is automatic invocation of completion disabled temporarily? */ bool m_temporaryAutomaticInvocationDisabled; public: /** * Returns the attribute for the default style \p defaultStyle. */ Attribute::Ptr defaultStyleAttribute(DefaultStyle defaultStyle) const Q_DECL_OVERRIDE; /** * Get the list of AttributeBlocks for a given \p line in the document. * * \return list of AttributeBlocks for given \p line. */ QList lineAttributes(int line) Q_DECL_OVERRIDE; private: // remember folding state to prevent refolding the first line if it was manually unfolded, // e.g. when saving a file or changing other config vars bool m_autoFoldedFirstLine; public: void setScrollPositionInternal(KTextEditor::Cursor &cursor); void setHorizontalScrollPositionInternal(int x); KTextEditor::Cursor maxScrollPositionInternal() const; int firstDisplayedLineInternal(LineType lineType) const; int lastDisplayedLineInternal(LineType lineType) const; QRect textAreaRectInternal() const; private: /** * script action menu, stored in scoped pointer to ensure * destruction before other QObject auto-cleanup as it * manage sub objects on its own that have this view as parent */ QScopedPointer m_scriptActionMenu; }; } #endif diff --git a/src/view/kateviewhelpers.cpp b/src/view/kateviewhelpers.cpp index 34c411ba..4121259e 100644 --- a/src/view/kateviewhelpers.cpp +++ b/src/view/kateviewhelpers.cpp @@ -1,2803 +1,2885 @@ /* This file is part of the KDE libraries Copyright (C) 2008, 2009 Matthew Woehlke Copyright (C) 2007 Mirko Stocker Copyright (C) 2002 John Firebaugh Copyright (C) 2001 Anders Lund Copyright (C) 2001 Christoph Cullmann Copyright (C) 2011 Svyatoslav Kuzmich Copyright (C) 2012 Kåre Särs (Minimap) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kateviewhelpers.h" #include "katecmd.h" #include #include #include #include "kateconfig.h" #include "katedocument.h" #include #include "katerenderer.h" #include "kateview.h" #include "kateviewinternal.h" #include "katelayoutcache.h" #include "katetextlayout.h" #include "kateglobal.h" #include "katepartdebug.h" #include "katecommandrangeexpressionparser.h" #include "kateabstractinputmode.h" #include "katetextpreview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +//BEGIN KateMessageLayout +KateMessageLayout::KateMessageLayout(QWidget *parent) + : QLayout(parent) +{ +} + +KateMessageLayout::~KateMessageLayout() +{ + while (QLayoutItem *item = takeAt(0)) + delete item; +} + +void KateMessageLayout::addItem(QLayoutItem *item) +{ + Q_ASSERT(false); + add(item, KTextEditor::Message::CenterInView); +} + +void KateMessageLayout::addWidget(QWidget *widget, KTextEditor::Message::MessagePosition pos) +{ + add(new QWidgetItem(widget), pos); +} + +int KateMessageLayout::count() const +{ + return m_items.size(); +} + +QLayoutItem *KateMessageLayout::itemAt(int index) const +{ + if (index < 0 || index >= m_items.size()) + return 0; + + return m_items[index]->item; +} + +void KateMessageLayout::setGeometry(const QRect &rect) +{ + QLayout::setGeometry(rect); + const int s = spacing(); + const QRect adjustedRect = rect.adjusted(s, s, -s, -s); + + for (auto wrapper : m_items) { + QLayoutItem *item = wrapper->item; + auto position = wrapper->position; + + if (position == KTextEditor::Message::TopInView) { + const QRect r(adjustedRect.width() - item->sizeHint().width(), s, item->sizeHint().width(), item->sizeHint().height()); + item->setGeometry(r); + } else if (position == KTextEditor::Message::BottomInView) { + const QRect r(adjustedRect.width() - item->sizeHint().width(), adjustedRect.height() - item->sizeHint().height(), item->sizeHint().width(), item->sizeHint().height()); + item->setGeometry(r); + } else if (position == KTextEditor::Message::CenterInView) { + QRect r(0, 0, item->sizeHint().width(), item->sizeHint().height()); + r.moveCenter(adjustedRect.center()); + item->setGeometry(r); + } else { + Q_ASSERT_X(false, "setGeometry", "Only TopInView, CenterInView, and BottomInView are supported."); + } + } +} + +QSize KateMessageLayout::sizeHint() const +{ + return QSize(); +} + +QLayoutItem *KateMessageLayout::takeAt(int index) +{ + if (index >= 0 && index < m_items.size()) { + ItemWrapper *layoutStruct = m_items.takeAt(index); + return layoutStruct->item; + } + return 0; +} + +void KateMessageLayout::add(QLayoutItem *item, KTextEditor::Message::MessagePosition pos) +{ + m_items.push_back(new ItemWrapper(item, pos)); +} +//END KateMessageLayout + //BEGIN KateScrollBar static const int s_lineWidth = 100; static const int s_pixelMargin = 8; static const int s_linePixelIncLimit = 6; const unsigned char KateScrollBar::characterOpacity[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 15 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 0, 0, 0, 0, // <- 31 0, 125, 41, 221, 138, 195, 218, 21, 142, 142, 137, 137, 97, 87, 87, 140, // <- 47 223, 164, 183, 190, 191, 193, 214, 158, 227, 216, 103, 113, 146, 140, 146, 149, // <- 63 248, 204, 240, 174, 217, 197, 178, 205, 209, 176, 168, 211, 160, 246, 238, 218, // <- 79 195, 229, 227, 196, 167, 212, 188, 238, 197, 169, 189, 158, 21, 151, 115, 90, // <- 95 15, 192, 209, 153, 208, 187, 162, 221, 183, 149, 161, 191, 146, 203, 167, 182, // <- 111 208, 203, 139, 166, 158, 167, 157, 189, 164, 179, 156, 167, 145, 166, 109, 0, // <- 127 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 143 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 159 0, 125, 184, 187, 146, 201, 127, 203, 89, 194, 156, 141, 117, 87, 202, 88, // <- 175 115, 165, 118, 121, 85, 190, 236, 87, 88, 111, 151, 140, 194, 191, 203, 148, // <- 191 215, 215, 222, 224, 223, 234, 230, 192, 208, 208, 216, 217, 187, 187, 194, 195, // <- 207 228, 255, 228, 228, 235, 239, 237, 150, 255, 222, 222, 229, 232, 180, 197, 225, // <- 223 208, 208, 216, 217, 212, 230, 218, 170, 202, 202, 211, 204, 156, 156, 165, 159, // <- 239 214, 194, 197, 197, 206, 206, 201, 132, 214, 183, 183, 192, 187, 195, 227, 198 }; KateScrollBar::KateScrollBar(Qt::Orientation orientation, KateViewInternal *parent) : QScrollBar(orientation, parent->m_view) , m_middleMouseDown(false) , m_leftMouseDown(false) , m_view(parent->m_view) , m_doc(parent->doc()) , m_viewInternal(parent) , m_textPreview(nullptr) , m_showMarks(false) , m_showMiniMap(false) , m_miniMapAll(true) , m_miniMapWidth(40) , m_grooveHeight(height()) , m_linesModified(0) { connect(this, SIGNAL(valueChanged(int)), this, SLOT(sliderMaybeMoved(int))); connect(m_doc, SIGNAL(marksChanged(KTextEditor::Document*)), this, SLOT(marksChanged())); m_updateTimer.setInterval(300); m_updateTimer.setSingleShot(true); QTimer::singleShot(10, this, SLOT(updatePixmap())); // track mouse for text preview widget setMouseTracking(orientation == Qt::Vertical); // setup text preview timer m_delayTextPreviewTimer.setSingleShot(true); m_delayTextPreviewTimer.setInterval(250); connect(&m_delayTextPreviewTimer, SIGNAL(timeout()), this, SLOT(showTextPreview())); } KateScrollBar::~KateScrollBar() { delete m_textPreview; } void KateScrollBar::setShowMiniMap(bool b) { if (b && !m_showMiniMap) { connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); connect(m_doc, SIGNAL(textChanged(KTextEditor::Document*)), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); connect(m_view, SIGNAL(delayedUpdateOfView()), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updatePixmap()), Qt::UniqueConnection); connect(&(m_view->textFolding()), SIGNAL(foldingRangesChanged()), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); } else if (!b) { disconnect(&m_updateTimer); } m_showMiniMap = b; updateGeometry(); update(); } QSize KateScrollBar::sizeHint() const { if (m_showMiniMap) { return QSize(m_miniMapWidth, QScrollBar::sizeHint().height()); } return QScrollBar::sizeHint(); } int KateScrollBar::minimapYToStdY(int y) { // Check if the minimap fills the whole scrollbar if (m_stdGroveRect.height() == m_mapGroveRect.height()) { return y; } // check if y is on the step up/down if ((y < m_stdGroveRect.top()) || (y > m_stdGroveRect.bottom())) { return y; } if (y < m_mapGroveRect.top()) { return m_stdGroveRect.top() + 1; } if (y > m_mapGroveRect.bottom()) { return m_stdGroveRect.bottom() - 1; } // check for div/0 if (m_mapGroveRect.height() == 0) { return y; } int newY = (y - m_mapGroveRect.top()) * m_stdGroveRect.height() / m_mapGroveRect.height(); newY += m_stdGroveRect.top(); return newY; } void KateScrollBar::mousePressEvent(QMouseEvent *e) { // delete text preview hideTextPreview(); if (e->button() == Qt::MidButton) { m_middleMouseDown = true; } else if (e->button() == Qt::LeftButton) { m_leftMouseDown = true; } if (m_showMiniMap) { if (m_leftMouseDown) { // if we show the minimap left-click jumps directly to the selected position int newVal = (e->pos().y()-m_mapGroveRect.top()) / (double)m_mapGroveRect.height() * (double)(maximum()+pageStep()) - pageStep()/2; newVal = qBound(0, newVal, maximum()); setSliderPosition(newVal); } QMouseEvent eMod(QEvent::MouseButtonPress, QPoint(6, minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers()); QScrollBar::mousePressEvent(&eMod); } else { QScrollBar::mousePressEvent(e); } m_toolTipPos = e->globalPos() - QPoint(e->pos().x(), 0); const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1; const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1; QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "
%1

%2
", fromLine, lastLine), this); redrawMarks(); } void KateScrollBar::mouseReleaseEvent(QMouseEvent *e) { if (e->button() == Qt::MidButton) { m_middleMouseDown = false; } else if (e->button() == Qt::LeftButton) { m_leftMouseDown = false; } redrawMarks(); if (m_leftMouseDown || m_middleMouseDown) { QToolTip::hideText(); } if (m_showMiniMap) { QMouseEvent eMod(QEvent::MouseButtonRelease, QPoint(e->pos().x(), minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers()); QScrollBar::mouseReleaseEvent(&eMod); } else { QScrollBar::mouseReleaseEvent(e); } } void KateScrollBar::mouseMoveEvent(QMouseEvent *e) { if (m_showMiniMap) { QMouseEvent eMod(QEvent::MouseMove, QPoint(e->pos().x(), minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers()); QScrollBar::mouseMoveEvent(&eMod); } else { QScrollBar::mouseMoveEvent(e); } if (e->buttons() & (Qt::LeftButton | Qt::MidButton)) { redrawMarks(); // current line tool tip m_toolTipPos = e->globalPos() - QPoint(e->pos().x(), 0); const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1; const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1; QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "
%1

%2
", fromLine, lastLine), this); } showTextPreviewDelayed(); } void KateScrollBar::leaveEvent(QEvent *event) { hideTextPreview(); QAbstractSlider::leaveEvent(event); } bool KateScrollBar::eventFilter(QObject *object, QEvent *event) { Q_UNUSED(object) if (m_textPreview && event->type() == QEvent::WindowDeactivate) { // We need hide the scrollbar TextPreview widget hideTextPreview(); } return false; } void KateScrollBar::paintEvent(QPaintEvent *e) { if (m_doc->marks().size() != m_lines.size()) { recomputeMarksPositions(); } if (m_showMiniMap) { miniMapPaintEvent(e); } else { normalPaintEvent(e); } } void KateScrollBar::showTextPreviewDelayed() { if (!m_textPreview) { if (!m_delayTextPreviewTimer.isActive()) { m_delayTextPreviewTimer.start(); } } else { showTextPreview(); } } void KateScrollBar::showTextPreview() { if (orientation() != Qt::Vertical || isSliderDown() || (minimum() == maximum()) || !m_view->config()->scrollBarPreview()) { return; } QRect grooveRect; if (m_showMiniMap) { // If mini-map is shown, the height of the map might not be the whole height grooveRect = m_mapGroveRect; } else { QStyleOptionSlider opt; opt.init(this); opt.subControls = QStyle::SC_None; opt.activeSubControls = QStyle::SC_None; opt.orientation = orientation(); opt.minimum = minimum(); opt.maximum = maximum(); opt.sliderPosition = sliderPosition(); opt.sliderValue = value(); opt.singleStep = singleStep(); opt.pageStep = pageStep(); grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this); } if (m_view->config()->scrollPastEnd()) { // Adjust the grove size to accommodate the added pageStep at the bottom int adjust = pageStep()*grooveRect.height() / (maximum() + pageStep() - minimum()); grooveRect.adjust(0,0,0, -adjust); } const QPoint cursorPos = mapFromGlobal(QCursor::pos()); if (grooveRect.contains(cursorPos)) { if (!m_textPreview) { m_textPreview = new KateTextPreview(m_view); m_textPreview->setAttribute(Qt::WA_ShowWithoutActivating); m_textPreview->setFrameStyle(QFrame::StyledPanel); // event filter to catch application WindowDeactivate event, to hide the preview window qApp->installEventFilter(this); } const qreal posInPercent = static_cast(cursorPos.y() - grooveRect.top()) / grooveRect.height(); const qreal startLine = posInPercent * m_view->textFolding().visibleLines(); m_textPreview->resize(m_view->width() / 2, m_view->height() / 5); const int xGlobal = mapToGlobal(QPoint(0, 0)).x(); const int yGlobal = qMin(mapToGlobal(QPoint(0, height())).y() - m_textPreview->height(), qMax(mapToGlobal(QPoint(0, 0)).y(), mapToGlobal(cursorPos).y() - m_textPreview->height() / 2)); m_textPreview->move(xGlobal - m_textPreview->width(), yGlobal); m_textPreview->setLine(startLine); m_textPreview->setCenterView(true); m_textPreview->setScaleFactor(0.8); m_textPreview->raise(); m_textPreview->show(); } else { hideTextPreview(); } } void KateScrollBar::hideTextPreview() { if (m_delayTextPreviewTimer.isActive()) { m_delayTextPreviewTimer.stop(); } qApp->removeEventFilter(this); delete m_textPreview; } // This function is optimized for bing called in sequence. const QColor KateScrollBar::charColor(const QVector &attributes, int &attributeIndex, const QList &decorations, const QColor &defaultColor, int x, QChar ch) { QColor color = defaultColor; bool styleFound = false; // Query the decorations, that is, things like search highlighting, or the // KDevelop DUChain highlighting, for a color to use foreach (const QTextLayout::FormatRange &range, decorations) { if (range.start <= x && range.start + range.length > x) { // If there's a different background color set (search markers, ...) // use that, otherwise use the foreground color. if (range.format.hasProperty(QTextFormat::BackgroundBrush)) { color = range.format.background().color(); } else { color = range.format.foreground().color(); } styleFound = true; break; } } // If there's no decoration set for the current character (this will mostly be the case for // plain Kate), query the styles, that is, the default kate syntax highlighting. if (!styleFound) { // go to the block containing x while ((attributeIndex < attributes.size()) && ((attributes[attributeIndex].offset + attributes[attributeIndex].length) < x)) { ++attributeIndex; } if ((attributeIndex < attributes.size()) && (x < attributes[attributeIndex].offset + attributes[attributeIndex].length)) { color = m_view->renderer()->attribute(attributes[attributeIndex].attributeValue)->foreground().color(); } } // Query how much "blackness" the character has. // This causes for example a dot or a dash to appear less intense // than an A or similar. // This gives the pixels created a bit of structure, which makes it look more // like real text. color.setAlpha((ch.unicode() < 256) ? characterOpacity[ch.unicode()] : 222); return color; } void KateScrollBar::updatePixmap() { //QTime time; //time.start(); if (!m_showMiniMap) { // make sure no time is wasted if the option is disabled return; } // For performance reason, only every n-th line will be drawn if the widget is // sufficiently small compared to the amount of lines in the document. int docLineCount = m_view->textFolding().visibleLines(); int pixmapLineCount = docLineCount; if (m_view->config()->scrollPastEnd()) { pixmapLineCount += pageStep(); } int pixmapLinesUnscaled = pixmapLineCount; if (m_grooveHeight < 5) { m_grooveHeight = 5; } int lineDivisor = pixmapLinesUnscaled / m_grooveHeight; if (lineDivisor < 1) { lineDivisor = 1; } int charIncrement = 1; int lineIncrement = 1; if ((m_grooveHeight > 10) && (pixmapLineCount >= m_grooveHeight * 2)) { charIncrement = pixmapLineCount / m_grooveHeight; while (charIncrement > s_linePixelIncLimit) { lineIncrement++; pixmapLineCount = pixmapLinesUnscaled / lineIncrement; charIncrement = pixmapLineCount / m_grooveHeight; } pixmapLineCount /= charIncrement; } int pixmapLineWidth = s_pixelMargin + s_lineWidth / charIncrement; //qCDebug(LOG_KTE) << "l" << lineIncrement << "c" << charIncrement << "d" << lineDivisor; //qCDebug(LOG_KTE) << "pixmap" << pixmapLineCount << pixmapLineWidth << "docLines" << m_view->textFolding().visibleLines() << "height" << m_grooveHeight; const QColor backgroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->background().color(); const QColor defaultTextColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); const QColor selectionBgColor = m_view->renderer()->config()->selectionColor(); QColor modifiedLineColor = m_view->renderer()->config()->modifiedLineColor(); QColor savedLineColor = m_view->renderer()->config()->savedLineColor(); // move the modified line color away from the background color modifiedLineColor.setHsv(modifiedLineColor.hue(), 255, 255 - backgroundColor.value() / 3); savedLineColor.setHsv(savedLineColor.hue(), 100, 255 - backgroundColor.value() / 3); // increase dimensions by ratio m_pixmap = QPixmap(pixmapLineWidth * m_view->devicePixelRatioF(), pixmapLineCount * m_view->devicePixelRatioF()); m_pixmap.fill(QColor("transparent")); // The text currently selected in the document, to be drawn later. const KTextEditor::Range &selection = m_view->selectionRange(); QPainter painter; if (painter.begin(&m_pixmap)) { // init pen once, afterwards, only change it if color changes to avoid a lot of allocation for setPen painter.setPen(selectionBgColor); // Do not force updates of the highlighting if the document is very large bool simpleMode = m_doc->lines() > 7500; int pixelY = 0; int drawnLines = 0; // Iterate over all visible lines, drawing them. for (int virtualLine = 0; virtualLine < docLineCount; virtualLine += lineIncrement) { int realLineNumber = m_view->textFolding().visibleLineToLine(virtualLine); QString lineText = m_doc->line(realLineNumber); if (!simpleMode) { m_doc->buffer().ensureHighlighted(realLineNumber); } const Kate::TextLine &kateline = m_doc->plainKateTextLine(realLineNumber); const QVector &attributes = kateline->attributesList(); QList< QTextLayout::FormatRange > decorations = m_view->renderer()->decorationsForLine(kateline, realLineNumber); int attributeIndex = 0; // Draw selection if it is on an empty line if (selection.contains(KTextEditor::Cursor(realLineNumber, 0)) && lineText.size() == 0) { if (selectionBgColor != painter.pen().color()) { painter.setPen(selectionBgColor); } painter.drawLine(s_pixelMargin, pixelY, s_pixelMargin + s_lineWidth - 1, pixelY); } // Iterate over the line to draw the background int selStartX = -1; int selEndX = -1; int pixelX = s_pixelMargin; // use this to control the offset of the text from the left for (int x = 0; (x < lineText.size() && x < s_lineWidth); x += charIncrement) { if (pixelX >= s_lineWidth + s_pixelMargin) { break; } // Query the selection and draw it behind the character if (selection.contains(KTextEditor::Cursor(realLineNumber, x))) { if (selStartX == -1) selStartX = pixelX; selEndX = pixelX; if (lineText.size() - 1 == x) { selEndX = s_lineWidth + s_pixelMargin-1; } } if (lineText[x] == QLatin1Char('\t')) { pixelX += qMax(4 / charIncrement, 1); // FIXME: tab width... } else { pixelX++; } } if (selStartX != -1) { if (selectionBgColor != painter.pen().color()) { painter.setPen(selectionBgColor); } painter.drawLine(selStartX, pixelY, selEndX, pixelY); } // Iterate over all the characters in the current line pixelX = s_pixelMargin; for (int x = 0; (x < lineText.size() && x < s_lineWidth); x += charIncrement) { if (pixelX >= s_lineWidth + s_pixelMargin) { break; } // draw the pixels if (lineText[x] == QLatin1Char(' ')) { pixelX++; } else if (lineText[x] == QLatin1Char('\t')) { pixelX += qMax(4 / charIncrement, 1); // FIXME: tab width... } else { const QColor newPenColor(charColor(attributes, attributeIndex, decorations, defaultTextColor, x, lineText[x])); if (newPenColor != painter.pen().color()) { painter.setPen(newPenColor); } // Actually draw the pixel with the color queried from the renderer. painter.drawPoint(pixelX, pixelY); pixelX++; } } drawnLines++; if (((drawnLines) % charIncrement) == 0) { pixelY++; } } //qCDebug(LOG_KTE) << drawnLines; // Draw line modification marker map. // Disable this if the document is really huge, // since it requires querying every line. if (m_doc->lines() < 50000) { for (int lineno = 0; lineno < docLineCount; lineno++) { int realLineNo = m_view->textFolding().visibleLineToLine(lineno); const Kate::TextLine &line = m_doc->plainKateTextLine(realLineNo); const QColor & col = line->markedAsModified() ? modifiedLineColor : savedLineColor; if (line->markedAsModified() || line->markedAsSavedOnDisk()) { painter.fillRect(2, lineno / lineDivisor, 3, 1, col); } } } // end painting painter.end(); } // set right ratio m_pixmap.setDevicePixelRatio(m_view->devicePixelRatioF()); //qCDebug(LOG_KTE) << time.elapsed(); // Redraw the scrollbar widget with the updated pixmap. update(); } void KateScrollBar::miniMapPaintEvent(QPaintEvent *e) { QScrollBar::paintEvent(e); QPainter painter(this); QStyleOptionSlider opt; opt.init(this); opt.subControls = QStyle::SC_None; opt.activeSubControls = QStyle::SC_None; opt.orientation = orientation(); opt.minimum = minimum(); opt.maximum = maximum(); opt.sliderPosition = sliderPosition(); opt.sliderValue = value(); opt.singleStep = singleStep(); opt.pageStep = pageStep(); QRect grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this); m_stdGroveRect = grooveRect; if (style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSubLine, this).height() == 0) { int alignMargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &opt, this); grooveRect.moveTop(alignMargin); grooveRect.setHeight(grooveRect.height() - alignMargin); } if (style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarAddLine, this).height() == 0) { int alignMargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &opt, this); grooveRect.setHeight(grooveRect.height() - alignMargin); } m_grooveHeight = grooveRect.height(); const int docXMargin = 1; QRect sliderRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, this); sliderRect.adjust(docXMargin, 0, 0, 0); //style()->drawControl(QStyle::CE_ScrollBarAddLine, &opt, &painter, this); //style()->drawControl(QStyle::CE_ScrollBarSubLine, &opt, &painter, this); // calculate the document size and position const int docHeight = qMin(grooveRect.height(), int(m_pixmap.height() / m_pixmap.devicePixelRatio() * 2)) - 2 * docXMargin; const int yoffset = 1; // top-aligned in stead of center-aligned (grooveRect.height() - docHeight) / 2; const QRect docRect(QPoint(grooveRect.left() + docXMargin, yoffset + grooveRect.top()), QSize(grooveRect.width() - docXMargin, docHeight)); m_mapGroveRect = docRect; // calculate the visible area int max = qMax(maximum() + 1, 1); int visibleStart = value() * docHeight / (max + pageStep()) + docRect.top() + 0.5; int visibleEnd = (value() + pageStep()) * docHeight / (max + pageStep()) + docRect.top(); QRect visibleRect = docRect; visibleRect.moveTop(visibleStart); visibleRect.setHeight(visibleEnd - visibleStart); // calculate colors const QColor backgroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->background().color(); const QColor foregroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); const QColor highlightColor = palette().link().color(); const int backgroundLightness = backgroundColor.lightness(); const int foregroundLightness = foregroundColor.lightness(); const int lighnessDiff = (foregroundLightness - backgroundLightness); // get a color suited for the color theme QColor darkShieldColor = palette().color(QPalette::Mid); int hue, sat, light; darkShieldColor.getHsl(&hue, &sat, &light); // apply suitable lightness darkShieldColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.35); // gradient for nicer results QLinearGradient gradient(0, 0, width(), 0); gradient.setColorAt(0, darkShieldColor); gradient.setColorAt(0.3, darkShieldColor.lighter(115)); gradient.setColorAt(1, darkShieldColor); QColor lightShieldColor; lightShieldColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.15); QColor outlineColor; outlineColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.5); // draw the grove background in case the document is small painter.setPen(Qt::NoPen); painter.setBrush(backgroundColor); painter.drawRect(grooveRect); // adjust the rectangles if ((docHeight + 2 * docXMargin >= grooveRect.height()) && (sliderRect.height() > visibleRect.height() + 2)) { visibleRect.adjust(2, 0, -3, 0); } else { visibleRect.adjust(1, 0, -1, 2); sliderRect.setTop(visibleRect.top() - 1); sliderRect.setBottom(visibleRect.bottom() + 1); } // Smooth transform only when squeezing if (grooveRect.height() < m_pixmap.height() / m_pixmap.devicePixelRatio()) { painter.setRenderHint(QPainter::SmoothPixmapTransform); } // draw the modified lines margin QRect pixmapMarginRect(QPoint(0, 0), QSize(s_pixelMargin, m_pixmap.height() / m_pixmap.devicePixelRatio())); QRect docPixmapMarginRect(QPoint(0, docRect.top()), QSize(s_pixelMargin, docRect.height())); painter.drawPixmap(docPixmapMarginRect, m_pixmap, pixmapMarginRect); // calculate the stretch and draw the stretched lines (scrollbar marks) QRect pixmapRect(QPoint(s_pixelMargin, 0), QSize(m_pixmap.width() / m_pixmap.devicePixelRatio() - s_pixelMargin, m_pixmap.height() / m_pixmap.devicePixelRatio())); QRect docPixmapRect(QPoint(s_pixelMargin, docRect.top()), QSize(docRect.width() - s_pixelMargin, docRect.height())); painter.drawPixmap(docPixmapRect, m_pixmap, pixmapRect); // delimit the end of the document const int y = docPixmapRect.height() + grooveRect.y(); if (y+2 < grooveRect.y() + grooveRect.height()) { QColor fg(foregroundColor); fg.setAlpha(30); painter.setBrush(Qt::NoBrush); painter.setPen(QPen(fg, 1)); painter.drawLine(grooveRect.x()+1,y+2,width()-1,y+2); } // fade the invisible sections const QRect top( grooveRect.x(), grooveRect.y(), grooveRect.width(), visibleRect.y()-grooveRect.y() //Pen width ); const QRect bottom( grooveRect.x(), grooveRect.y()+visibleRect.y()+visibleRect.height()-grooveRect.y(), //Pen width grooveRect.width(), grooveRect.height() - (visibleRect.y()-grooveRect.y())-visibleRect.height() ); QColor faded(backgroundColor); faded.setAlpha(110); painter.fillRect(top, faded); painter.fillRect(bottom, faded); // add a thin line to limit the scrollbar QColor c(foregroundColor); c.setAlpha(10); painter.setPen(QPen(c,1)); painter.drawLine(0, 0, 0, height()); if (m_showMarks) { QHashIterator it = m_lines; QPen penBg; penBg.setWidth(4); lightShieldColor.setAlpha(180); penBg.setColor(lightShieldColor); painter.setPen(penBg); while (it.hasNext()) { it.next(); int y = (it.key() - grooveRect.top()) * docHeight / grooveRect.height() + docRect.top();; painter.drawLine(6, y, width() - 6, y); } it = m_lines; QPen pen; pen.setWidth(2); while (it.hasNext()) { it.next(); pen.setColor(it.value()); painter.setPen(pen); int y = (it.key() - grooveRect.top()) * docHeight / grooveRect.height() + docRect.top(); painter.drawLine(6, y, width() - 6, y); } } // slider outline QColor sliderColor(highlightColor); sliderColor.setAlpha(50); painter.fillRect(sliderRect, sliderColor); painter.setPen(QPen(highlightColor, 0)); // rounded rect looks ugly for some reason, so we draw 4 lines. painter.drawLine(sliderRect.left(), sliderRect.top() + 1, sliderRect.left(), sliderRect.bottom() - 1); painter.drawLine(sliderRect.right(), sliderRect.top() + 1, sliderRect.right(), sliderRect.bottom() - 1); painter.drawLine(sliderRect.left() + 1, sliderRect.top(), sliderRect.right() - 1, sliderRect.top()); painter.drawLine(sliderRect.left() + 1, sliderRect.bottom(), sliderRect.right() - 1, sliderRect.bottom()); } void KateScrollBar::normalPaintEvent(QPaintEvent *e) { QScrollBar::paintEvent(e); if (!m_showMarks) { return; } QPainter painter(this); QStyleOptionSlider opt; opt.init(this); opt.subControls = QStyle::SC_None; opt.activeSubControls = QStyle::SC_None; opt.orientation = orientation(); opt.minimum = minimum(); opt.maximum = maximum(); opt.sliderPosition = sliderPosition(); opt.sliderValue = value(); opt.singleStep = singleStep(); opt.pageStep = pageStep(); QRect rect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, this); int sideMargin = width() - rect.width(); if (sideMargin < 4) { sideMargin = 4; } sideMargin /= 2; QHashIterator it = m_lines; while (it.hasNext()) { it.next(); painter.setPen(it.value()); if (it.key() < rect.top() || it.key() > rect.bottom()) { painter.drawLine(0, it.key(), width(), it.key()); } else { painter.drawLine(0, it.key(), sideMargin, it.key()); painter.drawLine(width() - sideMargin, it.key(), width(), it.key()); } } } void KateScrollBar::resizeEvent(QResizeEvent *e) { QScrollBar::resizeEvent(e); m_updateTimer.start(); m_lines.clear(); update(); } void KateScrollBar::sliderChange(SliderChange change) { // call parents implementation QScrollBar::sliderChange(change); if (change == QAbstractSlider::SliderValueChange) { redrawMarks(); } else if (change == QAbstractSlider::SliderRangeChange) { marksChanged(); } if (m_leftMouseDown || m_middleMouseDown) { const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1; const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1; QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "
%1

%2
", fromLine, lastLine), this); } } void KateScrollBar::marksChanged() { m_lines.clear(); update(); } void KateScrollBar::redrawMarks() { if (!m_showMarks) { return; } update(); } void KateScrollBar::recomputeMarksPositions() { // get the style options to compute the scrollbar pixels QStyleOptionSlider opt; initStyleOption(&opt); QRect grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this); // cache top margin and groove height const int top = grooveRect.top(); const int h = grooveRect.height() - 1; // make sure we have a sane height if (h <= 0) { return; } // get total visible (=without folded) lines in the document int visibleLines = m_view->textFolding().visibleLines() - 1; if (m_view->config()->scrollPastEnd()) { visibleLines += m_viewInternal->linesDisplayed() - 1; visibleLines -= m_view->config()->autoCenterLines(); } // now repopulate the scrollbar lines list m_lines.clear(); const QHash &marks = m_doc->marks(); for (QHash::const_iterator i = marks.constBegin(); i != marks.constEnd(); ++i) { KTextEditor::Mark *mark = i.value(); const int line = m_view->textFolding().lineToVisibleLine(mark->line); const double ratio = static_cast(line) / visibleLines; m_lines.insert(top + (int)(h * ratio), KateRendererConfig::global()->lineMarkerColor((KTextEditor::MarkInterface::MarkTypes)mark->type)); } } void KateScrollBar::sliderMaybeMoved(int value) { if (m_middleMouseDown) { // we only need to emit this signal once, as for the following slider // movements the signal sliderMoved() is already emitted. // Thus, set m_middleMouseDown to false right away. m_middleMouseDown = false; emit sliderMMBMoved(value); } } //END //BEGIN KateCmdLineEditFlagCompletion /** * This class provide completion of flags. It shows a short description of * each flag, and flags are appended. */ class KateCmdLineEditFlagCompletion : public KCompletion { public: KateCmdLineEditFlagCompletion() { ; } QString makeCompletion(const QString & /*s*/) Q_DECL_OVERRIDE { return QString(); } }; //END KateCmdLineEditFlagCompletion //BEGIN KateCmdLineEdit KateCommandLineBar::KateCommandLineBar(KTextEditor::ViewPrivate *view, QWidget *parent) : KateViewBarWidget(true, parent) { QHBoxLayout *topLayout = new QHBoxLayout(); centralWidget()->setLayout(topLayout); topLayout->setMargin(0); m_lineEdit = new KateCmdLineEdit(this, view); connect(m_lineEdit, SIGNAL(hideRequested()), SIGNAL(hideMe())); topLayout->addWidget(m_lineEdit); QToolButton *helpButton = new QToolButton(this); helpButton->setAutoRaise(true); helpButton->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual"))); topLayout->addWidget(helpButton); connect(helpButton, SIGNAL(clicked()), this, SLOT(showHelpPage())); setFocusProxy(m_lineEdit); } void KateCommandLineBar::showHelpPage() { KHelpClient::invokeHelp(QStringLiteral("advanced-editing-tools-commandline"), QStringLiteral("kate")); } KateCommandLineBar::~KateCommandLineBar() { } // inserts the given string in the command line edit and (if selected = true) selects it so the user // can type over it if they want to void KateCommandLineBar::setText(const QString &text, bool selected) { m_lineEdit->setText(text); if (selected) { m_lineEdit->selectAll(); } } void KateCommandLineBar::execute(const QString &text) { m_lineEdit->slotReturnPressed(text); } KateCmdLineEdit::KateCmdLineEdit(KateCommandLineBar *bar, KTextEditor::ViewPrivate *view) : KLineEdit() , m_view(view) , m_bar(bar) , m_msgMode(false) , m_histpos(0) , m_cmdend(0) , m_command(nullptr) { connect(this, SIGNAL(returnPressed(QString)), this, SLOT(slotReturnPressed(QString))); setCompletionObject(KateCmd::self()->commandCompletionObject()); setAutoDeleteCompletionObject(false); m_hideTimer = new QTimer(this); m_hideTimer->setSingleShot(true); connect(m_hideTimer, SIGNAL(timeout()), this, SLOT(hideLineEdit())); // make sure the timer is stopped when the user switches views. if not, focus will be given to the // wrong view when KateViewBar::hideCurrentBarWidget() is called after 4 seconds. (the timer is // used for showing things like "Success" for four seconds after the user has used the kate // command line) connect(m_view, SIGNAL(focusOut(KTextEditor::View*)), m_hideTimer, SLOT(stop())); } void KateCmdLineEdit::hideEvent(QHideEvent *e) { Q_UNUSED(e); } QString KateCmdLineEdit::helptext(const QPoint &) const { QString beg = QStringLiteral("
Help: "); QString mid = QStringLiteral("
"); QString end = QStringLiteral("
"); QString t = text(); QRegExp re(QLatin1String("\\s*help\\s+(.*)")); if (re.indexIn(t) > -1) { QString s; // get help for command QString name = re.cap(1); if (name == QLatin1String("list")) { return beg + i18n("Available Commands") + mid + KateCmd::self()->commandList().join(QLatin1Char(' ')) + i18n("

For help on individual commands, do 'help <command>'

") + end; } else if (! name.isEmpty()) { KTextEditor::Command *cmd = KateCmd::self()->queryCommand(name); if (cmd) { if (cmd->help(m_view, name, s)) { return beg + name + mid + s + end; } else { return beg + name + mid + i18n("No help for '%1'", name) + end; } } else { return beg + mid + i18n("No such command %1", name) + end; } } } return beg + mid + i18n( "

This is the Katepart command line.
" "Syntax: command [ arguments ]
" "For a list of available commands, enter help list
" "For help for individual commands, enter help <command>

") + end; } bool KateCmdLineEdit::event(QEvent *e) { if (e->type() == QEvent::QueryWhatsThis) { setWhatsThis(helptext(QPoint())); e->accept(); return true; } return KLineEdit::event(e); } /** * Parse the text as a command. * * The following is a simple PEG grammar for the syntax of the command. * (A PEG grammar is like a BNF grammar, except that "/" stands for * ordered choice: only the first matching rule is used. In other words, * the parsing is short-circuited in the manner of the "or" operator in * programming languages, and so the grammar is unambiguous.) * * Text <- Range? Command * / Position * Range <- Position ("," Position)? * / "%" * Position <- Base Offset? * Base <- Line * / LastLine * / ThisLine * / Mark * Offset <- [+-] Base * Line <- [0-9]+ * LastLine <- "$" * ThisLine <- "." * Mark <- "'" [a-z] */ void KateCmdLineEdit::slotReturnPressed(const QString &text) { if (text.isEmpty()) { return; } // silently ignore leading space characters uint n = 0; const uint textlen = text.length(); while ((n < textlen) && (text[n].isSpace())) { n++; } if (n >= textlen) { return; } QString cmd = text.mid(n); // Parse any leading range expression, and strip it (and maybe do some other transforms on the command). QString leadingRangeExpression; KTextEditor::Range range = CommandRangeExpressionParser::parseRangeExpression(cmd, m_view, leadingRangeExpression, cmd); // Built in help: if the command starts with "help", [try to] show some help if (cmd.startsWith(QLatin1String("help"))) { QWhatsThis::showText(mapToGlobal(QPoint(0, 0)), helptext(QPoint())); clear(); KateCmd::self()->appendHistory(cmd); m_histpos = KateCmd::self()->historyLength(); m_oldText.clear(); return; } if (cmd.length() > 0) { KTextEditor::Command *p = KateCmd::self()->queryCommand(cmd); m_oldText = leadingRangeExpression + cmd; m_msgMode = true; // the following commands changes the focus themselves, so bar should be hidden before execution. if (QRegExp(QLatin1String("buffer|b|new|vnew|bp|bprev|bn|bnext|bf|bfirst|bl|blast|edit|e")).exactMatch(cmd.split(QLatin1Char(' ')).at(0))) { emit hideRequested(); } if (!p) { setText(i18n("No such command: \"%1\"", cmd)); } else if (range.isValid() && !p->supportsRange(cmd)) { // Raise message, when the command does not support ranges. setText(i18n("Error: No range allowed for command \"%1\".", cmd)); } else { QString msg; if (p->exec(m_view, cmd, msg, range)) { // append command along with range (will be empty if none given) to history KateCmd::self()->appendHistory(leadingRangeExpression + cmd); m_histpos = KateCmd::self()->historyLength(); m_oldText.clear(); if (msg.length() > 0) { setText(i18n("Success: ") + msg); } else if (isVisible()) { // always hide on success without message emit hideRequested(); } } else { if (msg.length() > 0) { if (msg.contains(QLatin1Char('\n'))) { // multiline error, use widget with more space QWhatsThis::showText(mapToGlobal(QPoint(0, 0)), msg); } else { setText(msg); } } else { setText(i18n("Command \"%1\" failed.", cmd)); } } } } // clean up if (completionObject() != KateCmd::self()->commandCompletionObject()) { KCompletion *c = completionObject(); setCompletionObject(KateCmd::self()->commandCompletionObject()); delete c; } m_command = nullptr; m_cmdend = 0; // the following commands change the focus themselves if (!QRegExp(QLatin1String("buffer|b|new|vnew|bp|bprev|bn|bnext|bf|bfirst|bl|blast|edit|e")).exactMatch(cmd.split(QLatin1Char(' ')).at(0))) { m_view->setFocus(); } if (isVisible()) { m_hideTimer->start(4000); } } void KateCmdLineEdit::hideLineEdit() // unless i have focus ;) { if (! hasFocus()) { emit hideRequested(); } } void KateCmdLineEdit::focusInEvent(QFocusEvent *ev) { if (m_msgMode) { m_msgMode = false; setText(m_oldText); selectAll(); } KLineEdit::focusInEvent(ev); } void KateCmdLineEdit::keyPressEvent(QKeyEvent *ev) { if (ev->key() == Qt::Key_Escape || (ev->key() == Qt::Key_BracketLeft && ev->modifiers() == Qt::ControlModifier)) { m_view->setFocus(); hideLineEdit(); clear(); } else if (ev->key() == Qt::Key_Up) { fromHistory(true); } else if (ev->key() == Qt::Key_Down) { fromHistory(false); } uint cursorpos = cursorPosition(); KLineEdit::keyPressEvent(ev); // during typing, let us see if we have a valid command if (! m_cmdend || cursorpos <= m_cmdend) { QChar c; if (! ev->text().isEmpty()) { c = ev->text().at(0); } if (! m_cmdend && ! c.isNull()) { // we have no command, so lets see if we got one if (! c.isLetterOrNumber() && c != QLatin1Char('-') && c != QLatin1Char('_')) { m_command = KateCmd::self()->queryCommand(text().trimmed()); if (m_command) { //qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<queryCommand(text().trimmed()); if (m_command) { //qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<commandCompletionObject()) { KCompletion *c = completionObject(); setCompletionObject(KateCmd::self()->commandCompletionObject()); delete c; } m_cmdend = 0; } } // if we got a command, check if it wants to do something. if (m_command) { KCompletion *cmpl = m_command->completionObject(m_view, text().left(m_cmdend).trimmed()); if (cmpl) { // We need to prepend the current command name + flag string // when completion is done //qCDebug(LOG_KTE)<<"keypress in commandline: Setting completion object!"; setCompletionObject(cmpl); } } } else if (m_command && !ev->text().isEmpty()) { // check if we should call the commands processText() if (m_command->wantsToProcessText(text().left(m_cmdend).trimmed())) { m_command->processText(m_view, text()); } } } void KateCmdLineEdit::fromHistory(bool up) { if (! KateCmd::self()->historyLength()) { return; } QString s; if (up) { if (m_histpos > 0) { m_histpos--; s = KateCmd::self()->fromHistory(m_histpos); } } else { if (m_histpos < (KateCmd::self()->historyLength() - 1)) { m_histpos++; s = KateCmd::self()->fromHistory(m_histpos); } else { m_histpos = KateCmd::self()->historyLength(); setText(m_oldText); } } if (! s.isEmpty()) { // Select the argument part of the command, so that it is easy to overwrite setText(s); static QRegExp reCmd = QRegExp(QLatin1String(".*[\\w\\-]+(?:[^a-zA-Z0-9_-]|:\\w+)(.*)")); if (reCmd.indexIn(text()) == 0) { setSelection(text().length() - reCmd.cap(1).length(), reCmd.cap(1).length()); } } } //END KateCmdLineEdit //BEGIN KateIconBorder using namespace KTextEditor; KateIconBorder::KateIconBorder(KateViewInternal *internalView, QWidget *parent) : QWidget(parent) , m_view(internalView->m_view) , m_doc(internalView->doc()) , m_viewInternal(internalView) , m_iconBorderOn(false) , m_lineNumbersOn(false) , m_relLineNumbersOn(false) , m_updateRelLineNumbers(false) , m_foldingMarkersOn(false) , m_dynWrapIndicatorsOn(false) , m_annotationBorderOn(false) , m_dynWrapIndicators(0) , m_lastClickedLine(-1) , m_cachedLNWidth(0) , m_maxCharWidth(0.0) , iconPaneWidth(16) , m_annotationBorderWidth(6) , m_foldingPreview(nullptr) , m_foldingRange(nullptr) , m_nextHighlightBlock(-2) , m_currentBlockLine(-1) { setAttribute(Qt::WA_StaticContents); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); setMouseTracking(true); m_doc->setMarkDescription(MarkInterface::markType01, i18n("Bookmark")); m_doc->setMarkPixmap(MarkInterface::markType01, QIcon::fromTheme(QStringLiteral("bookmarks")).pixmap(16, 16)); updateFont(); m_delayFoldingHlTimer.setSingleShot(true); m_delayFoldingHlTimer.setInterval(150); connect(&m_delayFoldingHlTimer, SIGNAL(timeout()), this, SLOT(showBlock())); // user interaction (scrolling) hides e.g. preview connect(m_view, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), this, SLOT(displayRangeChanged())); } KateIconBorder::~KateIconBorder() { delete m_foldingPreview; delete m_foldingRange; } void KateIconBorder::setIconBorderOn(bool enable) { if (enable == m_iconBorderOn) { return; } m_iconBorderOn = enable; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::setAnnotationBorderOn(bool enable) { if (enable == m_annotationBorderOn) { return; } m_annotationBorderOn = enable; emit m_view->annotationBorderVisibilityChanged(m_view, enable); updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::removeAnnotationHovering() { // remove hovering if it's still there if (m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) { m_hoveredAnnotationGroupIdentifier.clear(); hideAnnotationTooltip(); QTimer::singleShot(0, this, SLOT(update())); } } void KateIconBorder::setLineNumbersOn(bool enable) { if (enable == m_lineNumbersOn) { return; } m_lineNumbersOn = enable; m_dynWrapIndicatorsOn = (m_dynWrapIndicators == 1) ? enable : m_dynWrapIndicators; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::setRelLineNumbersOn(bool enable) { if (enable == m_relLineNumbersOn) { return; } m_relLineNumbersOn = enable; /* * We don't have to touch the m_dynWrapIndicatorsOn because * we already got it right from the m_lineNumbersOn */ updateGeometry(); QTimer::singleShot( 0, this, SLOT(update()) ); } void KateIconBorder::updateForCursorLineChange() { if (m_relLineNumbersOn) { m_updateRelLineNumbers = true; } // always do normal update, e.g. for different current line color! update(); } void KateIconBorder::setDynWrapIndicators(int state) { if (state == m_dynWrapIndicators) { return; } m_dynWrapIndicators = state; m_dynWrapIndicatorsOn = (state == 1) ? m_lineNumbersOn : state; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::setFoldingMarkersOn(bool enable) { if (enable == m_foldingMarkersOn) { return; } m_foldingMarkersOn = enable; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } QSize KateIconBorder::sizeHint() const { int w = 0; if (m_iconBorderOn) { w += iconPaneWidth + 2; } if (m_annotationBorderOn) { w += m_annotationBorderWidth + 2; } if (m_lineNumbersOn || (m_view->dynWordWrap() && m_dynWrapIndicatorsOn)) { w += lineNumberWidth() + 2; } if (m_foldingMarkersOn) { w += iconPaneWidth; } // space for the line modification system border if (m_view->config()->lineModification()) { w += 3; } // two pixel space w += 2; return QSize(w, 0); } // This function (re)calculates the maximum width of any of the digit characters (0 -> 9) // for graceful handling of variable-width fonts as the linenumber font. void KateIconBorder::updateFont() { const QFontMetricsF &fm = m_view->renderer()->config()->fontMetrics(); m_maxCharWidth = 0.0; // Loop to determine the widest numeric character in the current font. // 48 is ascii '0' for (int i = 48; i < 58; i++) { const qreal charWidth = ceil(fm.width(QChar(i))); m_maxCharWidth = qMax(m_maxCharWidth, charWidth); } // the icon pane scales with the font... iconPaneWidth = fm.height(); updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } int KateIconBorder::lineNumberWidth() const { // width = (number of digits + 1) * char width const int digits = (int) ceil(log10((double)(m_view->doc()->lines() + 1))); int width = m_lineNumbersOn ? (int)ceil((digits + 1) * m_maxCharWidth) : 0; if (m_view->dynWordWrap() && m_dynWrapIndicatorsOn) { // HACK: 16 == style().scrollBarExtent().width() width = qMax(16 + 4, width); if (m_cachedLNWidth != width || m_oldBackgroundColor != m_view->renderer()->config()->iconBarColor()) { int w = 16;// HACK: 16 == style().scrollBarExtent().width() style().scrollBarExtent().width(); int h = m_view->renderer()->lineHeight(); QSize newSize(w * devicePixelRatio(), h * devicePixelRatio()); if ((m_arrow.size() != newSize || m_oldBackgroundColor != m_view->renderer()->config()->iconBarColor()) && !newSize.isEmpty()) { m_arrow = QPixmap(newSize); m_arrow.setDevicePixelRatio(devicePixelRatio()); QPainter p(&m_arrow); p.fillRect(0, 0, w, h, m_view->renderer()->config()->iconBarColor()); h = m_view->renderer()->config()->fontMetrics().ascent(); p.setPen(m_view->renderer()->config()->lineNumberColor()); QPainterPath path; path.moveTo(w / 2, h / 2); path.lineTo(w / 2, 0); path.lineTo(w / 4, h / 4); path.lineTo(0, 0); path.lineTo(0, h / 2); path.lineTo(w / 2, h - 1); path.lineTo(w * 3 / 4, h - 1); path.lineTo(w - 1, h * 3 / 4); path.lineTo(w * 3 / 4, h / 2); path.lineTo(0, h / 2); p.drawPath(path); } } } return width; } void KateIconBorder::paintEvent(QPaintEvent *e) { paintBorder(e->rect().x(), e->rect().y(), e->rect().width(), e->rect().height()); } static void paintTriangle(QPainter &painter, QColor c, int xOffset, int yOffset, int width, int height, bool open) { painter.setRenderHint(QPainter::Antialiasing); qreal size = qMin(width, height); if (KColorUtils::luma(c) > 0.25) { c = KColorUtils::darken(c); } else { c = KColorUtils::shade(c, 0.1); } QPen pen; pen.setJoinStyle(Qt::RoundJoin); pen.setColor(c); pen.setWidthF(1.5); painter.setPen(pen); painter.setBrush(c); // let some border, if possible size *= 0.6; qreal halfSize = size / 2; qreal halfSizeP = halfSize * 0.6; QPointF middle(xOffset + (qreal)width / 2, yOffset + (qreal)height / 2); if (open) { QPointF points[3] = { middle + QPointF(-halfSize, -halfSizeP), middle + QPointF(halfSize, -halfSizeP), middle + QPointF(0, halfSizeP) }; painter.drawConvexPolygon(points, 3); } else { QPointF points[3] = { middle + QPointF(-halfSizeP, -halfSize), middle + QPointF(-halfSizeP, halfSize), middle + QPointF(halfSizeP, 0) }; painter.drawConvexPolygon(points, 3); } painter.setRenderHint(QPainter::Antialiasing, false); } void KateIconBorder::paintBorder(int /*x*/, int y, int /*width*/, int height) { uint h = m_view->renderer()->lineHeight(); uint startz = (y / h); uint endz = startz + 1 + (height / h); uint lineRangesSize = m_viewInternal->cache()->viewCacheLineCount(); uint currentLine = m_view->cursorPosition().line(); // center the folding boxes int m_px = (h - 11) / 2; if (m_px < 0) { m_px = 0; } int lnWidth(0); if (m_lineNumbersOn || (m_view->dynWordWrap() && m_dynWrapIndicatorsOn)) { // avoid calculating unless needed ;-) lnWidth = lineNumberWidth(); if (lnWidth != m_cachedLNWidth || m_oldBackgroundColor != m_view->renderer()->config()->iconBarColor()) { // we went from n0 ->n9 lines or vice verca // this causes an extra updateGeometry() first time the line numbers // are displayed, but sizeHint() is supposed to be const so we can't set // the cached value there. m_cachedLNWidth = lnWidth; m_oldBackgroundColor = m_view->renderer()->config()->iconBarColor(); updateGeometry(); update(); return; } } int w(this->width()); // sane value/calc only once QPainter p(this); p.setRenderHints(QPainter::TextAntialiasing); p.setFont(m_view->renderer()->config()->font()); // for line numbers KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); for (uint z = startz; z <= endz; z++) { int y = h * z; int realLine = -1; if (z < lineRangesSize) { realLine = m_viewInternal->cache()->viewLine(z).line(); } int lnX = 0; p.fillRect(0, y, w - 5, h, m_view->renderer()->config()->iconBarColor()); p.fillRect(w - 5, y, 5, h, m_view->renderer()->config()->backgroundColor()); // icon pane if (m_iconBorderOn) { p.setPen(m_view->renderer()->config()->separatorColor()); p.setBrush(m_view->renderer()->config()->separatorColor()); p.drawLine(lnX + iconPaneWidth + 1, y, lnX + iconPaneWidth + 1, y + h); if ((realLine > -1) && (m_viewInternal->cache()->viewLine(z).startCol() == 0)) { uint mrk(m_doc->mark(realLine)); // call only once if (mrk) { for (uint bit = 0; bit < 32; bit++) { MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes)(1 << bit); if (mrk & markType) { QPixmap px_mark(m_doc->markPixmap(markType)); if (!px_mark.isNull() && h > 0 && iconPaneWidth > 0) { if (iconPaneWidth < px_mark.width() || h < (uint)px_mark.height()) { px_mark = px_mark.scaled(iconPaneWidth, h, Qt::KeepAspectRatio); } // center the mark pixmap int x_px = (iconPaneWidth - px_mark.width()) / 2; if (x_px < 0) { x_px = 0; } int y_px = (h - px_mark.height()) / 2; if (y_px < 0) { y_px = 0; } p.drawPixmap(lnX + x_px, y + y_px, px_mark); } } } } } lnX += iconPaneWidth + 2; } // annotation information if (m_annotationBorderOn) { // Draw a border line between annotations and the line numbers p.setPen(m_view->renderer()->config()->lineNumberColor()); p.setBrush(m_view->renderer()->config()->lineNumberColor()); int borderWidth = m_annotationBorderWidth; p.drawLine(lnX + borderWidth + 1, y, lnX + borderWidth + 1, y + h); if ((realLine > -1) && model) { // Fetch data from the model QVariant text = model->data(realLine, Qt::DisplayRole); QVariant foreground = model->data(realLine, Qt::ForegroundRole); QVariant background = model->data(realLine, Qt::BackgroundRole); // Fill the background if (background.isValid()) { p.fillRect(lnX, y, borderWidth + 1, h, background.value()); } // Set the pen for drawing the foreground if (foreground.isValid()) { p.setPen(foreground.value()); } // Draw a border around all adjacent entries that have the same text as the currently hovered one const QVariant identifier = model->data( realLine, (Qt::ItemDataRole) KTextEditor::AnnotationModel::GroupIdentifierRole ); if( m_hoveredAnnotationGroupIdentifier == identifier.toString() ) { p.drawLine(lnX, y, lnX, y + h); p.drawLine(lnX + borderWidth, y, lnX + borderWidth, y + h); QVariant beforeText = model->data(realLine - 1, Qt::DisplayRole); QVariant afterText = model->data(realLine + 1, Qt::DisplayRole); if (((beforeText.isValid() && beforeText.canConvert() && text.isValid() && text.canConvert() && beforeText.toString() != text.toString()) || realLine == 0) && m_viewInternal->cache()->viewLine(z).viewLine() == 0) { p.drawLine(lnX + 1, y, lnX + borderWidth, y); } if (((afterText.isValid() && afterText.canConvert() && text.isValid() && text.canConvert() && afterText.toString() != text.toString()) || realLine == m_view->doc()->lines() - 1) && m_viewInternal->cache()->viewLine(z).viewLine() == m_viewInternal->cache()->viewLineCount(realLine) - 1) { p.drawLine(lnX + 1, y + h - 1, lnX + borderWidth, y + h - 1); } } if (foreground.isValid()) { QPen pen = p.pen(); pen.setWidth(1); p.setPen(pen); } // Now draw the normal text if (text.isValid() && text.canConvert() && (m_viewInternal->cache()->viewLine(z).startCol() == 0)) { p.drawText(lnX + 3, y, borderWidth - 3, h, Qt::AlignLeft | Qt::AlignVCenter, text.toString()); } } // adjust current X position and reset the pen and brush lnX += borderWidth + 2; } // line number if (m_lineNumbersOn || (m_view->dynWordWrap() && m_dynWrapIndicatorsOn)) { if (realLine > -1) { int distanceToCurrent = abs(realLine - static_cast(currentLine)); QColor color; if (distanceToCurrent == 0) { color = m_view->renderer()->config()->currentLineNumberColor(); } else { color = m_view->renderer()->config()->lineNumberColor(); } p.setPen(color); p.setBrush(color); if (m_viewInternal->cache()->viewLine(z).startCol() == 0) { if (m_relLineNumbersOn) { if (distanceToCurrent == 0) { p.drawText(lnX + m_maxCharWidth / 2, y, lnWidth - m_maxCharWidth, h, Qt::TextDontClip|Qt::AlignLeft|Qt::AlignVCenter, QString::number(realLine + 1)); } else { p.drawText(lnX + m_maxCharWidth / 2, y, lnWidth - m_maxCharWidth, h, Qt::TextDontClip|Qt::AlignRight|Qt::AlignVCenter, QString::number(distanceToCurrent)); } if (m_updateRelLineNumbers) { m_updateRelLineNumbers = false; update(); } } else if (m_lineNumbersOn) { p.drawText(lnX + m_maxCharWidth / 2, y, lnWidth - m_maxCharWidth, h, Qt::TextDontClip | Qt::AlignRight | Qt::AlignVCenter, QString::number(realLine + 1)); } } else if (m_view->dynWordWrap() && m_dynWrapIndicatorsOn) { p.drawPixmap(lnX + lnWidth - (m_arrow.width() / m_arrow.devicePixelRatio()) - 2, y, m_arrow); } } lnX += lnWidth + 2; } // folding markers if (m_foldingMarkersOn) { // first icon border background p.fillRect(lnX, y, iconPaneWidth, h, m_view->renderer()->config()->iconBarColor()); // possible additional folding highlighting if ((realLine >= 0) && m_foldingRange && m_foldingRange->overlapsLine(realLine)) { p.save(); // use linear gradient as brush QLinearGradient g(lnX, y, lnX + iconPaneWidth, y); const QColor foldingColor(m_view->renderer()->config()->foldingColor()); g.setColorAt(0, foldingColor); g.setColorAt(0.3, foldingColor.lighter(110)); g.setColorAt(1, foldingColor); p.setBrush(g); p.setPen(foldingColor); p.setClipRect(lnX, y, iconPaneWidth, h); p.setRenderHint(QPainter::Antialiasing); const qreal r = 4.0; if (m_foldingRange->start().line() == realLine && m_viewInternal->cache()->viewLine(z).viewLine() == 0) { p.drawRect(lnX, y, iconPaneWidth, h + r); } else if (m_foldingRange->end().line() == realLine && m_viewInternal->cache()->viewLine(z).viewLine() == m_viewInternal->cache()->viewLineCount(realLine) - 1) { p.drawRect(lnX, y - r, iconPaneWidth, h + r); } else { p.drawRect(lnX, y - r, iconPaneWidth, h + 2 * r); } p.restore(); } if ((realLine >= 0) && (m_viewInternal->cache()->viewLine(z).startCol() == 0)) { QVector > startingRanges = m_view->textFolding().foldingRangesStartingOnLine(realLine); bool anyFolded = false; for (int i = 0; i < startingRanges.size(); ++i) if (startingRanges[i].second & Kate::TextFolding::Folded) { anyFolded = true; } Kate::TextLine tl = m_doc->kateTextLine(realLine); if (!startingRanges.isEmpty() || tl->markedAsFoldingStart()) { if (anyFolded) { paintTriangle(p, m_view->renderer()->config()->foldingColor(), lnX, y, iconPaneWidth, h, false); } else { paintTriangle(p, m_view->renderer()->config()->foldingColor(), lnX, y, iconPaneWidth, h, true); } } } lnX += iconPaneWidth; } // modified line system if (m_view->config()->lineModification() && realLine > -1 && !m_doc->url().isEmpty()) { // one pixel space ++lnX; Kate::TextLine tl = m_doc->plainKateTextLine(realLine); if (tl->markedAsModified()) { p.fillRect(lnX, y, 3, h, m_view->renderer()->config()->modifiedLineColor()); } if (tl->markedAsSavedOnDisk()) { p.fillRect(lnX, y, 3, h, m_view->renderer()->config()->savedLineColor()); } } } } KateIconBorder::BorderArea KateIconBorder::positionToArea(const QPoint &p) const { // see KateIconBorder::sizeHint() for pixel spacings int x = 0; if (m_iconBorderOn) { x += iconPaneWidth; if (p.x() <= x) { return IconBorder; } x += 2; } if (this->m_annotationBorderOn) { x += m_annotationBorderWidth; if (p.x() <= x) { return AnnotationBorder; } x += 2; } if (m_lineNumbersOn || m_dynWrapIndicators) { x += lineNumberWidth(); if (p.x() <= x) { return LineNumbers; } x += 2; } if (m_foldingMarkersOn) { x += iconPaneWidth; if (p.x() <= x) { return FoldingMarkers; } } if (m_view->config()->lineModification()) { x += 3 + 2; if (p.x() <= x) { return ModificationBorder; } } return None; } void KateIconBorder::mousePressEvent(QMouseEvent *e) { const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->y()); if (t.isValid()) { m_lastClickedLine = t.line(); if (positionToArea(e->pos()) != IconBorder && positionToArea(e->pos()) != AnnotationBorder) { QMouseEvent forward(QEvent::MouseButtonPress, QPoint(0, e->y()), e->button(), e->buttons(), e->modifiers()); m_viewInternal->mousePressEvent(&forward); } return e->accept(); } QWidget::mousePressEvent(e); } void KateIconBorder::showDelayedBlock(int line) { // save the line over which the mouse hovers // either we start the timer for delay, or we show the block immediately // if the moving range already exists m_nextHighlightBlock = line; if (!m_foldingRange) { if (!m_delayFoldingHlTimer.isActive()) { m_delayFoldingHlTimer.start(); } } else { showBlock(); } } void KateIconBorder::showBlock() { if (m_nextHighlightBlock == m_currentBlockLine) { return; } m_currentBlockLine = m_nextHighlightBlock; if (m_currentBlockLine >= m_doc->buffer().lines()) { return; } /** * compute to which folding range we belong * FIXME: optimize + perhaps have some better threshold or use timers! */ KTextEditor::Range newRange = KTextEditor::Range::invalid(); for (int line = m_currentBlockLine; line >= qMax(0, m_currentBlockLine - 1024); --line) { /** * try if we have folding range from that line, should be fast per call */ KTextEditor::Range foldingRange = m_doc->buffer().computeFoldingRangeForStartLine(line); if (!foldingRange.isValid()) { continue; } /** * does the range reach us? */ if (foldingRange.overlapsLine(m_currentBlockLine)) { newRange = foldingRange; break; } } if (newRange.isValid() && m_foldingRange && *m_foldingRange == newRange) { // new range equals the old one, nothing to do. return; } else { // the ranges differ, delete the old, if it exists delete m_foldingRange; m_foldingRange = nullptr; } if (newRange.isValid()) { //qCDebug(LOG_KTE) << "new folding hl-range:" << newRange; m_foldingRange = m_doc->newMovingRange(newRange, KTextEditor::MovingRange::ExpandRight); KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); /** * create highlighting color with alpha for the range! */ QColor result = m_view->renderer()->config()->foldingColor(); result.setAlphaF(0.5); attr->setBackground(QBrush(result)); m_foldingRange->setView(m_view); // use z depth defined in moving ranges interface m_foldingRange->setZDepth(-100.0); m_foldingRange->setAttribute(attr); } // show text preview, if a folded region starts here bool foldUnderMouse = false; if (m_foldingRange && m_view->config()->foldingPreview()) { const QPoint globalPos = QCursor::pos(); const QPoint pos = mapFromGlobal(globalPos); const KateTextLayout &t = m_view->m_viewInternal->yToKateTextLayout(pos.y()); if (t.isValid() && positionToArea(pos) == FoldingMarkers) { const int realLine = t.line(); foldUnderMouse = !m_view->textFolding().isLineVisible(realLine + 1); if (foldUnderMouse) { if (!m_foldingPreview) { m_foldingPreview = new KateTextPreview(m_view); m_foldingPreview->setAttribute(Qt::WA_ShowWithoutActivating); m_foldingPreview->setFrameStyle(QFrame::StyledPanel); // event filter to catch application WindowDeactivate event, to hide the preview window // qApp->installEventFilter(this); } // TODO: use KateViewInternal::maxLen() somehow to compute proper width for amount of lines to display // try using the end line of the range for proper popup height const int lineCount = qMin(m_foldingRange->numberOfLines() + 1, (height() - pos.y()) / m_view->renderer()->lineHeight()); m_foldingPreview->resize(m_view->width() / 2, lineCount * m_view->renderer()->lineHeight() + 2 * m_foldingPreview->frameWidth()); const int xGlobal = mapToGlobal(QPoint(width(), 0)).x(); const int yGlobal = m_view->mapToGlobal(m_view->cursorToCoordinate(KTextEditor::Cursor(realLine, 0))).y(); m_foldingPreview->move(QPoint(xGlobal, yGlobal) - m_foldingPreview->contentsRect().topLeft()); m_foldingPreview->setLine(realLine); m_foldingPreview->setCenterView(false); m_foldingPreview->setShowFoldedLines(true); m_foldingPreview->raise(); m_foldingPreview->show(); } } } if (!foldUnderMouse) { delete m_foldingPreview; } /** * repaint */ repaint(); } void KateIconBorder::hideBlock() { if (m_delayFoldingHlTimer.isActive()) { m_delayFoldingHlTimer.stop(); } m_nextHighlightBlock = -2; m_currentBlockLine = -1; delete m_foldingRange; m_foldingRange = nullptr; delete m_foldingPreview; } void KateIconBorder::leaveEvent(QEvent *event) { hideBlock(); removeAnnotationHovering(); QWidget::leaveEvent(event); } void KateIconBorder::mouseMoveEvent(QMouseEvent *e) { const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->y()); if (t.isValid()) { if (positionToArea(e->pos()) == FoldingMarkers) { showDelayedBlock(t.line()); } else { hideBlock(); } if (positionToArea(e->pos()) == AnnotationBorder) { KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { m_hoveredAnnotationGroupIdentifier = model->data( t.line(), (Qt::ItemDataRole) KTextEditor::AnnotationModel::GroupIdentifierRole ).toString(); showAnnotationTooltip(t.line(), e->globalPos()); QTimer::singleShot(0, this, SLOT(update())); } } else { if (positionToArea(e->pos()) == IconBorder) { m_doc->requestMarkTooltip(t.line(), e->globalPos()); } m_hoveredAnnotationGroupIdentifier.clear(); hideAnnotationTooltip(); QTimer::singleShot(0, this, SLOT(update())); } if (positionToArea(e->pos()) != IconBorder) { QPoint p = m_viewInternal->mapFromGlobal(e->globalPos()); QMouseEvent forward(QEvent::MouseMove, p, e->button(), e->buttons(), e->modifiers()); m_viewInternal->mouseMoveEvent(&forward); } } else { // remove hovering if it's still there removeAnnotationHovering(); } QWidget::mouseMoveEvent(e); } void KateIconBorder::mouseReleaseEvent(QMouseEvent *e) { const int cursorOnLine = m_viewInternal->yToKateTextLayout(e->y()).line(); if (cursorOnLine == m_lastClickedLine && cursorOnLine >= 0 && cursorOnLine <= m_doc->lastLine()) { BorderArea area = positionToArea(e->pos()); if (area == IconBorder) { if (e->button() == Qt::LeftButton) { if (!m_doc->handleMarkClick(cursorOnLine)) { KateViewConfig *config = m_view->config(); const uint editBits = m_doc->editableMarks(); // is the default or the only editable mark const uint singleMark = qPopulationCount(editBits) > 1 ? editBits & config->defaultMarkType() : editBits; if (singleMark) { if (m_doc->mark(cursorOnLine) & singleMark) { m_doc->removeMark(cursorOnLine, singleMark); } else { m_doc->addMark(cursorOnLine, singleMark); } } else if (config->allowMarkMenu()) { showMarkMenu(cursorOnLine, QCursor::pos()); } } } else if (e->button() == Qt::RightButton) { showMarkMenu(cursorOnLine, QCursor::pos()); } } if (area == FoldingMarkers) { // ask the folding info for this line, if any folds are around! QVector > startingRanges = m_view->textFolding().foldingRangesStartingOnLine(cursorOnLine); bool anyFolded = false; for (int i = 0; i < startingRanges.size(); ++i) if (startingRanges[i].second & Kate::TextFolding::Folded) { anyFolded = true; } // fold or unfold all ranges, remember if any action happened! bool actionDone = false; for (int i = 0; i < startingRanges.size(); ++i) { actionDone = (anyFolded ? m_view->textFolding().unfoldRange(startingRanges[i].first) : m_view->textFolding().foldRange(startingRanges[i].first)) || actionDone; } // if no action done, try to fold it, create non-persistent folded range, if possible! if (!actionDone) { // either use the fold for this line or the range that is highlighted ATM if any! KTextEditor::Range foldingRange = m_view->doc()->buffer().computeFoldingRangeForStartLine(cursorOnLine); if (!foldingRange.isValid() && m_foldingRange) { foldingRange = m_foldingRange->toRange(); } m_view->textFolding().newFoldingRange(foldingRange, Kate::TextFolding::Folded); } delete m_foldingPreview; } if (area == AnnotationBorder) { const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this); if (e->button() == Qt::LeftButton && singleClick) { emit m_view->annotationActivated(m_view, cursorOnLine); } else if (e->button() == Qt::RightButton) { showAnnotationMenu(cursorOnLine, e->globalPos()); } } } QMouseEvent forward(QEvent::MouseButtonRelease, QPoint(0, e->y()), e->button(), e->buttons(), e->modifiers()); m_viewInternal->mouseReleaseEvent(&forward); } void KateIconBorder::mouseDoubleClickEvent(QMouseEvent *e) { int cursorOnLine = m_viewInternal->yToKateTextLayout(e->y()).line(); if (cursorOnLine == m_lastClickedLine && cursorOnLine <= m_doc->lastLine()) { BorderArea area = positionToArea(e->pos()); const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this); if (area == AnnotationBorder && !singleClick) { emit m_view->annotationActivated(m_view, cursorOnLine); } } QMouseEvent forward(QEvent::MouseButtonDblClick, QPoint(0, e->y()), e->button(), e->buttons(), e->modifiers()); m_viewInternal->mouseDoubleClickEvent(&forward); } void KateIconBorder::wheelEvent(QWheelEvent *e) { QCoreApplication::sendEvent(m_viewInternal, e); } void KateIconBorder::showMarkMenu(uint line, const QPoint &pos) { if (m_doc->handleMarkContextMenu(line, pos)) { return; } if (!m_view->config()->allowMarkMenu()) { return; } QMenu markMenu; QMenu selectDefaultMark; auto selectDefaultMarkActionGroup = new QActionGroup(&selectDefaultMark); QVector vec(33); int i = 1; for (uint bit = 0; bit < 32; bit++) { MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes)(1 << bit); if (!(m_doc->editableMarks() & markType)) { continue; } QAction *mA; QAction *dMA; const QPixmap icon = m_doc->markPixmap(markType); if (!m_doc->markDescription(markType).isEmpty()) { mA = markMenu.addAction(icon, m_doc->markDescription(markType)); dMA = selectDefaultMark.addAction(icon, m_doc->markDescription(markType)); } else { mA = markMenu.addAction(icon, i18n("Mark Type %1", bit + 1)); dMA = selectDefaultMark.addAction(icon, i18n("Mark Type %1", bit + 1)); } selectDefaultMarkActionGroup->addAction(dMA); mA->setData(i); mA->setCheckable(true); dMA->setData(i + 100); dMA->setCheckable(true); if (m_doc->mark(line) & markType) { mA->setChecked(true); } if (markType & KateViewConfig::global()->defaultMarkType()) { dMA->setChecked(true); } vec[i++] = markType; } if (markMenu.actions().count() == 0) { return; } if (markMenu.actions().count() > 1) { markMenu.addAction(i18n("Set Default Mark Type"))->setMenu(&selectDefaultMark); } QAction *rA = markMenu.exec(pos); if (!rA) { return; } int result = rA->data().toInt(); if (result > 100) { KateViewConfig::global()->setDefaultMarkType(vec[result - 100]); } else { MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes) vec[result]; if (m_doc->mark(line) & markType) { m_doc->removeMark(line, markType); } else { m_doc->addMark(line, markType); } } } void KateIconBorder::showAnnotationTooltip(int line, const QPoint &pos) { KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { QVariant data = model->data(line, Qt::ToolTipRole); QString tip = data.toString(); if (!tip.isEmpty()) { QToolTip::showText(pos, data.toString(), this); } } } int KateIconBorder::annotationLineWidth(int line) { KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { QVariant data = model->data(line, Qt::DisplayRole); return data.toString().length() * m_maxCharWidth + 8; } return 8; } void KateIconBorder::updateAnnotationLine(int line) { if (annotationLineWidth(line) > m_annotationBorderWidth) { m_annotationBorderWidth = annotationLineWidth(line); updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } } void KateIconBorder::showAnnotationMenu(int line, const QPoint &pos) { QMenu menu; QAction a(i18n("Disable Annotation Bar"), &menu); a.setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); menu.addAction(&a); emit m_view->annotationContextMenuAboutToShow(m_view, &menu, line); if (menu.exec(pos) == &a) { m_view->setAnnotationBorderVisible(false); } } void KateIconBorder::hideAnnotationTooltip() { QToolTip::hideText(); } void KateIconBorder::updateAnnotationBorderWidth() { m_annotationBorderWidth = 6; KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { for (int i = 0; i < m_view->doc()->lines(); i++) { int curwidth = annotationLineWidth(i); if (curwidth > m_annotationBorderWidth) { m_annotationBorderWidth = curwidth; } } } updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::annotationModelChanged(KTextEditor::AnnotationModel *oldmodel, KTextEditor::AnnotationModel *newmodel) { if (oldmodel) { oldmodel->disconnect(this); } if (newmodel) { connect(newmodel, SIGNAL(reset()), this, SLOT(updateAnnotationBorderWidth())); connect(newmodel, SIGNAL(lineChanged(int)), this, SLOT(updateAnnotationLine(int))); } updateAnnotationBorderWidth(); } void KateIconBorder::displayRangeChanged() { hideBlock(); removeAnnotationHovering(); } //END KateIconBorder //BEGIN KateViewEncodingAction // Acording to http://www.iana.org/assignments/ianacharset-mib // the default/unknown mib value is 2. #define MIB_DEFAULT 2 bool lessThanAction(KSelectAction *a, KSelectAction *b) { return a->text() < b->text(); } void KateViewEncodingAction::Private::init() { QList actions; q->setToolBarMode(MenuMode); int i; foreach (const QStringList &encodingsForScript, KCharsets::charsets()->encodingsByScript()) { KSelectAction *tmp = new KSelectAction(encodingsForScript.at(0), q); for (i = 1; i < encodingsForScript.size(); ++i) { tmp->addAction(encodingsForScript.at(i)); } q->connect(tmp, SIGNAL(triggered(QAction*)), q, SLOT(_k_subActionTriggered(QAction*))); //tmp->setCheckable(true); actions << tmp; } qSort(actions.begin(), actions.end(), lessThanAction); foreach (KSelectAction *action, actions) { q->addAction(action); } } void KateViewEncodingAction::Private::_k_subActionTriggered(QAction *action) { if (currentSubAction == action) { return; } currentSubAction = action; bool ok = false; int mib = q->mibForName(action->text(), &ok); if (ok) { emit q->KSelectAction::triggered(action->text()); emit q->triggered(q->codecForMib(mib)); } } KateViewEncodingAction::KateViewEncodingAction(KTextEditor::DocumentPrivate *_doc, KTextEditor::ViewPrivate *_view, const QString &text, QObject *parent, bool saveAsMode) : KSelectAction(text, parent), doc(_doc), view(_view), d(new Private(this)) , m_saveAsMode(saveAsMode) { d->init(); connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); connect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString))); } KateViewEncodingAction::~KateViewEncodingAction() { delete d; } void KateViewEncodingAction::slotAboutToShow() { setCurrentCodec(doc->config()->encoding()); } void KateViewEncodingAction::setEncoding(const QString &e) { /** * in save as mode => trigger saveAs */ if (m_saveAsMode) { doc->documentSaveAsWithEncoding(e); return; } /** * else switch encoding */ doc->userSetEncodingForNextReload(); doc->setEncoding(e); view->reloadFile(); } int KateViewEncodingAction::mibForName(const QString &codecName, bool *ok) const { // FIXME logic is good but code is ugly bool success = false; int mib = MIB_DEFAULT; KCharsets *charsets = KCharsets::charsets(); QTextCodec *codec = charsets->codecForName(codecName, success); if (!success) { // Maybe we got a description name instead codec = charsets->codecForName(charsets->encodingForName(codecName), success); } if (codec) { mib = codec->mibEnum(); } if (ok) { *ok = success; } if (success) { return mib; } qCWarning(LOG_KTE) << "Invalid codec name: " << codecName; return MIB_DEFAULT; } QTextCodec *KateViewEncodingAction::codecForMib(int mib) const { if (mib == MIB_DEFAULT) { // FIXME offer to change the default codec return QTextCodec::codecForLocale(); } else { return QTextCodec::codecForMib(mib); } } QTextCodec *KateViewEncodingAction::currentCodec() const { return codecForMib(currentCodecMib()); } bool KateViewEncodingAction::setCurrentCodec(QTextCodec *codec) { disconnect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString))); int i, j; for (i = 0; i < actions().size(); ++i) { if (actions().at(i)->menu()) { for (j = 0; j < actions().at(i)->menu()->actions().size(); ++j) { if (!j && !actions().at(i)->menu()->actions().at(j)->data().isNull()) { continue; } if (actions().at(i)->menu()->actions().at(j)->isSeparator()) { continue; } if (codec == KCharsets::charsets()->codecForName(actions().at(i)->menu()->actions().at(j)->text())) { d->currentSubAction = actions().at(i)->menu()->actions().at(j); d->currentSubAction->setChecked(true); } else { actions().at(i)->menu()->actions().at(j)->setChecked(false); } } } } connect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString))); return true; } QString KateViewEncodingAction::currentCodecName() const { return d->currentSubAction->text(); } bool KateViewEncodingAction::setCurrentCodec(const QString &codecName) { return setCurrentCodec(KCharsets::charsets()->codecForName(codecName)); } int KateViewEncodingAction::currentCodecMib() const { return mibForName(currentCodecName()); } bool KateViewEncodingAction::setCurrentCodec(int mib) { return setCurrentCodec(codecForMib(mib)); } //END KateViewEncodingAction //BEGIN KateViewBar related classes KateViewBarWidget::KateViewBarWidget(bool addCloseButton, QWidget *parent) : QWidget(parent) , m_viewBar(nullptr) { QHBoxLayout *layout = new QHBoxLayout(this); // NOTE: Here be cosmetics. layout->setMargin(0); // hide button if (addCloseButton) { QToolButton *hideButton = new QToolButton(this); hideButton->setAutoRaise(true); hideButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); connect(hideButton, SIGNAL(clicked()), SIGNAL(hideMe())); layout->addWidget(hideButton); layout->setAlignment(hideButton, Qt::AlignLeft | Qt::AlignTop); } // widget to be used as parent for the real content m_centralWidget = new QWidget(this); layout->addWidget(m_centralWidget); setLayout(layout); setFocusProxy(m_centralWidget); } KateViewBar::KateViewBar(bool external, QWidget *parent, KTextEditor::ViewPrivate *view) : QWidget(parent), m_external(external), m_view(view), m_permanentBarWidget(nullptr) { m_layout = new QVBoxLayout(this); m_stack = new QStackedWidget(this); m_layout->addWidget(m_stack); m_layout->setMargin(0); m_stack->hide(); hide(); } void KateViewBar::addBarWidget(KateViewBarWidget *newBarWidget) { // just ignore additional adds for already existing widgets if (hasBarWidget(newBarWidget)) { return; } // add new widget, invisible... newBarWidget->hide(); m_stack->addWidget(newBarWidget); newBarWidget->setAssociatedViewBar(this); connect(newBarWidget, SIGNAL(hideMe()), SLOT(hideCurrentBarWidget())); } void KateViewBar::removeBarWidget(KateViewBarWidget *barWidget) { // remove only if there if (!hasBarWidget(barWidget)) { return; } m_stack->removeWidget(barWidget); barWidget->setAssociatedViewBar(nullptr); barWidget->hide(); disconnect(barWidget, nullptr, this, nullptr); } void KateViewBar::addPermanentBarWidget(KateViewBarWidget *barWidget) { Q_ASSERT(barWidget); Q_ASSERT(!m_permanentBarWidget); m_stack->addWidget(barWidget); m_stack->setCurrentWidget(barWidget); m_stack->show(); m_permanentBarWidget = barWidget; m_permanentBarWidget->show(); setViewBarVisible(true); } void KateViewBar::removePermanentBarWidget(KateViewBarWidget *barWidget) { Q_ASSERT(m_permanentBarWidget == barWidget); Q_UNUSED(barWidget); const bool hideBar = m_stack->currentWidget() == m_permanentBarWidget; m_permanentBarWidget->hide(); m_stack->removeWidget(m_permanentBarWidget); m_permanentBarWidget = nullptr; if (hideBar) { m_stack->hide(); setViewBarVisible(false); } } bool KateViewBar::hasPermanentWidget(KateViewBarWidget *barWidget) const { return (m_permanentBarWidget == barWidget); } void KateViewBar::showBarWidget(KateViewBarWidget *barWidget) { Q_ASSERT(barWidget != nullptr); if (barWidget != qobject_cast(m_stack->currentWidget())) { hideCurrentBarWidget(); } // raise correct widget m_stack->setCurrentWidget(barWidget); barWidget->show(); barWidget->setFocus(Qt::ShortcutFocusReason); m_stack->show(); setViewBarVisible(true); } bool KateViewBar::hasBarWidget(KateViewBarWidget *barWidget) const { return m_stack->indexOf(barWidget) != -1; } void KateViewBar::hideCurrentBarWidget() { KateViewBarWidget *current = qobject_cast(m_stack->currentWidget()); if (current) { current->closed(); } // if we have any permanent widget, make it visible again if (m_permanentBarWidget) { m_stack->setCurrentWidget (m_permanentBarWidget); } else { // else: hide the bar m_stack->hide(); setViewBarVisible(false); } m_view->setFocus(); } void KateViewBar::setViewBarVisible(bool visible) { if (m_external) { if (visible) { m_view->mainWindow()->showViewBar(m_view); } else { m_view->mainWindow()->hideViewBar(m_view); } } else { setVisible(visible); } } bool KateViewBar::hiddenOrPermanent() const { KateViewBarWidget *current = qobject_cast(m_stack->currentWidget()); if (!isVisible() || (m_permanentBarWidget && m_permanentBarWidget == current)) { return true; } return false; } void KateViewBar::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { hideCurrentBarWidget(); return; } QWidget::keyPressEvent(event); } void KateViewBar::hideEvent(QHideEvent *event) { Q_UNUSED(event); // if (!event->spontaneous()) // m_view->setFocus(); } //END KateViewBar related classes KatePasteMenu::KatePasteMenu(const QString &text, KTextEditor::ViewPrivate *view) : KActionMenu(text, view) , m_view(view) { connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); } void KatePasteMenu::slotAboutToShow() { menu()->clear(); /** * insert complete paste history */ int i = 0; Q_FOREACH (const QString &text, KTextEditor::EditorPrivate::self()->clipboardHistory()) { /** * get text for the menu ;) */ QString leftPart = (text.size() > 48) ? (text.left(48) + QLatin1String("...")) : text; QAction *a = menu()->addAction(leftPart.replace(QLatin1String("\n"), QLatin1String(" ")), this, SLOT(paste())); a->setData(i++); } } void KatePasteMenu::paste() { if (!sender()) { return; } QAction *action = qobject_cast(sender()); if (!action) { return; } // get index int i = action->data().toInt(); if (i >= KTextEditor::EditorPrivate::self()->clipboardHistory().size()) { return; } // paste m_view->paste(&KTextEditor::EditorPrivate::self()->clipboardHistory()[i]); } diff --git a/src/view/kateviewhelpers.h b/src/view/kateviewhelpers.h index 90cff889..9e2723ea 100644 --- a/src/view/kateviewhelpers.h +++ b/src/view/kateviewhelpers.h @@ -1,595 +1,634 @@ /* This file is part of the KDE libraries Copyright (C) 2002 John Firebaugh Copyright (C) 2001 Anders Lund Copyright (C) 2001 Christoph Cullmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KATE_VIEW_HELPERS_H__ #define __KATE_VIEW_HELPERS_H__ #include #include #include #include #include #include #include #include #include #include #include #include +#include +#include #include #include #include "katetextline.h" namespace KTextEditor { class DocumentPrivate; } namespace KTextEditor { class ViewPrivate; } class KateViewInternal; #define MAXFOLDINGCOLORS 16 class KateLineInfo; class KateTextPreview; namespace KTextEditor { class Command; class AnnotationModel; class MovingRange; } class QTimer; class QVBoxLayout; +/** + * Class to layout KTextEditor::Message%s in KateView. Only the floating + * positions TopInView, CenterInView, and BottomInView are supported. + * AboveView and BelowView are not supported and ASSERT. + */ +class KateMessageLayout : public QLayout +{ +public: + explicit KateMessageLayout (QWidget *parent); + ~KateMessageLayout(); + + void addWidget(QWidget *widget, KTextEditor::Message::MessagePosition pos); + int count() const Q_DECL_OVERRIDE; + QLayoutItem *itemAt(int index) const Q_DECL_OVERRIDE; + void setGeometry(const QRect &rect) Q_DECL_OVERRIDE; + QSize sizeHint() const Q_DECL_OVERRIDE; + QLayoutItem *takeAt(int index) Q_DECL_OVERRIDE; + + void add(QLayoutItem *item, KTextEditor::Message::MessagePosition pos); + +private: + void addItem(QLayoutItem *item) Q_DECL_OVERRIDE; // never called publically + + struct ItemWrapper + { + ItemWrapper(QLayoutItem *i, KTextEditor::Message::MessagePosition pos) + : item(i) + , position(pos) + {} + + QLayoutItem * item = nullptr; + KTextEditor::Message::MessagePosition position; + }; + + QVector m_items; +}; + /** * This class is required because QScrollBar's sliderMoved() signal is * really supposed to be a sliderDragged() signal... so this way we can capture * MMB slider moves as well * * Also, it adds some useful indicators on the scrollbar. */ class KateScrollBar : public QScrollBar { Q_OBJECT public: KateScrollBar(Qt::Orientation orientation, class KateViewInternal *parent); virtual ~KateScrollBar(); QSize sizeHint() const Q_DECL_OVERRIDE; inline bool showMarks() const { return m_showMarks; } inline void setShowMarks(bool b) { m_showMarks = b; update(); } inline bool showMiniMap() const { return m_showMiniMap; } void setShowMiniMap(bool b); inline bool miniMapAll() const { return m_miniMapAll; } inline void setMiniMapAll(bool b) { m_miniMapAll = b; updateGeometry(); update(); } inline bool miniMapWidth() const { return m_miniMapWidth; } inline void setMiniMapWidth(int width) { m_miniMapWidth = width; updateGeometry(); update(); } inline void queuePixmapUpdate() { m_updateTimer.start(); } Q_SIGNALS: void sliderMMBMoved(int value); protected: void mousePressEvent(QMouseEvent *e) Q_DECL_OVERRIDE; void mouseReleaseEvent(QMouseEvent *e) Q_DECL_OVERRIDE; void mouseMoveEvent(QMouseEvent *e) Q_DECL_OVERRIDE; void leaveEvent(QEvent *event) Q_DECL_OVERRIDE; bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE; void paintEvent(QPaintEvent *e) Q_DECL_OVERRIDE; void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE; void sliderChange(SliderChange change) Q_DECL_OVERRIDE; protected Q_SLOTS: void sliderMaybeMoved(int value); void marksChanged(); public Q_SLOTS: void updatePixmap(); private Q_SLOTS: void showTextPreview(); private: void showTextPreviewDelayed(); void hideTextPreview(); void redrawMarks(); void recomputeMarksPositions(); void miniMapPaintEvent(QPaintEvent *e); void normalPaintEvent(QPaintEvent *e); int minimapYToStdY(int y); const QColor charColor(const QVector &attributes, int &attributeIndex, const QList &decorations, const QColor &defaultColor, int x, QChar ch); bool m_middleMouseDown; bool m_leftMouseDown; KTextEditor::ViewPrivate *m_view; KTextEditor::DocumentPrivate *m_doc; class KateViewInternal *m_viewInternal; QPointer m_textPreview; QTimer m_delayTextPreviewTimer; QHash m_lines; bool m_showMarks; bool m_showMiniMap; bool m_miniMapAll; int m_miniMapWidth; QPixmap m_pixmap; int m_grooveHeight; QRect m_stdGroveRect; QRect m_mapGroveRect; QTimer m_updateTimer; QPoint m_toolTipPos; // lists of lines added/removed recently to avoid scrollbar flickering QHash m_linesAdded; int m_linesModified; static const unsigned char characterOpacity[256]; }; class KateIconBorder : public QWidget { Q_OBJECT public: KateIconBorder(KateViewInternal *internalView, QWidget *parent); virtual ~KateIconBorder(); // VERY IMPORTANT ;) QSize sizeHint() const Q_DECL_OVERRIDE; void updateFont(); int lineNumberWidth() const; void setIconBorderOn(bool enable); void setLineNumbersOn(bool enable); void setRelLineNumbersOn(bool enable); void setAnnotationBorderOn(bool enable); void setDynWrapIndicators(int state); int dynWrapIndicators() const { return m_dynWrapIndicators; } bool dynWrapIndicatorsOn() const { return m_dynWrapIndicatorsOn; } void setFoldingMarkersOn(bool enable); void toggleIconBorder() { setIconBorderOn(!iconBorderOn()); } void toggleLineNumbers() { setLineNumbersOn(!lineNumbersOn()); } void toggleFoldingMarkers() { setFoldingMarkersOn(!foldingMarkersOn()); } inline bool iconBorderOn() const { return m_iconBorderOn; } inline bool lineNumbersOn() const { return m_lineNumbersOn; } inline bool viRelNumbersOn() const { return m_relLineNumbersOn; } inline bool foldingMarkersOn() const { return m_foldingMarkersOn; } inline bool annotationBorderOn() const { return m_annotationBorderOn; } void updateForCursorLineChange(); enum BorderArea { None, LineNumbers, IconBorder, FoldingMarkers, AnnotationBorder, ModificationBorder }; BorderArea positionToArea(const QPoint &) const; public Q_SLOTS: void updateAnnotationBorderWidth(); void updateAnnotationLine(int line); void annotationModelChanged(KTextEditor::AnnotationModel *oldmodel, KTextEditor::AnnotationModel *newmodel); void displayRangeChanged(); private: void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE; void paintBorder(int x, int y, int width, int height); void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE; void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE; void mouseReleaseEvent(QMouseEvent *) Q_DECL_OVERRIDE; void mouseDoubleClickEvent(QMouseEvent *) Q_DECL_OVERRIDE; void leaveEvent(QEvent *event) Q_DECL_OVERRIDE; void wheelEvent(QWheelEvent *e) Q_DECL_OVERRIDE; void showMarkMenu(uint line, const QPoint &pos); void showAnnotationTooltip(int line, const QPoint &pos); void hideAnnotationTooltip(); void removeAnnotationHovering(); void showAnnotationMenu(int line, const QPoint &pos); int annotationLineWidth(int line); KTextEditor::ViewPrivate *m_view; KTextEditor::DocumentPrivate *m_doc; KateViewInternal *m_viewInternal; bool m_iconBorderOn: 1; bool m_lineNumbersOn: 1; bool m_relLineNumbersOn:1; bool m_updateRelLineNumbers:1; bool m_foldingMarkersOn: 1; bool m_dynWrapIndicatorsOn: 1; bool m_annotationBorderOn: 1; int m_dynWrapIndicators; int m_lastClickedLine; int m_cachedLNWidth; qreal m_maxCharWidth; int iconPaneWidth; int m_annotationBorderWidth; mutable QPixmap m_arrow; mutable QColor m_oldBackgroundColor; QPointer m_foldingPreview; KTextEditor::MovingRange *m_foldingRange; int m_nextHighlightBlock; int m_currentBlockLine; QTimer m_delayFoldingHlTimer; void showDelayedBlock(int line); void hideBlock(); private Q_SLOTS: void showBlock(); private: QString m_hoveredAnnotationGroupIdentifier; void initializeFoldingColors(); }; class KateViewEncodingAction: public KSelectAction { Q_OBJECT Q_PROPERTY(QString codecName READ currentCodecName WRITE setCurrentCodec) Q_PROPERTY(int codecMib READ currentCodecMib) public: KateViewEncodingAction(KTextEditor::DocumentPrivate *_doc, KTextEditor::ViewPrivate *_view, const QString &text, QObject *parent, bool saveAsMode = false); ~KateViewEncodingAction(); int mibForName(const QString &codecName, bool *ok = nullptr) const; QTextCodec *codecForMib(int mib) const; QTextCodec *currentCodec() const; bool setCurrentCodec(QTextCodec *codec); QString currentCodecName() const; bool setCurrentCodec(const QString &codecName); int currentCodecMib() const; bool setCurrentCodec(int mib); Q_SIGNALS: /** * Specific (proper) codec was selected */ void triggered(QTextCodec *codec); private: KTextEditor::DocumentPrivate *doc; KTextEditor::ViewPrivate *view; class Private { public: Private(KateViewEncodingAction *parent) : q(parent), currentSubAction(nullptr) { } void init(); void _k_subActionTriggered(QAction *); KateViewEncodingAction *q; QAction *currentSubAction; }; Private *const d; Q_PRIVATE_SLOT(d, void _k_subActionTriggered(QAction *)) const bool m_saveAsMode; private Q_SLOTS: void setEncoding(const QString &e); void slotAboutToShow(); }; class KateViewBar; class KateViewBarWidget : public QWidget { Q_OBJECT friend class KateViewBar; public: explicit KateViewBarWidget(bool addCloseButton, QWidget *parent = nullptr); virtual void closed() {} /// returns the currently associated KateViewBar and 0, if it is not associated KateViewBar *viewBar() { return m_viewBar; } protected: /** * @return widget that should be used to add controls to bar widget */ QWidget *centralWidget() { return m_centralWidget; } Q_SIGNALS: void hideMe(); // for friend class KateViewBar private: void setAssociatedViewBar(KateViewBar *bar) { m_viewBar = bar; } private: QWidget *m_centralWidget; KateViewBar *m_viewBar; // 0-pointer, if not added to a view bar }; class KateViewBar : public QWidget { Q_OBJECT public: KateViewBar(bool external, QWidget *parent, KTextEditor::ViewPrivate *view); /** * Adds a widget to this viewbar. * Widget is initially invisible, you should call showBarWidget, to show it. * Several widgets can be added to the bar, but only one can be visible */ void addBarWidget(KateViewBarWidget *newBarWidget); /** * Removes a widget from this viewbar. * Removing a widget makes sense if it takes a lot of space vertically, * because we use a QStackedWidget to maintain the same height for all * widgets in the viewbar. */ void removeBarWidget(KateViewBarWidget *barWidget); /** * @return if viewbar has widget @p barWidget */ bool hasBarWidget(KateViewBarWidget *barWidget) const; /** * Shows barWidget that was previously added with addBarWidget. * @see hideCurrentBarWidget */ void showBarWidget(KateViewBarWidget *barWidget); /** * Adds widget that will be always shown in the viewbar. * After adding permanent widget viewbar is immediately shown. * ViewBar with permanent widget won't hide itself * until permanent widget is removed. * OTOH showing/hiding regular barWidgets will work as usual * (they will be shown above permanent widget) * * If permanent widget already exists, asserts! */ void addPermanentBarWidget(KateViewBarWidget *barWidget); /** * Removes permanent bar widget from viewbar. * If no other viewbar widgets are shown, viewbar gets hidden. * * barWidget is not deleted, caller must do it if it wishes */ void removePermanentBarWidget(KateViewBarWidget *barWidget); /** * @return if viewbar has permanent widget @p barWidget */ bool hasPermanentWidget(KateViewBarWidget *barWidget) const; /** * @return true if the KateViewBar is hidden or displays a permanentBarWidget */ bool hiddenOrPermanent() const; public Q_SLOTS: /** * Hides currently shown bar widget */ void hideCurrentBarWidget(); protected: void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; void hideEvent(QHideEvent *event) Q_DECL_OVERRIDE; private: /** * Shows or hides whole viewbar */ void setViewBarVisible(bool visible); bool m_external; private: KTextEditor::ViewPrivate *m_view; QStackedWidget *m_stack; KateViewBarWidget *m_permanentBarWidget; QVBoxLayout *m_layout; }; class KTEXTEDITOR_EXPORT KateCommandLineBar : public KateViewBarWidget { Q_OBJECT public: explicit KateCommandLineBar(KTextEditor::ViewPrivate *view, QWidget *parent = nullptr); ~KateCommandLineBar(); void setText(const QString &text, bool selected = true); void execute(const QString &text); public Q_SLOTS: void showHelpPage(); private: class KateCmdLineEdit *m_lineEdit; }; class KateCmdLineEdit : public KLineEdit { Q_OBJECT public: KateCmdLineEdit(KateCommandLineBar *bar, KTextEditor::ViewPrivate *view); bool event(QEvent *e) Q_DECL_OVERRIDE; void hideEvent(QHideEvent *e) Q_DECL_OVERRIDE; Q_SIGNALS: void hideRequested(); public Q_SLOTS: void slotReturnPressed(const QString &cmd); private Q_SLOTS: void hideLineEdit(); protected: void focusInEvent(QFocusEvent *ev) Q_DECL_OVERRIDE; void keyPressEvent(QKeyEvent *ev) Q_DECL_OVERRIDE; private: /** * Parse an expression denoting a position in the document. * Return the position as an integer. * Examples of expressions are "10" (the 10th line), * "$" (the last line), "." (the current line), * "'a" (the mark 'a), "/foo/" (a forwards search for "foo"), * and "?bar?" (a backwards search for "bar"). * @param string the expression to parse * @return the position, an integer */ int calculatePosition(QString string); void fromHistory(bool up); QString helptext(const QPoint &) const; KTextEditor::ViewPrivate *m_view; KateCommandLineBar *m_bar; bool m_msgMode; QString m_oldText; uint m_histpos; ///< position in the history uint m_cmdend; ///< the point where a command ends in the text, if we have a valid one. KTextEditor::Command *m_command; ///< For completing flags/args and interactiveness class KateCmdLnWhatsThis *m_help; QTimer *m_hideTimer; }; class KatePasteMenu : public KActionMenu { Q_OBJECT public: KatePasteMenu(const QString &text, KTextEditor::ViewPrivate *view); private: KTextEditor::ViewPrivate *m_view; private Q_SLOTS: void slotAboutToShow(); void paste(); }; #endif diff --git a/src/view/kateviewinternal.cpp b/src/view/kateviewinternal.cpp index fc6218e0..c56bcff9 100644 --- a/src/view/kateviewinternal.cpp +++ b/src/view/kateviewinternal.cpp @@ -1,3833 +1,3837 @@ /* This file is part of the KDE libraries Copyright (C) 2002 John Firebaugh Copyright (C) 2002 Joseph Wenninger Copyright (C) 2002,2003 Christoph Cullmann Copyright (C) 2002-2007 Hamish Rodda Copyright (C) 2003 Anakim Border Copyright (C) 2007 Mirko Stocker Copyright (C) 2007 Matthew Woehlke Copyright (C) 2008 Erlend Hamberg Based on: KWriteView : Copyright (C) 1999 Jochen Wilhelmy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kateviewinternal.h" #include "kateview.h" #include "kateviewhelpers.h" #include "katehighlight.h" #include "katebuffer.h" #include "katerenderer.h" #include "kateconfig.h" #include "katelayoutcache.h" #include "katecompletionwidget.h" #include "spellcheck/spellingmenu.h" #include "kateviewaccessible.h" #include "katetextanimation.h" #include "katemessagewidget.h" #include "kateglobal.h" #include "kateabstractinputmodefactory.h" #include "kateabstractinputmode.h" #include "katepartdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const bool debugPainting = false; class ZoomEventFilter { public: ZoomEventFilter() = default; bool detectZoomingEvent(QWheelEvent *e, Qt::KeyboardModifiers modifier = Qt::ControlModifier) { Qt::KeyboardModifiers modState = e->modifiers(); if (modState == modifier) { if (m_lastWheelEvent.isValid()) { const qint64 deltaT = m_lastWheelEvent.elapsed(); // Pressing the specified modifier key within 200ms of the previous "unmodified" // wheelevent is not allowed to toggle on text zooming if (m_lastWheelEventUnmodified && deltaT < 200) { m_ignoreZoom = true; } else if (deltaT > 1000) { // the protection is kept active for 1s after the last wheel event // TODO: this value should be tuned, preferrably by someone using // Ctrl+Wheel zooming frequently. m_ignoreZoom = false; } } else { // we can't say anything and have to assume there's nothing // accidental to the modifier being pressed. m_ignoreZoom = false; } m_lastWheelEventUnmodified = false; if (m_ignoreZoom) { // unset the modifier so the view scrollbars can handle the scroll // event and produce normal, not accelerated scrolling modState &= ~modifier; e->setModifiers(modState); } } else { // state is reset after any wheel event without the zoom modifier m_lastWheelEventUnmodified = true; m_ignoreZoom = false; } m_lastWheelEvent.start(); // inform the caller whether this event is allowed to trigger text zooming. return !m_ignoreZoom && modState == modifier; } protected: QElapsedTimer m_lastWheelEvent; bool m_ignoreZoom = false; bool m_lastWheelEventUnmodified = false; }; KateViewInternal::KateViewInternal(KTextEditor::ViewPrivate *view) : QWidget(view) , editSessionNumber(0) , editIsRunning(false) , m_view(view) , m_cursor(doc()->buffer(), KTextEditor::Cursor(0, 0), Kate::TextCursor::MoveOnInsert) , m_mouse() , m_possibleTripleClick(false) , m_completionItemExpanded(false) , m_bm(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) , m_bmStart(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) , m_bmEnd(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) , m_bmLastFlashPos(doc()->newMovingCursor(KTextEditor::Cursor::invalid())) , m_dummy(nullptr) // stay on cursor will avoid that the view scroll around on press return at beginning , m_startPos(doc()->buffer(), KTextEditor::Cursor(0, 0), Kate::TextCursor::StayOnInsert) , m_visibleLineCount(0) , m_madeVisible(false) , m_shiftKeyPressed(false) , m_autoCenterLines(0) , m_minLinesVisible(0) , m_selChangedByUser(false) , m_selectAnchor(-1, -1) , m_selectionMode(Default) , m_layoutCache(new KateLayoutCache(renderer(), this)) , m_preserveX(false) , m_preservedX(0) , m_cachedMaxStartPos(-1, -1) , m_dragScrollTimer(this) , m_scrollTimer(this) , m_cursorTimer(this) , m_textHintTimer(this) , m_textHintDelay(500) , m_textHintPos(-1, -1) , m_imPreeditRange(nullptr) { QList factories = KTextEditor::EditorPrivate::self()->inputModeFactories(); Q_FOREACH(KateAbstractInputModeFactory *factory, factories) { KateAbstractInputMode *m = factory->createInputMode(this); m_inputModes.insert(m->viewInputMode(), m); } m_currentInputMode = m_inputModes[KTextEditor::View::NormalInputMode]; // TODO: twisted, but needed setMinimumSize(0, 0); setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_InputMethodEnabled); // invalidate m_selectionCached.start(), or keyb selection is screwed initially m_selectionCached = KTextEditor::Range::invalid(); // bracket markers are only for this view and should not be printed m_bm->setView(m_view); m_bmStart->setView(m_view); m_bmEnd->setView(m_view); m_bm->setAttributeOnlyForViews(true); m_bmStart->setAttributeOnlyForViews(true); m_bmEnd->setAttributeOnlyForViews(true); // use z depth defined in moving ranges interface m_bm->setZDepth(-1000.0); m_bmStart->setZDepth(-1000.0); m_bmEnd->setZDepth(-1000.0); // update mark attributes updateBracketMarkAttributes(); // // scrollbar for lines // m_lineScroll = new KateScrollBar(Qt::Vertical, this); m_lineScroll->show(); m_lineScroll->setTracking(true); m_lineScroll->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); // Hijack the line scroller's controls, so we can scroll nicely for word-wrap connect(m_lineScroll, SIGNAL(actionTriggered(int)), SLOT(scrollAction(int))); connect(m_lineScroll, SIGNAL(sliderMoved(int)), SLOT(scrollLines(int))); connect(m_lineScroll, SIGNAL(sliderMMBMoved(int)), SLOT(scrollLines(int))); connect(m_lineScroll, SIGNAL(valueChanged(int)), SLOT(scrollLines(int))); // // scrollbar for columns // m_columnScroll = new QScrollBar(Qt::Horizontal, m_view); if (m_view->dynWordWrap()) { m_columnScroll->hide(); } else { m_columnScroll->show(); } m_columnScroll->setTracking(true); m_startX = 0; connect(m_columnScroll, SIGNAL(valueChanged(int)), SLOT(scrollColumns(int))); // bottom corner box m_dummy = new QWidget(m_view); m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height()); m_dummy->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); if (m_view->dynWordWrap()) { m_dummy->hide(); } else { m_dummy->show(); } cache()->setWrap(m_view->dynWordWrap()); // // iconborder ;) // m_leftBorder = new KateIconBorder(this, m_view); m_leftBorder->show(); // update view if folding ranges change connect(&m_view->textFolding(), SIGNAL(foldingRangesChanged()), SLOT(slotRegionVisibilityChanged())); m_displayCursor.setPosition(0, 0); setAcceptDrops(true); m_zoomEventFilter = new ZoomEventFilter(); // event filter installEventFilter(this); // set initial cursor m_mouseCursor = Qt::IBeamCursor; setCursor(m_mouseCursor); // call mouseMoveEvent also if no mouse button is pressed setMouseTracking(true); m_dragInfo.state = diNone; // timers connect(&m_dragScrollTimer, SIGNAL(timeout()), this, SLOT(doDragScroll())); connect(&m_scrollTimer, SIGNAL(timeout()), this, SLOT(scrollTimeout())); connect(&m_cursorTimer, SIGNAL(timeout()), this, SLOT(cursorTimeout())); connect(&m_textHintTimer, SIGNAL(timeout()), this, SLOT(textHintTimeout())); // selection changed to set anchor connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(viewSelectionChanged())); #ifndef QT_NO_ACCESSIBILITY QAccessible::installFactory(accessibleInterfaceFactory); #endif connect(doc(), &KTextEditor::DocumentPrivate::textInserted, this, &KateViewInternal::documentTextInserted); connect(doc(), &KTextEditor::DocumentPrivate::textRemoved, this, &KateViewInternal::documentTextRemoved); // update is called in KTextEditor::ViewPrivate, after construction and layout is over // but before any other kateviewinternal call } KateViewInternal::~KateViewInternal() { // delete text animation object here, otherwise it updates the view in its destructor delete m_textAnimation; #ifndef QT_NO_ACCESSIBILITY QAccessible::removeFactory(accessibleInterfaceFactory); #endif // kill preedit ranges delete m_imPreeditRange; qDeleteAll(m_imPreeditRangeChildren); qDeleteAll(m_inputModes); // delete bracket markers delete m_bm; delete m_bmStart; delete m_bmEnd; delete m_zoomEventFilter; } void KateViewInternal::prepareForDynWrapChange() { // Which is the current view line? m_wrapChangeViewLine = cache()->displayViewLine(m_displayCursor, true); } void KateViewInternal::dynWrapChanged() { m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height()); if (m_view->dynWordWrap()) { m_columnScroll->hide(); m_dummy->hide(); } else { // column scrollbar + bottom corner box m_columnScroll->show(); m_dummy->show(); } cache()->setWrap(m_view->dynWordWrap()); updateView(); if (m_view->dynWordWrap()) { scrollColumns(0); } // Determine where the cursor should be to get the cursor on the same view line if (m_wrapChangeViewLine != -1) { KTextEditor::Cursor newStart = viewLineOffset(m_displayCursor, -m_wrapChangeViewLine); makeVisible(newStart, newStart.column(), true); } else { update(); } } KTextEditor::Cursor KateViewInternal::endPos() const { // Hrm, no lines laid out at all?? if (!cache()->viewCacheLineCount()) { return KTextEditor::Cursor(); } for (int i = qMin(linesDisplayed() - 1, cache()->viewCacheLineCount() - 1); i >= 0; i--) { const KateTextLayout &thisLine = cache()->viewLine(i); if (thisLine.line() == -1) { continue; } if (thisLine.virtualLine() >= m_view->textFolding().visibleLines()) { // Cache is too out of date return KTextEditor::Cursor(m_view->textFolding().visibleLines() - 1, doc()->lineLength(m_view->textFolding().visibleLineToLine(m_view->textFolding().visibleLines() - 1))); } return KTextEditor::Cursor(thisLine.virtualLine(), thisLine.wrap() ? thisLine.endCol() - 1 : thisLine.endCol()); } // can happen, if view is still invisible return KTextEditor::Cursor(); } int KateViewInternal::endLine() const { return endPos().line(); } KateTextLayout KateViewInternal::yToKateTextLayout(int y) const { if (y < 0 || y > size().height()) { return KateTextLayout::invalid(); } int range = y / renderer()->lineHeight(); // lineRanges is always bigger than 0, after the initial updateView call if (range >= 0 && range < cache()->viewCacheLineCount()) { return cache()->viewLine(range); } return KateTextLayout::invalid(); } int KateViewInternal::lineToY(int viewLine) const { return (viewLine - startLine()) * renderer()->lineHeight(); } void KateViewInternal::slotIncFontSizes(qreal step) { renderer()->increaseFontSizes(step); } void KateViewInternal::slotDecFontSizes(qreal step) { renderer()->decreaseFontSizes(step); } /** * Line is the real line number to scroll to. */ void KateViewInternal::scrollLines(int line) { KTextEditor::Cursor newPos(line, 0); scrollPos(newPos); } // This can scroll less than one true line void KateViewInternal::scrollViewLines(int offset) { KTextEditor::Cursor c = viewLineOffset(startPos(), offset); scrollPos(c); bool blocked = m_lineScroll->blockSignals(true); m_lineScroll->setValue(startLine()); m_lineScroll->blockSignals(blocked); } void KateViewInternal::scrollAction(int action) { switch (action) { case QAbstractSlider::SliderSingleStepAdd: scrollNextLine(); break; case QAbstractSlider::SliderSingleStepSub: scrollPrevLine(); break; case QAbstractSlider::SliderPageStepAdd: scrollNextPage(); break; case QAbstractSlider::SliderPageStepSub: scrollPrevPage(); break; case QAbstractSlider::SliderToMinimum: top_home(); break; case QAbstractSlider::SliderToMaximum: bottom_end(); break; } } void KateViewInternal::scrollNextPage() { scrollViewLines(qMax(linesDisplayed() - 1, 0)); } void KateViewInternal::scrollPrevPage() { scrollViewLines(-qMax(linesDisplayed() - 1, 0)); } void KateViewInternal::scrollPrevLine() { scrollViewLines(-1); } void KateViewInternal::scrollNextLine() { scrollViewLines(1); } KTextEditor::Cursor KateViewInternal::maxStartPos(bool changed) { cache()->setAcceptDirtyLayouts(true); if (m_cachedMaxStartPos.line() == -1 || changed) { KTextEditor::Cursor end(m_view->textFolding().visibleLines() - 1, doc()->lineLength(m_view->textFolding().visibleLineToLine(m_view->textFolding().visibleLines() - 1))); if (m_view->config()->scrollPastEnd()) { m_cachedMaxStartPos = viewLineOffset(end, -m_minLinesVisible); } else { m_cachedMaxStartPos = viewLineOffset(end, -(linesDisplayed() - 1)); } } cache()->setAcceptDirtyLayouts(false); return m_cachedMaxStartPos; } // c is a virtual cursor void KateViewInternal::scrollPos(KTextEditor::Cursor &c, bool force, bool calledExternally, bool emitSignals) { if (!force && ((!m_view->dynWordWrap() && c.line() == startLine()) || c == startPos())) { return; } if (c.line() < 0) { c.setLine(0); } KTextEditor::Cursor limit = maxStartPos(); if (c > limit) { c = limit; // Re-check we're not just scrolling to the same place if (!force && ((!m_view->dynWordWrap() && c.line() == startLine()) || c == startPos())) { return; } } int viewLinesScrolled = 0; // only calculate if this is really used and useful, could be wrong here, please recheck // for larger scrolls this makes 2-4 seconds difference on my xeon with dyn. word wrap on // try to get it really working ;) bool viewLinesScrolledUsable = !force && (c.line() >= startLine() - linesDisplayed() - 1) && (c.line() <= endLine() + linesDisplayed() + 1); if (viewLinesScrolledUsable) { viewLinesScrolled = cache()->displayViewLine(c); } m_startPos.setPosition(c); // set false here but reversed if we return to makeVisible m_madeVisible = false; if (viewLinesScrolledUsable) { int lines = linesDisplayed(); if (m_view->textFolding().visibleLines() < lines) { KTextEditor::Cursor end(m_view->textFolding().visibleLines() - 1, doc()->lineLength(m_view->textFolding().visibleLineToLine(m_view->textFolding().visibleLines() - 1))); lines = qMin(linesDisplayed(), cache()->displayViewLine(end) + 1); } Q_ASSERT(lines >= 0); if (!calledExternally && qAbs(viewLinesScrolled) < lines && // NOTE: on some machines we must update if the floating widget is visible // otherwise strange painting bugs may occur during scrolling... - !((m_view->m_floatTopMessageWidget && m_view->m_floatTopMessageWidget->isVisible()) || - (m_view->m_floatBottomMessageWidget && m_view->m_floatBottomMessageWidget->isVisible())) - ) - { + !((m_view->m_messageWidgets[KTextEditor::Message::TopInView] && + m_view->m_messageWidgets[KTextEditor::Message::TopInView]->isVisible()) + ||(m_view->m_messageWidgets[KTextEditor::Message::CenterInView] && + m_view->m_messageWidgets[KTextEditor::Message::CenterInView]->isVisible()) + ||(m_view->m_messageWidgets[KTextEditor::Message::BottomInView] && + m_view->m_messageWidgets[KTextEditor::Message::BottomInView]->isVisible()) + ) + ) { updateView(false, viewLinesScrolled); int scrollHeight = -(viewLinesScrolled * (int)renderer()->lineHeight()); // scroll excluding child widgets (floating notifications) scroll(0, scrollHeight, rect()); m_leftBorder->scroll(0, scrollHeight); if (emitSignals) { emit m_view->verticalScrollPositionChanged(m_view, c); emit m_view->displayRangeChanged(m_view); } return; } } updateView(); update(); m_leftBorder->update(); if (emitSignals) { emit m_view->verticalScrollPositionChanged(m_view, c); emit m_view->displayRangeChanged(m_view); } } void KateViewInternal::scrollColumns(int x) { if (x < 0) { x = 0; } if (x > m_columnScroll->maximum()) { x = m_columnScroll->maximum(); } if (x == m_startX) { return; } int dx = m_startX - x; m_startX = x; if (qAbs(dx) < width()) { // scroll excluding child widgets (floating notifications) scroll(dx, 0, rect()); } else { update(); } emit m_view->horizontalScrollPositionChanged(m_view); emit m_view->displayRangeChanged(m_view); bool blocked = m_columnScroll->blockSignals(true); m_columnScroll->setValue(m_startX); m_columnScroll->blockSignals(blocked); } // If changed is true, the lines that have been set dirty have been updated. void KateViewInternal::updateView(bool changed, int viewLinesScrolled) { doUpdateView(changed, viewLinesScrolled); if (changed) { updateDirty(); } } void KateViewInternal::doUpdateView(bool changed, int viewLinesScrolled) { if (!isVisible() && !viewLinesScrolled && !changed) { return; //When this view is not visible, don't do anything } bool blocked = m_lineScroll->blockSignals(true); if (width() != cache()->viewWidth()) { cache()->setViewWidth(width()); changed = true; } /* It was observed that height() could be negative here -- when the main Kate view has 0 as size (during creation), and there frame arount KateViewInternal. In which case we'd set the view cache to 0 (or less!) lines, and start allocating huge chunks of data, later. */ int newSize = (qMax(0, height()) / renderer()->lineHeight()) + 1; cache()->updateViewCache(startPos(), newSize, viewLinesScrolled); m_visibleLineCount = newSize; KTextEditor::Cursor maxStart = maxStartPos(changed); int maxLineScrollRange = maxStart.line(); if (m_view->dynWordWrap() && maxStart.column() != 0) { maxLineScrollRange++; } m_lineScroll->setRange(0, maxLineScrollRange); m_lineScroll->setValue(startPos().line()); m_lineScroll->setSingleStep(1); m_lineScroll->setPageStep(qMax(0, height()) / renderer()->lineHeight()); m_lineScroll->blockSignals(blocked); KateViewConfig::ScrollbarMode show_scrollbars = static_cast(view()->config()->showScrollbars()); bool visible = ((show_scrollbars == KateViewConfig::AlwaysOn) || ((show_scrollbars == KateViewConfig::ShowWhenNeeded) && (maxLineScrollRange != 0))); bool visible_dummy = visible; m_lineScroll->setVisible(visible); if (!m_view->dynWordWrap()) { int max = maxLen(startLine()) - width(); if (max < 0) { max = 0; } // if we lose the ability to scroll horizontally, move view to the far-left if (max == 0) { scrollColumns(0); } blocked = m_columnScroll->blockSignals(true); // disable scrollbar m_columnScroll->setDisabled(max == 0); visible = ((show_scrollbars == KateViewConfig::AlwaysOn) || ((show_scrollbars == KateViewConfig::ShowWhenNeeded) && (max != 0))); visible_dummy &= visible; m_columnScroll->setVisible(visible); m_columnScroll->setRange(0, max + (renderer()->spaceWidth() / 2)); // Add some space for the caret at EOL m_columnScroll->setValue(m_startX); // Approximate linescroll m_columnScroll->setSingleStep(renderer()->config()->fontMetrics().width(QLatin1Char('a'))); m_columnScroll->setPageStep(width()); m_columnScroll->blockSignals(blocked); } else { visible_dummy = false; } m_dummy->setVisible(visible_dummy); } /** * this function ensures a certain location is visible on the screen. * if endCol is -1, ignore making the columns visible. */ void KateViewInternal::makeVisible(const KTextEditor::Cursor &c, int endCol, bool force, bool center, bool calledExternally) { //qCDebug(LOG_KTE) << "MakeVisible start " << startPos() << " end " << endPos() << " -> request: " << c;// , new start [" << scroll.line << "," << scroll.col << "] lines " << (linesDisplayed() - 1) << " height " << height(); // if the line is in a folded region, unfold all the way up //if ( doc()->foldingTree()->findNodeForLine( c.line )->visible ) // qCDebug(LOG_KTE)<<"line ("< endPos())) { KTextEditor::Cursor scroll = viewLineOffset(c, -int(linesDisplayed()) / 2); scrollPos(scroll, false, calledExternally); } else if (c > viewLineOffset(startPos(), linesDisplayed() - m_minLinesVisible - 1)) { KTextEditor::Cursor scroll = viewLineOffset(c, -(linesDisplayed() - m_minLinesVisible - 1)); scrollPos(scroll, false, calledExternally); } else if (c < viewLineOffset(startPos(), m_minLinesVisible)) { KTextEditor::Cursor scroll = viewLineOffset(c, -m_minLinesVisible); scrollPos(scroll, false, calledExternally); } else { // Check to see that we're not showing blank lines KTextEditor::Cursor max = maxStartPos(); if (startPos() > max) { scrollPos(max, max.column(), calledExternally); } } if (!m_view->dynWordWrap() && (endCol != -1 || m_view->wrapCursor())) { KTextEditor::Cursor rc = toRealCursor(c); int sX = renderer()->cursorToX(cache()->textLayout(rc), rc, !m_view->wrapCursor()); int sXborder = sX - 8; if (sXborder < 0) { sXborder = 0; } if (sX < m_startX) { scrollColumns(sXborder); } else if (sX > m_startX + width()) { scrollColumns(sX - width() + 8); } } m_madeVisible = !force; #ifndef QT_NO_ACCESSIBILITY // FIXME -- is this needed? // QAccessible::updateAccessibility(this, KateCursorAccessible::ChildId, QAccessible::Focus); #endif } void KateViewInternal::slotRegionVisibilityChanged() { qCDebug(LOG_KTE); cache()->clear(); m_cachedMaxStartPos.setLine(-1); KTextEditor::Cursor max = maxStartPos(); if (startPos() > max) { scrollPos(max, false, false, false /* don't emit signals! */); } // if text was folded: make sure the cursor is on a visible line qint64 foldedRangeId = -1; if (!m_view->textFolding().isLineVisible(m_cursor.line(), &foldedRangeId)) { KTextEditor::Range foldingRange = m_view->textFolding().foldingRange(foldedRangeId); Q_ASSERT(foldingRange.start().isValid()); // set cursor to start of folding region updateCursor(foldingRange.start(), true); } else { // force an update of the cursor, since otherwise the m_displayCursor // line may be below the total amount of visible lines. updateCursor(m_cursor, true); } updateView(); update(); m_leftBorder->update(); // emit signals here, scrollPos has this disabled, to ensure we do this after all stuff is updated! emit m_view->verticalScrollPositionChanged(m_view, max); emit m_view->displayRangeChanged(m_view); } void KateViewInternal::slotRegionBeginEndAddedRemoved(unsigned int) { qCDebug(LOG_KTE); // FIXME: performance problem m_leftBorder->update(); } void KateViewInternal::showEvent(QShowEvent *e) { updateView(); QWidget::showEvent(e); } int KateViewInternal::linesDisplayed() const { int h = height(); // catch zero heights, even if should not happen int fh = qMax(1, renderer()->lineHeight()); // default to 1, there is always one line around.... // too many places calc with linesDisplayed() - 1 return qMax(1, (h - (h % fh)) / fh); } QPoint KateViewInternal::cursorToCoordinate(const KTextEditor::Cursor &cursor, bool realCursor, bool includeBorder) const { if (cursor.line() >= doc()->lines()) { return QPoint(-1, -1); } int viewLine = cache()->displayViewLine(realCursor ? toVirtualCursor(cursor) : cursor, true); if (viewLine < 0 || viewLine >= cache()->viewCacheLineCount()) { return QPoint(-1, -1); } const int y = (int)viewLine * renderer()->lineHeight(); KateTextLayout layout = cache()->viewLine(viewLine); if (cursor.column() > doc()->lineLength(cursor.line())) { return QPoint(-1, -1); } int x = 0; // only set x value if we have a valid layout (bug #171027) if (layout.isValid()) { x = (int)layout.lineLayout().cursorToX(cursor.column()); } // else // qCDebug(LOG_KTE) << "Invalid Layout"; if (includeBorder) { x += m_leftBorder->width(); } x -= startX(); return QPoint(x, y); } QPoint KateViewInternal::cursorCoordinates(bool includeBorder) const { return cursorToCoordinate(m_displayCursor, false, includeBorder); } KTextEditor::Cursor KateViewInternal::findMatchingBracket() { KTextEditor::Cursor c; if (!m_bm->toRange().isValid()) { return KTextEditor::Cursor::invalid(); } Q_ASSERT(m_bmEnd->toRange().isValid()); Q_ASSERT(m_bmStart->toRange().isValid()); if (m_bmStart->toRange().contains(m_cursor) || m_bmStart->end() == m_cursor.toCursor()) { c = m_bmEnd->end(); } else if (m_bmEnd->toRange().contains(m_cursor) || m_bmEnd->end() == m_cursor.toCursor()) { c = m_bmStart->start(); } else { // should never happen: a range exists, but the cursor position is // neither at the start nor at the end... return KTextEditor::Cursor::invalid(); } return c; } void KateViewInternal::doReturn() { doc()->newLine(m_view); m_leftBorder->updateForCursorLineChange(); updateView(); } void KateViewInternal::doSmartNewline() { int ln = m_cursor.line(); Kate::TextLine line = doc()->kateTextLine(ln); int col = qMin(m_cursor.column(), line->firstChar()); if (col != -1) { while (line->length() > col && !(line->at(col).isLetterOrNumber() || line->at(col) == QLatin1Char('_')) && col < m_cursor.column()) { ++col; } } else { col = line->length(); // stay indented } doc()->editStart(); doc()->editWrapLine(ln, m_cursor.column()); doc()->insertText(KTextEditor::Cursor(ln + 1, 0), line->string(0, col)); doc()->editEnd(); updateView(); } void KateViewInternal::doDelete() { doc()->del(m_view, m_cursor); } void KateViewInternal::doBackspace() { doc()->backspace(m_view, m_cursor); } void KateViewInternal::doTabulator() { doc()->insertTab(m_view, m_cursor); } void KateViewInternal::doTranspose() { doc()->transpose(m_cursor); } void KateViewInternal::doDeletePrevWord() { doc()->editStart(); wordPrev(true); KTextEditor::Range selection = m_view->selectionRange(); m_view->removeSelectedText(); doc()->editEnd(); tagRange(selection, true); updateDirty(); } void KateViewInternal::doDeleteNextWord() { doc()->editStart(); wordNext(true); KTextEditor::Range selection = m_view->selectionRange(); m_view->removeSelectedText(); doc()->editEnd(); tagRange(selection, true); updateDirty(); } class CalculatingCursor { public: // These constructors constrain their arguments to valid positions // before only the third one did, but that leads to crashs // see bug 227449 CalculatingCursor(KateViewInternal *vi) : m_vi(vi) { makeValid(); } CalculatingCursor(KateViewInternal *vi, const KTextEditor::Cursor &c) : m_cursor(c) , m_vi(vi) { makeValid(); } CalculatingCursor(KateViewInternal *vi, int line, int col) : m_cursor(line, col) , m_vi(vi) { makeValid(); } virtual ~CalculatingCursor() { } int line() const { return m_cursor.line(); } int column() const { return m_cursor.column(); } operator KTextEditor::Cursor() const { return m_cursor; } virtual CalculatingCursor &operator+=(int n) = 0; virtual CalculatingCursor &operator-=(int n) = 0; CalculatingCursor &operator++() { return operator+=(1); } CalculatingCursor &operator--() { return operator-=(1); } void makeValid() { m_cursor.setLine(qBound(0, line(), int(doc()->lines() - 1))); if (view()->wrapCursor()) { m_cursor.setColumn(qBound(0, column(), doc()->lineLength(line()))); } else { m_cursor.setColumn(qMax(0, column())); } Q_ASSERT(valid()); } void toEdge(KateViewInternal::Bias bias) { if (bias == KateViewInternal::left) { m_cursor.setColumn(0); } else if (bias == KateViewInternal::right) { m_cursor.setColumn(doc()->lineLength(line())); } } bool atEdge() const { return atEdge(KateViewInternal::left) || atEdge(KateViewInternal::right); } bool atEdge(KateViewInternal::Bias bias) const { switch (bias) { case KateViewInternal::left: return column() == 0; case KateViewInternal::none: return atEdge(); case KateViewInternal::right: return column() >= doc()->lineLength(line()); default: Q_ASSERT(false); return false; } } protected: bool valid() const { return line() >= 0 && line() < doc()->lines() && column() >= 0 && (!view()->wrapCursor() || column() <= doc()->lineLength(line())); } KTextEditor::ViewPrivate *view() { return m_vi->m_view; } const KTextEditor::ViewPrivate *view() const { return m_vi->m_view; } KTextEditor::DocumentPrivate *doc() { return view()->doc(); } const KTextEditor::DocumentPrivate *doc() const { return view()->doc(); } KTextEditor::Cursor m_cursor; KateViewInternal *m_vi; }; class BoundedCursor : public CalculatingCursor { public: BoundedCursor(KateViewInternal *vi) : CalculatingCursor(vi) {} BoundedCursor(KateViewInternal *vi, const KTextEditor::Cursor &c) : CalculatingCursor(vi, c) {} BoundedCursor(KateViewInternal *vi, int line, int col) : CalculatingCursor(vi, line, col) {} CalculatingCursor &operator+=(int n) Q_DECL_OVERRIDE { KateLineLayoutPtr thisLine = m_vi->cache()->line(line()); if (!thisLine->isValid()) { qCWarning(LOG_KTE) << "Did not retrieve valid layout for line " << line(); return *this; } const bool wrapCursor = view()->wrapCursor(); int maxColumn = -1; if (n >= 0) { for (int i = 0; i < n; i++) { if (column() >= thisLine->length()) { if (wrapCursor) { break; } else if (view()->dynWordWrap()) { // Don't go past the edge of the screen in dynamic wrapping mode if (maxColumn == -1) { maxColumn = thisLine->length() + ((m_vi->width() - thisLine->widthOfLastLine()) / m_vi->renderer()->spaceWidth()) - 1; } if (column() >= maxColumn) { m_cursor.setColumn(maxColumn); break; } m_cursor.setColumn(column() + 1); } else { m_cursor.setColumn(column() + 1); } } else { m_cursor.setColumn(thisLine->layout()->nextCursorPosition(column())); } } } else { for (int i = 0; i > n; i--) { if (column() >= thisLine->length()) { m_cursor.setColumn(column() - 1); } else if (column() == 0) { break; } else { m_cursor.setColumn(thisLine->layout()->previousCursorPosition(column())); } } } Q_ASSERT(valid()); return *this; } CalculatingCursor &operator-=(int n) Q_DECL_OVERRIDE { return operator+=(-n); } }; class WrappingCursor : public CalculatingCursor { public: WrappingCursor(KateViewInternal *vi) : CalculatingCursor(vi) {} WrappingCursor(KateViewInternal *vi, const KTextEditor::Cursor &c) : CalculatingCursor(vi, c) {} WrappingCursor(KateViewInternal *vi, int line, int col) : CalculatingCursor(vi, line, col) {} CalculatingCursor &operator+=(int n) Q_DECL_OVERRIDE { KateLineLayoutPtr thisLine = m_vi->cache()->line(line()); if (!thisLine->isValid()) { qCWarning(LOG_KTE) << "Did not retrieve a valid layout for line " << line(); return *this; } if (n >= 0) { for (int i = 0; i < n; i++) { if (column() >= thisLine->length()) { // Have come to the end of a line if (line() >= doc()->lines() - 1) // Have come to the end of the document { break; } // Advance to the beginning of the next line m_cursor.setColumn(0); m_cursor.setLine(line() + 1); // Retrieve the next text range thisLine = m_vi->cache()->line(line()); if (!thisLine->isValid()) { qCWarning(LOG_KTE) << "Did not retrieve a valid layout for line " << line(); return *this; } continue; } m_cursor.setColumn(thisLine->layout()->nextCursorPosition(column())); } } else { for (int i = 0; i > n; i--) { if (column() == 0) { // Have come to the start of the document if (line() == 0) { break; } // Start going back to the end of the last line m_cursor.setLine(line() - 1); // Retrieve the next text range thisLine = m_vi->cache()->line(line()); if (!thisLine->isValid()) { qCWarning(LOG_KTE) << "Did not retrieve a valid layout for line " << line(); return *this; } // Finish going back to the end of the last line m_cursor.setColumn(thisLine->length()); continue; } if (column() > thisLine->length()) { m_cursor.setColumn(column() - 1); } else { m_cursor.setColumn(thisLine->layout()->previousCursorPosition(column())); } } } Q_ASSERT(valid()); return *this; } CalculatingCursor &operator-=(int n) Q_DECL_OVERRIDE { return operator+=(-n); } }; void KateViewInternal::moveChar(KateViewInternal::Bias bias, bool sel) { KTextEditor::Cursor c; if (m_view->wrapCursor()) { c = WrappingCursor(this, m_cursor) += bias; } else { c = BoundedCursor(this, m_cursor) += bias; } updateSelection(c, sel); updateCursor(c); } void KateViewInternal::cursorPrevChar(bool sel) { if (! m_view->wrapCursor() && m_cursor.column() == 0) { return; } moveChar(KateViewInternal::left, sel); } void KateViewInternal::cursorNextChar(bool sel) { moveChar(KateViewInternal::right, sel); } void KateViewInternal::wordPrev(bool sel) { WrappingCursor c(this, m_cursor); // First we skip backwards all space. // Then we look up into which category the current position falls: // 1. a "word" character // 2. a "non-word" character (except space) // 3. the beginning of the line // and skip all preceding characters that fall into this class. // The code assumes that space is never part of the word character class. KateHighlighting *h = doc()->highlight(); if (!c.atEdge(left)) { while (!c.atEdge(left) && doc()->line(c.line())[ c.column() - 1 ].isSpace()) { --c; } } if (c.atEdge(left)) { --c; } else if (h->isInWord(doc()->line(c.line())[ c.column() - 1 ])) { while (!c.atEdge(left) && h->isInWord(doc()->line(c.line())[ c.column() - 1 ])) { --c; } } else { while (!c.atEdge(left) && !h->isInWord(doc()->line(c.line())[ c.column() - 1 ]) // in order to stay symmetric to wordLeft() // we must not skip space preceding a non-word sequence && !doc()->line(c.line())[ c.column() - 1 ].isSpace()) { --c; } } updateSelection(c, sel); updateCursor(c); } void KateViewInternal::wordNext(bool sel) { WrappingCursor c(this, m_cursor); // We look up into which category the current position falls: // 1. a "word" character // 2. a "non-word" character (except space) // 3. the end of the line // and skip all following characters that fall into this class. // If the skipped characters are followed by space, we skip that too. // The code assumes that space is never part of the word character class. KateHighlighting *h = doc()->highlight(); if (c.atEdge(right)) { ++c; } else if (h->isInWord(doc()->line(c.line())[ c.column() ])) { while (!c.atEdge(right) && h->isInWord(doc()->line(c.line())[ c.column() ])) { ++c; } } else { while (!c.atEdge(right) && !h->isInWord(doc()->line(c.line())[ c.column() ]) // we must not skip space, because if that space is followed // by more non-word characters, we would skip them, too && !doc()->line(c.line())[ c.column() ].isSpace()) { ++c; } } while (!c.atEdge(right) && doc()->line(c.line())[ c.column() ].isSpace()) { ++c; } updateSelection(c, sel); updateCursor(c); } void KateViewInternal::moveEdge(KateViewInternal::Bias bias, bool sel) { BoundedCursor c(this, m_cursor); c.toEdge(bias); updateSelection(c, sel); updateCursor(c); } void KateViewInternal::home(bool sel) { if (m_view->dynWordWrap() && currentLayout().startCol()) { // Allow us to go to the real start if we're already at the start of the view line if (m_cursor.column() != currentLayout().startCol()) { KTextEditor::Cursor c = currentLayout().start(); updateSelection(c, sel); updateCursor(c); return; } } if (!doc()->config()->smartHome()) { moveEdge(left, sel); return; } Kate::TextLine l = doc()->kateTextLine(m_cursor.line()); if (!l) { return; } KTextEditor::Cursor c = m_cursor; int lc = l->firstChar(); if (lc < 0 || c.column() == lc) { c.setColumn(0); } else { c.setColumn(lc); } updateSelection(c, sel); updateCursor(c, true); } void KateViewInternal::end(bool sel) { KateTextLayout layout = currentLayout(); if (m_view->dynWordWrap() && layout.wrap()) { // Allow us to go to the real end if we're already at the end of the view line if (m_cursor.column() < layout.endCol() - 1) { KTextEditor::Cursor c(m_cursor.line(), layout.endCol() - 1); updateSelection(c, sel); updateCursor(c); return; } } if (!doc()->config()->smartHome()) { moveEdge(right, sel); return; } Kate::TextLine l = doc()->kateTextLine(m_cursor.line()); if (!l) { return; } // "Smart End", as requested in bugs #78258 and #106970 if (m_cursor.column() == doc()->lineLength(m_cursor.line())) { KTextEditor::Cursor c = m_cursor; c.setColumn(l->lastChar() + 1); updateSelection(c, sel); updateCursor(c, true); } else { moveEdge(right, sel); } } KateTextLayout KateViewInternal::currentLayout() const { return cache()->textLayout(m_cursor); } KateTextLayout KateViewInternal::previousLayout() const { int currentViewLine = cache()->viewLine(m_cursor); if (currentViewLine) { return cache()->textLayout(m_cursor.line(), currentViewLine - 1); } else { return cache()->textLayout(m_view->textFolding().visibleLineToLine(m_displayCursor.line() - 1), -1); } } KateTextLayout KateViewInternal::nextLayout() const { int currentViewLine = cache()->viewLine(m_cursor) + 1; if (currentViewLine >= cache()->line(m_cursor.line())->viewLineCount()) { currentViewLine = 0; return cache()->textLayout(m_view->textFolding().visibleLineToLine(m_displayCursor.line() + 1), currentViewLine); } else { return cache()->textLayout(m_cursor.line(), currentViewLine); } } /* * This returns the cursor which is offset by (offset) view lines. * This is the main function which is called by code not specifically dealing with word-wrap. * The opposite conversion (cursor to offset) can be done with cache()->displayViewLine(). * * The cursors involved are virtual cursors (ie. equivalent to m_displayCursor) */ KTextEditor::Cursor KateViewInternal::viewLineOffset(const KTextEditor::Cursor &virtualCursor, int offset, bool keepX) { if (!m_view->dynWordWrap()) { KTextEditor::Cursor ret(qMin((int)m_view->textFolding().visibleLines() - 1, virtualCursor.line() + offset), 0); if (ret.line() < 0) { ret.setLine(0); } if (keepX) { int realLine = m_view->textFolding().visibleLineToLine(ret.line()); KateTextLayout t = cache()->textLayout(realLine, 0); Q_ASSERT(t.isValid()); ret.setColumn(renderer()->xToCursor(t, m_preservedX, !m_view->wrapCursor()).column()); } return ret; } KTextEditor::Cursor realCursor = virtualCursor; realCursor.setLine(m_view->textFolding().visibleLineToLine(m_view->textFolding().lineToVisibleLine(virtualCursor.line()))); int cursorViewLine = cache()->viewLine(realCursor); int currentOffset = 0; int virtualLine = 0; bool forwards = (offset > 0) ? true : false; if (forwards) { currentOffset = cache()->lastViewLine(realCursor.line()) - cursorViewLine; if (offset <= currentOffset) { // the answer is on the same line KateTextLayout thisLine = cache()->textLayout(realCursor.line(), cursorViewLine + offset); Q_ASSERT(thisLine.virtualLine() == (int) m_view->textFolding().lineToVisibleLine(virtualCursor.line())); return KTextEditor::Cursor(virtualCursor.line(), thisLine.startCol()); } virtualLine = virtualCursor.line() + 1; } else { offset = -offset; currentOffset = cursorViewLine; if (offset <= currentOffset) { // the answer is on the same line KateTextLayout thisLine = cache()->textLayout(realCursor.line(), cursorViewLine - offset); Q_ASSERT(thisLine.virtualLine() == (int) m_view->textFolding().lineToVisibleLine(virtualCursor.line())); return KTextEditor::Cursor(virtualCursor.line(), thisLine.startCol()); } virtualLine = virtualCursor.line() - 1; } currentOffset++; while (virtualLine >= 0 && virtualLine < (int)m_view->textFolding().visibleLines()) { int realLine = m_view->textFolding().visibleLineToLine(virtualLine); KateLineLayoutPtr thisLine = cache()->line(realLine, virtualLine); if (!thisLine) { break; } for (int i = 0; i < thisLine->viewLineCount(); ++i) { if (offset == currentOffset) { KateTextLayout thisViewLine = thisLine->viewLine(i); if (!forwards) { // We actually want it the other way around int requiredViewLine = cache()->lastViewLine(realLine) - thisViewLine.viewLine(); if (requiredViewLine != thisViewLine.viewLine()) { thisViewLine = thisLine->viewLine(requiredViewLine); } } KTextEditor::Cursor ret(virtualLine, thisViewLine.startCol()); // keep column position if (keepX) { KTextEditor::Cursor realCursor = toRealCursor(virtualCursor); KateTextLayout t = cache()->textLayout(realCursor); // renderer()->cursorToX(t, realCursor, !m_view->wrapCursor()); realCursor = renderer()->xToCursor(thisViewLine, m_preservedX, !m_view->wrapCursor()); ret.setColumn(realCursor.column()); } return ret; } currentOffset++; } if (forwards) { virtualLine++; } else { virtualLine--; } } // Looks like we were asked for something a bit exotic. // Return the max/min valid position. if (forwards) { return KTextEditor::Cursor(m_view->textFolding().visibleLines() - 1, doc()->lineLength(m_view->textFolding().visibleLineToLine(m_view->textFolding().visibleLines() - 1))); } else { return KTextEditor::Cursor(0, 0); } } int KateViewInternal::lineMaxCursorX(const KateTextLayout &range) { if (!m_view->wrapCursor() && !range.wrap()) { return INT_MAX; } int maxX = range.endX(); if (maxX && range.wrap()) { QChar lastCharInLine = doc()->kateTextLine(range.line())->at(range.endCol() - 1); maxX -= renderer()->config()->fontMetrics().width(lastCharInLine); } return maxX; } int KateViewInternal::lineMaxCol(const KateTextLayout &range) { int maxCol = range.endCol(); if (maxCol && range.wrap()) { maxCol--; } return maxCol; } void KateViewInternal::cursorUp(bool sel) { if (!sel && m_view->completionWidget()->isCompletionActive()) { m_view->completionWidget()->cursorUp(); return; } // assert that the display cursor is in visible lines Q_ASSERT(m_displayCursor.line() < m_view->textFolding().visibleLines()); /** * move cursor to start of line, if we are at first line! */ if (m_displayCursor.line() == 0 && (!m_view->dynWordWrap() || cache()->viewLine(m_cursor) == 0)) { home(sel); return; } m_preserveX = true; KateTextLayout thisLine = currentLayout(); // This is not the first line because that is already simplified out above KateTextLayout pRange = previousLayout(); // Ensure we're in the right spot Q_ASSERT(m_cursor.line() == thisLine.line()); Q_ASSERT(m_cursor.column() >= thisLine.startCol()); Q_ASSERT(!thisLine.wrap() || m_cursor.column() < thisLine.endCol()); KTextEditor::Cursor c = renderer()->xToCursor(pRange, m_preservedX, !m_view->wrapCursor()); updateSelection(c, sel); updateCursor(c); } void KateViewInternal::cursorDown(bool sel) { if (!sel && m_view->completionWidget()->isCompletionActive()) { m_view->completionWidget()->cursorDown(); return; } /** * move cursor to end of line, if we are at last line! */ if ((m_displayCursor.line() >= m_view->textFolding().visibleLines() - 1) && (!m_view->dynWordWrap() || cache()->viewLine(m_cursor) == cache()->lastViewLine(m_cursor.line()))) { end(sel); return; } m_preserveX = true; KateTextLayout thisLine = currentLayout(); // This is not the last line because that is already simplified out above KateTextLayout nRange = nextLayout(); // Ensure we're in the right spot Q_ASSERT((m_cursor.line() == thisLine.line()) && (m_cursor.column() >= thisLine.startCol()) && (!thisLine.wrap() || m_cursor.column() < thisLine.endCol())); KTextEditor::Cursor c = renderer()->xToCursor(nRange, m_preservedX, !m_view->wrapCursor()); updateSelection(c, sel); updateCursor(c); } void KateViewInternal::cursorToMatchingBracket(bool sel) { KTextEditor::Cursor c = findMatchingBracket(); if (c.isValid()) { updateSelection(c, sel); updateCursor(c); } } void KateViewInternal::topOfView(bool sel) { KTextEditor::Cursor c = viewLineOffset(startPos(), m_minLinesVisible); updateSelection(toRealCursor(c), sel); updateCursor(toRealCursor(c)); } void KateViewInternal::bottomOfView(bool sel) { KTextEditor::Cursor c = viewLineOffset(endPos(), -m_minLinesVisible); updateSelection(toRealCursor(c), sel); updateCursor(toRealCursor(c)); } // lines is the offset to scroll by void KateViewInternal::scrollLines(int lines, bool sel) { KTextEditor::Cursor c = viewLineOffset(m_displayCursor, lines, true); // Fix the virtual cursor -> real cursor c.setLine(m_view->textFolding().visibleLineToLine(c.line())); updateSelection(c, sel); updateCursor(c); } // This is a bit misleading... it's asking for the view to be scrolled, not the cursor void KateViewInternal::scrollUp() { KTextEditor::Cursor newPos = viewLineOffset(startPos(), -1); scrollPos(newPos); } void KateViewInternal::scrollDown() { KTextEditor::Cursor newPos = viewLineOffset(startPos(), 1); scrollPos(newPos); } void KateViewInternal::setAutoCenterLines(int viewLines, bool updateView) { m_autoCenterLines = viewLines; m_minLinesVisible = qMin(int((linesDisplayed() - 1) / 2), m_autoCenterLines); if (updateView) { KateViewInternal::updateView(); } } void KateViewInternal::pageUp(bool sel, bool half) { if (m_view->isCompletionActive()) { m_view->completionWidget()->pageUp(); return; } // remember the view line and x pos int viewLine = cache()->displayViewLine(m_displayCursor); bool atTop = startPos().atStartOfDocument(); // Adjust for an auto-centering cursor int lineadj = m_minLinesVisible; int linesToScroll; if (! half) { linesToScroll = -qMax((linesDisplayed() - 1) - lineadj, 0); } else { linesToScroll = -qMax((linesDisplayed() / 2 - 1) - lineadj, 0); } m_preserveX = true; if (!doc()->pageUpDownMovesCursor() && !atTop) { KTextEditor::Cursor newStartPos = viewLineOffset(startPos(), linesToScroll - 1); scrollPos(newStartPos); // put the cursor back approximately where it was KTextEditor::Cursor newPos = toRealCursor(viewLineOffset(newStartPos, viewLine, true)); KateTextLayout newLine = cache()->textLayout(newPos); newPos = renderer()->xToCursor(newLine, m_preservedX, !m_view->wrapCursor()); m_preserveX = true; updateSelection(newPos, sel); updateCursor(newPos); } else { scrollLines(linesToScroll, sel); } } void KateViewInternal::pageDown(bool sel, bool half) { if (m_view->isCompletionActive()) { m_view->completionWidget()->pageDown(); return; } // remember the view line int viewLine = cache()->displayViewLine(m_displayCursor); bool atEnd = startPos() >= m_cachedMaxStartPos; // Adjust for an auto-centering cursor int lineadj = m_minLinesVisible; int linesToScroll; if (! half) { linesToScroll = qMax((linesDisplayed() - 1) - lineadj, 0); } else { linesToScroll = qMax((linesDisplayed() / 2 - 1) - lineadj, 0); } m_preserveX = true; if (!doc()->pageUpDownMovesCursor() && !atEnd) { KTextEditor::Cursor newStartPos = viewLineOffset(startPos(), linesToScroll + 1); scrollPos(newStartPos); // put the cursor back approximately where it was KTextEditor::Cursor newPos = toRealCursor(viewLineOffset(newStartPos, viewLine, true)); KateTextLayout newLine = cache()->textLayout(newPos); newPos = renderer()->xToCursor(newLine, m_preservedX, !m_view->wrapCursor()); m_preserveX = true; updateSelection(newPos, sel); updateCursor(newPos); } else { scrollLines(linesToScroll, sel); } } int KateViewInternal::maxLen(int startLine) { Q_ASSERT(!m_view->dynWordWrap()); int displayLines = (m_view->height() / renderer()->lineHeight()) + 1; int maxLen = 0; for (int z = 0; z < displayLines; z++) { int virtualLine = startLine + z; if (virtualLine < 0 || virtualLine >= (int)m_view->textFolding().visibleLines()) { break; } maxLen = qMax(maxLen, cache()->line(m_view->textFolding().visibleLineToLine(virtualLine))->width()); } return maxLen; } bool KateViewInternal::columnScrollingPossible() { return !m_view->dynWordWrap() && m_columnScroll->isEnabled() && (m_columnScroll->maximum() > 0); } bool KateViewInternal::lineScrollingPossible() { return m_lineScroll->minimum() != m_lineScroll->maximum(); } void KateViewInternal::top(bool sel) { KTextEditor::Cursor newCursor(0, 0); newCursor = renderer()->xToCursor(cache()->textLayout(newCursor), m_preservedX, !m_view->wrapCursor()); updateSelection(newCursor, sel); updateCursor(newCursor); } void KateViewInternal::bottom(bool sel) { KTextEditor::Cursor newCursor(doc()->lastLine(), 0); newCursor = renderer()->xToCursor(cache()->textLayout(newCursor), m_preservedX, !m_view->wrapCursor()); updateSelection(newCursor, sel); updateCursor(newCursor); } void KateViewInternal::top_home(bool sel) { if (m_view->isCompletionActive()) { m_view->completionWidget()->top(); return; } KTextEditor::Cursor c(0, 0); updateSelection(c, sel); updateCursor(c); } void KateViewInternal::bottom_end(bool sel) { if (m_view->isCompletionActive()) { m_view->completionWidget()->bottom(); return; } KTextEditor::Cursor c(doc()->lastLine(), doc()->lineLength(doc()->lastLine())); updateSelection(c, sel); updateCursor(c); } void KateViewInternal::updateSelection(const KTextEditor::Cursor &_newCursor, bool keepSel) { KTextEditor::Cursor newCursor = _newCursor; if (keepSel) { if (!m_view->selection() || (m_selectAnchor.line() == -1) //don't kill the selection if we have a persistent selection and //the cursor is inside or at the boundaries of the selected area || (m_view->config()->persistentSelection() && !(m_view->selectionRange().contains(m_cursor) || m_view->selectionRange().boundaryAtCursor(m_cursor)))) { m_selectAnchor = m_cursor; setSelection(KTextEditor::Range(m_cursor, newCursor)); } else { bool doSelect = true; switch (m_selectionMode) { case Word: { // Restore selStartCached if needed. It gets nuked by // viewSelectionChanged if we drag the selection into non-existence, // which can legitimately happen if a shift+DC selection is unable to // set a "proper" (i.e. non-empty) cached selection, e.g. because the // start was on something that isn't a word. Word select mode relies // on the cached selection being set properly, even if it is empty // (i.e. selStartCached == selEndCached). if (!m_selectionCached.isValid()) { m_selectionCached.setStart(m_selectionCached.end()); } int c; if (newCursor > m_selectionCached.start()) { m_selectAnchor = m_selectionCached.start(); Kate::TextLine l = doc()->kateTextLine(newCursor.line()); c = newCursor.column(); if (c > 0 && doc()->highlight()->isInWord(l->at(c - 1))) { for (; c < l->length(); c++) if (!doc()->highlight()->isInWord(l->at(c))) { break; } } newCursor.setColumn(c); } else if (newCursor < m_selectionCached.start()) { m_selectAnchor = m_selectionCached.end(); Kate::TextLine l = doc()->kateTextLine(newCursor.line()); c = newCursor.column(); if (c > 0 && c < doc()->lineLength(newCursor.line()) && doc()->highlight()->isInWord(l->at(c)) && doc()->highlight()->isInWord(l->at(c - 1))) { for (c -= 2; c >= 0; c--) if (!doc()->highlight()->isInWord(l->at(c))) { break; } newCursor.setColumn(c + 1); } } else { doSelect = false; } } break; case Line: if (!m_selectionCached.isValid()) { m_selectionCached = KTextEditor::Range(endLine(), 0, endLine(), 0); } if (newCursor.line() > m_selectionCached.start().line()) { if (newCursor.line() + 1 >= doc()->lines()) { newCursor.setColumn(doc()->line(newCursor.line()).length()); } else { newCursor.setPosition(newCursor.line() + 1, 0); } // Grow to include the entire line m_selectAnchor = m_selectionCached.start(); m_selectAnchor.setColumn(0); } else if (newCursor.line() < m_selectionCached.start().line()) { newCursor.setColumn(0); // Grow to include entire line m_selectAnchor = m_selectionCached.end(); if (m_selectAnchor.column() > 0) { if (m_selectAnchor.line() + 1 >= doc()->lines()) { m_selectAnchor.setColumn(doc()->line(newCursor.line()).length()); } else { m_selectAnchor.setPosition(m_selectAnchor.line() + 1, 0); } } } else { // same line, ignore doSelect = false; } break; case Mouse: { if (!m_selectionCached.isValid()) { break; } if (newCursor > m_selectionCached.end()) { m_selectAnchor = m_selectionCached.start(); } else if (newCursor < m_selectionCached.start()) { m_selectAnchor = m_selectionCached.end(); } else { doSelect = false; } } break; default: /* nothing special to do */; } if (doSelect) { setSelection(KTextEditor::Range(m_selectAnchor, newCursor)); } else if (m_selectionCached.isValid()) { // we have a cached selection, so we restore that setSelection(m_selectionCached); } } m_selChangedByUser = true; } else if (!m_view->config()->persistentSelection()) { m_view->clearSelection(); m_selectionCached = KTextEditor::Range::invalid(); m_selectAnchor = KTextEditor::Cursor::invalid(); } #ifndef QT_NO_ACCESSIBILITY // FIXME KF5 // QAccessibleTextSelectionEvent ev(this, /* selection start, selection end*/); // QAccessible::updateAccessibility(&ev); #endif } void KateViewInternal::setSelection(const KTextEditor::Range &range) { disconnect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(viewSelectionChanged())); m_view->setSelection(range); connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(viewSelectionChanged())); } void KateViewInternal::moveCursorToSelectionEdge() { if (!m_view->selection()) { return; } int tmp = m_minLinesVisible; m_minLinesVisible = 0; if (m_view->selectionRange().start() < m_selectAnchor) { updateCursor(m_view->selectionRange().start()); } else { updateCursor(m_view->selectionRange().end()); } m_minLinesVisible = tmp; } void KateViewInternal::updateCursor(const KTextEditor::Cursor &newCursor, bool force, bool center, bool calledExternally) { if (!force && (m_cursor.toCursor() == newCursor)) { m_displayCursor = toVirtualCursor(newCursor); if (!m_madeVisible && m_view == doc()->activeView()) { // unfold if required m_view->textFolding().ensureLineIsVisible(newCursor.line()); makeVisible(m_displayCursor, m_displayCursor.column(), false, center, calledExternally); } return; } if (m_cursor.line() != newCursor.line()) { m_leftBorder->updateForCursorLineChange(); } // unfold if required m_view->textFolding().ensureLineIsVisible(newCursor.line()); KTextEditor::Cursor oldDisplayCursor = m_displayCursor; m_displayCursor = toVirtualCursor(newCursor); m_cursor.setPosition(newCursor); if (m_view == doc()->activeView()) { makeVisible(m_displayCursor, m_displayCursor.column(), false, center, calledExternally); } updateBracketMarks(); // It's efficient enough to just tag them both without checking to see if they're on the same view line /* kdDebug()<<"oldDisplayCursor:"< 0) { m_cursorTimer.start(QApplication::cursorFlashTime() / 2); } renderer()->setDrawCaret(true); } // Remember the maximum X position if requested if (m_preserveX) { m_preserveX = false; } else { m_preservedX = renderer()->cursorToX(cache()->textLayout(m_cursor), m_cursor, !m_view->wrapCursor()); } //qCDebug(LOG_KTE) << "m_preservedX: " << m_preservedX << " (was "<< oldmaxx << "), m_cursorX: " << m_cursorX; //qCDebug(LOG_KTE) << "Cursor now located at real " << cursor.line << "," << cursor.col << ", virtual " << m_displayCursor.line << ", " << m_displayCursor.col << "; Top is " << startLine() << ", " << startPos().col; cursorMoved(); updateDirty(); //paintText(0, 0, width(), height(), true); emit m_view->cursorPositionChanged(m_view, m_cursor); } void KateViewInternal::updateBracketMarkAttributes() { KTextEditor::Attribute::Ptr bracketFill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); bracketFill->setBackground(m_view->m_renderer->config()->highlightedBracketColor()); bracketFill->setBackgroundFillWhitespace(false); if (QFontInfo(renderer()->currentFont()).fixedPitch()) { // make font bold only for fixed fonts, otherwise text jumps around bracketFill->setFontBold(); } m_bmStart->setAttribute(bracketFill); m_bmEnd->setAttribute(bracketFill); if (m_view->m_renderer->config()->showWholeBracketExpression()) { KTextEditor::Attribute::Ptr expressionFill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); expressionFill->setBackground(m_view->m_renderer->config()->highlightedBracketColor()); expressionFill->setBackgroundFillWhitespace(false); m_bm->setAttribute(expressionFill); } else { m_bm->setAttribute(KTextEditor::Attribute::Ptr(new KTextEditor::Attribute())); } } void KateViewInternal::updateBracketMarks() { // add some limit to this, this is really endless on big files without limit const int maxLines = 5000; const KTextEditor::Range newRange = doc()->findMatchingBracket(m_cursor, maxLines); // new range valid, then set ranges to it if (newRange.isValid()) { if (m_bm->toRange() == newRange) { return; } // modify full range m_bm->setRange(newRange); // modify start and end ranges m_bmStart->setRange(KTextEditor::Range(m_bm->start(), KTextEditor::Cursor(m_bm->start().line(), m_bm->start().column() + 1))); m_bmEnd->setRange(KTextEditor::Range(m_bm->end(), KTextEditor::Cursor(m_bm->end().line(), m_bm->end().column() + 1))); // flash matching bracket if (!renderer()->config()->animateBracketMatching()) { return; } const KTextEditor::Cursor flashPos = (m_cursor == m_bmStart->start() || m_cursor == m_bmStart->end()) ? m_bmEnd->start() : m_bm->start(); if (flashPos != m_bmLastFlashPos->toCursor()) { m_bmLastFlashPos->setPosition(flashPos); KTextEditor::Attribute::Ptr attribute = doc()->attributeAt(flashPos); attribute->setBackground(m_view->m_renderer->config()->highlightedBracketColor()); attribute->setFontBold(m_bmStart->attribute()->fontBold()); flashChar(flashPos, attribute); } return; } // new range was invalid m_bm->setRange(KTextEditor::Range::invalid()); m_bmStart->setRange(KTextEditor::Range::invalid()); m_bmEnd->setRange(KTextEditor::Range::invalid()); m_bmLastFlashPos->setPosition(KTextEditor::Cursor::invalid()); } bool KateViewInternal::tagLine(const KTextEditor::Cursor &virtualCursor) { // FIXME may be a more efficient way for this if ((int)m_view->textFolding().visibleLineToLine(virtualCursor.line()) > doc()->lastLine()) { return false; } // End FIXME int viewLine = cache()->displayViewLine(virtualCursor, true); if (viewLine >= 0 && viewLine < cache()->viewCacheLineCount()) { cache()->viewLine(viewLine).setDirty(); // tag one line more because of overlapping things like _, bug 335079 if (viewLine+1 < cache()->viewCacheLineCount()) { cache()->viewLine(viewLine+1).setDirty(); } m_leftBorder->update(0, lineToY(viewLine), m_leftBorder->width(), renderer()->lineHeight()); return true; } return false; } bool KateViewInternal::tagLines(int start, int end, bool realLines) { return tagLines(KTextEditor::Cursor(start, 0), KTextEditor::Cursor(end, -1), realLines); } bool KateViewInternal::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors) { if (realCursors) { cache()->relayoutLines(start.line(), end.line()); //qCDebug(LOG_KTE)<<"realLines is true"; start = toVirtualCursor(start); end = toVirtualCursor(end); } else { cache()->relayoutLines(toRealCursor(start).line(), toRealCursor(end).line()); } if (end.line() < startLine()) { //qCDebug(LOG_KTE)<<"end endLine(), but cache may not be valid when checking, so use a // less optimal but still adequate approximation (potential overestimation but minimal performance difference) if (start.line() > startLine() + cache()->viewCacheLineCount()) { //qCDebug(LOG_KTE)<<"start> endLine"<updateViewCache(startPos()); //qCDebug(LOG_KTE) << "tagLines( [" << start << "], [" << end << "] )"; bool ret = false; for (int z = 0; z < cache()->viewCacheLineCount(); z++) { KateTextLayout &line = cache()->viewLine(z); if ((line.virtualLine() > start.line() || (line.virtualLine() == start.line() && line.endCol() >= start.column() && start.column() != -1)) && (line.virtualLine() < end.line() || (line.virtualLine() == end.line() && (line.startCol() <= end.column() || end.column() == -1)))) { ret = true; break; //qCDebug(LOG_KTE) << "Tagged line " << line.line(); } } if (!m_view->dynWordWrap()) { int y = lineToY(start.line()); // FIXME is this enough for when multiple lines are deleted int h = (end.line() - start.line() + 2) * renderer()->lineHeight(); if (end.line() >= m_view->textFolding().visibleLines() - 1) { h = height(); } m_leftBorder->update(0, y, m_leftBorder->width(), h); } else { // FIXME Do we get enough good info in editRemoveText to optimize this more? //bool justTagged = false; for (int z = 0; z < cache()->viewCacheLineCount(); z++) { KateTextLayout &line = cache()->viewLine(z); if (!line.isValid() || ((line.virtualLine() > start.line() || (line.virtualLine() == start.line() && line.endCol() >= start.column() && start.column() != -1)) && (line.virtualLine() < end.line() || (line.virtualLine() == end.line() && (line.startCol() <= end.column() || end.column() == -1))))) { //justTagged = true; m_leftBorder->update(0, z * renderer()->lineHeight(), m_leftBorder->width(), m_leftBorder->height()); break; } /*else if (justTagged) { justTagged = false; leftBorder->update (0, z * doc()->viewFont.fontHeight, leftBorder->width(), doc()->viewFont.fontHeight); break; }*/ } } return ret; } bool KateViewInternal::tagRange(const KTextEditor::Range &range, bool realCursors) { return tagLines(range.start(), range.end(), realCursors); } void KateViewInternal::tagAll() { // clear the cache... cache()->clear(); m_leftBorder->updateFont(); m_leftBorder->update(); } void KateViewInternal::paintCursor() { if (tagLine(m_displayCursor)) { updateDirty(); //paintText (0,0,width(), height(), true); } } // Point in content coordinates void KateViewInternal::placeCursor(const QPoint &p, bool keepSelection, bool updateSelection) { KateTextLayout thisLine = yToKateTextLayout(p.y()); KTextEditor::Cursor c; if (!thisLine.isValid()) { // probably user clicked below the last line -> use the last line thisLine = cache()->textLayout(doc()->lines() - 1, -1); } c = renderer()->xToCursor(thisLine, startX() + p.x(), !m_view->wrapCursor()); if (c.line() < 0 || c.line() >= doc()->lines()) { return; } if (updateSelection) { KateViewInternal::updateSelection(c, keepSelection); } int tmp = m_minLinesVisible; m_minLinesVisible = 0; updateCursor(c); m_minLinesVisible = tmp; if (updateSelection && keepSelection) { moveCursorToSelectionEdge(); } } // Point in content coordinates bool KateViewInternal::isTargetSelected(const QPoint &p) { const KateTextLayout &thisLine = yToKateTextLayout(p.y()); if (!thisLine.isValid()) { return false; } return m_view->cursorSelected(renderer()->xToCursor(thisLine, startX() + p.x(), !m_view->wrapCursor())); } //BEGIN EVENT HANDLING STUFF bool KateViewInternal::eventFilter(QObject *obj, QEvent *e) { switch (e->type()) { case QEvent::ChildAdded: case QEvent::ChildRemoved: { QChildEvent *c = static_cast(e); if (c->added()) { c->child()->installEventFilter(this); /*foreach (QWidget* child, c->child()->findChildren()) child->installEventFilter(this);*/ } else if (c->removed()) { c->child()->removeEventFilter(this); /*foreach (QWidget* child, c->child()->findChildren()) child->removeEventFilter(this);*/ } } break; case QEvent::ShortcutOverride: { QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { if (m_view->isCompletionActive()) { m_view->abortCompletion(); k->accept(); //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "aborting completion"; return true; } else if (!m_view->bottomViewBar()->hiddenOrPermanent()) { m_view->bottomViewBar()->hideCurrentBarWidget(); k->accept(); //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "closing view bar"; return true; } else if (!m_view->config()->persistentSelection() && m_view->selection()) { m_currentInputMode->clearSelection(); k->accept(); //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "clearing selection"; return true; } } if (m_currentInputMode->stealKey(k)) { k->accept(); return true; } } break; case QEvent::KeyPress: { QKeyEvent *k = static_cast(e); // Override all other single key shortcuts which do not use a modifier other than Shift if (obj == this && (!k->modifiers() || k->modifiers() == Qt::ShiftModifier)) { keyPressEvent(k); if (k->isAccepted()) { //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "using keystroke"; return true; } } //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "ignoring"; } break; case QEvent::DragMove: { QPoint currentPoint = ((QDragMoveEvent *) e)->pos(); QRect doNotScrollRegion(s_scrollMargin, s_scrollMargin, width() - s_scrollMargin * 2, height() - s_scrollMargin * 2); if (!doNotScrollRegion.contains(currentPoint)) { startDragScroll(); // Keep sending move events ((QDragMoveEvent *)e)->accept(QRect(0, 0, 0, 0)); } dragMoveEvent((QDragMoveEvent *)e); } break; case QEvent::DragLeave: // happens only when pressing ESC while dragging stopDragScroll(); break; default: break; } return QWidget::eventFilter(obj, e); } void KateViewInternal::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Left && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateLeft(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Right && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateRight(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Up && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateUp(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Down && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateDown(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Return && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateAccept(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Backspace && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateBack(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Alt && m_view->completionWidget()->isCompletionActive()) { m_completionItemExpanded = m_view->completionWidget()->toggleExpanded(true); m_view->completionWidget()->resetHadNavigation(); m_altDownTime.start(); } // Note: AND'ing with is a quick hack to fix Key_Enter const int key = e->key() | (e->modifiers() & Qt::ShiftModifier); if (m_currentInputMode->keyPress(e)) { return; } if (!doc()->isReadWrite()) { e->ignore(); return; } if ((key == Qt::Key_Return) || (key == Qt::Key_Enter) || (key == Qt::SHIFT + Qt::Key_Return) || (key == Qt::SHIFT + Qt::Key_Enter)) { doReturn(); e->accept(); return; } if (key == Qt::Key_Backspace || key == Qt::SHIFT + Qt::Key_Backspace) { //m_view->backspace(); e->accept(); return; } if (key == Qt::Key_Tab || key == Qt::SHIFT + Qt::Key_Backtab || key == Qt::Key_Backtab) { if (m_view->completionWidget()->isCompletionActive()) { e->accept(); m_view->completionWidget()->tab(key != Qt::Key_Tab); return; } if (key == Qt::Key_Tab) { uint tabHandling = doc()->config()->tabHandling(); // convert tabSmart into tabInsertsTab or tabIndents: if (tabHandling == KateDocumentConfig::tabSmart) { // multiple lines selected if (m_view->selection() && !m_view->selectionRange().onSingleLine()) { tabHandling = KateDocumentConfig::tabIndents; } // otherwise: take look at cursor position else { // if the cursor is at or before the first non-space character // or on an empty line, // Tab indents, otherwise it inserts a tab character. Kate::TextLine line = doc()->kateTextLine(m_cursor.line()); int first = line->firstChar(); if (first < 0 || m_cursor.column() <= first) { tabHandling = KateDocumentConfig::tabIndents; } else { tabHandling = KateDocumentConfig::tabInsertsTab; } } } if (tabHandling == KateDocumentConfig::tabInsertsTab) { doc()->typeChars(m_view, QStringLiteral("\t")); } else { doc()->indent(m_view->selection() ? m_view->selectionRange() : KTextEditor::Range(m_cursor.line(), 0, m_cursor.line(), 0), 1); } e->accept(); return; } else if (doc()->config()->tabHandling() != KateDocumentConfig::tabInsertsTab) { // key == Qt::SHIFT+Qt::Key_Backtab || key == Qt::Key_Backtab doc()->indent(m_view->selection() ? m_view->selectionRange() : KTextEditor::Range(m_cursor.line(), 0, m_cursor.line(), 0), -1); e->accept(); return; } } if (!(e->modifiers() & Qt::ControlModifier) && !e->text().isEmpty() && doc()->typeChars(m_view, e->text())) { e->accept(); return; } // allow composition of AltGr + (q|2|3) on windows static const int altGR = Qt::ControlModifier | Qt::AltModifier; if ((e->modifiers() & altGR) == altGR && !e->text().isEmpty() && doc()->typeChars(m_view, e->text())) { e->accept(); return; } e->ignore(); } void KateViewInternal::keyReleaseEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Alt && m_view->completionWidget()->isCompletionActive() && ((m_completionItemExpanded && (m_view->completionWidget()->hadNavigation() || m_altDownTime.elapsed() > 300)) || (!m_completionItemExpanded && !m_view->completionWidget()->hadNavigation()))) { m_view->completionWidget()->toggleExpanded(false, true); } if ((e->modifiers() & Qt::SHIFT) == Qt::SHIFT) { m_shiftKeyPressed = true; } else { if (m_shiftKeyPressed) { m_shiftKeyPressed = false; if (m_selChangedByUser) { if (m_view->selection()) { QApplication::clipboard()->setText(m_view->selectionText(), QClipboard::Selection); } m_selChangedByUser = false; } } } e->ignore(); return; } void KateViewInternal::contextMenuEvent(QContextMenuEvent *e) { // try to show popup menu QPoint p = e->pos(); if (e->reason() == QContextMenuEvent::Keyboard) { makeVisible(m_displayCursor, 0); p = cursorCoordinates(false); p.rx() -= startX(); } else if (! m_view->selection() || m_view->config()->persistentSelection()) { placeCursor(e->pos()); } // popup is a qguardedptr now if (m_view->contextMenu()) { m_view->spellingMenu()->setUseMouseForMisspelledRange((e->reason() == QContextMenuEvent::Mouse)); m_view->contextMenu()->popup(mapToGlobal(p)); e->accept(); } } void KateViewInternal::mousePressEvent(QMouseEvent *e) { switch (e->button()) { case Qt::LeftButton: m_selChangedByUser = false; if (m_possibleTripleClick) { m_possibleTripleClick = false; m_selectionMode = Line; if (e->modifiers() & Qt::ShiftModifier) { updateSelection(m_cursor, true); } else { m_view->selectLine(m_cursor); if (m_view->selection()) { m_selectAnchor = m_view->selectionRange().start(); } } if (m_view->selection()) { QApplication::clipboard()->setText(m_view->selectionText(), QClipboard::Selection); } // Keep the line at the select anchor selected during further // mouse selection if (m_selectAnchor.line() > m_view->selectionRange().start().line()) { // Preserve the last selected line if (m_selectAnchor == m_view->selectionRange().end() && m_selectAnchor.column() == 0) { m_selectionCached.setStart(KTextEditor::Cursor(m_selectAnchor.line() - 1, 0)); } else { m_selectionCached.setStart(KTextEditor::Cursor(m_selectAnchor.line(), 0)); } m_selectionCached.setEnd(m_view->selectionRange().end()); } else { // Preserve the first selected line m_selectionCached.setStart(m_view->selectionRange().start()); if (m_view->selectionRange().end().line() > m_view->selectionRange().start().line()) { m_selectionCached.setEnd(KTextEditor::Cursor(m_view->selectionRange().start().line() + 1, 0)); } else { m_selectionCached.setEnd(m_view->selectionRange().end()); } } moveCursorToSelectionEdge(); m_scrollX = 0; m_scrollY = 0; m_scrollTimer.start(50); e->accept(); return; } else if (m_selectionMode == Default) { m_selectionMode = Mouse; } // request the software keyboard, if any if (e->button() == Qt::LeftButton && qApp->autoSipEnabled()) { QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(style()->styleHint(QStyle::SH_RequestSoftwareInputPanel)); if (hasFocus() || behavior == QStyle::RSIP_OnMouseClick) { QEvent event(QEvent::RequestSoftwareInputPanel); QApplication::sendEvent(this, &event); } } if (e->modifiers() & Qt::ShiftModifier) { if (!m_selectAnchor.isValid()) { m_selectAnchor = m_cursor; } } else { m_selectionCached = KTextEditor::Range::invalid(); } if (!(e->modifiers() & Qt::ShiftModifier) && isTargetSelected(e->pos())) { m_dragInfo.state = diPending; m_dragInfo.start = e->pos(); } else { m_dragInfo.state = diNone; if (e->modifiers() & Qt::ShiftModifier) { placeCursor(e->pos(), true, false); if (m_selectionCached.start().isValid()) { if (m_cursor.toCursor() < m_selectionCached.start()) { m_selectAnchor = m_selectionCached.end(); } else { m_selectAnchor = m_selectionCached.start(); } } setSelection(KTextEditor::Range(m_selectAnchor, m_cursor)); } else { placeCursor(e->pos()); } m_scrollX = 0; m_scrollY = 0; m_scrollTimer.start(50); } e->accept(); break; default: e->ignore(); break; } } void KateViewInternal::mouseDoubleClickEvent(QMouseEvent *e) { switch (e->button()) { case Qt::LeftButton: m_selectionMode = Word; if (e->modifiers() & Qt::ShiftModifier) { // Now select the word under the select anchor int cs, ce; Kate::TextLine l = doc()->kateTextLine(m_selectAnchor.line()); ce = m_selectAnchor.column(); if (ce > 0 && doc()->highlight()->isInWord(l->at(ce))) { for (; ce < l->length(); ce++) if (!doc()->highlight()->isInWord(l->at(ce))) { break; } } cs = m_selectAnchor.column() - 1; if (cs < doc()->lineLength(m_selectAnchor.line()) && doc()->highlight()->isInWord(l->at(cs))) { for (cs--; cs >= 0; cs--) if (!doc()->highlight()->isInWord(l->at(cs))) { break; } } // ...and keep it selected if (cs + 1 < ce) { m_selectionCached.setStart(KTextEditor::Cursor(m_selectAnchor.line(), cs + 1)); m_selectionCached.setEnd(KTextEditor::Cursor(m_selectAnchor.line(), ce)); } else { m_selectionCached.setStart(m_selectAnchor); m_selectionCached.setEnd(m_selectAnchor); } // Now word select to the mouse cursor placeCursor(e->pos(), true); } else { // first clear the selection, otherwise we run into bug #106402 // ...and set the cursor position, for the same reason (otherwise there // are *other* idiosyncrasies we can't fix without reintroducing said // bug) // Parameters: don't redraw, and don't emit selectionChanged signal yet m_view->clearSelection(false, false); placeCursor(e->pos()); m_view->selectWord(m_cursor); cursorToMatchingBracket(true); if (m_view->selection()) { m_selectAnchor = m_view->selectionRange().start(); m_selectionCached = m_view->selectionRange(); } else { m_selectAnchor = m_cursor; m_selectionCached = KTextEditor::Range(m_cursor, m_cursor); } } // Move cursor to end (or beginning) of selected word if (m_view->selection()) { #if !defined(Q_OS_OSX) QApplication::clipboard()->setText(m_view->selectionText(), QClipboard::Selection); #endif } moveCursorToSelectionEdge(); m_possibleTripleClick = true; QTimer::singleShot(QApplication::doubleClickInterval(), this, SLOT(tripleClickTimeout())); m_scrollX = 0; m_scrollY = 0; m_scrollTimer.start(50); e->accept(); break; default: e->ignore(); break; } } void KateViewInternal::tripleClickTimeout() { m_possibleTripleClick = false; } void KateViewInternal::mouseReleaseEvent(QMouseEvent *e) { switch (e->button()) { case Qt::LeftButton: m_selectionMode = Default; // m_selectionCached.start().setLine( -1 ); if (m_selChangedByUser) { if (m_view->selection()) { QApplication::clipboard()->setText(m_view->selectionText(), QClipboard::Selection); } moveCursorToSelectionEdge(); m_selChangedByUser = false; } if (m_dragInfo.state == diPending) { placeCursor(e->pos(), e->modifiers() & Qt::ShiftModifier); } else if (m_dragInfo.state == diNone) { m_scrollTimer.stop(); } m_dragInfo.state = diNone; e->accept(); break; case Qt::MidButton: placeCursor(e->pos()); if (doc()->isReadWrite()) { QString clipboard = QApplication::clipboard()->text(QClipboard::Selection); m_view->paste(&clipboard); } e->accept(); break; default: e->ignore(); break; } } void KateViewInternal::leaveEvent(QEvent *) { m_textHintTimer.stop(); // fix bug 194452, scrolling keeps going if you scroll via mouse drag and press and other mouse // button outside the view area if (m_dragInfo.state == diNone) { m_scrollTimer.stop(); } } KTextEditor::Cursor KateViewInternal::coordinatesToCursor(const QPoint &_coord, bool includeBorder) const { QPoint coord(_coord); KTextEditor::Cursor ret = KTextEditor::Cursor::invalid(); if (includeBorder) { coord.rx() -= m_leftBorder->width(); } coord.rx() += startX(); const KateTextLayout &thisLine = yToKateTextLayout(coord.y()); if (thisLine.isValid()) { ret = renderer()->xToCursor(thisLine, coord.x(), !m_view->wrapCursor()); } if (ret.column() > view()->document()->lineLength(ret.line())) { // The cursor is beyond the end of the line; in that case the renderer // gives the index of the character behind the last one. return KTextEditor::Cursor::invalid(); } return ret; } void KateViewInternal::mouseMoveEvent(QMouseEvent *e) { KTextEditor::Cursor newPosition = coordinatesToCursor(e->pos(), false); if (newPosition != m_mouse) { m_mouse = newPosition; mouseMoved(); } if (e->buttons() & Qt::LeftButton) { if (m_dragInfo.state == diPending) { // we had a mouse down, but haven't confirmed a drag yet // if the mouse has moved sufficiently, we will confirm QPoint p(e->pos() - m_dragInfo.start); // we've left the drag square, we can start a real drag operation now if (p.manhattanLength() > QApplication::startDragDistance()) { doDrag(); } return; } else if (m_dragInfo.state == diDragging) { // Don't do anything after a canceled drag until the user lets go of // the mouse button! return; } m_mouseX = e->x(); m_mouseY = e->y(); m_scrollX = 0; m_scrollY = 0; int d = renderer()->lineHeight(); if (m_mouseX < 0) { m_scrollX = -d; } if (m_mouseX > width()) { m_scrollX = d; } if (m_mouseY < 0) { m_mouseY = 0; m_scrollY = -d; } if (m_mouseY > height()) { m_mouseY = height(); m_scrollY = d; } placeCursor(QPoint(m_mouseX, m_mouseY), true); } else { if (isTargetSelected(e->pos())) { // mouse is over selected text. indicate that the text is draggable by setting // the arrow cursor as other Qt text editing widgets do if (m_mouseCursor != Qt::ArrowCursor) { m_mouseCursor = Qt::ArrowCursor; setCursor(m_mouseCursor); } } else { // normal text cursor if (m_mouseCursor != Qt::IBeamCursor) { m_mouseCursor = Qt::IBeamCursor; setCursor(m_mouseCursor); } } //We need to check whether the mouse position is actually within the widget, //because other widgets like the icon border forward their events to this, //and we will create invalid text hint requests if we don't check if (textHintsEnabled() && geometry().contains(parentWidget()->mapFromGlobal(e->globalPos()))) { if (QToolTip::isVisible()) { QToolTip::hideText(); } m_textHintTimer.start(m_textHintDelay); m_textHintPos = e->pos(); } } } void KateViewInternal::updateDirty() { const int h = renderer()->lineHeight(); int currentRectStart = -1; int currentRectEnd = -1; QRegion updateRegion; { for (int i = 0; i < cache()->viewCacheLineCount(); ++i) { if (cache()->viewLine(i).isDirty()) { if (currentRectStart == -1) { currentRectStart = h * i; currentRectEnd = h; } else { currentRectEnd += h; } } else if (currentRectStart != -1) { updateRegion += QRect(0, currentRectStart, width(), currentRectEnd); currentRectStart = -1; currentRectEnd = -1; } } } if (currentRectStart != -1) { updateRegion += QRect(0, currentRectStart, width(), currentRectEnd); } if (!updateRegion.isEmpty()) { if (debugPainting) { qCDebug(LOG_KTE) << "Update dirty region " << updateRegion; } update(updateRegion); } } void KateViewInternal::hideEvent(QHideEvent *e) { Q_UNUSED(e); if (m_view->isCompletionActive()) { m_view->completionWidget()->abortCompletion(); } } void KateViewInternal::paintEvent(QPaintEvent *e) { if (debugPainting) { qCDebug(LOG_KTE) << "GOT PAINT EVENT: Region" << e->region(); } const QRect &unionRect = e->rect(); int xStart = startX() + unionRect.x(); int xEnd = xStart + unionRect.width(); uint h = renderer()->lineHeight(); uint startz = (unionRect.y() / h); uint endz = startz + 1 + (unionRect.height() / h); uint lineRangesSize = cache()->viewCacheLineCount(); const KTextEditor::Cursor pos = m_cursor; QPainter paint(this); paint.setRenderHints(QPainter::Antialiasing); paint.save(); renderer()->setCaretStyle(m_currentInputMode->caretStyle()); renderer()->setShowTabs(doc()->config()->showTabs()); renderer()->setShowTrailingSpaces(doc()->config()->showSpaces()); renderer()->updateMarkerSize(); int sy = startz * h; paint.translate(unionRect.x(), startz * h); for (uint z = startz; z <= endz; z++) { paint.save(); if ((z >= lineRangesSize) || (cache()->viewLine(z).line() == -1)) { if (!(z >= lineRangesSize)) { cache()->viewLine(z).setDirty(false); } paint.fillRect(0, 0, unionRect.width(), h, renderer()->config()->backgroundColor()); } else { //qCDebug(LOG_KTE)<<"KateViewInternal::paintEvent(QPaintEvent *e):cache()->viewLine"<viewLine(z); /* If viewLine() returns non-zero, then a document line was split in several visual lines, and we're trying to paint visual line that is not the first. In that case, this line was already painted previously, since KateRenderer::paintTextLine paints all visual lines. Except if we're at the start of the region that needs to be painted -- when no previous calls to paintTextLine were made. */ if (!thisLine.viewLine() || z == startz) { //qDebug() << "paint text: line: " << thisLine.line() << " viewLine " << thisLine.viewLine() << " x: " << unionRect.x() << " y: " << unionRect.y() << " width: " << xEnd-xStart << " height: " << h << endl; // first: paint our line paint.translate(QPoint(0, h * - thisLine.viewLine())); paint.setClipRect(QRect(0, 0, unionRect.width(), h * thisLine.kateLineLayout()->viewLineCount())); renderer()->paintTextLine(paint, thisLine.kateLineLayout(), xStart, xEnd, &pos); paint.translate(0, h * thisLine.viewLine()); // second: paint previous line elements, that span into our line like _, bug 335079 if (z > 0) { KateTextLayout &previousLine = cache()->viewLine(z-1); paint.translate(QPoint(0, h * - (previousLine.viewLine() + 1))); renderer()->paintTextLine(paint, previousLine.kateLineLayout(), xStart, xEnd, &pos); paint.translate(0, h * (previousLine.viewLine() + 1)); } /** * line painted, reset and state + mark line as non-dirty */ thisLine.setDirty(false); } } paint.restore(); paint.translate(0, h); sy += h; } paint.restore(); if (m_textAnimation) { m_textAnimation->draw(paint); } } void KateViewInternal::resizeEvent(QResizeEvent *e) { bool expandedHorizontally = width() > e->oldSize().width(); bool expandedVertically = height() > e->oldSize().height(); bool heightChanged = height() != e->oldSize().height(); m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height()); m_madeVisible = false; if (heightChanged) { setAutoCenterLines(m_autoCenterLines, false); m_cachedMaxStartPos.setPosition(-1, -1); } if (m_view->dynWordWrap()) { bool dirtied = false; for (int i = 0; i < cache()->viewCacheLineCount(); i++) { // find the first dirty line // the word wrap updateView algorithm is forced to check all lines after a dirty one KateTextLayout viewLine = cache()->viewLine(i); if (viewLine.wrap() || viewLine.isRightToLeft() || viewLine.width() > width()) { dirtied = true; viewLine.setDirty(); break; } } if (dirtied || heightChanged) { updateView(true); m_leftBorder->update(); } } else { updateView(); if (expandedHorizontally && startX() > 0) { scrollColumns(startX() - (width() - e->oldSize().width())); } } if (width() < e->oldSize().width() && !m_view->wrapCursor()) { // May have to restrain cursor to new smaller width... if (m_cursor.column() > doc()->lineLength(m_cursor.line())) { KateTextLayout thisLine = currentLayout(); KTextEditor::Cursor newCursor(m_cursor.line(), thisLine.endCol() + ((width() - thisLine.xOffset() - (thisLine.width() - m_startX)) / renderer()->spaceWidth()) - 1); if (newCursor.column() < m_cursor.column()) { updateCursor(newCursor); } } } if (expandedVertically) { KTextEditor::Cursor max = maxStartPos(); if (startPos() > max) { scrollPos(max); return; // already fired displayRangeChanged } } emit m_view->displayRangeChanged(m_view); } void KateViewInternal::scrollTimeout() { if (m_scrollX || m_scrollY) { scrollLines(startPos().line() + (m_scrollY / (int) renderer()->lineHeight())); placeCursor(QPoint(m_mouseX, m_mouseY), true); } } void KateViewInternal::cursorTimeout() { if (!debugPainting && m_currentInputMode->blinkCaret()) { renderer()->setDrawCaret(!renderer()->drawCaret()); paintCursor(); } } void KateViewInternal::textHintTimeout() { m_textHintTimer.stop(); KTextEditor::Cursor c = coordinatesToCursor(m_textHintPos, false); if (!c.isValid()) { return; } QStringList textHints; foreach(KTextEditor::TextHintProvider * const p, m_textHintProviders) { const QString hint = p->textHint(m_view, c); if (!hint.isEmpty()) { textHints.append(hint); } } if (!textHints.isEmpty()) { qCDebug(LOG_KTE) << "Hint text: " << textHints; QString hint; foreach(const QString & str, textHints) { hint += QStringLiteral("

%1

").arg(str); } QPoint pos(startX() + m_textHintPos.x(), m_textHintPos.y()); QToolTip::showText(mapToGlobal(pos), hint); } } void KateViewInternal::focusInEvent(QFocusEvent *) { if (QApplication::cursorFlashTime() > 0) { m_cursorTimer.start(QApplication::cursorFlashTime() / 2); } paintCursor(); doc()->setActiveView(m_view); // this will handle focus stuff in kateview m_view->slotGotFocus(); } void KateViewInternal::focusOutEvent(QFocusEvent *) { //if (m_view->isCompletionActive()) //m_view->abortCompletion(); m_cursorTimer.stop(); m_view->renderer()->setDrawCaret(true); paintCursor(); m_textHintTimer.stop(); m_view->slotLostFocus(); } void KateViewInternal::doDrag() { m_dragInfo.state = diDragging; m_dragInfo.dragObject = new QDrag(this); QMimeData *mimeData = new QMimeData(); mimeData->setText(m_view->selectionText()); m_dragInfo.dragObject->setMimeData(mimeData); m_dragInfo.dragObject->start(Qt::MoveAction); } void KateViewInternal::dragEnterEvent(QDragEnterEvent *event) { if (event->source() == this) { event->setDropAction(Qt::MoveAction); } event->setAccepted((event->mimeData()->hasText() && doc()->isReadWrite()) || event->mimeData()->hasUrls()); } void KateViewInternal::fixDropEvent(QDropEvent *event) { if (event->source() != this) { event->setDropAction(Qt::CopyAction); } else { Qt::DropAction action = Qt::MoveAction; #ifdef Q_WS_MAC if (event->keyboardModifiers() & Qt::AltModifier) { action = Qt::CopyAction; } #else if (event->keyboardModifiers() & Qt::ControlModifier) { action = Qt::CopyAction; } #endif event->setDropAction(action); } } void KateViewInternal::dragMoveEvent(QDragMoveEvent *event) { // track the cursor to the current drop location placeCursor(event->pos(), true, false); // important: accept action to switch between copy and move mode // without this, the text will always be copied. fixDropEvent(event); } void KateViewInternal::dropEvent(QDropEvent *event) { /** * if we have urls, pass this event off to the hosting application */ if (event->mimeData()->hasUrls()) { emit dropEventPass(event); return; } if (event->mimeData()->hasText() && doc()->isReadWrite()) { const QString text = event->mimeData()->text(); // is the source our own document? bool priv = false; if (KateViewInternal *vi = qobject_cast(event->source())) { priv = doc()->ownedView(vi->m_view); } // dropped on a text selection area? bool selected = m_view->cursorSelected(m_cursor); fixDropEvent(event); if (priv && selected && event->dropAction() != Qt::CopyAction) { // this is a drag that we started and dropped on our selection // ignore this case return; } // fix the cursor position before editStart(), so that it is correctly // stored for the undo action KTextEditor::Cursor targetCursor(m_cursor); // backup current cursor int selectionWidth = m_view->selectionRange().columnWidth(); // for block selection int selectionHeight = m_view->selectionRange().numberOfLines(); // for block selection if (event->dropAction() != Qt::CopyAction) { editSetCursor(m_view->selectionRange().end()); } else { m_view->clearSelection(); } // use one transaction doc()->editStart(); // on move: remove selected text; on copy: duplicate text doc()->insertText(targetCursor, text, m_view->blockSelection()); KTextEditor::DocumentCursor startCursor(doc(), targetCursor); KTextEditor::DocumentCursor endCursor1(doc(), targetCursor); const int textLength = text.length(); if (event->dropAction() != Qt::CopyAction) { m_view->removeSelectedText(); if (m_cursor.toCursor() < startCursor.toCursor()) { startCursor.move(-textLength); endCursor1.move(-textLength); } } if (!m_view->blockSelection()) { endCursor1.move(textLength); } else { endCursor1.setColumn(startCursor.column() + selectionWidth); endCursor1.setLine(startCursor.line() + selectionHeight); } KTextEditor::Cursor endCursor(endCursor1); qCDebug(LOG_KTE) << startCursor << "---(" << textLength << ")---" << endCursor; setSelection(KTextEditor::Range(startCursor, endCursor)); editSetCursor(endCursor); doc()->editEnd(); event->acceptProposedAction(); updateView(); } // finally finish drag and drop mode m_dragInfo.state = diNone; // important, because the eventFilter`s DragLeave does not occur stopDragScroll(); } //END EVENT HANDLING STUFF void KateViewInternal::clear() { m_startPos.setPosition(0, 0); m_displayCursor = KTextEditor::Cursor(0, 0); m_cursor.setPosition(0, 0); cache()->clear(); updateView(true); } void KateViewInternal::wheelEvent(QWheelEvent *e) { // check if this event should change the font size (Ctrl pressed, angle reported and not accidentally so) // Note: if detectZoomingEvent() doesn't unset the ControlModifier we'll get accelerated scrolling. if (m_zoomEventFilter->detectZoomingEvent(e)) { if (e->angleDelta().y() > 0) { slotIncFontSizes(qreal(e->angleDelta().y()) / QWheelEvent::DefaultDeltasPerStep); } else if (e->angleDelta().y() < 0) { slotDecFontSizes(qreal(-e->angleDelta().y()) / QWheelEvent::DefaultDeltasPerStep); } // accept always and be done for zooming e->accept(); return; } // handle vertical scrolling via the scrollbar if (e->orientation() == Qt::Vertical) { QWheelEvent copy = *e; QApplication::sendEvent(m_lineScroll, ©); if (copy.isAccepted()) { e->accept(); } } // handle horizontal scrolling via the scrollbar if (e->orientation() == Qt::Horizontal) { // if we have dyn word wrap, we should ignore the scroll events if (m_view->dynWordWrap()) { e->accept(); return; } QWheelEvent copy = *e; QApplication::sendEvent(m_columnScroll, ©); if (copy.isAccepted()) { e->accept(); } } } void KateViewInternal::startDragScroll() { if (!m_dragScrollTimer.isActive()) { m_dragScrollTimer.start(s_scrollTime); } } void KateViewInternal::stopDragScroll() { m_dragScrollTimer.stop(); updateView(); } void KateViewInternal::doDragScroll() { QPoint p = this->mapFromGlobal(QCursor::pos()); int dx = 0, dy = 0; if (p.y() < s_scrollMargin) { dy = p.y() - s_scrollMargin; } else if (p.y() > height() - s_scrollMargin) { dy = s_scrollMargin - (height() - p.y()); } if (p.x() < s_scrollMargin) { dx = p.x() - s_scrollMargin; } else if (p.x() > width() - s_scrollMargin) { dx = s_scrollMargin - (width() - p.x()); } dy /= 4; if (dy) { scrollLines(startPos().line() + dy); } if (columnScrollingPossible() && dx) { scrollColumns(qMin(m_startX + dx, m_columnScroll->maximum())); } if (!dy && !dx) { stopDragScroll(); } } void KateViewInternal::registerTextHintProvider(KTextEditor::TextHintProvider *provider) { if (! m_textHintProviders.contains(provider)) { m_textHintProviders.append(provider); } // we have a client, so start timeout m_textHintTimer.start(m_textHintDelay); } void KateViewInternal::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider) { const int index = m_textHintProviders.indexOf(provider); if (index >= 0) { m_textHintProviders.removeAt(index); } if (m_textHintProviders.isEmpty()) { m_textHintTimer.stop(); } } void KateViewInternal::setTextHintDelay(int delay) { if (delay <= 0) { m_textHintDelay = 200; // ms } else { m_textHintDelay = delay; // ms } } int KateViewInternal::textHintDelay() const { return m_textHintDelay; } bool KateViewInternal::textHintsEnabled() { return ! m_textHintProviders.isEmpty(); } //BEGIN EDIT STUFF void KateViewInternal::editStart() { editSessionNumber++; if (editSessionNumber > 1) { return; } editIsRunning = true; editOldCursor = m_cursor; editOldSelection = m_view->selectionRange(); } void KateViewInternal::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom) { if (editSessionNumber == 0) { return; } editSessionNumber--; if (editSessionNumber > 0) { return; } // fix start position, might have moved from column 0 // try to clever calculate the right start column for the tricky dyn word wrap case int col = 0; if (m_view->dynWordWrap()) { if (KateLineLayoutPtr layout = cache()->line(m_startPos.line())) { int index = layout->viewLineForColumn(m_startPos.column()); if (index >= 0 && index < layout->viewLineCount()) { col = layout->viewLine(index).startCol(); } } } m_startPos.setPosition(m_startPos.line(), col); if (tagFrom && (editTagLineStart <= int(m_view->textFolding().visibleLineToLine(startLine())))) { tagAll(); } else { tagLines(editTagLineStart, tagFrom ? qMax(doc()->lastLine() + 1, editTagLineEnd) : editTagLineEnd, true); } if (editOldCursor == m_cursor.toCursor()) { updateBracketMarks(); } updateView(true); if (editOldCursor != m_cursor.toCursor() || m_view == doc()->activeView()) { // Only scroll the view to the cursor if the insertion happens at the cursor. // This might not be the case for e.g. collaborative editing, when a remote user // inserts text at a position not at the caret. if (m_cursor.line() >= editTagLineStart && m_cursor.line() <= editTagLineEnd) { m_madeVisible = false; updateCursor(m_cursor, true); } } /** * selection changed? * fixes bug 316226 */ if (editOldSelection != m_view->selectionRange() || (editOldSelection.isValid() && !editOldSelection.isEmpty() && !(editTagLineStart > editOldSelection.end().line() && editTagLineEnd < editOldSelection.start().line()))) { emit m_view->selectionChanged(m_view); } editIsRunning = false; } void KateViewInternal::editSetCursor(const KTextEditor::Cursor &_cursor) { if (m_cursor.toCursor() != _cursor) { m_cursor.setPosition(_cursor); } } //END void KateViewInternal::viewSelectionChanged() { if (!m_view->selection()) { m_selectAnchor = KTextEditor::Cursor::invalid(); } else { m_selectAnchor = m_view->selectionRange().start(); } // Do NOT nuke the entire range! The reason is that a shift+DC selection // might (correctly) set the range to be empty (i.e. start() == end()), and // subsequent dragging might shrink the selection into non-existence. When // this happens, we use the cached end to restore the cached start so that // updateSelection is not confused. See also comments in updateSelection. m_selectionCached.setStart(KTextEditor::Cursor::invalid()); } KateLayoutCache *KateViewInternal::cache() const { return m_layoutCache; } KTextEditor::Cursor KateViewInternal::toRealCursor(const KTextEditor::Cursor &virtualCursor) const { return KTextEditor::Cursor(m_view->textFolding().visibleLineToLine(virtualCursor.line()), virtualCursor.column()); } KTextEditor::Cursor KateViewInternal::toVirtualCursor(const KTextEditor::Cursor &realCursor) const { /** * only convert valid lines, folding doesn't like invalid input! * don't validate whole cursor, column might be -1 */ if (realCursor.line() < 0) { return KTextEditor::Cursor::invalid(); } return KTextEditor::Cursor(m_view->textFolding().lineToVisibleLine(realCursor.line()), realCursor.column()); } KateRenderer *KateViewInternal::renderer() const { return m_view->renderer(); } void KateViewInternal::mouseMoved() { m_view->notifyMousePositionChanged(m_mouse); m_view->updateRangesIn(KTextEditor::Attribute::ActivateMouseIn); } void KateViewInternal::cursorMoved() { m_view->updateRangesIn(KTextEditor::Attribute::ActivateCaretIn); #ifndef QT_NO_ACCESSIBILITY QAccessibleTextCursorEvent ev(this, KateViewAccessible::positionFromCursor(this, m_cursor)); QAccessible::updateAccessibility(&ev); #endif } bool KateViewInternal::rangeAffectsView(const KTextEditor::Range &range, bool realCursors) const { int startLine = m_startPos.line(); int endLine = startLine + (int)m_visibleLineCount; if (realCursors) { startLine = (int)m_view->textFolding().visibleLineToLine(startLine); endLine = (int)m_view->textFolding().visibleLineToLine(endLine); } return (range.end().line() >= startLine) || (range.start().line() <= endLine); } //BEGIN IM INPUT STUFF QVariant KateViewInternal::inputMethodQuery(Qt::InputMethodQuery query) const { switch (query) { case Qt::ImCursorRectangle: { // Cursor placement code is changed for Asian input method that // shows candidate window. This behavior is same as Qt/E 2.3.7 // which supports Asian input methods. Asian input methods need // start point of IM selection text to place candidate window as // adjacent to the selection text. // // in Qt5, cursor rectangle is used as QRectF internally, and it // will be checked by QRectF::isValid(), which will mark rectangle // with width == 0 or height == 0 as invalid. auto lineHeight = renderer()->lineHeight(); return QRect(cursorToCoordinate(m_cursor, true, false), QSize(1, lineHeight ? lineHeight : 1)); } case Qt::ImFont: return renderer()->currentFont(); case Qt::ImCursorPosition: return m_cursor.column(); case Qt::ImAnchorPosition: // If selectAnchor is at the same line, return the real anchor position // Otherwise return the same position of cursor if (m_view->selection() && m_selectAnchor.line() == m_cursor.line()) { return m_selectAnchor.column(); } else { return m_cursor.column(); } case Qt::ImSurroundingText: if (Kate::TextLine l = doc()->kateTextLine(m_cursor.line())) { return l->string(); } else { return QString(); } case Qt::ImCurrentSelection: if (m_view->selection()) { return m_view->selectionText(); } else { return QString(); } default: /* values: ImMaximumTextLength */ break; } return QWidget::inputMethodQuery(query); } void KateViewInternal::inputMethodEvent(QInputMethodEvent *e) { if (doc()->readOnly()) { e->ignore(); return; } //qCDebug(LOG_KTE) << "Event: cursor" << m_cursor << "commit" << e->commitString() << "preedit" << e->preeditString() << "replacement start" << e->replacementStart() << "length" << e->replacementLength(); if (!m_imPreeditRange) { m_imPreeditRange = doc()->newMovingRange(KTextEditor::Range(m_cursor, m_cursor), KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight); } if (!m_imPreeditRange->toRange().isEmpty()) { doc()->inputMethodStart(); doc()->removeText(*m_imPreeditRange); doc()->inputMethodEnd(); } if (!e->commitString().isEmpty() || e->replacementLength()) { m_view->removeSelectedText(); KTextEditor::Range preeditRange = *m_imPreeditRange; KTextEditor::Cursor start(m_imPreeditRange->start().line(), m_imPreeditRange->start().column() + e->replacementStart()); KTextEditor::Cursor removeEnd = start + KTextEditor::Cursor(0, e->replacementLength()); doc()->editStart(); if (start != removeEnd) { doc()->removeText(KTextEditor::Range(start, removeEnd)); } if (!e->commitString().isEmpty()) { // if the input method event is text that should be inserted, call KTextEditor::DocumentPrivate::typeChars() // with the text. that method will handle the input and take care of overwrite mode, etc. doc()->typeChars(m_view, e->commitString()); } doc()->editEnd(); // Revert to the same range as above m_imPreeditRange->setRange(preeditRange); } if (!e->preeditString().isEmpty()) { doc()->inputMethodStart(); doc()->insertText(m_imPreeditRange->start(), e->preeditString()); doc()->inputMethodEnd(); // The preedit range gets automatically repositioned } // Finished this input method context? if (m_imPreeditRange && e->preeditString().isEmpty()) { // delete the range and reset the pointer delete m_imPreeditRange; m_imPreeditRange = nullptr; qDeleteAll(m_imPreeditRangeChildren); m_imPreeditRangeChildren.clear(); if (QApplication::cursorFlashTime() > 0) { renderer()->setDrawCaret(false); } renderer()->setCaretOverrideColor(QColor()); e->accept(); return; } KTextEditor::Cursor newCursor = m_cursor; bool hideCursor = false; QColor caretColor; if (m_imPreeditRange) { qDeleteAll(m_imPreeditRangeChildren); m_imPreeditRangeChildren.clear(); int decorationColumn = 0; foreach (const QInputMethodEvent::Attribute &a, e->attributes()) { if (a.type == QInputMethodEvent::Cursor) { newCursor = m_imPreeditRange->start() + KTextEditor::Cursor(0, a.start); hideCursor = !a.length; QColor c = qvariant_cast(a.value); if (c.isValid()) { caretColor = c; } } else if (a.type == QInputMethodEvent::TextFormat) { QTextCharFormat f = qvariant_cast(a.value).toCharFormat(); if (f.isValid() && decorationColumn <= a.start) { KTextEditor::Range fr(m_imPreeditRange->start().line(), m_imPreeditRange->start().column() + a.start, m_imPreeditRange->start().line(), m_imPreeditRange->start().column() + a.start + a.length); KTextEditor::MovingRange *formatRange = doc()->newMovingRange(fr); KTextEditor::Attribute::Ptr attribute(new KTextEditor::Attribute()); attribute->merge(f); formatRange->setAttribute(attribute); decorationColumn = a.start + a.length; m_imPreeditRangeChildren.push_back(formatRange); } } } } renderer()->setDrawCaret(hideCursor); renderer()->setCaretOverrideColor(caretColor); if (newCursor != m_cursor.toCursor()) { updateCursor(newCursor); } e->accept(); } //END IM INPUT STUFF void KateViewInternal::flashChar(const KTextEditor::Cursor &pos, KTextEditor::Attribute::Ptr attribute) { Q_ASSERT(pos.isValid()); Q_ASSERT(attribute.constData()); // if line is folded away, do nothing if (!m_view->textFolding().isLineVisible(pos.line())) { return; } KTextEditor::Range range(pos, KTextEditor::Cursor(pos.line(), pos.column() + 1)); if (m_textAnimation) { m_textAnimation->deleteLater(); } m_textAnimation = new KateTextAnimation(range, attribute, this); } void KateViewInternal::documentTextInserted(KTextEditor::Document *document, const KTextEditor::Range &range) { #ifndef QT_NO_ACCESSIBILITY if (QAccessible::isActive()) { QAccessibleTextInsertEvent ev(this, KateViewAccessible::positionFromCursor(this, range.start()), document->text(range)); QAccessible::updateAccessibility(&ev); } #endif } void KateViewInternal::documentTextRemoved(KTextEditor::Document * /*document*/, const KTextEditor::Range &range, const QString &oldText) { #ifndef QT_NO_ACCESSIBILITY if (QAccessible::isActive()) { QAccessibleTextRemoveEvent ev(this, KateViewAccessible::positionFromCursor(this, range.start()), oldText); QAccessible::updateAccessibility(&ev); } #endif }