File indexing completed on 2024-05-05 16:18:20
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2009 Michel Ludwig <michel.ludwig@kdemail.net> 0004 SPDX-FileCopyrightText: 2007 Mirko Stocker <me@misto.ch> 0005 SPDX-FileCopyrightText: 2003 Hamish Rodda <rodda@kde.org> 0006 SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org> 0007 SPDX-FileCopyrightText: 2001-2004 Christoph Cullmann <cullmann@kde.org> 0008 SPDX-FileCopyrightText: 2001-2010 Joseph Wenninger <jowenn@kde.org> 0009 SPDX-FileCopyrightText: 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> 0010 0011 SPDX-License-Identifier: LGPL-2.0-only 0012 */ 0013 0014 // BEGIN includes 0015 #include "kateview.h" 0016 0017 #include "clipboardhistorydialog.h" 0018 #include "export/exporter.h" 0019 #include "inlinenotedata.h" 0020 #include "kateabstractinputmode.h" 0021 #include "kateautoindent.h" 0022 #include "katebookmarks.h" 0023 #include "katebuffer.h" 0024 #include "katecompletionwidget.h" 0025 #include "kateconfig.h" 0026 #include "katedialogs.h" 0027 #include "katedocument.h" 0028 #include "kateglobal.h" 0029 #include "katehighlight.h" 0030 #include "katehighlightmenu.h" 0031 #include "katekeywordcompletion.h" 0032 #include "katelayoutcache.h" 0033 #include "katemessagewidget.h" 0034 #include "katemodemenu.h" 0035 #include "katepartdebug.h" 0036 #include "katestatusbar.h" 0037 #include "katetemplatehandler.h" 0038 #include "katetextline.h" 0039 #include "kateundomanager.h" 0040 #include "kateviewhelpers.h" 0041 #include "kateviewinternal.h" 0042 #include "katewordcompletion.h" 0043 #include "printing/kateprinter.h" 0044 #include "script/katescriptaction.h" 0045 #include "script/katescriptmanager.h" 0046 #include "spellcheck/spellcheck.h" 0047 #include "spellcheck/spellcheckdialog.h" 0048 #include "spellcheck/spellingmenu.h" 0049 0050 #include <KTextEditor/Message> 0051 #include <ktexteditor/inlinenoteprovider.h> 0052 0053 #include <KParts/Event> 0054 0055 #include <KActionCollection> 0056 #include <KConfig> 0057 #include <KConfigGroup> 0058 #include <KCursor> 0059 #include <KMessageBox> 0060 #include <KSelectAction> 0061 #include <KStandardAction> 0062 #include <KStandardShortcut> 0063 #include <KToggleAction> 0064 #include <KXMLGUIFactory> 0065 0066 #include <QActionGroup> 0067 #include <QApplication> 0068 #include <QClipboard> 0069 #include <QFileDialog> 0070 #include <QFileInfo> 0071 #include <QFont> 0072 #include <QInputDialog> 0073 #include <QKeyEvent> 0074 #include <QLayout> 0075 #include <QMimeData> 0076 #include <QPainter> 0077 #include <QRegularExpression> 0078 #include <QToolTip> 0079 0080 //#define VIEW_RANGE_DEBUG 0081 0082 // END includes 0083 0084 namespace 0085 { 0086 bool hasCommentInFirstLine(KTextEditor::DocumentPrivate *doc) 0087 { 0088 const Kate::TextLine &line = doc->kateTextLine(0); 0089 Q_ASSERT(line); 0090 return doc->isComment(0, line->firstChar()); 0091 } 0092 0093 } 0094 0095 void KTextEditor::ViewPrivate::blockFix(KTextEditor::Range &range) 0096 { 0097 if (range.start().column() > range.end().column()) { 0098 int tmp = range.start().column(); 0099 range.setStart(KTextEditor::Cursor(range.start().line(), range.end().column())); 0100 range.setEnd(KTextEditor::Cursor(range.end().line(), tmp)); 0101 } 0102 } 0103 0104 KTextEditor::ViewPrivate::ViewPrivate(KTextEditor::DocumentPrivate *doc, QWidget *parent, KTextEditor::MainWindow *mainWindow) 0105 : KTextEditor::View(this, parent) 0106 , m_completionWidget(nullptr) 0107 , m_annotationModel(nullptr) 0108 , m_hasWrap(false) 0109 , m_doc(doc) 0110 , m_textFolding(doc->buffer()) 0111 , m_config(new KateViewConfig(this)) 0112 , m_renderer(new KateRenderer(doc, m_textFolding, this)) 0113 , m_viewInternal(new KateViewInternal(this)) 0114 , m_spell(new KateSpellCheckDialog(this)) 0115 , m_bookmarks(new KateBookmarks(this)) 0116 , m_topSpacer(new QSpacerItem(0, 0)) 0117 , m_leftSpacer(new QSpacerItem(0, 0)) 0118 , m_rightSpacer(new QSpacerItem(0, 0)) 0119 , m_bottomSpacer(new QSpacerItem(0, 0)) 0120 , m_startingUp(true) 0121 , m_updatingDocumentConfig(false) 0122 , m_selection(m_doc->buffer(), KTextEditor::Range::invalid(), Kate::TextRange::ExpandLeft, Kate::TextRange::AllowEmpty) 0123 , blockSelect(false) 0124 , m_bottomViewBar(nullptr) 0125 , m_gotoBar(nullptr) 0126 , m_dictionaryBar(nullptr) 0127 , m_spellingMenu(new KateSpellingMenu(this)) 0128 , m_userContextMenuSet(false) 0129 , m_lineToUpdateRange(KTextEditor::LineRange::invalid()) 0130 , m_mainWindow(mainWindow ? mainWindow : KTextEditor::EditorPrivate::self()->dummyMainWindow()) // use dummy window if no window there! 0131 , m_statusBar(nullptr) 0132 , m_temporaryAutomaticInvocationDisabled(false) 0133 , m_autoFoldedFirstLine(false) 0134 { 0135 // queued connect to collapse view updates for range changes, INIT THIS EARLY ENOUGH! 0136 connect(this, &KTextEditor::ViewPrivate::delayedUpdateOfView, this, &KTextEditor::ViewPrivate::slotDelayedUpdateOfView, Qt::QueuedConnection); 0137 0138 m_delayedUpdateTimer.setSingleShot(true); 0139 m_delayedUpdateTimer.setInterval(0); 0140 connect(&m_delayedUpdateTimer, &QTimer::timeout, this, &KTextEditor::ViewPrivate::delayedUpdateOfView); 0141 0142 KXMLGUIClient::setComponentName(KTextEditor::EditorPrivate::self()->aboutData().componentName(), 0143 KTextEditor::EditorPrivate::self()->aboutData().displayName()); 0144 0145 // selection if for this view only and will invalidate if becoming empty 0146 m_selection.setView(this); 0147 0148 // use z depth defined in moving ranges interface 0149 m_selection.setZDepth(-100000.0); 0150 0151 KTextEditor::EditorPrivate::self()->registerView(this); 0152 0153 // try to let the main window, if any, create a view bar for this view 0154 QWidget *bottomBarParent = m_mainWindow->createViewBar(this); 0155 0156 m_bottomViewBar = new KateViewBar(bottomBarParent != nullptr, bottomBarParent ? bottomBarParent : this, this); 0157 0158 // ugly workaround: 0159 // Force the layout to be left-to-right even on RTL desktop, as discussed 0160 // on the mailing list. This will cause the lines and icons panel to be on 0161 // the left, even for Arabic/Hebrew/Farsi/whatever users. 0162 setLayoutDirection(Qt::LeftToRight); 0163 0164 m_bottomViewBar->installEventFilter(m_viewInternal); 0165 0166 // add KateMessageWidget for KTE::MessageInterface immediately above view 0167 m_messageWidgets[KTextEditor::Message::AboveView] = new KateMessageWidget(this); 0168 m_messageWidgets[KTextEditor::Message::AboveView]->hide(); 0169 0170 // add KateMessageWidget for KTE::MessageInterface immediately above view 0171 m_messageWidgets[KTextEditor::Message::BelowView] = new KateMessageWidget(this); 0172 m_messageWidgets[KTextEditor::Message::BelowView]->hide(); 0173 0174 // add bottom viewbar... 0175 if (bottomBarParent) { 0176 m_mainWindow->addWidgetToViewBar(this, m_bottomViewBar); 0177 } 0178 0179 // add layout for floating message widgets to KateViewInternal 0180 m_notificationLayout = new KateMessageLayout(m_viewInternal); 0181 m_notificationLayout->setContentsMargins(20, 20, 20, 20); 0182 m_viewInternal->setLayout(m_notificationLayout); 0183 0184 // this really is needed :) 0185 m_viewInternal->updateView(); 0186 0187 doc->addView(this); 0188 0189 setFocusProxy(m_viewInternal); 0190 setFocusPolicy(Qt::StrongFocus); 0191 0192 setXMLFile(QStringLiteral("katepart5ui.rc")); 0193 0194 setupConnections(); 0195 setupActions(); 0196 0197 // auto word completion 0198 new KateWordCompletionView(this, actionCollection()); 0199 0200 // update the enabled state of the undo/redo actions... 0201 slotUpdateUndo(); 0202 0203 // create the status bar of this view 0204 // do this after action creation, we use some of them! 0205 toggleStatusBar(); 0206 0207 m_startingUp = false; 0208 updateConfig(); 0209 0210 slotHlChanged(); 0211 KCursor::setAutoHideCursor(m_viewInternal, true); 0212 0213 for (auto messageWidget : m_messageWidgets) { 0214 if (messageWidget) { 0215 // user interaction (scrolling) starts notification auto-hide timer 0216 connect(this, &KTextEditor::ViewPrivate::displayRangeChanged, messageWidget, &KateMessageWidget::startAutoHideTimer); 0217 0218 // user interaction (cursor navigation) starts notification auto-hide timer 0219 connect(this, &KTextEditor::ViewPrivate::cursorPositionChanged, messageWidget, &KateMessageWidget::startAutoHideTimer); 0220 } 0221 } 0222 0223 // folding restoration on reload 0224 connect(m_doc, &KTextEditor::DocumentPrivate::aboutToReload, this, &KTextEditor::ViewPrivate::saveFoldingState); 0225 connect(m_doc, &KTextEditor::DocumentPrivate::reloaded, this, &KTextEditor::ViewPrivate::applyFoldingState); 0226 0227 connect(m_doc, &KTextEditor::DocumentPrivate::reloaded, this, &KTextEditor::ViewPrivate::slotDocumentReloaded); 0228 connect(m_doc, &KTextEditor::DocumentPrivate::aboutToReload, this, &KTextEditor::ViewPrivate::slotDocumentAboutToReload); 0229 0230 // update highlights on scrolling and co 0231 connect(this, &KTextEditor::ViewPrivate::displayRangeChanged, this, &KTextEditor::ViewPrivate::createHighlights); 0232 0233 // clear highlights on reload 0234 connect(m_doc, &KTextEditor::DocumentPrivate::aboutToReload, this, &KTextEditor::ViewPrivate::clearHighlights); 0235 0236 // setup layout 0237 setupLayout(); 0238 } 0239 0240 KTextEditor::ViewPrivate::~ViewPrivate() 0241 { 0242 // de-register views early from global collections 0243 // otherwise we might "use" them again during destruction in a half-valid state 0244 // see e.g. bug 422546 0245 // Kate::TextBuffer::notifyAboutRangeChange will access views() in a chain during 0246 // deletion of m_viewInternal 0247 doc()->removeView(this); 0248 KTextEditor::EditorPrivate::self()->deregisterView(this); 0249 0250 // remove from xmlgui factory, to be safe 0251 if (factory()) { 0252 factory()->removeClient(this); 0253 } 0254 0255 // delete internal view before view bar! 0256 delete m_viewInternal; 0257 0258 // remove view bar again, if needed 0259 m_mainWindow->deleteViewBar(this); 0260 m_bottomViewBar = nullptr; 0261 0262 delete m_renderer; 0263 0264 delete m_config; 0265 } 0266 0267 void KTextEditor::ViewPrivate::toggleStatusBar() 0268 { 0269 // if there, delete it 0270 if (m_statusBar) { 0271 bottomViewBar()->removePermanentBarWidget(m_statusBar); 0272 delete m_statusBar; 0273 m_statusBar = nullptr; 0274 Q_EMIT statusBarEnabledChanged(this, false); 0275 return; 0276 } 0277 0278 // else: create it 0279 m_statusBar = new KateStatusBar(this); 0280 bottomViewBar()->addPermanentBarWidget(m_statusBar); 0281 Q_EMIT statusBarEnabledChanged(this, true); 0282 } 0283 0284 void KTextEditor::ViewPrivate::setupLayout() 0285 { 0286 // delete old layout if any 0287 if (layout()) { 0288 delete layout(); 0289 0290 // need to recreate spacers because they are deleted with 0291 // their belonging layout 0292 m_topSpacer = new QSpacerItem(0, 0); 0293 m_leftSpacer = new QSpacerItem(0, 0); 0294 m_rightSpacer = new QSpacerItem(0, 0); 0295 m_bottomSpacer = new QSpacerItem(0, 0); 0296 } 0297 0298 // set margins 0299 QStyleOptionFrame opt; 0300 opt.initFrom(this); 0301 opt.frameShape = QFrame::StyledPanel; 0302 opt.state |= QStyle::State_Sunken; 0303 const int margin = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this); 0304 m_topSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); 0305 m_leftSpacer->changeSize(margin, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); 0306 m_rightSpacer->changeSize(margin, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); 0307 m_bottomSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); 0308 0309 // define layout 0310 QGridLayout *layout = new QGridLayout(this); 0311 layout->setContentsMargins(0, 0, 0, 0); 0312 layout->setSpacing(0); 0313 0314 const bool frameAroundContents = style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &opt, this); 0315 if (frameAroundContents) { 0316 // top message widget 0317 layout->addWidget(m_messageWidgets[KTextEditor::Message::AboveView], 0, 0, 1, 5); 0318 0319 // top spacer 0320 layout->addItem(m_topSpacer, 1, 0, 1, 4); 0321 0322 // left spacer 0323 layout->addItem(m_leftSpacer, 2, 0, 1, 1); 0324 0325 // left border 0326 layout->addWidget(m_viewInternal->m_leftBorder, 2, 1, 1, 1); 0327 0328 // view 0329 layout->addWidget(m_viewInternal, 2, 2, 1, 1); 0330 0331 // right spacer 0332 layout->addItem(m_rightSpacer, 2, 3, 1, 1); 0333 0334 // bottom spacer 0335 layout->addItem(m_bottomSpacer, 3, 0, 1, 4); 0336 0337 // vertical scrollbar 0338 layout->addWidget(m_viewInternal->m_lineScroll, 1, 4, 3, 1); 0339 0340 // horizontal scrollbar 0341 layout->addWidget(m_viewInternal->m_columnScroll, 4, 0, 1, 4); 0342 0343 // dummy 0344 layout->addWidget(m_viewInternal->m_dummy, 4, 4, 1, 1); 0345 0346 // bottom message 0347 layout->addWidget(m_messageWidgets[KTextEditor::Message::BelowView], 5, 0, 1, 5); 0348 0349 // bottom viewbar 0350 if (m_bottomViewBar->parentWidget() == this) { 0351 layout->addWidget(m_bottomViewBar, 6, 0, 1, 5); 0352 } 0353 0354 // stretch 0355 layout->setColumnStretch(2, 1); 0356 layout->setRowStretch(2, 1); 0357 0358 // adjust scrollbar background 0359 m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Window); 0360 m_viewInternal->m_lineScroll->setAutoFillBackground(false); 0361 0362 m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Window); 0363 m_viewInternal->m_columnScroll->setAutoFillBackground(false); 0364 0365 } else { 0366 // top message widget 0367 layout->addWidget(m_messageWidgets[KTextEditor::Message::AboveView], 0, 0, 1, 5); 0368 0369 // top spacer 0370 layout->addItem(m_topSpacer, 1, 0, 1, 5); 0371 0372 // left spacer 0373 layout->addItem(m_leftSpacer, 2, 0, 1, 1); 0374 0375 // left border 0376 layout->addWidget(m_viewInternal->m_leftBorder, 2, 1, 1, 1); 0377 0378 // view 0379 layout->addWidget(m_viewInternal, 2, 2, 1, 1); 0380 0381 // vertical scrollbar 0382 layout->addWidget(m_viewInternal->m_lineScroll, 2, 3, 1, 1); 0383 0384 // right spacer 0385 layout->addItem(m_rightSpacer, 2, 4, 1, 1); 0386 0387 // horizontal scrollbar 0388 layout->addWidget(m_viewInternal->m_columnScroll, 3, 1, 1, 2); 0389 0390 // dummy 0391 layout->addWidget(m_viewInternal->m_dummy, 3, 3, 1, 1); 0392 0393 // bottom spacer 0394 layout->addItem(m_bottomSpacer, 4, 0, 1, 5); 0395 0396 // bottom message 0397 layout->addWidget(m_messageWidgets[KTextEditor::Message::BelowView], 5, 0, 1, 5); 0398 0399 // bottom viewbar 0400 if (m_bottomViewBar->parentWidget() == this) { 0401 layout->addWidget(m_bottomViewBar, 6, 0, 1, 5); 0402 } 0403 0404 // stretch 0405 layout->setColumnStretch(2, 1); 0406 layout->setRowStretch(2, 1); 0407 0408 // adjust scrollbar background 0409 m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Base); 0410 m_viewInternal->m_lineScroll->setAutoFillBackground(true); 0411 0412 m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Base); 0413 m_viewInternal->m_columnScroll->setAutoFillBackground(true); 0414 } 0415 } 0416 0417 void KTextEditor::ViewPrivate::setupConnections() 0418 { 0419 connect(m_doc, &KTextEditor::DocumentPrivate::undoChanged, this, &KTextEditor::ViewPrivate::slotUpdateUndo); 0420 connect(m_doc, &KTextEditor::DocumentPrivate::highlightingModeChanged, this, &KTextEditor::ViewPrivate::slotHlChanged); 0421 connect(m_doc, &KTextEditor::DocumentPrivate::canceled, this, &KTextEditor::ViewPrivate::slotSaveCanceled); 0422 connect(m_viewInternal, &KateViewInternal::dropEventPass, this, &KTextEditor::ViewPrivate::dropEventPass); 0423 0424 connect(m_doc, &KTextEditor::DocumentPrivate::annotationModelChanged, m_viewInternal->m_leftBorder, &KateIconBorder::annotationModelChanged); 0425 } 0426 0427 void KTextEditor::ViewPrivate::goToPreviousEditingPosition() 0428 { 0429 auto c = doc()->lastEditingPosition(KTextEditor::DocumentPrivate::Previous, cursorPosition()); 0430 if (c.isValid()) { 0431 setCursorPosition(c); 0432 } 0433 } 0434 0435 void KTextEditor::ViewPrivate::goToNextEditingPosition() 0436 { 0437 auto c = doc()->lastEditingPosition(KTextEditor::DocumentPrivate::Next, cursorPosition()); 0438 if (c.isValid()) { 0439 setCursorPosition(c); 0440 } 0441 } 0442 void KTextEditor::ViewPrivate::setupActions() 0443 { 0444 KActionCollection *ac = actionCollection(); 0445 QAction *a; 0446 0447 m_toggleWriteLock = nullptr; 0448 0449 m_cut = a = ac->addAction(KStandardAction::Cut, this, SLOT(cut())); 0450 a->setWhatsThis(i18n("Cut the selected text and move it to the clipboard")); 0451 0452 m_paste = a = ac->addAction(KStandardAction::Paste, this, SLOT(paste())); 0453 a->setWhatsThis(i18n("Paste previously copied or cut clipboard contents")); 0454 0455 m_copy = a = ac->addAction(KStandardAction::Copy, this, SLOT(copy())); 0456 a->setWhatsThis(i18n("Use this command to copy the currently selected text to the system clipboard.")); 0457 0458 m_clipboardHistory = a = ac->addAction(QStringLiteral("clipboard_history_paste"), this, [this] { 0459 ClipboardHistoryDialog chd(mainWindow()->window(), this); 0460 chd.openDialog(KTextEditor::EditorPrivate::self()->clipboardHistory()); 0461 }); 0462 a->setText(i18n("Clipboard &History Paste")); 0463 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::ALT | Qt::Key_V)); 0464 0465 if (QApplication::clipboard()->supportsSelection()) { 0466 m_pasteSelection = a = ac->addAction(QStringLiteral("edit_paste_selection"), this, SLOT(pasteSelection())); 0467 a->setText(i18n("Paste Selection")); 0468 ac->setDefaultShortcuts(a, KStandardShortcut::pasteSelection()); 0469 a->setWhatsThis(i18n("Paste previously mouse selection contents")); 0470 } 0471 0472 m_swapWithClipboard = a = ac->addAction(QStringLiteral("edit_swap_with_clipboard"), this, SLOT(swapWithClipboard())); 0473 a->setText(i18n("Swap with Clipboard Contents")); 0474 a->setWhatsThis(i18n("Swap the selected text with the clipboard contents")); 0475 0476 if (!doc()->readOnly()) { 0477 a = ac->addAction(KStandardAction::Save, m_doc, SLOT(documentSave())); 0478 a->setWhatsThis(i18n("Save the current document")); 0479 0480 a = m_editUndo = ac->addAction(KStandardAction::Undo, m_doc, SLOT(undo())); 0481 a->setWhatsThis(i18n("Revert the most recent editing actions")); 0482 0483 a = m_editRedo = ac->addAction(KStandardAction::Redo, m_doc, SLOT(redo())); 0484 a->setWhatsThis(i18n("Revert the most recent undo operation")); 0485 0486 // Tools > Scripts 0487 // stored inside scoped pointer to ensure we destruct it early enough as it does internal cleanups of other child objects 0488 m_scriptActionMenu.reset(new KateScriptActionMenu(this, i18n("&Scripts"))); 0489 ac->addAction(QStringLiteral("tools_scripts"), m_scriptActionMenu.data()); 0490 0491 a = ac->addAction(QStringLiteral("tools_apply_wordwrap")); 0492 a->setText(i18n("Apply &Word Wrap")); 0493 a->setWhatsThis( 0494 i18n("Use this to wrap the current line, or to reformat the selected lines as paragraph, " 0495 "to fit the 'Wrap words at' setting in the configuration dialog.<br /><br />" 0496 "This is a static word wrap, meaning the document is changed.")); 0497 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::applyWordWrap); 0498 0499 a = ac->addAction(QStringLiteral("tools_cleanIndent")); 0500 a->setText(i18n("&Clean Indentation")); 0501 a->setWhatsThis( 0502 i18n("Use this to clean the indentation of a selected block of text (only tabs/only spaces).<br /><br />" 0503 "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog.")); 0504 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::cleanIndent); 0505 0506 a = ac->addAction(QStringLiteral("tools_formatIndent")); 0507 a->setText(i18n("&Format Indentation")); 0508 a->setWhatsThis(i18n("Use this to auto indent the current line or block of text to its proper indent level.")); 0509 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::formatIndent); 0510 0511 a = ac->addAction(QStringLiteral("tools_alignOn")); 0512 a->setText(i18n("&Align On...")); 0513 a->setWhatsThis( 0514 i18n("This command aligns lines in the selected block or whole document on the column given by a regular expression " 0515 "that you will be prompted for.<br /><br />" 0516 "If you give an empty pattern it will align on the first non-blank character by default.<br />" 0517 "If the pattern has a capture it will indent on the captured match.<br /><br />" 0518 "<i>Examples</i>:<br />" 0519 "With '-' it will insert spaces before the first '-' of each lines to align them all on the same column.<br />" 0520 "With ':\\s+(.)' it will insert spaces before the first non-blank character that occurs after a colon to align " 0521 "them all on the same column.")); 0522 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::alignOn); 0523 0524 a = ac->addAction(QStringLiteral("tools_comment")); 0525 a->setText(i18n("C&omment")); 0526 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_D)); 0527 a->setWhatsThis( 0528 i18n("This command comments out the current line or a selected block of text.<br /><br />" 0529 "The characters for single/multiple line comments are defined within the language's highlighting.")); 0530 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::comment); 0531 0532 a = ac->addAction(QStringLiteral("Previous Editing Line")); 0533 a->setText(i18n("Go to Previous Editing Location")); 0534 a->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left"))); 0535 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_E)); 0536 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::goToPreviousEditingPosition); 0537 0538 a = ac->addAction(QStringLiteral("Next Editing Line")); 0539 a->setText(i18n("Go to Next Editing Location")); 0540 a->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right"))); 0541 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_E)); 0542 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::goToNextEditingPosition); 0543 0544 a = ac->addAction(QStringLiteral("tools_uncomment")); 0545 a->setText(i18n("Unco&mment")); 0546 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_D)); 0547 a->setWhatsThis( 0548 i18n("This command removes comments from the current line or a selected block of text.<br /><br />" 0549 "The characters for single/multiple line comments are defined within the language's highlighting.")); 0550 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::uncomment); 0551 0552 a = ac->addAction(QStringLiteral("tools_toggle_comment")); 0553 a->setText(i18n("Toggle Comment")); 0554 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_Slash)); 0555 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleComment); 0556 0557 a = m_toggleWriteLock = new KToggleAction(i18n("&Read Only Mode"), this); 0558 a->setWhatsThis(i18n("Lock/unlock the document for writing")); 0559 a->setChecked(!doc()->isReadWrite()); 0560 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleWriteLock); 0561 ac->addAction(QStringLiteral("tools_toggle_write_lock"), a); 0562 0563 a = ac->addAction(QStringLiteral("tools_uppercase")); 0564 a->setIcon(QIcon::fromTheme(QStringLiteral("format-text-uppercase"))); 0565 a->setText(i18n("Uppercase")); 0566 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_U)); 0567 a->setWhatsThis( 0568 i18n("Convert the selection to uppercase, or the character to the " 0569 "right of the cursor if no text is selected.")); 0570 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::uppercase); 0571 0572 a = ac->addAction(QStringLiteral("tools_lowercase")); 0573 a->setIcon(QIcon::fromTheme(QStringLiteral("format-text-lowercase"))); 0574 a->setText(i18n("Lowercase")); 0575 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_U)); 0576 a->setWhatsThis( 0577 i18n("Convert the selection to lowercase, or the character to the " 0578 "right of the cursor if no text is selected.")); 0579 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::lowercase); 0580 0581 a = ac->addAction(QStringLiteral("tools_capitalize")); 0582 a->setIcon(QIcon::fromTheme(QStringLiteral("format-text-capitalize"))); 0583 a->setText(i18n("Capitalize")); 0584 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_U)); 0585 a->setWhatsThis( 0586 i18n("Capitalize the selection, or the word under the " 0587 "cursor if no text is selected.")); 0588 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::capitalize); 0589 0590 a = ac->addAction(QStringLiteral("tools_join_lines")); 0591 a->setText(i18n("Join Lines")); 0592 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_J)); 0593 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::joinLines); 0594 0595 a = ac->addAction(QStringLiteral("tools_invoke_code_completion")); 0596 a->setText(i18n("Invoke Code Completion")); 0597 a->setWhatsThis(i18n("Manually invoke command completion, usually by using a shortcut bound to this action.")); 0598 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_Space)); 0599 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::userInvokedCompletion); 0600 } else { 0601 for (auto *action : {m_cut, m_paste, m_clipboardHistory, m_swapWithClipboard}) { 0602 action->setEnabled(false); 0603 } 0604 0605 if (m_pasteSelection) { 0606 m_pasteSelection->setEnabled(false); 0607 } 0608 0609 m_editUndo = nullptr; 0610 m_editRedo = nullptr; 0611 } 0612 0613 a = ac->addAction(KStandardAction::Print, this, SLOT(print())); 0614 a->setWhatsThis(i18n("Print the current document.")); 0615 0616 a = ac->addAction(KStandardAction::PrintPreview, this, SLOT(printPreview())); 0617 a->setWhatsThis(i18n("Show print preview of current document")); 0618 0619 a = ac->addAction(QStringLiteral("file_reload")); 0620 a->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); 0621 a->setText(i18n("Reloa&d")); 0622 ac->setDefaultShortcuts(a, KStandardShortcut::reload()); 0623 a->setWhatsThis(i18n("Reload the current document from disk.")); 0624 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::reloadFile); 0625 0626 a = ac->addAction(KStandardAction::SaveAs, m_doc, SLOT(documentSaveAs())); 0627 a->setWhatsThis(i18n("Save the current document to disk, with a name of your choice.")); 0628 0629 a = new KateViewEncodingAction(m_doc, this, i18n("Save As with Encoding..."), this, true /* special mode for save as */); 0630 a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); 0631 ac->addAction(QStringLiteral("file_save_as_with_encoding"), a); 0632 0633 a = ac->addAction(QStringLiteral("file_save_copy_as")); 0634 a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); 0635 a->setText(i18n("Save &Copy As...")); 0636 a->setWhatsThis(i18n("Save a copy of the current document to disk.")); 0637 connect(a, &QAction::triggered, m_doc, &KTextEditor::DocumentPrivate::documentSaveCopyAs); 0638 0639 a = ac->addAction(KStandardAction::GotoLine, this, SLOT(gotoLine())); 0640 a->setWhatsThis(i18n("This command opens a dialog and lets you choose a line that you want the cursor to move to.")); 0641 0642 a = ac->addAction(QStringLiteral("modified_line_up")); 0643 a->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); 0644 a->setText(i18n("Go to Previous Modified Line")); 0645 a->setWhatsThis(i18n("Move upwards to the previous modified line.")); 0646 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toPrevModifiedLine); 0647 0648 a = ac->addAction(QStringLiteral("modified_line_down")); 0649 a->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); 0650 a->setText(i18n("Go to Next Modified Line")); 0651 a->setWhatsThis(i18n("Move downwards to the next modified line.")); 0652 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toNextModifiedLine); 0653 0654 a = ac->addAction(QStringLiteral("set_confdlg")); 0655 a->setText(i18n("&Configure Editor...")); 0656 a->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other"))); 0657 a->setWhatsThis(i18n("Configure various aspects of this editor.")); 0658 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::slotConfigDialog); 0659 0660 m_modeAction = new KateModeMenu(i18n("&Mode"), this); 0661 ac->addAction(QStringLiteral("tools_mode"), m_modeAction); 0662 m_modeAction->setWhatsThis(i18n( 0663 "Here you can choose which mode should be used for the current document. This will influence the highlighting and folding being used, for example.")); 0664 m_modeAction->updateMenu(m_doc); 0665 0666 KateHighlightingMenu *menu = new KateHighlightingMenu(i18n("&Highlighting"), this); 0667 ac->addAction(QStringLiteral("tools_highlighting"), menu); 0668 menu->setWhatsThis(i18n("Here you can choose how the current document should be highlighted.")); 0669 menu->updateMenu(m_doc); 0670 0671 KateViewSchemaAction *schemaMenu = new KateViewSchemaAction(i18n("&Editor Color Theme"), this); 0672 schemaMenu->setIcon(QIcon::fromTheme(QStringLiteral("kcolorchooser"))); 0673 ac->addAction(QStringLiteral("view_schemas"), schemaMenu); 0674 schemaMenu->updateMenu(this); 0675 0676 // indentation menu 0677 KateViewIndentationAction *indentMenu = new KateViewIndentationAction(m_doc, i18n("&Indentation"), this); 0678 ac->addAction(QStringLiteral("tools_indentation"), indentMenu); 0679 0680 m_selectAll = ac->addAction(KStandardAction::SelectAll); 0681 connect(m_selectAll, &QAction::triggered, this, [this] { 0682 selectAll(); 0683 qApp->clipboard()->setText(selectionText(), QClipboard::Selection); 0684 }); 0685 a->setWhatsThis(i18n("Select the entire text of the current document.")); 0686 0687 m_deSelect = a = ac->addAction(KStandardAction::Deselect, this, SLOT(clearSelection())); 0688 a->setWhatsThis(i18n("If you have selected something within the current document, this will no longer be selected.")); 0689 0690 a = ac->addAction(QStringLiteral("view_inc_font_sizes")); 0691 a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in"))); 0692 a->setText(i18n("Enlarge Font")); 0693 ac->setDefaultShortcuts(a, KStandardShortcut::zoomIn()); 0694 a->setWhatsThis(i18n("This increases the display font size.")); 0695 connect(a, &QAction::triggered, m_viewInternal, [this]() { 0696 m_viewInternal->slotIncFontSizes(); 0697 }); 0698 0699 a = ac->addAction(QStringLiteral("view_dec_font_sizes")); 0700 a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out"))); 0701 a->setText(i18n("Shrink Font")); 0702 ac->setDefaultShortcuts(a, KStandardShortcut::zoomOut()); 0703 a->setWhatsThis(i18n("This decreases the display font size.")); 0704 connect(a, &QAction::triggered, m_viewInternal, [this]() { 0705 m_viewInternal->slotDecFontSizes(); 0706 }); 0707 0708 a = ac->addAction(QStringLiteral("view_reset_font_sizes")); 0709 a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-original"))); 0710 a->setText(i18n("Reset Font Size")); 0711 ac->setDefaultShortcuts(a, KStandardShortcut::shortcut(KStandardShortcut::ActualSize)); 0712 a->setWhatsThis(i18n("This resets the display font size.")); 0713 connect(a, &QAction::triggered, m_viewInternal, &KateViewInternal::slotResetFontSizes); 0714 0715 a = m_toggleBlockSelection = new KToggleAction(i18n("Bl&ock Selection Mode"), this); 0716 ac->addAction(QStringLiteral("set_verticalSelect"), a); 0717 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_B)); 0718 a->setWhatsThis(i18n("This command allows switching between the normal (line based) selection mode and the block selection mode.")); 0719 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleBlockSelection); 0720 0721 a = ac->addAction(QStringLiteral("switch_next_input_mode")); 0722 a->setText(i18n("Switch to Next Input Mode")); 0723 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_V)); 0724 a->setWhatsThis(i18n("Switch to the next input mode.")); 0725 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::cycleInputMode); 0726 0727 a = m_toggleInsert = new KToggleAction(i18n("Overwr&ite Mode"), this); 0728 ac->addAction(QStringLiteral("set_insert"), a); 0729 ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Insert)); 0730 a->setWhatsThis(i18n("Choose whether you want the text you type to be inserted or to overwrite existing text.")); 0731 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleInsert); 0732 0733 KToggleAction *toggleAction; 0734 a = m_toggleDynWrap = toggleAction = new KToggleAction(i18n("&Dynamic Word Wrap"), this); 0735 a->setIcon(QIcon::fromTheme(QStringLiteral("text-wrap"))); 0736 ac->addAction(QStringLiteral("view_dynamic_word_wrap"), a); 0737 a->setWhatsThis( 0738 i18n("If this option is checked, the text lines will be wrapped at the view border on the screen.<br /><br />" 0739 "This is only a view option, meaning the document will not changed.")); 0740 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleDynWordWrap); 0741 0742 a = m_setDynWrapIndicators = new KSelectAction(i18n("Dynamic Word Wrap Indicators"), this); 0743 ac->addAction(QStringLiteral("dynamic_word_wrap_indicators"), a); 0744 a->setWhatsThis(i18n("Choose when the Dynamic Word Wrap Indicators should be displayed")); 0745 connect(m_setDynWrapIndicators, &KSelectAction::indexTriggered, this, &KTextEditor::ViewPrivate::setDynWrapIndicators); 0746 const QStringList list2{i18n("&Off"), i18n("Follow &Line Numbers"), i18n("&Always On")}; 0747 m_setDynWrapIndicators->setItems(list2); 0748 m_setDynWrapIndicators->setEnabled(m_toggleDynWrap->isChecked()); // only synced on real change, later 0749 0750 a = toggleAction = new KToggleAction(i18n("Static Word Wrap"), this); 0751 ac->addAction(QStringLiteral("view_static_word_wrap"), a); 0752 a->setWhatsThis(i18n("If this option is checked, the text lines will be wrapped at the column defined in the editing properties.")); 0753 connect(a, &KToggleAction::triggered, [=] { 0754 if (m_doc) { 0755 m_doc->setWordWrap(!m_doc->wordWrap()); 0756 } 0757 }); 0758 0759 a = toggleAction = m_toggleWWMarker = new KToggleAction(i18n("Show Static &Word Wrap Marker"), this); 0760 ac->addAction(QStringLiteral("view_word_wrap_marker"), a); 0761 a->setWhatsThis( 0762 i18n("Show/hide the Word Wrap Marker, a vertical line drawn at the word " 0763 "wrap column as defined in the editing properties")); 0764 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleWWMarker); 0765 0766 a = toggleAction = m_toggleFoldingMarkers = new KToggleAction(i18n("Show Folding &Markers"), this); 0767 ac->addAction(QStringLiteral("view_folding_markers"), a); 0768 a->setWhatsThis(i18n("You can choose if the codefolding marks should be shown, if codefolding is possible.")); 0769 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleFoldingMarkers); 0770 0771 a = m_toggleIconBar = toggleAction = new KToggleAction(i18n("Show &Icon Border"), this); 0772 ac->addAction(QStringLiteral("view_border"), a); 0773 a->setWhatsThis(i18n("Show/hide the icon border.<br /><br />The icon border shows bookmark symbols, for instance.")); 0774 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleIconBorder); 0775 0776 a = toggleAction = m_toggleLineNumbers = new KToggleAction(i18n("Show &Line Numbers"), this); 0777 ac->addAction(QStringLiteral("view_line_numbers"), a); 0778 a->setWhatsThis(i18n("Show/hide the line numbers on the left hand side of the view.")); 0779 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleLineNumbersOn); 0780 0781 a = m_toggleScrollBarMarks = toggleAction = new KToggleAction(i18n("Show Scroll&bar Marks"), this); 0782 ac->addAction(QStringLiteral("view_scrollbar_marks"), a); 0783 a->setWhatsThis(i18n("Show/hide the marks on the vertical scrollbar.<br /><br />The marks show bookmarks, for instance.")); 0784 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleScrollBarMarks); 0785 0786 a = m_toggleScrollBarMiniMap = toggleAction = new KToggleAction(i18n("Show Scrollbar Mini-Map"), this); 0787 ac->addAction(QStringLiteral("view_scrollbar_minimap"), a); 0788 a->setWhatsThis(i18n("Show/hide the mini-map on the vertical scrollbar.<br /><br />The mini-map shows an overview of the whole document.")); 0789 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleScrollBarMiniMap); 0790 0791 a = m_doc->autoReloadToggleAction(); 0792 ac->addAction(QStringLiteral("view_auto_reload"), a); 0793 0794 // a = m_toggleScrollBarMiniMapAll = toggleAction = new KToggleAction(i18n("Show the whole document in the Mini-Map"), this); 0795 // ac->addAction(QLatin1String("view_scrollbar_minimap_all"), a); 0796 // a->setWhatsThis(i18n("Display the whole document in the mini-map.<br /><br />With this option set the whole document will be visible in the 0797 // mini-map.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleScrollBarMiniMapAll())); connect(m_toggleScrollBarMiniMap, SIGNAL(triggered(bool)), 0798 // m_toggleScrollBarMiniMapAll, SLOT(setEnabled(bool))); 0799 0800 a = m_toggleNPSpaces = new KToggleAction(i18n("Show Non-Printable Spaces"), this); 0801 ac->addAction(QStringLiteral("view_non_printable_spaces"), a); 0802 a->setWhatsThis(i18n("Show/hide bounding box around non-printable spaces")); 0803 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleNPSpaces); 0804 0805 a = m_switchCmdLine = ac->addAction(QStringLiteral("switch_to_cmd_line")); 0806 a->setText(i18n("Switch to Command Line")); 0807 ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F7)); 0808 a->setWhatsThis(i18n("Show/hide the command line on the bottom of the view.")); 0809 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::switchToCmdLine); 0810 0811 KActionMenu *am = new KActionMenu(i18n("Input Modes"), this); 0812 m_inputModeActions = new QActionGroup(am); 0813 ac->addAction(QStringLiteral("view_input_modes"), am); 0814 auto switchInputModeAction = ac->action(QStringLiteral("switch_next_input_mode")); 0815 am->addAction(switchInputModeAction); 0816 am->addSeparator(); 0817 for (const auto &mode : m_viewInternal->m_inputModes) { 0818 a = new QAction(mode->viewInputModeHuman(), m_inputModeActions); 0819 am->addAction(a); 0820 a->setWhatsThis(i18n("Activate/deactivate %1", mode->viewInputModeHuman())); 0821 const InputMode im = mode->viewInputMode(); 0822 a->setData(static_cast<int>(im)); 0823 a->setCheckable(true); 0824 if (im == m_config->inputMode()) { 0825 a->setChecked(true); 0826 } 0827 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleInputMode); 0828 } 0829 0830 a = m_setEndOfLine = new KSelectAction(i18n("&End of Line"), this); 0831 ac->addAction(QStringLiteral("set_eol"), a); 0832 a->setWhatsThis(i18n("Choose which line endings should be used, when you save the document")); 0833 const QStringList list{i18nc("@item:inmenu End of Line", "&UNIX"), 0834 i18nc("@item:inmenu End of Line", "&Windows/DOS"), 0835 i18nc("@item:inmenu End of Line", "&Macintosh")}; 0836 m_setEndOfLine->setItems(list); 0837 m_setEndOfLine->setCurrentItem(doc()->config()->eol()); 0838 connect(m_setEndOfLine, &KSelectAction::indexTriggered, this, &KTextEditor::ViewPrivate::setEol); 0839 0840 a = m_addBom = new KToggleAction(i18n("Add &Byte Order Mark (BOM)"), this); 0841 m_addBom->setChecked(doc()->config()->bom()); 0842 ac->addAction(QStringLiteral("add_bom"), a); 0843 a->setWhatsThis(i18n("Enable/disable adding of byte order marks for UTF-8/UTF-16 encoded files while saving")); 0844 connect(m_addBom, &KToggleAction::triggered, this, &KTextEditor::ViewPrivate::setAddBom); 0845 0846 // encoding menu 0847 m_encodingAction = new KateViewEncodingAction(m_doc, this, i18n("E&ncoding"), this); 0848 ac->addAction(QStringLiteral("set_encoding"), m_encodingAction); 0849 0850 a = ac->addAction(KStandardAction::Find, this, SLOT(find())); 0851 a->setWhatsThis(i18n("Look up the first occurrence of a piece of text or regular expression.")); 0852 addAction(a); 0853 0854 a = ac->addAction(QStringLiteral("edit_find_selected")); 0855 a->setText(i18n("Find Selected")); 0856 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_H)); 0857 a->setWhatsThis(i18n("Finds next occurrence of selected text.")); 0858 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::findSelectedForwards); 0859 0860 a = ac->addAction(QStringLiteral("edit_find_selected_backwards")); 0861 a->setText(i18n("Find Selected Backwards")); 0862 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_H)); 0863 a->setWhatsThis(i18n("Finds previous occurrence of selected text.")); 0864 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::findSelectedBackwards); 0865 0866 a = ac->addAction(QStringLiteral("edit_find_multicursor_next_occurrence")); 0867 a->setText(i18n("Find and Select Next Occurrence")); 0868 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::Key_J)); 0869 a->setWhatsThis(i18n("Finds next occurrence of the word under cursor and add it to selection.")); 0870 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::findNextOccurunceAndSelect); 0871 0872 a = ac->addAction(QStringLiteral("edit_skip_multicursor_current_occurrence")); 0873 a->setText(i18n("Mark Currently Selected Occurrence as Skipped")); 0874 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::Key_K)); 0875 a->setWhatsThis(i18n("Marks the currently selected word as skipped.")); 0876 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::skipCurrentOccurunceSelection); 0877 0878 a = ac->addAction(QStringLiteral("edit_find_multicursor_all_occurrences")); 0879 a->setText(i18n("Find and Select All Occurrences")); 0880 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::SHIFT | Qt::CTRL | Qt::Key_J)); 0881 a->setWhatsThis(i18n("Finds all occurrences of the word under cursor and select them.")); 0882 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::findAllOccuruncesAndSelect); 0883 0884 a = ac->addAction(KStandardAction::FindNext, this, SLOT(findNext())); 0885 a->setWhatsThis(i18n("Look up the next occurrence of the search phrase.")); 0886 addAction(a); 0887 0888 a = ac->addAction(KStandardAction::FindPrev, QStringLiteral("edit_find_prev"), this, SLOT(findPrevious())); 0889 a->setWhatsThis(i18n("Look up the previous occurrence of the search phrase.")); 0890 addAction(a); 0891 0892 a = ac->addAction(KStandardAction::Replace, this, SLOT(replace())); 0893 a->setWhatsThis(i18n("Look up a piece of text or regular expression and replace the result with some given text.")); 0894 0895 a = ac->addAction(QStringLiteral("edit_create_multi_cursor_from_sel")); 0896 a->setText(i18n("Add Cursors to Line Ends")); 0897 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_I)); 0898 a->setWhatsThis(i18n("Creates a cursor at the end of every line in selection.")); 0899 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::createMultiCursorsFromSelection); 0900 0901 a = ac->addAction(QStringLiteral("edit_create_multi_cursor_down")); 0902 a->setText(i18n("Add Caret below Cursor")); 0903 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::CTRL | Qt::Key_Down)); 0904 a->setWhatsThis(i18n("Adds a caret in the line below the current caret.")); 0905 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::addSecondaryCursorDown); 0906 0907 a = ac->addAction(QStringLiteral("edit_create_multi_cursor_up")); 0908 a->setText(i18n("Add Caret above Cursor")); 0909 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::CTRL | Qt::Key_Up)); 0910 a->setWhatsThis(i18n("Adds a caret in the line above the current caret.")); 0911 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::addSecondaryCursorUp); 0912 0913 a = ac->addAction(QStringLiteral("edit_toggle_camel_case_cursor")); 0914 a->setText(i18n("Toggle Camel Case Cursor Movement")); 0915 a->setWhatsThis(i18n("Toggle between normal word movement and camel case cursor movement.")); 0916 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleCamelCaseCursor); 0917 0918 a = ac->addAction(QStringLiteral("edit_remove_cursors_from_empty_lines")); 0919 a->setText(i18n("Remove Cursors from Empty Lines")); 0920 a->setWhatsThis(i18n("Remove cursors from empty lines")); 0921 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::removeCursorsFromEmptyLines); 0922 0923 m_spell->createActions(ac); 0924 m_toggleOnTheFlySpellCheck = new KToggleAction(i18n("Automatic Spell Checking"), this); 0925 m_toggleOnTheFlySpellCheck->setWhatsThis(i18n("Enable/disable automatic spell checking")); 0926 connect(m_toggleOnTheFlySpellCheck, &KToggleAction::triggered, this, &KTextEditor::ViewPrivate::toggleOnTheFlySpellCheck); 0927 ac->addAction(QStringLiteral("tools_toggle_automatic_spell_checking"), m_toggleOnTheFlySpellCheck); 0928 ac->setDefaultShortcut(m_toggleOnTheFlySpellCheck, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_O)); 0929 0930 a = ac->addAction(QStringLiteral("tools_change_dictionary")); 0931 a->setText(i18n("Change Dictionary...")); 0932 a->setWhatsThis(i18n("Change the dictionary that is used for spell checking.")); 0933 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::changeDictionary); 0934 0935 a = ac->addAction(QStringLiteral("tools_clear_dictionary_ranges")); 0936 a->setText(i18n("Clear Dictionary Ranges")); 0937 a->setEnabled(false); 0938 a->setWhatsThis(i18n("Remove all the separate dictionary ranges that were set for spell checking.")); 0939 connect(a, &QAction::triggered, m_doc, &KTextEditor::DocumentPrivate::clearDictionaryRanges); 0940 connect(m_doc, &KTextEditor::DocumentPrivate::dictionaryRangesPresent, a, &QAction::setEnabled); 0941 0942 m_copyHtmlAction = ac->addAction(QStringLiteral("edit_copy_html"), this, SLOT(exportHtmlToClipboard())); 0943 m_copyHtmlAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); 0944 m_copyHtmlAction->setText(i18n("Copy as &HTML")); 0945 m_copyHtmlAction->setWhatsThis(i18n("Use this command to copy the currently selected text as HTML to the system clipboard.")); 0946 0947 a = ac->addAction(QStringLiteral("file_export_html"), this, SLOT(exportHtmlToFile())); 0948 a->setIcon(QIcon::fromTheme(QStringLiteral("document-export"))); 0949 a->setText(i18n("E&xport as HTML...")); 0950 a->setWhatsThis( 0951 i18n("This command allows you to export the current document" 0952 " with all highlighting information into a HTML document.")); 0953 0954 m_spellingMenu->createActions(ac); 0955 0956 m_bookmarks->createActions(ac); 0957 0958 slotSelectionChanged(); 0959 0960 // Now setup the editing actions before adding the associated 0961 // widget and setting the shortcut context 0962 setupEditActions(); 0963 setupCodeFolding(); 0964 0965 ac->addAssociatedWidget(m_viewInternal); 0966 0967 const auto actions = ac->actions(); 0968 for (QAction *action : actions) { 0969 action->setShortcutContext(Qt::WidgetWithChildrenShortcut); 0970 } 0971 0972 connect(this, &KTextEditor::ViewPrivate::selectionChanged, this, &KTextEditor::ViewPrivate::slotSelectionChanged); 0973 } 0974 0975 void KTextEditor::ViewPrivate::slotConfigDialog() 0976 { 0977 // invoke config dialog, will auto-save configuration to katepartrc 0978 KTextEditor::EditorPrivate::self()->configDialog(this); 0979 } 0980 0981 void KTextEditor::ViewPrivate::setupEditActions() 0982 { 0983 // If you add an editing action to this 0984 // function make sure to include the line 0985 // m_editActions << a after creating the action 0986 KActionCollection *ac = actionCollection(); 0987 0988 QAction *a = ac->addAction(QStringLiteral("word_left")); 0989 a->setText(i18n("Move Word Left")); 0990 ac->setDefaultShortcuts(a, KStandardShortcut::backwardWord()); 0991 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::wordLeft); 0992 m_editActions.push_back(a); 0993 0994 a = ac->addAction(QStringLiteral("select_char_left")); 0995 a->setText(i18n("Select Character Left")); 0996 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_Left)); 0997 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftCursorLeft); 0998 m_editActions.push_back(a); 0999 1000 a = ac->addAction(QStringLiteral("select_word_left")); 1001 a->setText(i18n("Select Word Left")); 1002 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_Left)); 1003 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftWordLeft); 1004 m_editActions.push_back(a); 1005 1006 a = ac->addAction(QStringLiteral("word_right")); 1007 a->setText(i18n("Move Word Right")); 1008 ac->setDefaultShortcuts(a, KStandardShortcut::forwardWord()); 1009 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::wordRight); 1010 m_editActions.push_back(a); 1011 1012 a = ac->addAction(QStringLiteral("select_char_right")); 1013 a->setText(i18n("Select Character Right")); 1014 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_Right)); 1015 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftCursorRight); 1016 m_editActions.push_back(a); 1017 1018 a = ac->addAction(QStringLiteral("select_word_right")); 1019 a->setText(i18n("Select Word Right")); 1020 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_Right)); 1021 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftWordRight); 1022 m_editActions.push_back(a); 1023 1024 a = ac->addAction(QStringLiteral("beginning_of_line")); 1025 a->setText(i18n("Move to Beginning of Line")); 1026 ac->setDefaultShortcuts(a, KStandardShortcut::beginningOfLine()); 1027 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::home); 1028 m_editActions.push_back(a); 1029 1030 a = ac->addAction(QStringLiteral("beginning_of_document")); 1031 a->setText(i18n("Move to Beginning of Document")); 1032 ac->setDefaultShortcuts(a, KStandardShortcut::begin()); 1033 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::top); 1034 m_editActions.push_back(a); 1035 1036 a = ac->addAction(QStringLiteral("select_beginning_of_line")); 1037 a->setText(i18n("Select to Beginning of Line")); 1038 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_Home)); 1039 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftHome); 1040 m_editActions.push_back(a); 1041 1042 a = ac->addAction(QStringLiteral("select_beginning_of_document")); 1043 a->setText(i18n("Select to Beginning of Document")); 1044 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_Home)); 1045 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftTop); 1046 m_editActions.push_back(a); 1047 1048 a = ac->addAction(QStringLiteral("end_of_line")); 1049 a->setText(i18n("Move to End of Line")); 1050 ac->setDefaultShortcuts(a, KStandardShortcut::endOfLine()); 1051 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::end); 1052 m_editActions.push_back(a); 1053 1054 a = ac->addAction(QStringLiteral("end_of_document")); 1055 a->setText(i18n("Move to End of Document")); 1056 ac->setDefaultShortcuts(a, KStandardShortcut::end()); 1057 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::bottom); 1058 m_editActions.push_back(a); 1059 1060 a = ac->addAction(QStringLiteral("select_end_of_line")); 1061 a->setText(i18n("Select to End of Line")); 1062 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_End)); 1063 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftEnd); 1064 m_editActions.push_back(a); 1065 1066 a = ac->addAction(QStringLiteral("select_end_of_document")); 1067 a->setText(i18n("Select to End of Document")); 1068 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_End)); 1069 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftBottom); 1070 m_editActions.push_back(a); 1071 1072 a = ac->addAction(QStringLiteral("select_line_up")); 1073 a->setText(i18n("Select to Previous Line")); 1074 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_Up)); 1075 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftUp); 1076 m_editActions.push_back(a); 1077 1078 a = ac->addAction(QStringLiteral("scroll_line_up")); 1079 a->setText(i18n("Scroll Line Up")); 1080 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_Up)); 1081 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::scrollUp); 1082 m_editActions.push_back(a); 1083 1084 a = ac->addAction(QStringLiteral("move_line_down")); 1085 a->setText(i18n("Move to Next Line")); 1086 ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Down)); 1087 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::down); 1088 m_editActions.push_back(a); 1089 1090 a = ac->addAction(QStringLiteral("move_line_up")); 1091 a->setText(i18n("Move to Previous Line")); 1092 ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Up)); 1093 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::up); 1094 m_editActions.push_back(a); 1095 1096 a = ac->addAction(QStringLiteral("move_cursor_right")); 1097 a->setText(i18n("Move Cursor Right")); 1098 ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Right)); 1099 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::cursorRight); 1100 m_editActions.push_back(a); 1101 1102 a = ac->addAction(QStringLiteral("move_cursor_left")); 1103 a->setText(i18n("Move Cursor Left")); 1104 ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Left)); 1105 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::cursorLeft); 1106 m_editActions.push_back(a); 1107 1108 a = ac->addAction(QStringLiteral("select_line_down")); 1109 a->setText(i18n("Select to Next Line")); 1110 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_Down)); 1111 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftDown); 1112 m_editActions.push_back(a); 1113 1114 a = ac->addAction(QStringLiteral("scroll_line_down")); 1115 a->setText(i18n("Scroll Line Down")); 1116 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_Down)); 1117 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::scrollDown); 1118 m_editActions.push_back(a); 1119 1120 a = ac->addAction(QStringLiteral("scroll_page_up")); 1121 a->setText(i18n("Scroll Page Up")); 1122 ac->setDefaultShortcuts(a, KStandardShortcut::prior()); 1123 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::pageUp); 1124 m_editActions.push_back(a); 1125 1126 a = ac->addAction(QStringLiteral("select_page_up")); 1127 a->setText(i18n("Select Page Up")); 1128 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_PageUp)); 1129 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftPageUp); 1130 m_editActions.push_back(a); 1131 1132 a = ac->addAction(QStringLiteral("move_top_of_view")); 1133 a->setText(i18n("Move to Top of View")); 1134 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::Key_Home)); 1135 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::topOfView); 1136 m_editActions.push_back(a); 1137 1138 a = ac->addAction(QStringLiteral("select_top_of_view")); 1139 a->setText(i18n("Select to Top of View")); 1140 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_Home)); 1141 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftTopOfView); 1142 m_editActions.push_back(a); 1143 1144 a = ac->addAction(QStringLiteral("scroll_page_down")); 1145 a->setText(i18n("Scroll Page Down")); 1146 ac->setDefaultShortcuts(a, KStandardShortcut::next()); 1147 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::pageDown); 1148 m_editActions.push_back(a); 1149 1150 a = ac->addAction(QStringLiteral("select_page_down")); 1151 a->setText(i18n("Select Page Down")); 1152 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_PageDown)); 1153 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftPageDown); 1154 m_editActions.push_back(a); 1155 1156 a = ac->addAction(QStringLiteral("move_bottom_of_view")); 1157 a->setText(i18n("Move to Bottom of View")); 1158 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::Key_End)); 1159 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::bottomOfView); 1160 m_editActions.push_back(a); 1161 1162 a = ac->addAction(QStringLiteral("select_bottom_of_view")); 1163 a->setText(i18n("Select to Bottom of View")); 1164 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_End)); 1165 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftBottomOfView); 1166 m_editActions.push_back(a); 1167 1168 a = ac->addAction(QStringLiteral("to_matching_bracket")); 1169 a->setText(i18n("Go to Matching Bracket")); 1170 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_6)); 1171 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toMatchingBracket); 1172 // m_editActions << a; 1173 1174 a = ac->addAction(QStringLiteral("select_matching_bracket")); 1175 a->setText(i18n("Select to Matching Bracket")); 1176 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_6)); 1177 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftToMatchingBracket); 1178 // m_editActions << a; 1179 1180 // anders: shortcuts doing any changes should not be created in read-only mode 1181 if (!doc()->readOnly()) { 1182 a = ac->addAction(QStringLiteral("transpose_char")); 1183 a->setText(i18n("Transpose Characters")); 1184 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::transpose); 1185 m_editActions.push_back(a); 1186 1187 a = ac->addAction(QStringLiteral("transpose_word")); 1188 a->setText(i18n("Transpose Words")); 1189 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::transposeWord); 1190 m_editActions.push_back(a); 1191 1192 a = ac->addAction(QStringLiteral("delete_line")); 1193 a->setText(i18n("Delete Line")); 1194 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_K)); 1195 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::killLine); 1196 m_editActions.push_back(a); 1197 1198 a = ac->addAction(QStringLiteral("delete_word_left")); 1199 a->setText(i18n("Delete Word Left")); 1200 ac->setDefaultShortcuts(a, KStandardShortcut::deleteWordBack()); 1201 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::deleteWordLeft); 1202 m_editActions.push_back(a); 1203 1204 a = ac->addAction(QStringLiteral("delete_word_right")); 1205 a->setText(i18n("Delete Word Right")); 1206 ac->setDefaultShortcuts(a, KStandardShortcut::deleteWordForward()); 1207 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::deleteWordRight); 1208 m_editActions.push_back(a); 1209 1210 a = ac->addAction(QStringLiteral("delete_next_character")); 1211 a->setText(i18n("Delete Next Character")); 1212 ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Delete)); 1213 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::keyDelete); 1214 m_editActions.push_back(a); 1215 1216 a = ac->addAction(QStringLiteral("backspace")); 1217 a->setText(i18n("Backspace")); 1218 QList<QKeySequence> scuts; 1219 scuts << QKeySequence(Qt::Key_Backspace) << QKeySequence(Qt::SHIFT | Qt::Key_Backspace); 1220 ac->setDefaultShortcuts(a, scuts); 1221 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::backspace); 1222 m_editActions.push_back(a); 1223 1224 a = ac->addAction(QStringLiteral("insert_tabulator")); 1225 a->setText(i18n("Insert Tab Character")); 1226 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::insertTab); 1227 m_editActions.push_back(a); 1228 1229 a = ac->addAction(QStringLiteral("smart_newline")); 1230 a->setText(i18n("Insert Smart Newline")); 1231 a->setWhatsThis(i18n("Insert newline including leading characters of the current line which are not letters or numbers.")); 1232 scuts.clear(); 1233 scuts << QKeySequence(Qt::SHIFT | Qt::Key_Return) << QKeySequence(Qt::SHIFT | Qt::Key_Enter); 1234 ac->setDefaultShortcuts(a, scuts); 1235 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::smartNewline); 1236 m_editActions.push_back(a); 1237 1238 a = ac->addAction(QStringLiteral("no_indent_newline")); 1239 a->setText(i18n("Insert a Non-Indented Newline")); 1240 a->setWhatsThis(i18n("Insert a new line without indentation, regardless of indentation settings.")); 1241 scuts.clear(); 1242 scuts << QKeySequence(Qt::CTRL | Qt::Key_Return) << QKeySequence(Qt::CTRL | Qt::Key_Enter); 1243 ac->setDefaultShortcuts(a, scuts); 1244 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::noIndentNewline); 1245 m_editActions.push_back(a); 1246 1247 a = ac->addAction(QStringLiteral("newline_above")); 1248 a->setText(i18n("Insert a Newline Above Current Line")); 1249 a->setWhatsThis(i18n("Insert a new line above current line without modifying the current line.")); 1250 scuts.clear(); 1251 scuts << QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Return) << QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Enter); 1252 ac->setDefaultShortcuts(a, scuts); 1253 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::newLineAbove); 1254 m_editActions.push_back(a); 1255 1256 a = ac->addAction(QStringLiteral("newline_below")); 1257 a->setText(i18n("Insert a Newline Below Current Line")); 1258 a->setWhatsThis(i18n("Insert a new line below current line without modifying the current line.")); 1259 scuts.clear(); 1260 scuts << QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Return) << QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Enter); 1261 ac->setDefaultShortcuts(a, scuts); 1262 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::newLineBelow); 1263 m_editActions.push_back(a); 1264 1265 a = ac->addAction(QStringLiteral("tools_indent")); 1266 a->setIcon(QIcon::fromTheme(QStringLiteral("format-indent-more"))); 1267 a->setText(i18n("&Indent")); 1268 a->setWhatsThis( 1269 i18n("Use this to indent a selected block of text.<br /><br />" 1270 "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog.")); 1271 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_I)); 1272 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::indent); 1273 1274 a = ac->addAction(QStringLiteral("tools_unindent")); 1275 a->setIcon(QIcon::fromTheme(QStringLiteral("format-indent-less"))); 1276 a->setText(i18n("&Unindent")); 1277 a->setWhatsThis(i18n("Use this to unindent a selected block of text.")); 1278 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_I)); 1279 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::unIndent); 1280 } 1281 1282 if (hasFocus()) { 1283 slotGotFocus(); 1284 } else { 1285 slotLostFocus(); 1286 } 1287 } 1288 1289 void KTextEditor::ViewPrivate::setupCodeFolding() 1290 { 1291 KActionCollection *ac = this->actionCollection(); 1292 QAction *a; 1293 1294 a = ac->addAction(QStringLiteral("folding_toplevel")); 1295 a->setText(i18n("Fold Toplevel Nodes")); 1296 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Minus)); 1297 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::slotFoldToplevelNodes); 1298 1299 a = ac->addAction(QStringLiteral("folding_expandtoplevel")); 1300 a->setText(i18n("Unfold Toplevel Nodes")); 1301 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Plus)); 1302 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::slotExpandToplevelNodes); 1303 1304 /*a = ac->addAction(QLatin1String("folding_expandall")); 1305 a->setText(i18n("Unfold All Nodes")); 1306 connect(a, SIGNAL(triggered(bool)), doc()->foldingTree(), SLOT(expandAll())); 1307 1308 a = ac->addAction(QLatin1String("folding_collapse_dsComment")); 1309 a->setText(i18n("Fold Multiline Comments")); 1310 connect(a, SIGNAL(triggered(bool)), doc()->foldingTree(), SLOT(collapseAll_dsComments())); 1311 */ 1312 a = ac->addAction(QStringLiteral("folding_toggle_current")); 1313 a->setText(i18n("Toggle Current Node")); 1314 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::slotToggleFolding); 1315 1316 a = ac->addAction(QStringLiteral("folding_toggle_in_current")); 1317 a->setText(i18n("Toggle Contained Nodes")); 1318 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::slotToggleFoldingsInRange); 1319 } 1320 1321 void KTextEditor::ViewPrivate::slotFoldToplevelNodes() 1322 { 1323 for (int line = 0; line < doc()->lines(); ++line) { 1324 if (textFolding().isLineVisible(line)) { 1325 foldLine(line); 1326 } 1327 } 1328 } 1329 1330 void KTextEditor::ViewPrivate::slotExpandToplevelNodes() 1331 { 1332 const auto topLevelRanges(textFolding().foldingRangesForParentRange()); 1333 for (const auto &range : topLevelRanges) { 1334 textFolding().unfoldRange(range.first); 1335 } 1336 } 1337 1338 void KTextEditor::ViewPrivate::slotToggleFolding() 1339 { 1340 int line = cursorPosition().line(); 1341 bool actionDone = false; 1342 while (!actionDone && (line > -1)) { 1343 actionDone = unfoldLine(line); 1344 if (!actionDone) { 1345 actionDone = foldLine(line--).isValid(); 1346 } 1347 } 1348 } 1349 1350 void KTextEditor::ViewPrivate::slotToggleFoldingsInRange() 1351 { 1352 int line = cursorPosition().line(); 1353 while (!toggleFoldingsInRange(line) && (line > -1)) { 1354 --line; 1355 } 1356 } 1357 1358 KTextEditor::Range KTextEditor::ViewPrivate::foldLine(int line) 1359 { 1360 KTextEditor::Range foldingRange = doc()->buffer().computeFoldingRangeForStartLine(line); 1361 if (!foldingRange.isValid()) { 1362 return foldingRange; 1363 } 1364 1365 // Ensure not to fold the end marker to avoid a deceptive look, but only on token based folding 1366 // ensure we don't compute an invalid line by moving outside of the foldingRange range by checking onSingleLine(), see bug 417890 1367 if (!m_doc->buffer().isFoldingStartingOnLine(line).second && !foldingRange.onSingleLine()) { 1368 const int adjustedLine = foldingRange.end().line() - 1; 1369 foldingRange.setEnd(KTextEditor::Cursor(adjustedLine, doc()->buffer().plainLine(adjustedLine)->length())); 1370 } 1371 1372 // Don't try to fold a single line, which can happens due to adjustment above 1373 // FIXME Avoid to offer such a folding marker 1374 if (!foldingRange.onSingleLine()) { 1375 textFolding().newFoldingRange(foldingRange, Kate::TextFolding::Folded); 1376 } 1377 1378 return foldingRange; 1379 } 1380 1381 bool KTextEditor::ViewPrivate::unfoldLine(int line) 1382 { 1383 bool actionDone = false; 1384 const KTextEditor::Cursor currentCursor = cursorPosition(); 1385 1386 // ask the folding info for this line, if any folds are around! 1387 // auto = QVector<QPair<qint64, Kate::TextFolding::FoldingRangeFlags>> 1388 auto startingRanges = textFolding().foldingRangesStartingOnLine(line); 1389 for (int i = 0; i < startingRanges.size() && !actionDone; ++i) { 1390 // Avoid jumping view in case of a big unfold and ensure nice highlight of folding marker 1391 setCursorPosition(textFolding().foldingRange(startingRanges[i].first).start()); 1392 1393 actionDone |= textFolding().unfoldRange(startingRanges[i].first); 1394 } 1395 1396 if (!actionDone) { 1397 // Nothing unfolded? Restore old cursor position! 1398 setCursorPosition(currentCursor); 1399 } 1400 1401 return actionDone; 1402 } 1403 1404 bool KTextEditor::ViewPrivate::toggleFoldingOfLine(int line) 1405 { 1406 bool actionDone = unfoldLine(line); 1407 if (!actionDone) { 1408 actionDone = foldLine(line).isValid(); 1409 } 1410 1411 return actionDone; 1412 } 1413 1414 bool KTextEditor::ViewPrivate::toggleFoldingsInRange(int line) 1415 { 1416 KTextEditor::Range foldingRange = doc()->buffer().computeFoldingRangeForStartLine(line); 1417 if (!foldingRange.isValid()) { 1418 // Either line is not valid or there is no start range 1419 return false; 1420 } 1421 1422 bool actionDone = false; // Track success 1423 const KTextEditor::Cursor currentCursor = cursorPosition(); 1424 1425 // Don't be too eager but obliging! Only toggle containing ranges which are 1426 // visible -> Be done when the range is folded 1427 actionDone |= unfoldLine(line); 1428 1429 if (!actionDone) { 1430 // Unfold all in range, but not the range itself 1431 for (int ln = foldingRange.start().line() + 1; ln < foldingRange.end().line(); ++ln) { 1432 actionDone |= unfoldLine(ln); 1433 } 1434 1435 if (actionDone) { 1436 // In most cases we want now a not moved cursor 1437 setCursorPosition(currentCursor); 1438 } 1439 } 1440 1441 if (!actionDone) { 1442 // Fold all in range, but not the range itself 1443 for (int ln = foldingRange.start().line() + 1; ln < foldingRange.end().line(); ++ln) { 1444 KTextEditor::Range fr = foldLine(ln); 1445 if (fr.isValid()) { 1446 // qMax to avoid infinite loop in case of range without content 1447 ln = qMax(ln, fr.end().line() - 1); 1448 actionDone = true; 1449 } 1450 } 1451 } 1452 1453 if (!actionDone) { 1454 // At this point was an unfolded range clicked which contains no "childs" 1455 // We assume the user want to fold it by the wrong button, be obliging! 1456 actionDone |= foldLine(line).isValid(); 1457 } 1458 1459 // At this point we should be always true 1460 return actionDone; 1461 } 1462 1463 KTextEditor::View::ViewMode KTextEditor::ViewPrivate::viewMode() const 1464 { 1465 return currentInputMode()->viewMode(); 1466 } 1467 1468 QString KTextEditor::ViewPrivate::viewModeHuman() const 1469 { 1470 QString currentMode = currentInputMode()->viewModeHuman(); 1471 1472 // append read-only if needed 1473 if (!doc()->isReadWrite()) { 1474 currentMode = i18n("(R/O) %1", currentMode); 1475 } 1476 1477 // return full mode 1478 return currentMode; 1479 } 1480 1481 KTextEditor::View::InputMode KTextEditor::ViewPrivate::viewInputMode() const 1482 { 1483 return currentInputMode()->viewInputMode(); 1484 } 1485 1486 QString KTextEditor::ViewPrivate::viewInputModeHuman() const 1487 { 1488 return currentInputMode()->viewInputModeHuman(); 1489 } 1490 1491 void KTextEditor::ViewPrivate::setInputMode(KTextEditor::View::InputMode mode, const bool rememberInConfig) 1492 { 1493 if (currentInputMode()->viewInputMode() == mode) { 1494 return; 1495 } 1496 1497 // No multi cursors for vi 1498 if (mode == KTextEditor::View::InputMode::ViInputMode) { 1499 clearSecondaryCursors(); 1500 } 1501 1502 m_viewInternal->m_currentInputMode->deactivate(); 1503 m_viewInternal->m_currentInputMode = m_viewInternal->m_inputModes[mode].get(); 1504 m_viewInternal->m_currentInputMode->activate(); 1505 1506 // remember in local config if requested, we skip this for the calls in updateConfig 1507 if (rememberInConfig) { 1508 config()->setValue(KateViewConfig::InputMode, mode); 1509 } 1510 1511 /* small duplication, but need to do this if not toggled by action */ 1512 const auto inputModeActions = m_inputModeActions->actions(); 1513 for (QAction *action : inputModeActions) { 1514 if (static_cast<InputMode>(action->data().toInt()) == mode) { 1515 action->setChecked(true); 1516 break; 1517 } 1518 } 1519 1520 /* inform the rest of the system about the change */ 1521 Q_EMIT viewInputModeChanged(this, mode); 1522 Q_EMIT viewModeChanged(this, viewMode()); 1523 } 1524 1525 void KTextEditor::ViewPrivate::slotDocumentAboutToReload() 1526 { 1527 if (doc()->isAutoReload()) { 1528 const int lastVisibleLine = m_viewInternal->endLine(); 1529 const int currentLine = cursorPosition().line(); 1530 m_gotoBottomAfterReload = (lastVisibleLine == currentLine) && (currentLine == doc()->lastLine()); 1531 if (!m_gotoBottomAfterReload) { 1532 // Ensure the view jumps not back when user scrolls around 1533 const int firstVisibleLine = 1 + lastVisibleLine - m_viewInternal->linesDisplayed(); 1534 const int newLine = qBound(firstVisibleLine, currentLine, lastVisibleLine); 1535 setCursorPositionVisual(KTextEditor::Cursor(newLine, cursorPosition().column())); 1536 } 1537 } else { 1538 m_gotoBottomAfterReload = false; 1539 } 1540 } 1541 1542 void KTextEditor::ViewPrivate::slotDocumentReloaded() 1543 { 1544 if (m_gotoBottomAfterReload) { 1545 bottom(); 1546 } 1547 } 1548 1549 void KTextEditor::ViewPrivate::slotGotFocus() 1550 { 1551 // qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotGotFocus"; 1552 currentInputMode()->gotFocus(); 1553 1554 // update current view and scrollbars 1555 // it is needed for styles that implement different frame and scrollbar 1556 // rendering when focused 1557 update(); 1558 if (m_viewInternal->m_lineScroll->isVisible()) { 1559 m_viewInternal->m_lineScroll->update(); 1560 } 1561 1562 if (m_viewInternal->m_columnScroll->isVisible()) { 1563 m_viewInternal->m_columnScroll->update(); 1564 } 1565 1566 Q_EMIT focusIn(this); 1567 } 1568 1569 void KTextEditor::ViewPrivate::slotLostFocus() 1570 { 1571 // qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotLostFocus"; 1572 currentInputMode()->lostFocus(); 1573 1574 // update current view and scrollbars 1575 // it is needed for styles that implement different frame and scrollbar 1576 // rendering when focused 1577 update(); 1578 if (m_viewInternal->m_lineScroll->isVisible()) { 1579 m_viewInternal->m_lineScroll->update(); 1580 } 1581 1582 if (m_viewInternal->m_columnScroll->isVisible()) { 1583 m_viewInternal->m_columnScroll->update(); 1584 } 1585 1586 if (doc()->config()->autoSave() && doc()->config()->autoSaveOnFocusOut() && doc()->isModified() && doc()->url().isLocalFile()) { 1587 doc()->documentSave(); 1588 } 1589 1590 Q_EMIT focusOut(this); 1591 } 1592 1593 void KTextEditor::ViewPrivate::setDynWrapIndicators(int mode) 1594 { 1595 config()->setValue(KateViewConfig::DynWordWrapIndicators, mode); 1596 } 1597 1598 bool KTextEditor::ViewPrivate::isOverwriteMode() const 1599 { 1600 return doc()->config()->ovr(); 1601 } 1602 1603 void KTextEditor::ViewPrivate::reloadFile() 1604 { 1605 // bookmarks and cursor positions are temporarily saved by the document 1606 doc()->documentReload(); 1607 } 1608 1609 void KTextEditor::ViewPrivate::slotReadWriteChanged() 1610 { 1611 if (m_toggleWriteLock) { 1612 m_toggleWriteLock->setChecked(!doc()->isReadWrite()); 1613 } 1614 1615 m_cut->setEnabled(doc()->isReadWrite() && (selection() || m_config->smartCopyCut())); 1616 m_paste->setEnabled(doc()->isReadWrite()); 1617 if (m_pasteSelection) { 1618 m_pasteSelection->setEnabled(doc()->isReadWrite()); 1619 } 1620 m_swapWithClipboard->setEnabled(doc()->isReadWrite()); 1621 m_setEndOfLine->setEnabled(doc()->isReadWrite()); 1622 1623 static const auto l = {QStringLiteral("edit_replace"), 1624 QStringLiteral("tools_spelling"), 1625 QStringLiteral("tools_indent"), 1626 QStringLiteral("tools_unindent"), 1627 QStringLiteral("tools_cleanIndent"), 1628 QStringLiteral("tools_formatIndet"), 1629 QStringLiteral("tools_alignOn"), 1630 QStringLiteral("tools_comment"), 1631 QStringLiteral("tools_uncomment"), 1632 QStringLiteral("tools_toggle_comment"), 1633 QStringLiteral("tools_uppercase"), 1634 QStringLiteral("tools_lowercase"), 1635 QStringLiteral("tools_capitalize"), 1636 QStringLiteral("tools_join_lines"), 1637 QStringLiteral("tools_apply_wordwrap"), 1638 QStringLiteral("tools_spelling_from_cursor"), 1639 QStringLiteral("tools_spelling_selection")}; 1640 1641 for (const auto &action : l) { 1642 QAction *a = actionCollection()->action(action); 1643 if (a) { 1644 a->setEnabled(doc()->isReadWrite()); 1645 } 1646 } 1647 slotUpdateUndo(); 1648 1649 currentInputMode()->readWriteChanged(doc()->isReadWrite()); 1650 1651 // => view mode changed 1652 Q_EMIT viewModeChanged(this, viewMode()); 1653 Q_EMIT viewInputModeChanged(this, viewInputMode()); 1654 } 1655 1656 void KTextEditor::ViewPrivate::toggleCamelCaseCursor() 1657 { 1658 const auto enabled = doc()->config()->camelCursor(); 1659 doc()->config()->setCamelCursor(!enabled); 1660 KTextEditor::Message *m; 1661 if (enabled) { 1662 m = new KTextEditor::Message(i18n("Camel case movement disabled")); 1663 } else { 1664 m = new KTextEditor::Message(i18n("Camel case movement enabled")); 1665 } 1666 m->setPosition(KTextEditor::Message::TopInView); 1667 m->setAutoHide(1000); 1668 m->setAutoHideMode(KTextEditor::Message::Immediate); 1669 doc()->postMessage(m); 1670 } 1671 1672 void KTextEditor::ViewPrivate::slotUpdateUndo() 1673 { 1674 if (doc()->readOnly()) { 1675 return; 1676 } 1677 1678 m_editUndo->setEnabled(doc()->isReadWrite() && doc()->undoCount() > 0); 1679 m_editRedo->setEnabled(doc()->isReadWrite() && doc()->redoCount() > 0); 1680 } 1681 1682 bool KTextEditor::ViewPrivate::setCursorPositionInternal(const KTextEditor::Cursor position, uint tabwidth, bool calledExternally) 1683 { 1684 Kate::TextLine l = doc()->kateTextLine(position.line()); 1685 1686 if (!l) { 1687 return false; 1688 } 1689 1690 const QString line_str = l->text(); 1691 1692 int x = 0; 1693 int z = 0; 1694 for (; z < line_str.length() && z < position.column(); z++) { 1695 if (line_str[z] == QLatin1Char('\t')) { 1696 x += tabwidth - (x % tabwidth); 1697 } else { 1698 x++; 1699 } 1700 } 1701 1702 if (blockSelection()) { 1703 if (z < position.column()) { 1704 x += position.column() - z; 1705 } 1706 } 1707 1708 m_viewInternal->updateCursor(KTextEditor::Cursor(position.line(), x), 1709 false, 1710 calledExternally /* force center for external calls, see bug 408418 */, 1711 calledExternally); 1712 1713 return true; 1714 } 1715 1716 void KTextEditor::ViewPrivate::toggleInsert() 1717 { 1718 doc()->config()->setOvr(!doc()->config()->ovr()); 1719 m_toggleInsert->setChecked(isOverwriteMode()); 1720 1721 // No multi cursors for overwrite mode 1722 if (isOverwriteMode()) { 1723 clearSecondaryCursors(); 1724 } 1725 1726 Q_EMIT viewModeChanged(this, viewMode()); 1727 Q_EMIT viewInputModeChanged(this, viewInputMode()); 1728 } 1729 1730 void KTextEditor::ViewPrivate::slotSaveCanceled(const QString &error) 1731 { 1732 if (!error.isEmpty()) { // happens when canceling a job 1733 KMessageBox::error(this, error); 1734 } 1735 } 1736 1737 void KTextEditor::ViewPrivate::gotoLine() 1738 { 1739 gotoBar()->updateData(); 1740 bottomViewBar()->showBarWidget(gotoBar()); 1741 } 1742 1743 void KTextEditor::ViewPrivate::changeDictionary() 1744 { 1745 dictionaryBar()->updateData(); 1746 bottomViewBar()->showBarWidget(dictionaryBar()); 1747 } 1748 1749 void KTextEditor::ViewPrivate::joinLines() 1750 { 1751 int first = selectionRange().start().line(); 1752 int last = selectionRange().end().line(); 1753 // int left = doc()->line( last ).length() - doc()->selEndCol(); 1754 if (first == last) { 1755 first = cursorPosition().line(); 1756 last = first + 1; 1757 } 1758 doc()->joinLines(first, last); 1759 } 1760 1761 void KTextEditor::ViewPrivate::readSessionConfig(const KConfigGroup &config, const QSet<QString> &flags) 1762 { 1763 Q_UNUSED(flags) 1764 1765 // cursor position 1766 setCursorPositionInternal(KTextEditor::Cursor(config.readEntry("CursorLine", 0), config.readEntry("CursorColumn", 0))); 1767 1768 m_config->setDynWordWrap(config.readEntry("Dynamic Word Wrap", false)); 1769 1770 // restore text folding 1771 m_savedFoldingState = QJsonDocument::fromJson(config.readEntry("TextFolding", QByteArray())); 1772 applyFoldingState(); 1773 1774 for (const auto &mode : m_viewInternal->m_inputModes) { 1775 mode->readSessionConfig(config); 1776 } 1777 } 1778 1779 void KTextEditor::ViewPrivate::writeSessionConfig(KConfigGroup &config, const QSet<QString> &flags) 1780 { 1781 Q_UNUSED(flags) 1782 1783 // cursor position 1784 config.writeEntry("CursorLine", cursorPosition().line()); 1785 config.writeEntry("CursorColumn", cursorPosition().column()); 1786 1787 config.writeEntry("Dynamic Word Wrap", m_config->dynWordWrap()); 1788 1789 // save text folding state 1790 saveFoldingState(); 1791 config.writeEntry("TextFolding", m_savedFoldingState.toJson(QJsonDocument::Compact)); 1792 m_savedFoldingState = QJsonDocument(); 1793 1794 for (const auto &mode : m_viewInternal->m_inputModes) { 1795 mode->writeSessionConfig(config); 1796 } 1797 } 1798 1799 int KTextEditor::ViewPrivate::getEol() const 1800 { 1801 return doc()->config()->eol(); 1802 } 1803 1804 QMenu *KTextEditor::ViewPrivate::getEolMenu() 1805 { 1806 return m_setEndOfLine->menu(); 1807 } 1808 1809 void KTextEditor::ViewPrivate::setEol(int eol) 1810 { 1811 if (!doc()->isReadWrite()) { 1812 return; 1813 } 1814 1815 if (m_updatingDocumentConfig) { 1816 return; 1817 } 1818 1819 if (eol != doc()->config()->eol()) { 1820 doc()->setModified(true); // mark modified (bug #143120) 1821 doc()->config()->setEol(eol); 1822 } 1823 } 1824 1825 void KTextEditor::ViewPrivate::setAddBom(bool enabled) 1826 { 1827 if (!doc()->isReadWrite()) { 1828 return; 1829 } 1830 1831 if (m_updatingDocumentConfig) { 1832 return; 1833 } 1834 1835 doc()->config()->setBom(enabled); 1836 doc()->bomSetByUser(); 1837 } 1838 1839 void KTextEditor::ViewPrivate::setIconBorder(bool enable) 1840 { 1841 config()->setValue(KateViewConfig::ShowIconBar, enable); 1842 } 1843 1844 void KTextEditor::ViewPrivate::toggleIconBorder() 1845 { 1846 config()->setValue(KateViewConfig::ShowIconBar, !config()->iconBar()); 1847 } 1848 1849 void KTextEditor::ViewPrivate::setLineNumbersOn(bool enable) 1850 { 1851 config()->setValue(KateViewConfig::ShowLineNumbers, enable); 1852 } 1853 1854 void KTextEditor::ViewPrivate::toggleLineNumbersOn() 1855 { 1856 config()->setValue(KateViewConfig::ShowLineNumbers, !config()->lineNumbers()); 1857 } 1858 1859 void KTextEditor::ViewPrivate::setScrollBarMarks(bool enable) 1860 { 1861 config()->setValue(KateViewConfig::ShowScrollBarMarks, enable); 1862 } 1863 1864 void KTextEditor::ViewPrivate::toggleScrollBarMarks() 1865 { 1866 config()->setValue(KateViewConfig::ShowScrollBarMarks, !config()->scrollBarMarks()); 1867 } 1868 1869 void KTextEditor::ViewPrivate::setScrollBarMiniMap(bool enable) 1870 { 1871 config()->setValue(KateViewConfig::ShowScrollBarMiniMap, enable); 1872 } 1873 1874 void KTextEditor::ViewPrivate::toggleScrollBarMiniMap() 1875 { 1876 config()->setValue(KateViewConfig::ShowScrollBarMiniMap, !config()->scrollBarMiniMap()); 1877 } 1878 1879 void KTextEditor::ViewPrivate::setScrollBarMiniMapAll(bool enable) 1880 { 1881 config()->setValue(KateViewConfig::ShowScrollBarMiniMapAll, enable); 1882 } 1883 1884 void KTextEditor::ViewPrivate::toggleScrollBarMiniMapAll() 1885 { 1886 config()->setValue(KateViewConfig::ShowScrollBarMiniMapAll, !config()->scrollBarMiniMapAll()); 1887 } 1888 1889 void KTextEditor::ViewPrivate::setScrollBarMiniMapWidth(int width) 1890 { 1891 config()->setValue(KateViewConfig::ScrollBarMiniMapWidth, width); 1892 } 1893 1894 void KTextEditor::ViewPrivate::toggleDynWordWrap() 1895 { 1896 config()->setDynWordWrap(!config()->dynWordWrap()); 1897 } 1898 1899 void KTextEditor::ViewPrivate::toggleWWMarker() 1900 { 1901 m_renderer->config()->setWordWrapMarker(!m_renderer->config()->wordWrapMarker()); 1902 } 1903 1904 void KTextEditor::ViewPrivate::toggleNPSpaces() 1905 { 1906 m_renderer->setShowNonPrintableSpaces(!m_renderer->showNonPrintableSpaces()); 1907 m_viewInternal->update(); // force redraw 1908 } 1909 1910 void KTextEditor::ViewPrivate::toggleWordCount(bool on) 1911 { 1912 config()->setShowWordCount(on); 1913 } 1914 1915 void KTextEditor::ViewPrivate::setFoldingMarkersOn(bool enable) 1916 { 1917 config()->setValue(KateViewConfig::ShowFoldingBar, enable); 1918 } 1919 1920 void KTextEditor::ViewPrivate::toggleFoldingMarkers() 1921 { 1922 config()->setValue(KateViewConfig::ShowFoldingBar, !config()->foldingBar()); 1923 } 1924 1925 bool KTextEditor::ViewPrivate::iconBorder() 1926 { 1927 return m_viewInternal->m_leftBorder->iconBorderOn(); 1928 } 1929 1930 bool KTextEditor::ViewPrivate::lineNumbersOn() 1931 { 1932 return m_viewInternal->m_leftBorder->lineNumbersOn(); 1933 } 1934 1935 bool KTextEditor::ViewPrivate::scrollBarMarks() 1936 { 1937 return m_viewInternal->m_lineScroll->showMarks(); 1938 } 1939 1940 bool KTextEditor::ViewPrivate::scrollBarMiniMap() 1941 { 1942 return m_viewInternal->m_lineScroll->showMiniMap(); 1943 } 1944 1945 int KTextEditor::ViewPrivate::dynWrapIndicators() 1946 { 1947 return m_viewInternal->m_leftBorder->dynWrapIndicators(); 1948 } 1949 1950 bool KTextEditor::ViewPrivate::foldingMarkersOn() 1951 { 1952 return m_viewInternal->m_leftBorder->foldingMarkersOn(); 1953 } 1954 1955 void KTextEditor::ViewPrivate::toggleWriteLock() 1956 { 1957 doc()->setReadWrite(!doc()->isReadWrite()); 1958 } 1959 1960 void KTextEditor::ViewPrivate::registerTextHintProvider(KTextEditor::TextHintProvider *provider) 1961 { 1962 m_viewInternal->registerTextHintProvider(provider); 1963 } 1964 1965 void KTextEditor::ViewPrivate::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider) 1966 { 1967 m_viewInternal->unregisterTextHintProvider(provider); 1968 } 1969 1970 void KTextEditor::ViewPrivate::setTextHintDelay(int delay) 1971 { 1972 m_viewInternal->setTextHintDelay(delay); 1973 } 1974 1975 int KTextEditor::ViewPrivate::textHintDelay() const 1976 { 1977 return m_viewInternal->textHintDelay(); 1978 } 1979 1980 void KTextEditor::ViewPrivate::find() 1981 { 1982 currentInputMode()->find(); 1983 } 1984 1985 void KTextEditor::ViewPrivate::findSelectedForwards() 1986 { 1987 currentInputMode()->findSelectedForwards(); 1988 } 1989 1990 void KTextEditor::ViewPrivate::findSelectedBackwards() 1991 { 1992 currentInputMode()->findSelectedBackwards(); 1993 } 1994 1995 void KTextEditor::ViewPrivate::skipCurrentOccurunceSelection() 1996 { 1997 if (isMulticursorNotAllowed()) { 1998 return; 1999 } 2000 m_skipCurrentSelection = true; 2001 } 2002 2003 void KTextEditor::ViewPrivate::findNextOccurunceAndSelect() 2004 { 2005 if (isMulticursorNotAllowed()) { 2006 return; 2007 } 2008 2009 const auto text = selection() ? doc()->text(selectionRange()) : QString(); 2010 if (text.isEmpty()) { 2011 const auto selection = doc()->wordRangeAt(cursorPosition()); 2012 // We don't want matching word highlights 2013 setSelection(selection); 2014 setCursorPosition(selection.end()); 2015 clearHighlights(); 2016 2017 for (auto &c : m_secondaryCursors) { 2018 const auto range = doc()->wordRangeAt(c.cursor()); 2019 if (!c.range && !c.anchor.isValid()) { 2020 c.anchor = range.start(); 2021 c.range.reset(newSecondarySelectionRange(range)); 2022 c.pos->setPosition(range.end()); 2023 } 2024 tagLines(range); 2025 } 2026 return; 2027 } else if (!m_rangesForHighlights.empty()) { 2028 clearHighlights(); 2029 } 2030 2031 // Use selection range end as starting point 2032 const auto lastSelectionRange = selectionRange(); 2033 2034 KTextEditor::Range searchRange(lastSelectionRange.end(), doc()->documentRange().end()); 2035 QVector<KTextEditor::Range> matches = doc()->searchText(searchRange, text, KTextEditor::Default); 2036 if (!matches.isEmpty() && !matches.constFirst().isValid()) { 2037 searchRange.setRange(doc()->documentRange().start(), lastSelectionRange.end()); 2038 matches = doc()->searchText(searchRange, text, KTextEditor::Default); 2039 } 2040 2041 // No match found or only one possible match 2042 if (matches.empty() || !matches.constFirst().isValid() || matches.constFirst() == selectionRange()) { 2043 return; 2044 } 2045 2046 auto it = std::find_if(m_secondaryCursors.begin(), m_secondaryCursors.end(), [&](const SecondaryCursor &c) { 2047 return c.range && c.range->toRange() == matches.constFirst(); 2048 }); 2049 2050 if (it != m_secondaryCursors.end()) { 2051 m_secondaryCursors.erase(it); 2052 } 2053 2054 // Move our primary to cursor to this match and select it 2055 // Ensure we don't create occurence highlights 2056 setSelection(matches.constFirst()); 2057 setCursorPosition(matches.constFirst().end()); 2058 clearHighlights(); 2059 2060 // If we are skipping this selection, then we don't have to do anything 2061 if (!m_skipCurrentSelection) { 2062 PlainSecondaryCursor c; 2063 c.pos = lastSelectionRange.end(); 2064 c.range = lastSelectionRange; 2065 // make our previous primary selection a secondary 2066 addSecondaryCursorsWithSelection({c}); 2067 } 2068 // reset value 2069 m_skipCurrentSelection = false; 2070 } 2071 2072 void KTextEditor::ViewPrivate::findAllOccuruncesAndSelect() 2073 { 2074 if (isMulticursorNotAllowed()) { 2075 return; 2076 } 2077 2078 QString text = selection() ? doc()->text(selectionRange()) : QString(); 2079 if (text.isEmpty()) { 2080 const auto selection = doc()->wordRangeAt(cursorPosition()); 2081 setSelection(selection); 2082 setCursorPosition(selection.end()); 2083 clearHighlights(); 2084 text = doc()->text(selection); 2085 2086 for (auto &c : m_secondaryCursors) { 2087 const auto range = doc()->wordRangeAt(c.cursor()); 2088 if (!c.range && !c.anchor.isValid()) { 2089 c.anchor = range.start(); 2090 c.range.reset(newSecondarySelectionRange(range)); 2091 c.pos->setPosition(range.end()); 2092 } 2093 tagLines(range); 2094 } 2095 } 2096 2097 KTextEditor::Range searchRange(doc()->documentRange()); 2098 QVector<KTextEditor::Range> matches; 2099 QVector<PlainSecondaryCursor> resultRanges; 2100 do { 2101 matches = doc()->searchText(searchRange, text, KTextEditor::Default); 2102 2103 if (matches.constFirst().isValid()) { 2104 // Dont add if matches primary selection 2105 if (matches.constFirst() != selectionRange()) { 2106 PlainSecondaryCursor c; 2107 c.pos = matches.constFirst().end(); 2108 c.range = matches.constFirst(); 2109 resultRanges.push_back(c); 2110 } 2111 searchRange.setStart(matches.constFirst().end()); 2112 } 2113 } while (matches.first().isValid()); 2114 2115 // ensure to clear occurence highlights 2116 if (!resultRanges.empty()) { 2117 clearHighlights(); 2118 } 2119 2120 clearSecondaryCursors(); 2121 addSecondaryCursorsWithSelection(resultRanges); 2122 } 2123 2124 void KTextEditor::ViewPrivate::replace() 2125 { 2126 currentInputMode()->findReplace(); 2127 } 2128 2129 void KTextEditor::ViewPrivate::findNext() 2130 { 2131 currentInputMode()->findNext(); 2132 } 2133 2134 void KTextEditor::ViewPrivate::findPrevious() 2135 { 2136 currentInputMode()->findPrevious(); 2137 } 2138 2139 void KTextEditor::ViewPrivate::showSearchWrappedHint(bool isReverseSearch) 2140 { 2141 // show message widget when wrapping 2142 const QIcon icon = isReverseSearch ? QIcon::fromTheme(QStringLiteral("go-up-search")) : QIcon::fromTheme(QStringLiteral("go-down-search")); 2143 2144 if (!m_wrappedMessage || m_isLastSearchReversed != isReverseSearch) { 2145 m_isLastSearchReversed = isReverseSearch; 2146 m_wrappedMessage = new KTextEditor::Message(i18n("Search wrapped"), KTextEditor::Message::Information); 2147 m_wrappedMessage->setIcon(icon); 2148 m_wrappedMessage->setPosition(KTextEditor::Message::BottomInView); 2149 m_wrappedMessage->setAutoHide(2000); 2150 m_wrappedMessage->setAutoHideMode(KTextEditor::Message::Immediate); 2151 m_wrappedMessage->setView(this); 2152 this->doc()->postMessage(m_wrappedMessage); 2153 } 2154 } 2155 2156 void KTextEditor::ViewPrivate::createMultiCursorsFromSelection() 2157 { 2158 if (!selection() || selectionRange().isEmpty()) { 2159 return; 2160 } 2161 // Is this really needed? 2162 // Lets just clear them now for simplicity 2163 clearSecondaryCursors(); 2164 2165 const auto range = selectionRange(); 2166 QVector<KTextEditor::Cursor> cursorsToAdd; 2167 const auto start = range.start().line() < 0 ? 0 : range.start().line(); 2168 const auto end = range.end().line() > doc()->lines() ? doc()->lines() : range.end().line(); 2169 const auto currentLine = cursorPosition().line(); 2170 setCursorPosition({currentLine, doc()->lineLength(currentLine)}); 2171 for (int line = start; line <= end; ++line) { 2172 if (line != currentLine) { 2173 cursorsToAdd.push_back({line, doc()->lineLength(line)}); 2174 } 2175 } 2176 // clear selection 2177 setSelection({}); 2178 setSecondaryCursors(cursorsToAdd); 2179 } 2180 2181 void KTextEditor::ViewPrivate::removeCursorsFromEmptyLines() 2182 { 2183 if (!m_secondaryCursors.empty()) { 2184 std::vector<KTextEditor::Cursor> cursorsToRemove; 2185 for (const auto &c : m_secondaryCursors) { 2186 auto cursor = c.cursor(); 2187 if (doc()->lineLength(cursor.line()) == 0) { 2188 cursorsToRemove.push_back(cursor); 2189 } 2190 } 2191 removeSecondaryCursors(cursorsToRemove); 2192 } 2193 } 2194 2195 void KTextEditor::ViewPrivate::slotSelectionChanged() 2196 { 2197 m_copy->setEnabled(selection() || m_config->smartCopyCut()); 2198 m_deSelect->setEnabled(selection()); 2199 m_copyHtmlAction->setEnabled(selection()); 2200 2201 // update highlighting of current selected word 2202 selectionChangedForHighlights(); 2203 2204 if (doc()->readOnly()) { 2205 return; 2206 } 2207 2208 m_cut->setEnabled(selection() || m_config->smartCopyCut()); 2209 } 2210 2211 void KTextEditor::ViewPrivate::switchToCmdLine() 2212 { 2213 currentInputMode()->activateCommandLine(); 2214 } 2215 2216 KateRenderer *KTextEditor::ViewPrivate::renderer() 2217 { 2218 return m_renderer; 2219 } 2220 2221 void KTextEditor::ViewPrivate::updateConfig() 2222 { 2223 if (m_startingUp) { 2224 return; 2225 } 2226 2227 // dyn. word wrap & markers 2228 if (m_hasWrap != config()->dynWordWrap()) { 2229 m_hasWrap = config()->dynWordWrap(); 2230 2231 m_viewInternal->dynWrapChanged(); 2232 2233 m_setDynWrapIndicators->setEnabled(config()->dynWordWrap()); 2234 m_toggleDynWrap->setChecked(config()->dynWordWrap()); 2235 } 2236 2237 m_viewInternal->m_leftBorder->setDynWrapIndicators(config()->dynWordWrapIndicators()); 2238 m_setDynWrapIndicators->setCurrentItem(config()->dynWordWrapIndicators()); 2239 2240 // line numbers 2241 m_viewInternal->m_leftBorder->setLineNumbersOn(config()->lineNumbers()); 2242 m_toggleLineNumbers->setChecked(config()->lineNumbers()); 2243 2244 // icon bar 2245 m_viewInternal->m_leftBorder->setIconBorderOn(config()->iconBar()); 2246 m_toggleIconBar->setChecked(config()->iconBar()); 2247 2248 // scrollbar marks 2249 m_viewInternal->m_lineScroll->setShowMarks(config()->scrollBarMarks()); 2250 m_toggleScrollBarMarks->setChecked(config()->scrollBarMarks()); 2251 2252 // scrollbar mini-map 2253 m_viewInternal->m_lineScroll->setShowMiniMap(config()->scrollBarMiniMap()); 2254 m_toggleScrollBarMiniMap->setChecked(config()->scrollBarMiniMap()); 2255 2256 // scrollbar mini-map - (whole document) 2257 m_viewInternal->m_lineScroll->setMiniMapAll(config()->scrollBarMiniMapAll()); 2258 // m_toggleScrollBarMiniMapAll->setChecked( config()->scrollBarMiniMapAll() ); 2259 2260 // scrollbar mini-map.width 2261 m_viewInternal->m_lineScroll->setMiniMapWidth(config()->scrollBarMiniMapWidth()); 2262 2263 // misc edit 2264 m_toggleBlockSelection->setChecked(blockSelection()); 2265 m_toggleInsert->setChecked(isOverwriteMode()); 2266 2267 updateFoldingConfig(); 2268 2269 // bookmark 2270 m_bookmarks->setSorting((KateBookmarks::Sorting)config()->bookmarkSort()); 2271 2272 m_viewInternal->setAutoCenterLines(config()->autoCenterLines()); 2273 2274 for (const auto &input : m_viewInternal->m_inputModes) { 2275 input->updateConfig(); 2276 } 2277 2278 setInputMode(config()->inputMode(), false /* don't remember in config for these calls */); 2279 2280 reflectOnTheFlySpellCheckStatus(doc()->isOnTheFlySpellCheckingEnabled()); 2281 2282 // register/unregister word completion... 2283 bool wc = config()->wordCompletion(); 2284 if (wc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->wordCompletionModel())) { 2285 if (wc) { 2286 registerCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); 2287 } else { 2288 unregisterCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); 2289 } 2290 } 2291 2292 bool kc = config()->keywordCompletion(); 2293 if (kc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->keywordCompletionModel())) { 2294 if (kc) { 2295 registerCompletionModel(KTextEditor::EditorPrivate::self()->keywordCompletionModel()); 2296 } else { 2297 unregisterCompletionModel(KTextEditor::EditorPrivate::self()->keywordCompletionModel()); 2298 } 2299 } 2300 2301 m_cut->setEnabled(doc()->isReadWrite() && (selection() || m_config->smartCopyCut())); 2302 m_copy->setEnabled(selection() || m_config->smartCopyCut()); 2303 2304 // if not disabled, update status bar 2305 if (m_statusBar) { 2306 m_statusBar->updateStatus(); 2307 } 2308 2309 // now redraw... 2310 m_viewInternal->cache()->clear(); 2311 tagAll(); 2312 updateView(true); 2313 2314 Q_EMIT configChanged(this); 2315 } 2316 2317 void KTextEditor::ViewPrivate::updateDocumentConfig() 2318 { 2319 if (m_startingUp) { 2320 return; 2321 } 2322 2323 m_updatingDocumentConfig = true; 2324 2325 m_setEndOfLine->setCurrentItem(doc()->config()->eol()); 2326 2327 m_addBom->setChecked(doc()->config()->bom()); 2328 2329 m_updatingDocumentConfig = false; 2330 2331 // maybe block selection or wrap-cursor mode changed 2332 ensureCursorColumnValid(); 2333 2334 // first change this 2335 m_renderer->setTabWidth(doc()->config()->tabWidth()); 2336 m_renderer->setIndentWidth(doc()->config()->indentationWidth()); 2337 2338 // now redraw... 2339 m_viewInternal->cache()->clear(); 2340 tagAll(); 2341 updateView(true); 2342 } 2343 2344 void KTextEditor::ViewPrivate::updateRendererConfig() 2345 { 2346 if (m_startingUp) { 2347 return; 2348 } 2349 2350 m_toggleWWMarker->setChecked(m_renderer->config()->wordWrapMarker()); 2351 2352 m_viewInternal->updateBracketMarkAttributes(); 2353 m_viewInternal->updateBracketMarks(); 2354 2355 // now redraw... 2356 m_viewInternal->cache()->clear(); 2357 tagAll(); 2358 m_viewInternal->updateView(true); 2359 2360 // update the left border right, for example linenumbers 2361 m_viewInternal->m_leftBorder->updateFont(); 2362 m_viewInternal->m_leftBorder->repaint(); 2363 2364 m_viewInternal->m_lineScroll->queuePixmapUpdate(); 2365 2366 currentInputMode()->updateRendererConfig(); 2367 2368 // @@ showIndentLines is not cached anymore. 2369 // m_renderer->setShowIndentLines (m_renderer->config()->showIndentationLines()); 2370 Q_EMIT configChanged(this); 2371 } 2372 2373 void KTextEditor::ViewPrivate::updateFoldingConfig() 2374 { 2375 // folding bar 2376 m_viewInternal->m_leftBorder->setFoldingMarkersOn(config()->foldingBar()); 2377 m_toggleFoldingMarkers->setChecked(config()->foldingBar()); 2378 2379 if (hasCommentInFirstLine(m_doc)) { 2380 if (config()->foldFirstLine() && !m_autoFoldedFirstLine) { 2381 foldLine(0); 2382 m_autoFoldedFirstLine = true; 2383 } else if (!config()->foldFirstLine() && m_autoFoldedFirstLine) { 2384 unfoldLine(0); 2385 m_autoFoldedFirstLine = false; 2386 } 2387 } else { 2388 m_autoFoldedFirstLine = false; 2389 } 2390 2391 #if 0 2392 // FIXME: FOLDING 2393 const QStringList l = { 2394 QStringLiteral("folding_toplevel") 2395 , QStringLiteral("folding_expandtoplevel") 2396 , QStringLiteral("folding_toggle_current") 2397 , QStringLiteral("folding_toggle_in_current") 2398 }; 2399 2400 QAction *a = 0; 2401 for (int z = 0; z < l.size(); z++) 2402 if ((a = actionCollection()->action(l[z].toAscii().constData()))) { 2403 a->setEnabled(doc()->highlight() && doc()->highlight()->allowsFolding()); 2404 } 2405 #endif 2406 } 2407 2408 void KTextEditor::ViewPrivate::ensureCursorColumnValid() 2409 { 2410 KTextEditor::Cursor c = m_viewInternal->cursorPosition(); 2411 2412 // make sure the cursor is valid: 2413 // - in block selection mode or if wrap cursor is off, the column is arbitrary 2414 // - otherwise: it's bounded by the line length 2415 if (!blockSelection() && wrapCursor() && (!c.isValid() || c.column() > doc()->lineLength(c.line()))) { 2416 c.setColumn(doc()->lineLength(cursorPosition().line())); 2417 setCursorPosition(c); 2418 } 2419 } 2420 2421 // BEGIN EDIT STUFF 2422 void KTextEditor::ViewPrivate::editStart() 2423 { 2424 m_viewInternal->editStart(); 2425 } 2426 2427 void KTextEditor::ViewPrivate::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom) 2428 { 2429 m_viewInternal->editEnd(editTagLineStart, editTagLineEnd, tagFrom); 2430 textFolding().editEnd(editTagLineStart, editTagLineEnd, [this](int line) { 2431 return m_doc->buffer().isFoldingStartingOnLine(line).first; 2432 }); 2433 } 2434 2435 void KTextEditor::ViewPrivate::editSetCursor(const KTextEditor::Cursor cursor) 2436 { 2437 m_viewInternal->editSetCursor(cursor); 2438 } 2439 // END 2440 2441 // BEGIN TAG & CLEAR 2442 bool KTextEditor::ViewPrivate::tagLine(const KTextEditor::Cursor virtualCursor) 2443 { 2444 return m_viewInternal->tagLine(virtualCursor); 2445 } 2446 2447 bool KTextEditor::ViewPrivate::tagRange(KTextEditor::Range range, bool realLines) 2448 { 2449 return m_viewInternal->tagRange(range, realLines); 2450 } 2451 2452 bool KTextEditor::ViewPrivate::tagLines(KTextEditor::LineRange lineRange, bool realLines) 2453 { 2454 return m_viewInternal->tagLines(lineRange.start(), lineRange.end(), realLines); 2455 } 2456 2457 bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors) 2458 { 2459 return m_viewInternal->tagLines(start, end, realCursors); 2460 } 2461 2462 void KTextEditor::ViewPrivate::tagAll() 2463 { 2464 m_viewInternal->tagAll(); 2465 } 2466 2467 void KTextEditor::ViewPrivate::clear() 2468 { 2469 m_viewInternal->clear(); 2470 } 2471 2472 void KTextEditor::ViewPrivate::repaintText(bool paintOnlyDirty) 2473 { 2474 if (paintOnlyDirty) { 2475 m_viewInternal->updateDirty(); 2476 } else { 2477 m_viewInternal->update(); 2478 } 2479 } 2480 2481 void KTextEditor::ViewPrivate::updateView(bool changed) 2482 { 2483 // qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::updateView"; 2484 2485 m_viewInternal->updateView(changed); 2486 m_viewInternal->m_leftBorder->update(); 2487 } 2488 2489 // END 2490 2491 void KTextEditor::ViewPrivate::slotHlChanged() 2492 { 2493 KateHighlighting *hl = doc()->highlight(); 2494 bool ok(!hl->getCommentStart(0).isEmpty() || !hl->getCommentSingleLineStart(0).isEmpty()); 2495 2496 if (actionCollection()->action(QStringLiteral("tools_comment"))) { 2497 actionCollection()->action(QStringLiteral("tools_comment"))->setEnabled(ok); 2498 } 2499 2500 if (actionCollection()->action(QStringLiteral("tools_uncomment"))) { 2501 actionCollection()->action(QStringLiteral("tools_uncomment"))->setEnabled(ok); 2502 } 2503 2504 if (actionCollection()->action(QStringLiteral("tools_toggle_comment"))) { 2505 actionCollection()->action(QStringLiteral("tools_toggle_comment"))->setEnabled(ok); 2506 } 2507 2508 // show folding bar if "view defaults" says so, otherwise enable/disable only the menu entry 2509 updateFoldingConfig(); 2510 } 2511 2512 int KTextEditor::ViewPrivate::virtualCursorColumn() const 2513 { 2514 return doc()->toVirtualColumn(m_viewInternal->cursorPosition()); 2515 } 2516 2517 void KTextEditor::ViewPrivate::notifyMousePositionChanged(const KTextEditor::Cursor newPosition) 2518 { 2519 Q_EMIT mousePositionChanged(this, newPosition); 2520 } 2521 2522 // BEGIN KTextEditor::SelectionInterface stuff 2523 2524 bool KTextEditor::ViewPrivate::setSelection(const KTextEditor::Range &selection) 2525 { 2526 // anything to do? 2527 if (selection == m_selection) { 2528 return true; 2529 } 2530 2531 // backup old range 2532 KTextEditor::Range oldSelection = m_selection; 2533 2534 // set new range 2535 m_selection.setRange(selection.isEmpty() ? KTextEditor::Range::invalid() : selection); 2536 2537 // trigger update of correct area 2538 tagSelection(oldSelection); 2539 repaintText(true); 2540 2541 // emit holy signal 2542 Q_EMIT selectionChanged(this); 2543 2544 // be done 2545 return true; 2546 } 2547 2548 bool KTextEditor::ViewPrivate::clearSelection() 2549 { 2550 return clearSelection(true); 2551 } 2552 2553 bool KTextEditor::ViewPrivate::clearSelection(bool redraw, bool finishedChangingSelection) 2554 { 2555 // no selection, nothing to do... 2556 if (!selection()) { 2557 return false; 2558 } 2559 2560 // backup old range 2561 KTextEditor::Range oldSelection = m_selection; 2562 2563 // invalidate current selection 2564 m_selection.setRange(KTextEditor::Range::invalid()); 2565 2566 // trigger update of correct area 2567 tagSelection(oldSelection); 2568 if (redraw) { 2569 repaintText(true); 2570 } 2571 2572 // emit holy signal 2573 if (finishedChangingSelection) { 2574 Q_EMIT selectionChanged(this); 2575 } 2576 2577 m_viewInternal->m_selChangedByUser = false; 2578 // be done 2579 return true; 2580 } 2581 2582 bool KTextEditor::ViewPrivate::selection() const 2583 { 2584 if (!wrapCursor()) { 2585 return m_selection != KTextEditor::Range::invalid(); 2586 } else { 2587 return m_selection.toRange().isValid(); 2588 } 2589 } 2590 2591 QString KTextEditor::ViewPrivate::selectionText() const 2592 { 2593 if (blockSelect) { 2594 return doc()->text(m_selection, blockSelect); 2595 } 2596 2597 QVarLengthArray<KTextEditor::Range, 16> ranges; 2598 for (const auto &c : m_secondaryCursors) { 2599 if (c.range) { 2600 ranges.push_back(c.range->toRange()); 2601 } 2602 } 2603 ranges.push_back(m_selection.toRange()); 2604 std::sort(ranges.begin(), ranges.end()); 2605 2606 QString text; 2607 text.reserve(ranges.size() * m_selection.toRange().columnWidth()); 2608 for (int i = 0; i < ranges.size() - 1; ++i) { 2609 text += doc()->text(ranges[i]) + QStringLiteral("\n"); 2610 } 2611 text += doc()->text(ranges.last()); 2612 2613 return text; 2614 } 2615 2616 bool KTextEditor::ViewPrivate::removeSelectedText() 2617 { 2618 if (!hasSelections()) { 2619 return false; 2620 } 2621 2622 KTextEditor::Document::EditingTransaction t(doc()); 2623 2624 bool removed = false; 2625 // Handle multicursors selection removal 2626 if (!blockSelect) { 2627 completionWidget()->setIgnoreBufferSignals(true); 2628 for (auto &c : m_secondaryCursors) { 2629 if (c.range) { 2630 removed = true; 2631 doc()->removeText(c.range->toRange()); 2632 c.clearSelection(); 2633 } 2634 } 2635 completionWidget()->setIgnoreBufferSignals(false); 2636 } 2637 2638 // Optimization: clear selection before removing text 2639 KTextEditor::Range selection = m_selection; 2640 if (!selection.isValid()) { 2641 return removed; 2642 } 2643 doc()->removeText(selection, blockSelect); 2644 removed = true; 2645 2646 // don't redraw the cleared selection - that's done in editEnd(). 2647 if (blockSelect) { 2648 int selectionColumn = qMin(doc()->toVirtualColumn(selection.start()), doc()->toVirtualColumn(selection.end())); 2649 KTextEditor::Range newSelection = selection; 2650 newSelection.setStart(KTextEditor::Cursor(newSelection.start().line(), doc()->fromVirtualColumn(newSelection.start().line(), selectionColumn))); 2651 newSelection.setEnd(KTextEditor::Cursor(newSelection.end().line(), doc()->fromVirtualColumn(newSelection.end().line(), selectionColumn))); 2652 setSelection(newSelection); 2653 setCursorPositionInternal(newSelection.start()); 2654 } else { 2655 clearSecondarySelections(); 2656 clearSelection(false); 2657 } 2658 2659 return removed; 2660 } 2661 2662 bool KTextEditor::ViewPrivate::selectAll() 2663 { 2664 clearSecondaryCursors(); 2665 setBlockSelection(false); 2666 // We use setSelection here to ensure we don't scroll anywhere 2667 // The cursor stays in place i.e., it doesn't move to end of selection 2668 // that is okay and expected. 2669 // The idea here is to maintain scroll position in case select all was 2670 // mistakenly triggered, and also to if you just want to copy text, 2671 // there is no need to scroll anywhere. 2672 setSelection(doc()->documentRange()); 2673 m_viewInternal->moveCursorToSelectionEdge(/*scroll=*/false); 2674 m_viewInternal->updateMicroFocus(); 2675 return true; 2676 } 2677 2678 bool KTextEditor::ViewPrivate::cursorSelected(const KTextEditor::Cursor cursor) 2679 { 2680 KTextEditor::Cursor ret = cursor; 2681 if ((!blockSelect) && (ret.column() < 0)) { 2682 ret.setColumn(0); 2683 } 2684 2685 if (blockSelect) { 2686 return cursor.line() >= m_selection.start().line() && ret.line() <= m_selection.end().line() && ret.column() >= m_selection.start().column() 2687 && ret.column() <= m_selection.end().column(); 2688 } else { 2689 return m_selection.toRange().contains(cursor) || m_selection.end() == cursor; 2690 } 2691 } 2692 2693 bool KTextEditor::ViewPrivate::lineSelected(int line) 2694 { 2695 return !blockSelect && m_selection.toRange().containsLine(line); 2696 } 2697 2698 bool KTextEditor::ViewPrivate::lineEndSelected(const KTextEditor::Cursor lineEndPos) 2699 { 2700 return (!blockSelect) 2701 && (lineEndPos.line() > m_selection.start().line() 2702 || (lineEndPos.line() == m_selection.start().line() && (m_selection.start().column() < lineEndPos.column() || lineEndPos.column() == -1))) 2703 && (lineEndPos.line() < m_selection.end().line() 2704 || (lineEndPos.line() == m_selection.end().line() && (lineEndPos.column() <= m_selection.end().column() && lineEndPos.column() != -1))); 2705 } 2706 2707 bool KTextEditor::ViewPrivate::lineHasSelected(int line) 2708 { 2709 return selection() && m_selection.toRange().containsLine(line); 2710 } 2711 2712 bool KTextEditor::ViewPrivate::lineIsSelection(int line) 2713 { 2714 return (line == m_selection.start().line() && line == m_selection.end().line()); 2715 } 2716 2717 void KTextEditor::ViewPrivate::tagSelection(KTextEditor::Range oldSelection) 2718 { 2719 if (selection()) { 2720 if (oldSelection.start().line() == -1) { 2721 // We have to tag the whole lot if 2722 // 1) we have a selection, and: 2723 // a) it's new; or 2724 tagLines(m_selection, true); 2725 2726 } else if (blockSelection() 2727 && (oldSelection.start().column() != m_selection.start().column() || oldSelection.end().column() != m_selection.end().column())) { 2728 // b) we're in block selection mode and the columns have changed 2729 tagLines(m_selection, true); 2730 tagLines(oldSelection, true); 2731 2732 } else { 2733 if (oldSelection.start() != m_selection.start()) { 2734 tagLines(KTextEditor::LineRange(oldSelection.start().line(), m_selection.start().line()), true); 2735 } 2736 2737 if (oldSelection.end() != m_selection.end()) { 2738 tagLines(KTextEditor::LineRange(oldSelection.end().line(), m_selection.end().line()), true); 2739 } 2740 } 2741 2742 } else { 2743 // No more selection, clean up 2744 tagLines(oldSelection, true); 2745 } 2746 } 2747 2748 void KTextEditor::ViewPrivate::selectWord(const KTextEditor::Cursor cursor) 2749 { 2750 setSelection(doc()->wordRangeAt(cursor)); 2751 } 2752 2753 void KTextEditor::ViewPrivate::selectLine(const KTextEditor::Cursor cursor) 2754 { 2755 int line = cursor.line(); 2756 if (line + 1 >= doc()->lines()) { 2757 setSelection(KTextEditor::Range(line, 0, line, doc()->lineLength(line))); 2758 } else { 2759 setSelection(KTextEditor::Range(line, 0, line + 1, 0)); 2760 } 2761 } 2762 2763 void KTextEditor::ViewPrivate::cut() 2764 { 2765 if (!selection() && !m_config->smartCopyCut()) { 2766 return; 2767 } 2768 2769 copy(); 2770 if (!selection()) { 2771 selectLine(cursorPosition()); 2772 } 2773 removeSelectedText(); 2774 } 2775 2776 void KTextEditor::ViewPrivate::copy() const 2777 { 2778 QString text; 2779 KTextEditor::EditorPrivate::self()->copyToMulticursorClipboard({}); 2780 2781 if (!selection()) { 2782 if (!m_config->smartCopyCut()) { 2783 return; 2784 } 2785 text = doc()->line(cursorPosition().line()) + QLatin1Char('\n'); 2786 m_viewInternal->moveEdge(KateViewInternal::left, false); 2787 } else { 2788 text = selectionText(); 2789 2790 // Multicursor copy 2791 if (!m_secondaryCursors.empty()) { 2792 QVarLengthArray<KTextEditor::Range, 16> ranges; 2793 for (const auto &c : m_secondaryCursors) { 2794 if (c.range) { 2795 ranges.push_back(c.range->toRange()); 2796 } 2797 } 2798 ranges.push_back(m_selection.toRange()); 2799 std::sort(ranges.begin(), ranges.end()); 2800 QStringList texts; 2801 for (auto range : ranges) { 2802 texts.append(doc()->text(range)); 2803 } 2804 KTextEditor::EditorPrivate::self()->copyToMulticursorClipboard(texts); 2805 } 2806 } 2807 2808 // copy to clipboard and our history! 2809 KTextEditor::EditorPrivate::self()->copyToClipboard(text, m_doc->url().fileName()); 2810 } 2811 2812 void KTextEditor::ViewPrivate::pasteSelection() 2813 { 2814 m_temporaryAutomaticInvocationDisabled = true; 2815 doc()->paste(this, QApplication::clipboard()->text(QClipboard::Selection)); 2816 m_temporaryAutomaticInvocationDisabled = false; 2817 } 2818 2819 void KTextEditor::ViewPrivate::swapWithClipboard() 2820 { 2821 m_temporaryAutomaticInvocationDisabled = true; 2822 2823 // get text to paste 2824 const auto text = QApplication::clipboard()->text(QClipboard::Clipboard); 2825 2826 // do copy 2827 copy(); 2828 2829 // do paste of "previous" clipboard content we saved 2830 doc()->paste(this, text); 2831 2832 m_temporaryAutomaticInvocationDisabled = false; 2833 } 2834 2835 void KTextEditor::ViewPrivate::applyWordWrap() 2836 { 2837 int first = selectionRange().start().line(); 2838 int last = selectionRange().end().line(); 2839 2840 if (first == last) { 2841 // Either no selection or only one line selected, wrap only the current line 2842 first = cursorPosition().line(); 2843 last = first; 2844 } 2845 2846 doc()->wrapParagraph(first, last); 2847 } 2848 2849 // END 2850 2851 // BEGIN KTextEditor::BlockSelectionInterface stuff 2852 2853 bool KTextEditor::ViewPrivate::blockSelection() const 2854 { 2855 return blockSelect; 2856 } 2857 2858 bool KTextEditor::ViewPrivate::setBlockSelection(bool on) 2859 { 2860 if (on != blockSelect) { 2861 blockSelect = on; 2862 2863 KTextEditor::Range oldSelection = m_selection; 2864 2865 const bool hadSelection = clearSelection(false, false); 2866 2867 setSelection(oldSelection); 2868 2869 m_toggleBlockSelection->setChecked(blockSelection()); 2870 2871 // when leaving block selection mode, if cursor is at an invalid position or past the end of the 2872 // line, move the cursor to the last column of the current line unless cursor wrapping is off 2873 ensureCursorColumnValid(); 2874 2875 if (!hadSelection) { 2876 // emit selectionChanged() according to the KTextEditor::View api 2877 // documentation also if there is no selection around. This is needed, 2878 // as e.g. the Kate App status bar uses this signal to update the state 2879 // of the selection mode (block selection, line based selection) 2880 Q_EMIT selectionChanged(this); 2881 } 2882 } 2883 2884 return true; 2885 } 2886 2887 bool KTextEditor::ViewPrivate::toggleBlockSelection() 2888 { 2889 // no multicursors for blockselect 2890 clearSecondaryCursors(); 2891 2892 m_toggleBlockSelection->setChecked(!blockSelect); 2893 return setBlockSelection(!blockSelect); 2894 } 2895 2896 bool KTextEditor::ViewPrivate::wrapCursor() const 2897 { 2898 return !blockSelection(); 2899 } 2900 2901 // END 2902 2903 void KTextEditor::ViewPrivate::slotTextInserted(KTextEditor::View *view, const KTextEditor::Cursor position, const QString &text) 2904 { 2905 Q_EMIT textInserted(view, position, text); 2906 } 2907 2908 bool KTextEditor::ViewPrivate::insertTemplateInternal(const KTextEditor::Cursor c, const QString &templateString, const QString &script) 2909 { 2910 // no empty templates 2911 if (templateString.isEmpty()) { 2912 return false; 2913 } 2914 2915 // not for read-only docs 2916 if (!doc()->isReadWrite()) { 2917 return false; 2918 } 2919 2920 // only one handler maybe active at a time; store it in the document. 2921 // Clear it first to make sure at no time two handlers are active at once 2922 doc()->setActiveTemplateHandler(nullptr); 2923 doc()->setActiveTemplateHandler(new KateTemplateHandler(this, c, templateString, script, doc()->undoManager())); 2924 return true; 2925 } 2926 2927 bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Range range, bool realRange) 2928 { 2929 return tagLines(range.start(), range.end(), realRange); 2930 } 2931 2932 void KTextEditor::ViewPrivate::deactivateEditActions() 2933 { 2934 for (QAction *action : std::as_const(m_editActions)) { 2935 action->setEnabled(false); 2936 } 2937 } 2938 2939 void KTextEditor::ViewPrivate::activateEditActions() 2940 { 2941 for (QAction *action : std::as_const(m_editActions)) { 2942 action->setEnabled(true); 2943 } 2944 } 2945 2946 bool KTextEditor::ViewPrivate::mouseTrackingEnabled() const 2947 { 2948 // FIXME support 2949 return true; 2950 } 2951 2952 bool KTextEditor::ViewPrivate::setMouseTrackingEnabled(bool) 2953 { 2954 // FIXME support 2955 return true; 2956 } 2957 2958 bool KTextEditor::ViewPrivate::isMulticursorNotAllowed() const 2959 { 2960 return blockSelection() || isOverwriteMode() || currentInputMode()->viewInputMode() == KTextEditor::View::InputMode::ViInputMode; 2961 } 2962 2963 void KTextEditor::ViewPrivate::addSecondaryCursor(const KTextEditor::Cursor &pos) 2964 { 2965 auto primaryCursor = cursorPosition(); 2966 const bool overlapsOrOnPrimary = pos == primaryCursor || (selection() && selectionRange().contains(pos)); 2967 if (overlapsOrOnPrimary && m_secondaryCursors.empty()) { 2968 // Clicking on primary cursor while it is the only cursor, 2969 // we do nothing 2970 return; 2971 } else if (overlapsOrOnPrimary) { 2972 // Clicking on primary cursor, we have secondaries 2973 // so just make the last secondary cursor primary 2974 // and remove caret at current primary cursor position 2975 auto &last = m_secondaryCursors.back(); 2976 setCursorPosition(last.cursor()); 2977 if (last.range) { 2978 setSelection(last.range->toRange()); 2979 Q_ASSERT(last.anchor.isValid()); 2980 m_viewInternal->m_selectAnchor = last.anchor; 2981 } 2982 m_secondaryCursors.pop_back(); 2983 return; 2984 } 2985 2986 // If there are any existing cursors at this position 2987 // remove them and be done i.e., if you click on an 2988 // existing cursor it is removed. 2989 if (removeSecondaryCursors({pos}, /*removeIfSelectionOverlap=*/true)) { 2990 return; 2991 } 2992 2993 // We are adding a new cursor! 2994 // - Move primary cursor to the position where the click happened 2995 // - Old primary cursor becomes a secondary cursor 2996 // Doing it like this makes multi mouse selections very easy 2997 setCursorPosition(pos); 2998 KTextEditor::ViewPrivate::PlainSecondaryCursor p; 2999 p.pos = primaryCursor; 3000 p.range = selection() ? selectionRange() : KTextEditor::Range::invalid(); 3001 clearSelection(); 3002 addSecondaryCursorsWithSelection({p}); 3003 } 3004 3005 void KTextEditor::ViewPrivate::setSecondaryCursors(const QVector<KTextEditor::Cursor> &positions) 3006 { 3007 clearSecondaryCursors(); 3008 3009 if (positions.isEmpty() || isMulticursorNotAllowed()) { 3010 return; 3011 } 3012 3013 const auto totalLines = doc()->lines(); 3014 for (auto p : positions) { 3015 if (p != cursorPosition() && p.line() < totalLines) { 3016 SecondaryCursor c; 3017 c.pos.reset(static_cast<Kate::TextCursor *>(doc()->newMovingCursor(p))); 3018 m_secondaryCursors.push_back(std::move(c)); 3019 tagLine(p); 3020 } 3021 } 3022 sortCursors(); 3023 paintCursors(); 3024 } 3025 3026 void KTextEditor::ViewPrivate::clearSecondarySelections() 3027 { 3028 for (auto &c : m_secondaryCursors) { 3029 c.range.reset(); 3030 c.anchor = KTextEditor::Cursor::invalid(); 3031 } 3032 } 3033 3034 void KTextEditor::ViewPrivate::clearSecondaryCursors() 3035 { 3036 if (m_secondaryCursors.empty()) { 3037 return; 3038 } 3039 for (const auto &c : m_secondaryCursors) { 3040 tagLine(c.cursor()); 3041 } 3042 m_secondaryCursors.clear(); 3043 m_viewInternal->updateDirty(); 3044 } 3045 3046 const std::vector<KTextEditor::ViewPrivate::SecondaryCursor> &KTextEditor::ViewPrivate::secondaryCursors() const 3047 { 3048 return m_secondaryCursors; 3049 } 3050 3051 QVector<KTextEditor::ViewPrivate::PlainSecondaryCursor> KTextEditor::ViewPrivate::plainSecondaryCursors() const 3052 { 3053 QVector<PlainSecondaryCursor> cursors; 3054 cursors.reserve(m_secondaryCursors.size()); 3055 std::transform(m_secondaryCursors.begin(), m_secondaryCursors.end(), std::back_inserter(cursors), [](const SecondaryCursor &c) { 3056 if (c.range) { 3057 return PlainSecondaryCursor{c.cursor(), c.range->toRange()}; 3058 } 3059 return PlainSecondaryCursor{c.cursor(), KTextEditor::Range::invalid()}; 3060 }); 3061 return cursors; 3062 } 3063 3064 bool KTextEditor::ViewPrivate::removeSecondaryCursors(const std::vector<KTextEditor::Cursor> &cursorsToRemove, bool removeIfOverlapsSelection) 3065 { 3066 Q_ASSERT(std::is_sorted(cursorsToRemove.begin(), cursorsToRemove.end())); 3067 3068 QVarLengthArray<KTextEditor::Cursor, 8> linesToTag; 3069 3070 if (removeIfOverlapsSelection) { 3071 m_secondaryCursors.erase(std::remove_if(m_secondaryCursors.begin(), 3072 m_secondaryCursors.end(), 3073 [&](const SecondaryCursor &c) { 3074 auto it = 3075 std::find_if(cursorsToRemove.begin(), cursorsToRemove.end(), [&c](const KTextEditor::Cursor &pos) { 3076 return c.cursor() == pos || (c.range && c.range->contains(pos)); 3077 }); 3078 const bool match = it != cursorsToRemove.end(); 3079 if (match) { 3080 linesToTag.push_back(c.cursor()); 3081 } 3082 return match; 3083 }), 3084 m_secondaryCursors.end()); 3085 } else { 3086 m_secondaryCursors.erase(std::remove_if(m_secondaryCursors.begin(), 3087 m_secondaryCursors.end(), 3088 [&](const SecondaryCursor &c) { 3089 auto it = 3090 std::find_if(cursorsToRemove.begin(), cursorsToRemove.end(), [&c](const KTextEditor::Cursor &pos) { 3091 return c.cursor() == pos; 3092 }); 3093 const bool match = it != cursorsToRemove.end(); 3094 if (match) { 3095 linesToTag.push_back(c.cursor()); 3096 } 3097 return match; 3098 }), 3099 m_secondaryCursors.end()); 3100 } 3101 3102 for (const auto &c : linesToTag) { 3103 tagLine(m_viewInternal->toVirtualCursor(c)); 3104 } 3105 return !linesToTag.empty(); 3106 3107 for (auto cur : cursorsToRemove) { 3108 auto &sec = m_secondaryCursors; 3109 auto it = std::find_if(sec.begin(), sec.end(), [cur](const SecondaryCursor &c) { 3110 return c.cursor() == cur; 3111 }); 3112 if (it != sec.end()) { 3113 // removedAny = true; 3114 m_secondaryCursors.erase(it); 3115 tagLine(m_viewInternal->toVirtualCursor(cur)); 3116 } 3117 } 3118 3119 // if (removedAny) { 3120 m_viewInternal->updateDirty(); 3121 if (cursorPosition() == KTextEditor::Cursor(0, 0)) { 3122 m_viewInternal->paintCursor(); 3123 } 3124 return !linesToTag.empty(); 3125 // } 3126 // return removedAny; 3127 } 3128 3129 void KTextEditor::ViewPrivate::ensureUniqueCursors(bool matchLine) 3130 { 3131 if (m_secondaryCursors.empty()) { 3132 return; 3133 } 3134 3135 std::vector<SecondaryCursor>::iterator it; 3136 if (matchLine) { 3137 auto matchLine = [](const SecondaryCursor &l, const SecondaryCursor &r) { 3138 return l.cursor().line() == r.cursor().line(); 3139 }; 3140 it = std::unique(m_secondaryCursors.begin(), m_secondaryCursors.end(), matchLine); 3141 } else { 3142 it = std::unique(m_secondaryCursors.begin(), m_secondaryCursors.end()); 3143 } 3144 if (it != m_secondaryCursors.end()) { 3145 m_secondaryCursors.erase(it, m_secondaryCursors.end()); 3146 } 3147 3148 if (matchLine) { 3149 const int ln = cursorPosition().line(); 3150 m_secondaryCursors.erase(std::remove_if(m_secondaryCursors.begin(), 3151 m_secondaryCursors.end(), 3152 [ln](const SecondaryCursor &c) { 3153 return c.cursor().line() == ln; 3154 }), 3155 m_secondaryCursors.end()); 3156 } else { 3157 const auto cp = cursorPosition(); 3158 m_secondaryCursors.erase(std::remove_if(m_secondaryCursors.begin(), 3159 m_secondaryCursors.end(), 3160 [cp](const SecondaryCursor &c) { 3161 return c.cursor() == cp; 3162 }), 3163 m_secondaryCursors.end()); 3164 } 3165 } 3166 3167 void KTextEditor::ViewPrivate::addSecondaryCursorsWithSelection(const QVector<PlainSecondaryCursor> &cursorsWithSelection) 3168 { 3169 if (isMulticursorNotAllowed() || cursorsWithSelection.isEmpty()) { 3170 return; 3171 } 3172 3173 for (const auto &c : cursorsWithSelection) { 3174 // We don't want to add on top of primary cursor 3175 if (c.pos == cursorPosition()) { 3176 continue; 3177 } 3178 SecondaryCursor n; 3179 n.pos.reset(static_cast<Kate::TextCursor *>(doc()->newMovingCursor(c.pos))); 3180 if (c.range.isValid()) { 3181 n.range.reset(newSecondarySelectionRange(c.range)); 3182 n.anchor = c.range.start() == c.pos ? c.range.end() : c.range.start(); 3183 } 3184 m_secondaryCursors.push_back(std::move(n)); 3185 } 3186 sortCursors(); 3187 paintCursors(); 3188 } 3189 3190 Kate::TextRange *KTextEditor::ViewPrivate::newSecondarySelectionRange(KTextEditor::Range selRange) 3191 { 3192 constexpr auto expandBehaviour = KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight; 3193 auto range = new Kate::TextRange(doc()->buffer(), selRange, expandBehaviour); 3194 static KTextEditor::Attribute::Ptr selAttr; 3195 if (!selAttr) { 3196 selAttr = new KTextEditor::Attribute; 3197 auto color = QColor::fromRgba(theme().editorColor(KSyntaxHighlighting::Theme::TextSelection)); 3198 selAttr->setBackground(color); 3199 } 3200 range->setZDepth(-999999.); 3201 range->setAttribute(selAttr); 3202 return range; 3203 } 3204 3205 bool KTextEditor::ViewPrivate::hasSelections() const 3206 { 3207 if (selection()) 3208 return true; 3209 return std::any_of(m_secondaryCursors.cbegin(), m_secondaryCursors.cend(), [](const SecondaryCursor &c) { 3210 return c.range && !c.range->isEmpty(); 3211 }); 3212 } 3213 3214 void KTextEditor::ViewPrivate::addSecondaryCursorDown() 3215 { 3216 KTextEditor::Cursor last = cursorPosition(); 3217 const auto &secondary = secondaryCursors(); 3218 if (!secondary.empty()) { 3219 last = secondary.back().cursor(); 3220 last = std::max(cursorPosition(), last); 3221 } 3222 if (last.line() >= doc()->lastLine()) { 3223 return; 3224 } 3225 3226 auto nextRange = m_viewInternal->nextLayout(last); 3227 if (!nextRange.isValid()) { 3228 return; 3229 } 3230 auto primaryCursorLineLayout = m_viewInternal->currentLayout(cursorPosition()); 3231 if (!primaryCursorLineLayout.isValid()) { 3232 return; 3233 } 3234 3235 int x = renderer()->cursorToX(primaryCursorLineLayout, cursorPosition().column(), !wrapCursor()); 3236 auto next = renderer()->xToCursor(nextRange, x, !wrapCursor()); 3237 addSecondaryCursor(next); 3238 } 3239 3240 void KTextEditor::ViewPrivate::addSecondaryCursorUp() 3241 { 3242 KTextEditor::Cursor last = cursorPosition(); 3243 const auto &secondary = secondaryCursors(); 3244 if (!secondary.empty()) { 3245 last = secondary.front().cursor(); 3246 last = std::min(cursorPosition(), last); 3247 } 3248 if (last.line() == 0) { 3249 return; 3250 } 3251 auto nextRange = m_viewInternal->previousLayout(last); 3252 if (!nextRange.isValid()) { 3253 return; 3254 } 3255 3256 auto primaryCursorLineLayout = m_viewInternal->currentLayout(cursorPosition()); 3257 if (!primaryCursorLineLayout.isValid()) { 3258 return; 3259 } 3260 3261 int x = renderer()->cursorToX(primaryCursorLineLayout, cursorPosition().column(), !wrapCursor()); 3262 auto next = renderer()->xToCursor(nextRange, x, !wrapCursor()); 3263 addSecondaryCursor(next); 3264 } 3265 3266 QVector<KTextEditor::Cursor> KTextEditor::ViewPrivate::cursors() const 3267 { 3268 QVector<KTextEditor::Cursor> ret; 3269 ret.reserve(m_secondaryCursors.size() + 1); 3270 ret << cursorPosition(); 3271 std::transform(m_secondaryCursors.begin(), m_secondaryCursors.end(), std::back_inserter(ret), [](const SecondaryCursor &c) { 3272 return c.cursor(); 3273 }); 3274 return ret; 3275 } 3276 3277 QVector<KTextEditor::Range> KTextEditor::ViewPrivate::selectionRanges() const 3278 { 3279 if (!selection()) { 3280 return {}; 3281 } 3282 3283 QVector<KTextEditor::Range> ret; 3284 ret.reserve(m_secondaryCursors.size() + 1); 3285 ret << selectionRange(); 3286 std::transform(m_secondaryCursors.begin(), m_secondaryCursors.end(), std::back_inserter(ret), [](const SecondaryCursor &c) { 3287 if (!c.range) { 3288 qWarning() << "selectionRanges(): Unexpected null selection range, please fix"; 3289 return KTextEditor::Range::invalid(); 3290 } 3291 return c.range->toRange(); 3292 }); 3293 return ret; 3294 } 3295 3296 void KTextEditor::ViewPrivate::setCursors(const QVector<KTextEditor::Cursor> &cursorPositions) 3297 { 3298 if (isMulticursorNotAllowed()) { 3299 qWarning() << "setCursors failed: Multicursors not allowed because one of the following is true" 3300 << ", blockSelection: " << blockSelection() << ", overwriteMode: " << isOverwriteMode() 3301 << ", viMode: " << (currentInputMode()->viewInputMode() == KTextEditor::View::InputMode::ViInputMode); 3302 return; 3303 } 3304 3305 clearSecondaryCursors(); 3306 if (cursorPositions.empty()) { 3307 return; 3308 } 3309 3310 const auto primary = cursorPositions.front(); 3311 // We clear primary selection because primary and secondary 3312 // cursors should always have same selection state 3313 setSelection({}); 3314 setCursorPosition(primary); 3315 // First will be auto ignored because it equals cursorPosition() 3316 setSecondaryCursors(cursorPositions); 3317 } 3318 3319 void KTextEditor::ViewPrivate::setSelections(const QVector<KTextEditor::Range> &selectionRanges) 3320 { 3321 if (isMulticursorNotAllowed()) { 3322 qWarning() << "setSelections failed: Multicursors not allowed because one of the following is true" 3323 << ", blockSelection: " << blockSelection() << ", overwriteMode: " << isOverwriteMode() 3324 << ", viMode: " << (currentInputMode()->viewInputMode() == KTextEditor::View::InputMode::ViInputMode); 3325 return; 3326 } 3327 3328 clearSecondaryCursors(); 3329 setSelection({}); 3330 if (selectionRanges.isEmpty()) { 3331 return; 3332 } 3333 3334 auto first = selectionRanges.front(); 3335 setCursorPosition(first.end()); 3336 setSelection(first); 3337 3338 if (selectionRanges.size() == 1) { 3339 return; 3340 } 3341 3342 const auto docRange = doc()->documentRange(); 3343 for (auto it = selectionRanges.begin() + 1; it != selectionRanges.end(); ++it) { 3344 KTextEditor::Range r = *it; 3345 KTextEditor::Cursor c = r.end(); 3346 if (c == cursorPosition() || !r.isValid() || r.isEmpty() || !docRange.contains(r)) { 3347 continue; 3348 } 3349 3350 SecondaryCursor n; 3351 n.pos.reset(static_cast<Kate::TextCursor *>(doc()->newMovingCursor(c))); 3352 n.range.reset(newSecondarySelectionRange(r)); 3353 n.anchor = r.start(); 3354 m_secondaryCursors.push_back(std::move(n)); 3355 } 3356 m_viewInternal->mergeSelections(); 3357 3358 sortCursors(); 3359 paintCursors(); 3360 } 3361 3362 void KTextEditor::ViewPrivate::sortCursors() 3363 { 3364 std::sort(m_secondaryCursors.begin(), m_secondaryCursors.end()); 3365 ensureUniqueCursors(); 3366 } 3367 3368 void KTextEditor::ViewPrivate::paintCursors() 3369 { 3370 if (m_viewInternal->m_cursorTimer.isActive()) { 3371 if (QApplication::cursorFlashTime() > 0) { 3372 m_viewInternal->m_cursorTimer.start(QApplication::cursorFlashTime() / 2); 3373 } 3374 renderer()->setDrawCaret(true); 3375 } 3376 m_viewInternal->paintCursor(); 3377 } 3378 3379 bool KTextEditor::ViewPrivate::isCompletionActive() const 3380 { 3381 return completionWidget()->isCompletionActive(); 3382 } 3383 3384 KateCompletionWidget *KTextEditor::ViewPrivate::completionWidget() const 3385 { 3386 if (!m_completionWidget) { 3387 m_completionWidget = new KateCompletionWidget(const_cast<KTextEditor::ViewPrivate *>(this)); 3388 } 3389 3390 return m_completionWidget; 3391 } 3392 3393 void KTextEditor::ViewPrivate::startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model) 3394 { 3395 completionWidget()->startCompletion(word, model); 3396 } 3397 3398 void KTextEditor::ViewPrivate::startCompletion(const Range &word, 3399 const QList<KTextEditor::CodeCompletionModel *> &models, 3400 KTextEditor::CodeCompletionModel::InvocationType invocationType) 3401 { 3402 completionWidget()->startCompletion(word, models, invocationType); 3403 } 3404 3405 void KTextEditor::ViewPrivate::abortCompletion() 3406 { 3407 completionWidget()->abortCompletion(); 3408 } 3409 3410 void KTextEditor::ViewPrivate::forceCompletion() 3411 { 3412 completionWidget()->execute(); 3413 } 3414 3415 void KTextEditor::ViewPrivate::registerCompletionModel(KTextEditor::CodeCompletionModel *model) 3416 { 3417 completionWidget()->registerCompletionModel(model); 3418 } 3419 3420 void KTextEditor::ViewPrivate::unregisterCompletionModel(KTextEditor::CodeCompletionModel *model) 3421 { 3422 completionWidget()->unregisterCompletionModel(model); 3423 } 3424 3425 bool KTextEditor::ViewPrivate::isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const 3426 { 3427 return completionWidget()->isCompletionModelRegistered(model); 3428 } 3429 3430 QList<KTextEditor::CodeCompletionModel *> KTextEditor::ViewPrivate::codeCompletionModels() const 3431 { 3432 return completionWidget()->codeCompletionModels(); 3433 } 3434 3435 bool KTextEditor::ViewPrivate::isAutomaticInvocationEnabled() const 3436 { 3437 return !m_temporaryAutomaticInvocationDisabled && m_config->automaticCompletionInvocation(); 3438 } 3439 3440 void KTextEditor::ViewPrivate::setAutomaticInvocationEnabled(bool enabled) 3441 { 3442 config()->setValue(KateViewConfig::AutomaticCompletionInvocation, enabled); 3443 } 3444 3445 void KTextEditor::ViewPrivate::sendCompletionExecuted(const KTextEditor::Cursor position, KTextEditor::CodeCompletionModel *model, const QModelIndex &index) 3446 { 3447 Q_EMIT completionExecuted(this, position, model, index); 3448 } 3449 3450 void KTextEditor::ViewPrivate::sendCompletionAborted() 3451 { 3452 Q_EMIT completionAborted(this); 3453 } 3454 3455 void KTextEditor::ViewPrivate::paste(const QString *textToPaste) 3456 { 3457 const int cursorCount = m_secondaryCursors.size() + 1; // 1 primary cursor 3458 const auto multicursorClipboard = KTextEditor::EditorPrivate::self()->multicursorClipboard(); 3459 if (cursorCount == multicursorClipboard.size() && !textToPaste) { 3460 if (doc()->multiPaste(this, multicursorClipboard)) { 3461 return; 3462 } 3463 } else if (!textToPaste && cursorCount > 1) { 3464 // We still have multiple cursors, but the amount 3465 // of multicursors doesn't match the entry count in clipboard 3466 QStringList texts; 3467 texts.reserve(cursorCount); 3468 QString clipboard = QApplication::clipboard()->text(QClipboard::Clipboard); 3469 for (int i = 0; i < cursorCount; ++i) { 3470 texts << clipboard; 3471 } 3472 // It might still fail for e.g., if we are in block mode, 3473 // in that case we will fallback to normal pasting below 3474 if (doc()->multiPaste(this, texts)) { 3475 return; 3476 } 3477 } 3478 3479 m_temporaryAutomaticInvocationDisabled = true; 3480 doc()->paste(this, textToPaste ? *textToPaste : QApplication::clipboard()->text(QClipboard::Clipboard)); 3481 m_temporaryAutomaticInvocationDisabled = false; 3482 } 3483 3484 bool KTextEditor::ViewPrivate::setCursorPosition(KTextEditor::Cursor position) 3485 { 3486 return setCursorPositionInternal(position, 1, true); 3487 } 3488 3489 KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPosition() const 3490 { 3491 return m_viewInternal->cursorPosition(); 3492 } 3493 3494 KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPositionVirtual() const 3495 { 3496 return KTextEditor::Cursor(m_viewInternal->cursorPosition().line(), virtualCursorColumn()); 3497 } 3498 3499 QPoint KTextEditor::ViewPrivate::cursorToCoordinate(const KTextEditor::Cursor &cursor) const 3500 { 3501 // map from ViewInternal to View coordinates 3502 const QPoint pt = m_viewInternal->cursorToCoordinate(cursor, true, false); 3503 return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt); 3504 } 3505 3506 KTextEditor::Cursor KTextEditor::ViewPrivate::coordinatesToCursor(const QPoint &coords) const 3507 { 3508 // map from View to ViewInternal coordinates 3509 return m_viewInternal->coordinatesToCursor(m_viewInternal->mapFromParent(coords), false); 3510 } 3511 3512 QPoint KTextEditor::ViewPrivate::cursorPositionCoordinates() const 3513 { 3514 // map from ViewInternal to View coordinates 3515 const QPoint pt = m_viewInternal->cursorCoordinates(false); 3516 return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt); 3517 } 3518 3519 void KTextEditor::ViewPrivate::setScrollPositionInternal(KTextEditor::Cursor cursor) 3520 { 3521 m_viewInternal->scrollPos(cursor, false, true, false); 3522 } 3523 3524 void KTextEditor::ViewPrivate::setHorizontalScrollPositionInternal(int x) 3525 { 3526 m_viewInternal->scrollColumns(x); 3527 } 3528 3529 KTextEditor::Cursor KTextEditor::ViewPrivate::maxScrollPositionInternal() const 3530 { 3531 return m_viewInternal->maxStartPos(true); 3532 } 3533 3534 int KTextEditor::ViewPrivate::firstDisplayedLineInternal(LineType lineType) const 3535 { 3536 if (lineType == RealLine) { 3537 return m_textFolding.visibleLineToLine(m_viewInternal->startLine()); 3538 } else { 3539 return m_viewInternal->startLine(); 3540 } 3541 } 3542 3543 int KTextEditor::ViewPrivate::lastDisplayedLineInternal(LineType lineType) const 3544 { 3545 if (lineType == RealLine) { 3546 return m_textFolding.visibleLineToLine(m_viewInternal->endLine()); 3547 } else { 3548 return m_viewInternal->endLine(); 3549 } 3550 } 3551 3552 QRect KTextEditor::ViewPrivate::textAreaRectInternal() const 3553 { 3554 const auto sourceRect = m_viewInternal->rect(); 3555 const auto topLeft = m_viewInternal->mapTo(this, sourceRect.topLeft()); 3556 const auto bottomRight = m_viewInternal->mapTo(this, sourceRect.bottomRight()); 3557 return {topLeft, bottomRight}; 3558 } 3559 3560 bool KTextEditor::ViewPrivate::setCursorPositionVisual(const KTextEditor::Cursor position) 3561 { 3562 return setCursorPositionInternal(position, doc()->config()->tabWidth(), true); 3563 } 3564 3565 bool KTextEditor::ViewPrivate::isLineRTL(int line) const 3566 { 3567 const QString s = doc()->line(line); 3568 if (s.isEmpty()) { 3569 int line = cursorPosition().line(); 3570 if (line == 0) { 3571 const int count = doc()->lines(); 3572 for (int i = 1; i < count; ++i) { 3573 const QString ln = doc()->line(i); 3574 if (ln.isEmpty()) { 3575 continue; 3576 } 3577 return ln.isRightToLeft(); 3578 } 3579 } else { 3580 int line = cursorPosition().line(); 3581 for (; line >= 0; --line) { 3582 const QString s = doc()->line(line); 3583 if (s.isEmpty()) { 3584 continue; 3585 } 3586 return s.isRightToLeft(); 3587 } 3588 } 3589 return false; 3590 } else { 3591 return s.isRightToLeft(); 3592 } 3593 } 3594 3595 QTextLayout *KTextEditor::ViewPrivate::textLayout(int line) const 3596 { 3597 KateLineLayoutPtr thisLine = m_viewInternal->cache()->line(line); 3598 3599 return thisLine->isValid() ? thisLine->layout() : nullptr; 3600 } 3601 3602 QTextLayout *KTextEditor::ViewPrivate::textLayout(const KTextEditor::Cursor pos) const 3603 { 3604 KateLineLayoutPtr thisLine = m_viewInternal->cache()->line(pos); 3605 3606 return thisLine->isValid() ? thisLine->layout() : nullptr; 3607 } 3608 3609 void KTextEditor::ViewPrivate::indent() 3610 { 3611 KTextEditor::Cursor c(cursorPosition().line(), 0); 3612 KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); 3613 doc()->indent(r, 1); 3614 } 3615 3616 void KTextEditor::ViewPrivate::unIndent() 3617 { 3618 KTextEditor::Cursor c(cursorPosition().line(), 0); 3619 KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); 3620 doc()->indent(r, -1); 3621 } 3622 3623 void KTextEditor::ViewPrivate::cleanIndent() 3624 { 3625 KTextEditor::Cursor c(cursorPosition().line(), 0); 3626 KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); 3627 doc()->indent(r, 0); 3628 } 3629 3630 void KTextEditor::ViewPrivate::formatIndent() 3631 { 3632 // no selection: align current line; selection: use selection range 3633 const int line = cursorPosition().line(); 3634 KTextEditor::Range formatRange(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, 0)); 3635 if (selection()) { 3636 formatRange = selectionRange(); 3637 } 3638 3639 doc()->align(this, formatRange); 3640 } 3641 3642 // alias of formatIndent, for backward compatibility 3643 void KTextEditor::ViewPrivate::align() 3644 { 3645 formatIndent(); 3646 } 3647 3648 void KTextEditor::ViewPrivate::alignOn() 3649 { 3650 static QString pattern; 3651 KTextEditor::Range range; 3652 if (!selection()) { 3653 range = doc()->documentRange(); 3654 } else { 3655 range = selectionRange(); 3656 } 3657 bool ok; 3658 pattern = QInputDialog::getText(window(), i18n("Align On"), i18n("Alignment pattern:"), QLineEdit::Normal, pattern, &ok); 3659 if (!ok) { 3660 return; 3661 } 3662 doc()->alignOn(range, pattern, this->blockSelection()); 3663 } 3664 3665 void KTextEditor::ViewPrivate::comment() 3666 { 3667 m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight); 3668 doc()->comment(this, cursorPosition().line(), cursorPosition().column(), DocumentPrivate::Comment); 3669 m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight); 3670 } 3671 3672 void KTextEditor::ViewPrivate::uncomment() 3673 { 3674 doc()->comment(this, cursorPosition().line(), cursorPosition().column(), DocumentPrivate::UnComment); 3675 } 3676 3677 void KTextEditor::ViewPrivate::toggleComment() 3678 { 3679 m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight); 3680 doc()->comment(this, cursorPosition().line(), cursorPosition().column(), DocumentPrivate::ToggleComment); 3681 m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight); 3682 } 3683 3684 void KTextEditor::ViewPrivate::uppercase() 3685 { 3686 doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Uppercase); 3687 } 3688 3689 void KTextEditor::ViewPrivate::killLine() 3690 { 3691 std::vector<int> linesToRemove; 3692 if (m_selection.isEmpty()) { 3693 // collect lines of all cursors 3694 linesToRemove.reserve(m_secondaryCursors.size() + 1); 3695 for (const auto &c : m_secondaryCursors) { 3696 linesToRemove.push_back(c.pos->line()); 3697 } 3698 // add primary cursor line 3699 linesToRemove.push_back(cursorPosition().line()); 3700 } else { 3701 linesToRemove.reserve(m_secondaryCursors.size() + 1); 3702 for (const auto &c : m_secondaryCursors) { 3703 const auto &range = c.range; 3704 if (!range) { 3705 continue; 3706 } 3707 for (int line = range->end().line(); line >= range->start().line(); line--) { 3708 linesToRemove.push_back(line); 3709 } 3710 } 3711 3712 // cache endline, else that moves and we might delete complete document if last line is selected! 3713 for (int line = m_selection.end().line(), endLine = m_selection.start().line(); line >= endLine; line--) { 3714 linesToRemove.push_back(line); 3715 } 3716 } 3717 3718 std::sort(linesToRemove.begin(), linesToRemove.end(), std::greater{}); 3719 linesToRemove.erase(std::unique(linesToRemove.begin(), linesToRemove.end()), linesToRemove.end()); 3720 3721 doc()->editStart(); 3722 std::for_each(linesToRemove.begin(), linesToRemove.end(), [this](int line) { 3723 doc()->removeLine(line); 3724 }); 3725 doc()->editEnd(); 3726 3727 ensureUniqueCursors(); 3728 } 3729 3730 void KTextEditor::ViewPrivate::lowercase() 3731 { 3732 doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Lowercase); 3733 } 3734 3735 void KTextEditor::ViewPrivate::capitalize() 3736 { 3737 doc()->editStart(); 3738 doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Lowercase); 3739 doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Capitalize); 3740 doc()->editEnd(); 3741 } 3742 3743 void KTextEditor::ViewPrivate::keyReturn() 3744 { 3745 doc()->newLine(this); 3746 m_viewInternal->iconBorder()->updateForCursorLineChange(); 3747 m_viewInternal->updateView(); 3748 } 3749 3750 void KTextEditor::ViewPrivate::smartNewline() 3751 { 3752 const KTextEditor::Cursor cursor = cursorPosition(); 3753 const int ln = cursor.line(); 3754 Kate::TextLine line = doc()->kateTextLine(ln); 3755 int col = qMin(cursor.column(), line->firstChar()); 3756 if (col != -1) { 3757 while (line->length() > col && !(line->at(col).isLetterOrNumber() || line->at(col) == QLatin1Char('_')) && col < cursor.column()) { 3758 ++col; 3759 } 3760 } else { 3761 col = line->length(); // stay indented 3762 } 3763 doc()->editStart(); 3764 doc()->editWrapLine(ln, cursor.column()); 3765 doc()->insertText(KTextEditor::Cursor(ln + 1, 0), line->string(0, col)); 3766 doc()->editEnd(); 3767 3768 m_viewInternal->updateView(); 3769 } 3770 3771 void KTextEditor::ViewPrivate::noIndentNewline() 3772 { 3773 doc()->newLine(this, KTextEditor::DocumentPrivate::NoIndent); 3774 m_viewInternal->iconBorder()->updateForCursorLineChange(); 3775 m_viewInternal->updateView(); 3776 } 3777 3778 void KTextEditor::ViewPrivate::newLineAbove() 3779 { 3780 doc()->newLine(this, KTextEditor::DocumentPrivate::Indent, KTextEditor::DocumentPrivate::Above); 3781 m_viewInternal->iconBorder()->updateForCursorLineChange(); 3782 m_viewInternal->updateView(); 3783 } 3784 3785 void KTextEditor::ViewPrivate::newLineBelow() 3786 { 3787 doc()->newLine(this, KTextEditor::DocumentPrivate::Indent, KTextEditor::DocumentPrivate::Below); 3788 m_viewInternal->iconBorder()->updateForCursorLineChange(); 3789 m_viewInternal->updateView(); 3790 } 3791 3792 void KTextEditor::ViewPrivate::backspace() 3793 { 3794 // Will take care of both multi and primary cursors 3795 doc()->backspace(this); 3796 } 3797 3798 void KTextEditor::ViewPrivate::insertTab() 3799 { 3800 doc()->insertTab(this, cursorPosition()); 3801 } 3802 3803 void KTextEditor::ViewPrivate::deleteWordLeft() 3804 { 3805 doc()->editStart(); 3806 m_viewInternal->wordPrev(true); 3807 KTextEditor::Range selection = selectionRange(); 3808 removeSelectedText(); 3809 doc()->editEnd(); 3810 3811 ensureUniqueCursors(); 3812 3813 m_viewInternal->tagRange(selection, true); 3814 m_viewInternal->updateDirty(); 3815 } 3816 3817 void KTextEditor::ViewPrivate::keyDelete() 3818 { 3819 KTextEditor::Document::EditingTransaction t(doc()); 3820 // multi cursor 3821 3822 if (removeSelectedText()) { 3823 return; 3824 } 3825 3826 for (const auto &c : m_secondaryCursors) { 3827 if (c.range) { 3828 doc()->removeText(c.range->toRange()); 3829 } else { 3830 doc()->del(this, c.cursor()); 3831 } 3832 } 3833 3834 // primary cursor 3835 doc()->del(this, cursorPosition()); 3836 3837 ensureUniqueCursors(); 3838 } 3839 3840 void KTextEditor::ViewPrivate::deleteWordRight() 3841 { 3842 doc()->editStart(); 3843 m_viewInternal->wordNext(true); 3844 KTextEditor::Range selection = selectionRange(); 3845 removeSelectedText(); 3846 doc()->editEnd(); 3847 3848 ensureUniqueCursors(); 3849 3850 m_viewInternal->tagRange(selection, true); 3851 m_viewInternal->updateDirty(); 3852 } 3853 3854 void KTextEditor::ViewPrivate::transpose() 3855 { 3856 doc()->editBegin(); 3857 for (const auto &c : m_secondaryCursors) { 3858 doc()->transpose(c.cursor()); 3859 } 3860 doc()->transpose(cursorPosition()); 3861 doc()->editEnd(); 3862 } 3863 3864 void KTextEditor::ViewPrivate::transposeWord() 3865 { 3866 const KTextEditor::Cursor originalCurPos = cursorPosition(); 3867 const KTextEditor::Range firstWord = doc()->wordRangeAt(originalCurPos); 3868 if (!firstWord.isValid()) { 3869 return; 3870 } 3871 3872 auto wordIsInvalid = [](QStringView word) { 3873 for (const QChar &character : word) { 3874 if (character.isLetterOrNumber()) { 3875 return false; 3876 } 3877 } 3878 return true; 3879 }; 3880 3881 if (wordIsInvalid(doc()->text(firstWord))) { 3882 return; 3883 } 3884 3885 setCursorPosition(firstWord.end()); 3886 wordRight(); 3887 KTextEditor::Cursor curPos = cursorPosition(); 3888 // swap with the word to the right if it exists, otherwise try to swap with word to the left 3889 if (curPos.line() != firstWord.end().line() || curPos.column() == firstWord.end().column()) { 3890 setCursorPosition(firstWord.start()); 3891 wordLeft(); 3892 curPos = cursorPosition(); 3893 // if there is still no next word in this line, no swapping will be done 3894 if (curPos.line() != firstWord.start().line() || curPos.column() == firstWord.start().column() || wordIsInvalid(doc()->wordAt(curPos))) { 3895 setCursorPosition(originalCurPos); 3896 return; 3897 } 3898 } 3899 3900 if (wordIsInvalid(doc()->wordAt(curPos))) { 3901 setCursorPosition(originalCurPos); 3902 return; 3903 } 3904 3905 const KTextEditor::Range secondWord = doc()->wordRangeAt(curPos); 3906 doc()->swapTextRanges(firstWord, secondWord); 3907 3908 // return cursor to its original position inside the word before swap 3909 // after the swap, the cursor will be at the end of the word, so we compute the position relative to the end of the word 3910 const int offsetFromWordEnd = firstWord.end().column() - originalCurPos.column(); 3911 setCursorPosition(cursorPosition() - KTextEditor::Cursor(0, offsetFromWordEnd)); 3912 } 3913 3914 void KTextEditor::ViewPrivate::cursorLeft() 3915 { 3916 if (selection() && !config()->persistentSelection()) { 3917 if (isLineRTL(cursorPosition().line())) { 3918 m_viewInternal->updateCursor(selectionRange().end()); 3919 setSelection(KTextEditor::Range::invalid()); 3920 } else { 3921 m_viewInternal->updateCursor(selectionRange().start()); 3922 setSelection(KTextEditor::Range::invalid()); 3923 } 3924 3925 for (const auto &c : m_secondaryCursors) { 3926 if (!c.range) { 3927 continue; 3928 } 3929 const bool rtl = isLineRTL(c.cursor().line()); 3930 c.pos->setPosition(rtl ? c.range->end() : c.range->start()); 3931 } 3932 clearSecondarySelections(); 3933 } else { 3934 if (isLineRTL(cursorPosition().line())) { 3935 m_viewInternal->cursorNextChar(); 3936 } else { 3937 m_viewInternal->cursorPrevChar(); 3938 } 3939 } 3940 } 3941 3942 void KTextEditor::ViewPrivate::shiftCursorLeft() 3943 { 3944 if (isLineRTL(cursorPosition().line())) { 3945 m_viewInternal->cursorNextChar(true); 3946 } else { 3947 m_viewInternal->cursorPrevChar(true); 3948 } 3949 } 3950 3951 void KTextEditor::ViewPrivate::cursorRight() 3952 { 3953 if (selection() && !config()->persistentSelection()) { 3954 if (isLineRTL(cursorPosition().line())) { 3955 m_viewInternal->updateCursor(selectionRange().start()); 3956 setSelection(KTextEditor::Range::invalid()); 3957 } else { 3958 m_viewInternal->updateCursor(selectionRange().end()); 3959 setSelection(KTextEditor::Range::invalid()); 3960 } 3961 3962 for (const auto &c : m_secondaryCursors) { 3963 if (!c.range) { 3964 continue; 3965 } 3966 const bool rtl = doc()->line(c.cursor().line()).isRightToLeft(); 3967 c.pos->setPosition(rtl ? c.range->start() : c.range->end()); 3968 } 3969 clearSecondarySelections(); 3970 } else { 3971 if (isLineRTL(cursorPosition().line())) { 3972 m_viewInternal->cursorPrevChar(); 3973 } else { 3974 m_viewInternal->cursorNextChar(); 3975 } 3976 } 3977 } 3978 3979 void KTextEditor::ViewPrivate::shiftCursorRight() 3980 { 3981 if (isLineRTL(cursorPosition().line())) { 3982 m_viewInternal->cursorPrevChar(true); 3983 } else { 3984 m_viewInternal->cursorNextChar(true); 3985 } 3986 } 3987 3988 void KTextEditor::ViewPrivate::wordLeft() 3989 { 3990 if (isLineRTL(cursorPosition().line())) { 3991 m_viewInternal->wordNext(); 3992 } else { 3993 m_viewInternal->wordPrev(); 3994 } 3995 } 3996 3997 void KTextEditor::ViewPrivate::shiftWordLeft() 3998 { 3999 if (isLineRTL(cursorPosition().line())) { 4000 m_viewInternal->wordNext(true); 4001 } else { 4002 m_viewInternal->wordPrev(true); 4003 } 4004 } 4005 4006 void KTextEditor::ViewPrivate::wordRight() 4007 { 4008 if (isLineRTL(cursorPosition().line())) { 4009 m_viewInternal->wordPrev(); 4010 } else { 4011 m_viewInternal->wordNext(); 4012 } 4013 } 4014 4015 void KTextEditor::ViewPrivate::shiftWordRight() 4016 { 4017 if (isLineRTL(cursorPosition().line())) { 4018 m_viewInternal->wordPrev(true); 4019 } else { 4020 m_viewInternal->wordNext(true); 4021 } 4022 } 4023 4024 void KTextEditor::ViewPrivate::home() 4025 { 4026 m_viewInternal->home(); 4027 } 4028 4029 void KTextEditor::ViewPrivate::shiftHome() 4030 { 4031 m_viewInternal->home(true); 4032 } 4033 4034 void KTextEditor::ViewPrivate::end() 4035 { 4036 m_viewInternal->end(); 4037 } 4038 4039 void KTextEditor::ViewPrivate::shiftEnd() 4040 { 4041 m_viewInternal->end(true); 4042 } 4043 4044 void KTextEditor::ViewPrivate::up() 4045 { 4046 m_viewInternal->cursorUp(); 4047 } 4048 4049 void KTextEditor::ViewPrivate::shiftUp() 4050 { 4051 m_viewInternal->cursorUp(true); 4052 } 4053 4054 void KTextEditor::ViewPrivate::down() 4055 { 4056 m_viewInternal->cursorDown(); 4057 } 4058 4059 void KTextEditor::ViewPrivate::shiftDown() 4060 { 4061 m_viewInternal->cursorDown(true); 4062 } 4063 4064 void KTextEditor::ViewPrivate::scrollUp() 4065 { 4066 m_viewInternal->scrollUp(); 4067 } 4068 4069 void KTextEditor::ViewPrivate::scrollDown() 4070 { 4071 m_viewInternal->scrollDown(); 4072 } 4073 4074 void KTextEditor::ViewPrivate::topOfView() 4075 { 4076 m_viewInternal->topOfView(); 4077 } 4078 4079 void KTextEditor::ViewPrivate::shiftTopOfView() 4080 { 4081 m_viewInternal->topOfView(true); 4082 } 4083 4084 void KTextEditor::ViewPrivate::bottomOfView() 4085 { 4086 m_viewInternal->bottomOfView(); 4087 } 4088 4089 void KTextEditor::ViewPrivate::shiftBottomOfView() 4090 { 4091 m_viewInternal->bottomOfView(true); 4092 } 4093 4094 void KTextEditor::ViewPrivate::pageUp() 4095 { 4096 m_viewInternal->pageUp(); 4097 } 4098 4099 void KTextEditor::ViewPrivate::shiftPageUp() 4100 { 4101 m_viewInternal->pageUp(true); 4102 } 4103 4104 void KTextEditor::ViewPrivate::pageDown() 4105 { 4106 m_viewInternal->pageDown(); 4107 } 4108 4109 void KTextEditor::ViewPrivate::shiftPageDown() 4110 { 4111 m_viewInternal->pageDown(true); 4112 } 4113 4114 void KTextEditor::ViewPrivate::top() 4115 { 4116 m_viewInternal->top_home(); 4117 } 4118 4119 void KTextEditor::ViewPrivate::shiftTop() 4120 { 4121 m_viewInternal->top_home(true); 4122 } 4123 4124 void KTextEditor::ViewPrivate::bottom() 4125 { 4126 m_viewInternal->bottom_end(); 4127 } 4128 4129 void KTextEditor::ViewPrivate::shiftBottom() 4130 { 4131 m_viewInternal->bottom_end(true); 4132 } 4133 4134 void KTextEditor::ViewPrivate::toMatchingBracket() 4135 { 4136 m_viewInternal->cursorToMatchingBracket(); 4137 } 4138 4139 void KTextEditor::ViewPrivate::shiftToMatchingBracket() 4140 { 4141 m_viewInternal->cursorToMatchingBracket(true); 4142 } 4143 4144 void KTextEditor::ViewPrivate::toPrevModifiedLine() 4145 { 4146 const int startLine = cursorPosition().line() - 1; 4147 const int line = doc()->findTouchedLine(startLine, false); 4148 if (line >= 0) { 4149 KTextEditor::Cursor c(line, 0); 4150 m_viewInternal->updateSelection(c, false); 4151 m_viewInternal->updateCursor(c); 4152 } 4153 } 4154 4155 void KTextEditor::ViewPrivate::toNextModifiedLine() 4156 { 4157 const int startLine = cursorPosition().line() + 1; 4158 const int line = doc()->findTouchedLine(startLine, true); 4159 if (line >= 0) { 4160 KTextEditor::Cursor c(line, 0); 4161 m_viewInternal->updateSelection(c, false); 4162 m_viewInternal->updateCursor(c); 4163 } 4164 } 4165 4166 KTextEditor::Range KTextEditor::ViewPrivate::selectionRange() const 4167 { 4168 return m_selection; 4169 } 4170 4171 KTextEditor::Document *KTextEditor::ViewPrivate::document() const 4172 { 4173 return m_doc; 4174 } 4175 4176 void KTextEditor::ViewPrivate::setContextMenu(QMenu *menu) 4177 { 4178 if (m_contextMenu) { 4179 disconnect(m_contextMenu.data(), &QMenu::aboutToShow, this, &KTextEditor::ViewPrivate::aboutToShowContextMenu); 4180 disconnect(m_contextMenu.data(), &QMenu::aboutToHide, this, &KTextEditor::ViewPrivate::aboutToHideContextMenu); 4181 } 4182 m_contextMenu = menu; 4183 m_userContextMenuSet = true; 4184 4185 if (m_contextMenu) { 4186 connect(m_contextMenu.data(), &QMenu::aboutToShow, this, &KTextEditor::ViewPrivate::aboutToShowContextMenu); 4187 connect(m_contextMenu.data(), &QMenu::aboutToHide, this, &KTextEditor::ViewPrivate::aboutToHideContextMenu); 4188 } 4189 } 4190 4191 QMenu *KTextEditor::ViewPrivate::contextMenu() const 4192 { 4193 if (m_userContextMenuSet) { 4194 return m_contextMenu; 4195 } else { 4196 KXMLGUIClient *client = const_cast<KTextEditor::ViewPrivate *>(this); 4197 while (client->parentClient()) { 4198 client = client->parentClient(); 4199 } 4200 4201 // qCDebug(LOG_KTE) << "looking up all menu containers"; 4202 if (client->factory()) { 4203 const QList<QWidget *> menuContainers = client->factory()->containers(QStringLiteral("menu")); 4204 for (QWidget *w : menuContainers) { 4205 if (w->objectName() == QLatin1String("ktexteditor_popup")) { 4206 // perhaps optimize this block 4207 QMenu *menu = (QMenu *)w; 4208 // menu is a reusable instance shared among all views. Therefore, 4209 // disconnect the current receiver(s) from the menu show/hide signals 4210 // before connecting `this` view. This ensures that only the current 4211 // view gets a signal when the menu is about to be shown or hidden, 4212 // and not also the view(s) that previously had the menu open. 4213 disconnect(menu, &QMenu::aboutToShow, nullptr, nullptr); 4214 disconnect(menu, &QMenu::aboutToHide, nullptr, nullptr); 4215 connect(menu, &QMenu::aboutToShow, this, &KTextEditor::ViewPrivate::aboutToShowContextMenu); 4216 connect(menu, &QMenu::aboutToHide, this, &KTextEditor::ViewPrivate::aboutToHideContextMenu); 4217 return menu; 4218 } 4219 } 4220 } 4221 } 4222 return nullptr; 4223 } 4224 4225 QMenu *KTextEditor::ViewPrivate::defaultContextMenu(QMenu *menu) const 4226 { 4227 if (!menu) { 4228 menu = new QMenu(const_cast<KTextEditor::ViewPrivate *>(this)); 4229 } 4230 4231 if (m_editUndo) { 4232 menu->addAction(m_editUndo); 4233 } 4234 if (m_editRedo) { 4235 menu->addAction(m_editRedo); 4236 } 4237 4238 menu->addSeparator(); 4239 menu->addAction(m_cut); 4240 menu->addAction(m_copy); 4241 menu->addAction(m_paste); 4242 if (m_pasteSelection) { 4243 menu->addAction(m_pasteSelection); 4244 } 4245 menu->addAction(m_swapWithClipboard); 4246 menu->addSeparator(); 4247 menu->addAction(m_selectAll); 4248 menu->addAction(m_deSelect); 4249 QAction *editing = actionCollection()->action(QStringLiteral("tools_scripts_Editing")); 4250 if (editing) { 4251 menu->addAction(editing); 4252 } 4253 if (QAction *spellingSuggestions = actionCollection()->action(QStringLiteral("spelling_suggestions"))) { 4254 menu->addSeparator(); 4255 menu->addAction(spellingSuggestions); 4256 } 4257 if (QAction *bookmark = actionCollection()->action(QStringLiteral("bookmarks"))) { 4258 menu->addSeparator(); 4259 menu->addAction(bookmark); 4260 } 4261 4262 return menu; 4263 } 4264 4265 void KTextEditor::ViewPrivate::aboutToShowContextMenu() 4266 { 4267 QMenu *menu = qobject_cast<QMenu *>(sender()); 4268 4269 if (menu) { 4270 Q_EMIT contextMenuAboutToShow(this, menu); 4271 } 4272 } 4273 4274 void KTextEditor::ViewPrivate::aboutToHideContextMenu() 4275 { 4276 m_spellingMenu->cleanUpAfterShown(); 4277 } 4278 4279 // BEGIN ConfigInterface stff 4280 QStringList KTextEditor::ViewPrivate::configKeys() const 4281 { 4282 static const QStringList keys = {QStringLiteral("icon-bar"), 4283 QStringLiteral("line-numbers"), 4284 QStringLiteral("dynamic-word-wrap"), 4285 QStringLiteral("background-color"), 4286 QStringLiteral("selection-color"), 4287 QStringLiteral("search-highlight-color"), 4288 QStringLiteral("replace-highlight-color"), 4289 QStringLiteral("default-mark-type"), 4290 QStringLiteral("allow-mark-menu"), 4291 QStringLiteral("folding-bar"), 4292 QStringLiteral("folding-preview"), 4293 QStringLiteral("icon-border-color"), 4294 QStringLiteral("folding-marker-color"), 4295 QStringLiteral("line-number-color"), 4296 QStringLiteral("current-line-number-color"), 4297 QStringLiteral("modification-markers"), 4298 QStringLiteral("keyword-completion"), 4299 QStringLiteral("word-count"), 4300 QStringLiteral("line-count"), 4301 QStringLiteral("scrollbar-minimap"), 4302 QStringLiteral("scrollbar-preview"), 4303 QStringLiteral("font"), 4304 QStringLiteral("theme")}; 4305 return keys; 4306 } 4307 4308 QVariant KTextEditor::ViewPrivate::configValue(const QString &key) 4309 { 4310 if (key == QLatin1String("icon-bar")) { 4311 return config()->iconBar(); 4312 } else if (key == QLatin1String("line-numbers")) { 4313 return config()->lineNumbers(); 4314 } else if (key == QLatin1String("dynamic-word-wrap")) { 4315 return config()->dynWordWrap(); 4316 } else if (key == QLatin1String("background-color")) { 4317 return renderer()->config()->backgroundColor(); 4318 } else if (key == QLatin1String("selection-color")) { 4319 return renderer()->config()->selectionColor(); 4320 } else if (key == QLatin1String("search-highlight-color")) { 4321 return renderer()->config()->searchHighlightColor(); 4322 } else if (key == QLatin1String("replace-highlight-color")) { 4323 return renderer()->config()->replaceHighlightColor(); 4324 } else if (key == QLatin1String("default-mark-type")) { 4325 return config()->defaultMarkType(); 4326 } else if (key == QLatin1String("allow-mark-menu")) { 4327 return config()->allowMarkMenu(); 4328 } else if (key == QLatin1String("folding-bar")) { 4329 return config()->foldingBar(); 4330 } else if (key == QLatin1String("folding-preview")) { 4331 return config()->foldingPreview(); 4332 } else if (key == QLatin1String("icon-border-color")) { 4333 return renderer()->config()->iconBarColor(); 4334 } else if (key == QLatin1String("folding-marker-color")) { 4335 return renderer()->config()->foldingColor(); 4336 } else if (key == QLatin1String("line-number-color")) { 4337 return renderer()->config()->lineNumberColor(); 4338 } else if (key == QLatin1String("current-line-number-color")) { 4339 return renderer()->config()->currentLineNumberColor(); 4340 } else if (key == QLatin1String("modification-markers")) { 4341 return config()->lineModification(); 4342 } else if (key == QLatin1String("keyword-completion")) { 4343 return config()->keywordCompletion(); 4344 } else if (key == QLatin1String("word-count")) { 4345 return config()->showWordCount(); 4346 } else if (key == QLatin1String("line-count")) { 4347 return config()->showLineCount(); 4348 } else if (key == QLatin1String("scrollbar-minimap")) { 4349 return config()->scrollBarMiniMap(); 4350 } else if (key == QLatin1String("scrollbar-preview")) { 4351 return config()->scrollBarPreview(); 4352 } else if (key == QLatin1String("font")) { 4353 return renderer()->config()->baseFont(); 4354 } else if (key == QLatin1String("theme")) { 4355 return renderer()->config()->schema(); 4356 } 4357 4358 // return invalid variant 4359 return QVariant(); 4360 } 4361 4362 void KTextEditor::ViewPrivate::setConfigValue(const QString &key, const QVariant &value) 4363 { 4364 // First, try the new config interface 4365 if (config()->setValue(key, value)) { 4366 return; 4367 4368 } else if (renderer()->config()->setValue(key, value)) { 4369 return; 4370 } 4371 4372 // No success? Go the old way 4373 if (value.canConvert<QColor>()) { 4374 if (key == QLatin1String("background-color")) { 4375 renderer()->config()->setBackgroundColor(value.value<QColor>()); 4376 } else if (key == QLatin1String("selection-color")) { 4377 renderer()->config()->setSelectionColor(value.value<QColor>()); 4378 } else if (key == QLatin1String("search-highlight-color")) { 4379 renderer()->config()->setSearchHighlightColor(value.value<QColor>()); 4380 } else if (key == QLatin1String("replace-highlight-color")) { 4381 renderer()->config()->setReplaceHighlightColor(value.value<QColor>()); 4382 } else if (key == QLatin1String("icon-border-color")) { 4383 renderer()->config()->setIconBarColor(value.value<QColor>()); 4384 } else if (key == QLatin1String("folding-marker-color")) { 4385 renderer()->config()->setFoldingColor(value.value<QColor>()); 4386 } else if (key == QLatin1String("line-number-color")) { 4387 renderer()->config()->setLineNumberColor(value.value<QColor>()); 4388 } else if (key == QLatin1String("current-line-number-color")) { 4389 renderer()->config()->setCurrentLineNumberColor(value.value<QColor>()); 4390 } 4391 } 4392 if (value.type() == QVariant::Bool) { 4393 // Note explicit type check above. If we used canConvert, then 4394 // values of type UInt will be trapped here. 4395 if (key == QLatin1String("dynamic-word-wrap")) { 4396 config()->setDynWordWrap(value.toBool()); 4397 } else if (key == QLatin1String("word-count")) { 4398 config()->setShowWordCount(value.toBool()); 4399 } else if (key == QLatin1String("line-count")) { 4400 config()->setShowLineCount(value.toBool()); 4401 } 4402 } else if (key == QLatin1String("font") && value.canConvert<QFont>()) { 4403 renderer()->config()->setFont(value.value<QFont>()); 4404 } else if (key == QLatin1String("theme") && value.type() == QVariant::String) { 4405 renderer()->config()->setSchema(value.toString()); 4406 } 4407 } 4408 4409 // END ConfigInterface 4410 4411 void KTextEditor::ViewPrivate::userInvokedCompletion() 4412 { 4413 completionWidget()->userInvokedCompletion(); 4414 } 4415 4416 KateViewBar *KTextEditor::ViewPrivate::bottomViewBar() const 4417 { 4418 return m_bottomViewBar; 4419 } 4420 4421 KateGotoBar *KTextEditor::ViewPrivate::gotoBar() 4422 { 4423 if (!m_gotoBar) { 4424 m_gotoBar = new KateGotoBar(this); 4425 bottomViewBar()->addBarWidget(m_gotoBar); 4426 } 4427 4428 return m_gotoBar; 4429 } 4430 4431 KateDictionaryBar *KTextEditor::ViewPrivate::dictionaryBar() 4432 { 4433 if (!m_dictionaryBar) { 4434 m_dictionaryBar = new KateDictionaryBar(this); 4435 bottomViewBar()->addBarWidget(m_dictionaryBar); 4436 } 4437 4438 return m_dictionaryBar; 4439 } 4440 4441 void KTextEditor::ViewPrivate::setAnnotationModel(KTextEditor::AnnotationModel *model) 4442 { 4443 KTextEditor::AnnotationModel *oldmodel = m_annotationModel; 4444 m_annotationModel = model; 4445 m_viewInternal->m_leftBorder->annotationModelChanged(oldmodel, m_annotationModel); 4446 } 4447 4448 KTextEditor::AnnotationModel *KTextEditor::ViewPrivate::annotationModel() const 4449 { 4450 return m_annotationModel; 4451 } 4452 4453 void KTextEditor::ViewPrivate::setAnnotationBorderVisible(bool visible) 4454 { 4455 m_viewInternal->m_leftBorder->setAnnotationBorderOn(visible); 4456 } 4457 4458 bool KTextEditor::ViewPrivate::isAnnotationBorderVisible() const 4459 { 4460 return m_viewInternal->m_leftBorder->annotationBorderOn(); 4461 } 4462 4463 KTextEditor::AbstractAnnotationItemDelegate *KTextEditor::ViewPrivate::annotationItemDelegate() const 4464 { 4465 return m_viewInternal->m_leftBorder->annotationItemDelegate(); 4466 } 4467 4468 void KTextEditor::ViewPrivate::setAnnotationItemDelegate(KTextEditor::AbstractAnnotationItemDelegate *delegate) 4469 { 4470 m_viewInternal->m_leftBorder->setAnnotationItemDelegate(delegate); 4471 } 4472 4473 bool KTextEditor::ViewPrivate::uniformAnnotationItemSizes() const 4474 { 4475 return m_viewInternal->m_leftBorder->uniformAnnotationItemSizes(); 4476 } 4477 4478 void KTextEditor::ViewPrivate::setAnnotationUniformItemSizes(bool enable) 4479 { 4480 m_viewInternal->m_leftBorder->setAnnotationUniformItemSizes(enable); 4481 } 4482 4483 KTextEditor::Range KTextEditor::ViewPrivate::visibleRange() 4484 { 4485 // ensure that the view is up-to-date, otherwise 'endPos()' might fail! 4486 if (!m_viewInternal->endPos().isValid()) { 4487 m_viewInternal->updateView(); 4488 } 4489 return KTextEditor::Range(m_viewInternal->toRealCursor(m_viewInternal->startPos()), m_viewInternal->toRealCursor(m_viewInternal->endPos())); 4490 } 4491 4492 bool KTextEditor::ViewPrivate::event(QEvent *e) 4493 { 4494 switch (e->type()) { 4495 case QEvent::StyleChange: 4496 setupLayout(); 4497 return true; 4498 default: 4499 return KTextEditor::View::event(e); 4500 } 4501 } 4502 4503 void KTextEditor::ViewPrivate::paintEvent(QPaintEvent *e) 4504 { 4505 // base class 4506 KTextEditor::View::paintEvent(e); 4507 4508 if (!config()->showFocusFrame()) { 4509 return; 4510 } 4511 4512 const QRect contentsRect = m_topSpacer->geometry() | m_bottomSpacer->geometry() | m_leftSpacer->geometry() | m_rightSpacer->geometry(); 4513 4514 if (contentsRect.isValid()) { 4515 QStyleOptionFrame opt; 4516 opt.initFrom(this); 4517 opt.frameShape = QFrame::StyledPanel; 4518 opt.state |= QStyle::State_Sunken; 4519 4520 // clear mouseOver and focus state 4521 // update from relevant widgets 4522 opt.state &= ~(QStyle::State_HasFocus | QStyle::State_MouseOver); 4523 const QList<QWidget *> widgets = QList<QWidget *>() 4524 << m_viewInternal << m_viewInternal->m_leftBorder << m_viewInternal->m_lineScroll << m_viewInternal->m_columnScroll; 4525 for (const QWidget *w : widgets) { 4526 if (w->hasFocus()) { 4527 opt.state |= QStyle::State_HasFocus; 4528 } 4529 if (w->underMouse()) { 4530 opt.state |= QStyle::State_MouseOver; 4531 } 4532 } 4533 4534 // update rect 4535 opt.rect = contentsRect; 4536 4537 // render 4538 QPainter paint(this); 4539 paint.setClipRegion(e->region()); 4540 paint.setRenderHints(QPainter::Antialiasing); 4541 style()->drawControl(QStyle::CE_ShapedFrame, &opt, &paint, this); 4542 } 4543 } 4544 4545 void KTextEditor::ViewPrivate::toggleOnTheFlySpellCheck(bool b) 4546 { 4547 doc()->onTheFlySpellCheckingEnabled(b); 4548 } 4549 4550 void KTextEditor::ViewPrivate::reflectOnTheFlySpellCheckStatus(bool enabled) 4551 { 4552 m_spellingMenu->setVisible(enabled); 4553 m_toggleOnTheFlySpellCheck->setChecked(enabled); 4554 } 4555 4556 KateSpellingMenu *KTextEditor::ViewPrivate::spellingMenu() 4557 { 4558 return m_spellingMenu; 4559 } 4560 4561 void KTextEditor::ViewPrivate::notifyAboutRangeChange(KTextEditor::LineRange lineRange, bool needsRepaint) 4562 { 4563 #ifdef VIEW_RANGE_DEBUG 4564 // output args 4565 qCDebug(LOG_KTE) << "trigger attribute changed in line range " << lineRange << "needsRepaint" << needsRepaint; 4566 #endif 4567 4568 // if we need repaint, we will need to collect the line ranges we will update 4569 if (needsRepaint && lineRange.isValid()) { 4570 if (m_lineToUpdateRange.isValid()) { 4571 m_lineToUpdateRange.expandToRange(lineRange); 4572 } else { 4573 m_lineToUpdateRange = lineRange; 4574 } 4575 } 4576 4577 // first call => trigger later update of view via delayed signal to group updates 4578 if (!m_delayedUpdateTimer.isActive()) { 4579 m_delayedUpdateTimer.start(); 4580 } 4581 } 4582 4583 void KTextEditor::ViewPrivate::slotDelayedUpdateOfView() 4584 { 4585 #ifdef VIEW_RANGE_DEBUG 4586 // output args 4587 qCDebug(LOG_KTE) << "delayed attribute changed in line range" << m_lineToUpdateRange; 4588 #endif 4589 // update ranges in 4590 updateRangesIn(KTextEditor::Attribute::ActivateMouseIn); 4591 updateRangesIn(KTextEditor::Attribute::ActivateCaretIn); 4592 4593 // update view, if valid line range, else only feedback update wanted anyway 4594 if (m_lineToUpdateRange.isValid()) { 4595 tagLines(m_lineToUpdateRange, true); 4596 updateView(true); 4597 } 4598 4599 // reset flags 4600 m_lineToUpdateRange = KTextEditor::LineRange::invalid(); 4601 } 4602 4603 void KTextEditor::ViewPrivate::updateRangesIn(KTextEditor::Attribute::ActivationType activationType) 4604 { 4605 // new ranges with cursor in, default none 4606 QSet<Kate::TextRange *> newRangesIn; 4607 4608 // on which range set we work? 4609 QSet<Kate::TextRange *> &oldSet = (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_rangesMouseIn : m_rangesCaretIn; 4610 4611 // which cursor position to honor? 4612 KTextEditor::Cursor currentCursor = 4613 (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_viewInternal->mousePosition() : m_viewInternal->cursorPosition(); 4614 4615 // first: validate the remembered ranges 4616 QSet<Kate::TextRange *> validRanges; 4617 for (Kate::TextRange *range : std::as_const(oldSet)) { 4618 if (doc()->buffer().rangePointerValid(range)) { 4619 validRanges.insert(range); 4620 } 4621 } 4622 4623 // cursor valid? else no new ranges can be found 4624 if (currentCursor.isValid() && currentCursor.line() < doc()->buffer().lines()) { 4625 // now: get current ranges for the line of cursor with an attribute 4626 const QVector<Kate::TextRange *> rangesForCurrentCursor = doc()->buffer().rangesForLine(currentCursor.line(), this, false); 4627 4628 // match which ranges really fit the given cursor 4629 for (Kate::TextRange *range : rangesForCurrentCursor) { 4630 // range has no dynamic attribute of right type and no feedback object 4631 auto attribute = range->attribute(); 4632 if ((!attribute || !attribute->dynamicAttribute(activationType)) && !range->feedback()) { 4633 continue; 4634 } 4635 4636 // range doesn't contain cursor, not interesting 4637 if ((range->startInternal().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (currentCursor < range->toRange().start()) 4638 : (currentCursor <= range->toRange().start())) { 4639 continue; 4640 } 4641 4642 if ((range->endInternal().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (range->toRange().end() <= currentCursor) 4643 : (range->toRange().end() < currentCursor)) { 4644 continue; 4645 } 4646 4647 // range contains cursor, was it already in old set? 4648 auto it = validRanges.find(range); 4649 if (it != validRanges.end()) { 4650 // insert in new, remove from old, be done with it 4651 newRangesIn.insert(range); 4652 validRanges.erase(it); 4653 continue; 4654 } 4655 4656 // oh, new range, trigger update and insert into new set 4657 newRangesIn.insert(range); 4658 4659 if (attribute && attribute->dynamicAttribute(activationType)) { 4660 notifyAboutRangeChange(range->toLineRange(), true); 4661 } 4662 4663 // feedback 4664 if (range->feedback()) { 4665 if (activationType == KTextEditor::Attribute::ActivateMouseIn) { 4666 range->feedback()->mouseEnteredRange(range, this); 4667 } else { 4668 range->feedback()->caretEnteredRange(range, this); 4669 Q_EMIT caretChangedRange(this); 4670 } 4671 } 4672 4673 #ifdef VIEW_RANGE_DEBUG 4674 // found new range for activation 4675 qCDebug(LOG_KTE) << "activated new range" << range << "by" << activationType; 4676 #endif 4677 } 4678 } 4679 4680 // now: notify for left ranges! 4681 for (Kate::TextRange *range : std::as_const(validRanges)) { 4682 // range valid + right dynamic attribute, trigger update 4683 if (range->toRange().isValid() && range->attribute() && range->attribute()->dynamicAttribute(activationType)) { 4684 notifyAboutRangeChange(range->toLineRange(), true); 4685 } 4686 4687 // feedback 4688 if (range->feedback()) { 4689 if (activationType == KTextEditor::Attribute::ActivateMouseIn) { 4690 range->feedback()->mouseExitedRange(range, this); 4691 } else { 4692 range->feedback()->caretExitedRange(range, this); 4693 Q_EMIT caretChangedRange(this); 4694 } 4695 } 4696 } 4697 4698 // set new ranges 4699 oldSet = newRangesIn; 4700 } 4701 4702 void KTextEditor::ViewPrivate::postMessage(KTextEditor::Message *message, QList<QSharedPointer<QAction>> actions) 4703 { 4704 // just forward to KateMessageWidget :-) 4705 auto messageWidget = m_messageWidgets[message->position()]; 4706 if (!messageWidget) { 4707 // this branch is used for: TopInView, CenterInView, and BottomInView 4708 messageWidget = new KateMessageWidget(m_viewInternal, true); 4709 m_messageWidgets[message->position()] = messageWidget; 4710 m_notificationLayout->addWidget(messageWidget, message->position()); 4711 connect(this, &KTextEditor::ViewPrivate::displayRangeChanged, messageWidget, &KateMessageWidget::startAutoHideTimer); 4712 connect(this, &KTextEditor::ViewPrivate::cursorPositionChanged, messageWidget, &KateMessageWidget::startAutoHideTimer); 4713 } 4714 messageWidget->postMessage(message, std::move(actions)); 4715 } 4716 4717 KateMessageWidget *KTextEditor::ViewPrivate::messageWidget() 4718 { 4719 return m_messageWidgets[KTextEditor::Message::TopInView]; 4720 } 4721 4722 void KTextEditor::ViewPrivate::saveFoldingState() 4723 { 4724 m_savedFoldingState = m_textFolding.exportFoldingRanges(); 4725 } 4726 4727 void KTextEditor::ViewPrivate::clearFoldingState() 4728 { 4729 m_savedFoldingState = {}; 4730 } 4731 4732 void KTextEditor::ViewPrivate::applyFoldingState() 4733 { 4734 m_textFolding.importFoldingRanges(m_savedFoldingState); 4735 m_savedFoldingState = QJsonDocument(); 4736 } 4737 4738 void KTextEditor::ViewPrivate::exportHtmlToFile(const QString &file) 4739 { 4740 KateExporter(this).exportToFile(file); 4741 } 4742 4743 void KTextEditor::ViewPrivate::exportHtmlToClipboard() 4744 { 4745 KateExporter(this).exportToClipboard(); 4746 } 4747 4748 void KTextEditor::ViewPrivate::exportHtmlToFile() 4749 { 4750 const QString file = QFileDialog::getSaveFileName(this, i18n("Export File as HTML"), doc()->documentName()); 4751 if (!file.isEmpty()) { 4752 KateExporter(this).exportToFile(file); 4753 } 4754 } 4755 4756 void KTextEditor::ViewPrivate::clearHighlights() 4757 { 4758 m_rangesForHighlights.clear(); 4759 m_currentTextForHighlights.clear(); 4760 } 4761 4762 void KTextEditor::ViewPrivate::selectionChangedForHighlights() 4763 { 4764 QString text; 4765 // if text of selection is still the same, abort 4766 if (selection() && selectionRange().onSingleLine()) { 4767 text = selectionText(); 4768 if (text == m_currentTextForHighlights) { 4769 return; 4770 } 4771 } 4772 4773 // text changed: remove all highlights + create new ones 4774 // (do not call clearHighlights(), since this also resets the m_currentTextForHighlights 4775 m_rangesForHighlights.clear(); 4776 4777 // do not highlight strings with leading and trailing spaces 4778 if (!text.isEmpty() && (text.at(0).isSpace() || text.at(text.length() - 1).isSpace())) { 4779 return; 4780 } 4781 4782 // trigger creation of ranges for current view range 4783 m_currentTextForHighlights = text; 4784 createHighlights(); 4785 } 4786 4787 void KTextEditor::ViewPrivate::createHighlights() 4788 { 4789 // do nothing if no text to highlight 4790 if (m_currentTextForHighlights.isEmpty()) { 4791 return; 4792 } 4793 4794 // clear existing highlighting ranges, otherwise we stack over and over the same ones eventually 4795 m_rangesForHighlights.clear(); 4796 4797 KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); 4798 attr->setBackground(Qt::yellow); 4799 4800 // set correct highlight color from Kate's color schema 4801 QColor fgColor = defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); 4802 QColor bgColor = renderer()->config()->searchHighlightColor(); 4803 attr->setForeground(fgColor); 4804 attr->setBackground(bgColor); 4805 4806 KTextEditor::Cursor start(visibleRange().start()); 4807 KTextEditor::Range searchRange; 4808 4809 // only add word boundary if we can find the text then 4810 // fixes $lala hl 4811 QString pattern = QRegularExpression::escape(m_currentTextForHighlights); 4812 if (m_currentTextForHighlights.contains(QRegularExpression(QLatin1String("\\b") + pattern, QRegularExpression::UseUnicodePropertiesOption))) { 4813 pattern.prepend(QLatin1String("\\b")); 4814 } 4815 4816 if (m_currentTextForHighlights.contains(QRegularExpression(pattern + QLatin1String("\\b"), QRegularExpression::UseUnicodePropertiesOption))) { 4817 pattern += QLatin1String("\\b"); 4818 } 4819 4820 QVector<KTextEditor::Range> matches; 4821 do { 4822 searchRange.setRange(start, visibleRange().end()); 4823 4824 matches = doc()->searchText(searchRange, pattern, KTextEditor::Regex); 4825 4826 if (matches.first().isValid()) { 4827 if (matches.first() != selectionRange()) { 4828 std::unique_ptr<KTextEditor::MovingRange> mr(doc()->newMovingRange(matches.first())); 4829 mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection 4830 mr->setAttribute(attr); 4831 mr->setView(this); 4832 mr->setAttributeOnlyForViews(true); 4833 m_rangesForHighlights.push_back(std::move(mr)); 4834 } 4835 start = matches.first().end(); 4836 } 4837 } while (matches.first().isValid()); 4838 } 4839 4840 KateAbstractInputMode *KTextEditor::ViewPrivate::currentInputMode() const 4841 { 4842 return m_viewInternal->m_currentInputMode; 4843 } 4844 4845 void KTextEditor::ViewPrivate::toggleInputMode() 4846 { 4847 if (QAction *a = qobject_cast<QAction *>(sender())) { 4848 setInputMode(static_cast<KTextEditor::View::InputMode>(a->data().toInt())); 4849 } 4850 } 4851 4852 void KTextEditor::ViewPrivate::cycleInputMode() 4853 { 4854 InputMode current = currentInputMode()->viewInputMode(); 4855 InputMode to = (current == KTextEditor::View::NormalInputMode) ? KTextEditor::View::ViInputMode : KTextEditor::View::NormalInputMode; 4856 setInputMode(to); 4857 } 4858 4859 // BEGIN KTextEditor::PrintInterface stuff 4860 bool KTextEditor::ViewPrivate::print() 4861 { 4862 return KatePrinter::print(this); 4863 } 4864 4865 void KTextEditor::ViewPrivate::printPreview() 4866 { 4867 KatePrinter::printPreview(this); 4868 } 4869 4870 // END 4871 4872 // BEGIN KTextEditor::InlineNoteInterface 4873 void KTextEditor::ViewPrivate::registerInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) 4874 { 4875 if (std::find(m_inlineNoteProviders.cbegin(), m_inlineNoteProviders.cend(), provider) == m_inlineNoteProviders.cend()) { 4876 m_inlineNoteProviders.push_back(provider); 4877 4878 connect(provider, &KTextEditor::InlineNoteProvider::inlineNotesReset, this, &KTextEditor::ViewPrivate::inlineNotesReset); 4879 connect(provider, &KTextEditor::InlineNoteProvider::inlineNotesChanged, this, &KTextEditor::ViewPrivate::inlineNotesLineChanged); 4880 4881 inlineNotesReset(); 4882 } 4883 } 4884 4885 void KTextEditor::ViewPrivate::unregisterInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) 4886 { 4887 auto it = std::find(m_inlineNoteProviders.cbegin(), m_inlineNoteProviders.cend(), provider); 4888 if (it != m_inlineNoteProviders.cend()) { 4889 m_inlineNoteProviders.erase(it); 4890 provider->disconnect(this); 4891 4892 inlineNotesReset(); 4893 } 4894 } 4895 4896 QVarLengthArray<KateInlineNoteData, 8> KTextEditor::ViewPrivate::inlineNotes(int line) const 4897 { 4898 QVarLengthArray<KateInlineNoteData, 8> allInlineNotes; 4899 for (KTextEditor::InlineNoteProvider *provider : m_inlineNoteProviders) { 4900 int index = 0; 4901 const auto columns = provider->inlineNotes(line); 4902 for (int column : columns) { 4903 const bool underMouse = Cursor(line, column) == m_viewInternal->m_activeInlineNote.m_position; 4904 KateInlineNoteData note = 4905 {provider, this, {line, column}, index, underMouse, m_viewInternal->renderer()->currentFont(), m_viewInternal->renderer()->lineHeight()}; 4906 allInlineNotes.append(note); 4907 index++; 4908 } 4909 } 4910 return allInlineNotes; 4911 } 4912 4913 QRect KTextEditor::ViewPrivate::inlineNoteRect(const KateInlineNoteData ¬e) const 4914 { 4915 return m_viewInternal->inlineNoteRect(note); 4916 } 4917 4918 void KTextEditor::ViewPrivate::inlineNotesReset() 4919 { 4920 m_viewInternal->m_activeInlineNote = {}; 4921 tagLines(KTextEditor::LineRange(0, doc()->lastLine()), true); 4922 } 4923 4924 void KTextEditor::ViewPrivate::inlineNotesLineChanged(int line) 4925 { 4926 if (line == m_viewInternal->m_activeInlineNote.m_position.line()) { 4927 m_viewInternal->m_activeInlineNote = {}; 4928 } 4929 tagLines({line, line}, true); 4930 } 4931 4932 // END KTextEditor::InlineNoteInterface 4933 4934 KTextEditor::Attribute::Ptr KTextEditor::ViewPrivate::defaultStyleAttribute(KTextEditor::DefaultStyle defaultStyle) const 4935 { 4936 KateRendererConfig *renderConfig = const_cast<KTextEditor::ViewPrivate *>(this)->renderer()->config(); 4937 4938 KTextEditor::Attribute::Ptr style = doc()->highlight()->attributes(renderConfig->schema()).at(defaultStyle); 4939 if (!style->hasProperty(QTextFormat::BackgroundBrush)) { 4940 // make sure the returned style has the default background color set 4941 style = new KTextEditor::Attribute(*style); 4942 style->setBackground(QBrush(renderConfig->backgroundColor())); 4943 } 4944 return style; 4945 } 4946 4947 QList<KTextEditor::AttributeBlock> KTextEditor::ViewPrivate::lineAttributes(int line) 4948 { 4949 QList<KTextEditor::AttributeBlock> attribs; 4950 4951 if (line < 0 || line >= doc()->lines()) { 4952 return attribs; 4953 } 4954 4955 Kate::TextLine kateLine = doc()->kateTextLine(line); 4956 if (!kateLine) { 4957 return attribs; 4958 } 4959 4960 const QVector<Kate::TextLineData::Attribute> &intAttrs = kateLine->attributesList(); 4961 for (int i = 0; i < intAttrs.size(); ++i) { 4962 if (intAttrs[i].length > 0 && intAttrs[i].attributeValue > 0) { 4963 attribs << KTextEditor::AttributeBlock(intAttrs.at(i).offset, intAttrs.at(i).length, renderer()->attribute(intAttrs.at(i).attributeValue)); 4964 } 4965 } 4966 4967 return attribs; 4968 } 4969 4970 #include "moc_kateview.cpp"