QGIS API Documentation 3.38.0-Grenoble (exported)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
qgscodeeditorwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscodeeditorwidget.cpp
3 --------------------------------------
4 Date : May 2024
5 Copyright : (C) 2024 by Nyall Dawson
6 Email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgscodeeditorwidget.h"
17#include "qgscodeeditor.h"
18#include "qgsfilterlineedit.h"
19#include "qgsapplication.h"
20#include "qgsguiutils.h"
21#include "qgsmessagebar.h"
23#include "qgscodeeditorpython.h"
24
25#include <QVBoxLayout>
26#include <QToolButton>
27#include <QCheckBox>
28#include <QShortcut>
29#include <QGridLayout>
30#include <QDesktopServices>
31#include <QProcess>
32#include <QFileInfo>
33#include <QDir>
34
36 QgsCodeEditor *editor,
37 QgsMessageBar *messageBar,
38 QWidget *parent )
39 : QgsPanelWidget( parent )
40 , mEditor( editor )
41 , mMessageBar( messageBar )
42{
43 Q_ASSERT( mEditor );
44
45 mEditor->installEventFilter( this );
46 installEventFilter( this );
47
48 QVBoxLayout *vl = new QVBoxLayout();
49 vl->setContentsMargins( 0, 0, 0, 0 );
50 vl->setSpacing( 0 );
51 vl->addWidget( editor, 1 );
52
53 if ( !mMessageBar )
54 {
55 QGridLayout *layout = new QGridLayout( mEditor );
56 layout->setContentsMargins( 0, 0, 0, 0 );
57 layout->addItem( new QSpacerItem( 20, 40, QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Expanding ), 1, 0, 1, 1 );
58
59 mMessageBar = new QgsMessageBar();
60 QSizePolicy sizePolicy = QSizePolicy( QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Fixed );
61 mMessageBar->setSizePolicy( sizePolicy );
62 layout->addWidget( mMessageBar, 0, 0, 1, 1 );
63 }
64
65 mFindWidget = new QWidget();
66 QGridLayout *layoutFind = new QGridLayout();
67 layoutFind->setContentsMargins( 0, 2, 0, 0 );
68 layoutFind->setSpacing( 1 );
69
70 if ( !mEditor->isReadOnly() )
71 {
72 mShowReplaceBarButton = new QToolButton();
73 mShowReplaceBarButton->setToolTip( tr( "Replace" ) );
74 mShowReplaceBarButton->setCheckable( true );
75 mShowReplaceBarButton->setAutoRaise( true );
76 mShowReplaceBarButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionReplace.svg" ) ) );
77 layoutFind->addWidget( mShowReplaceBarButton, 0, 0 );
78
79 connect( mShowReplaceBarButton, &QCheckBox::toggled, this, &QgsCodeEditorWidget::setReplaceBarVisible );
80 }
81
82 mLineEditFind = new QgsFilterLineEdit();
83 mLineEditFind->setShowSearchIcon( true );
84 mLineEditFind->setPlaceholderText( tr( "Enter text to find…" ) );
85 layoutFind->addWidget( mLineEditFind, 0, mShowReplaceBarButton ? 1 : 0 );
86
87 mLineEditReplace = new QgsFilterLineEdit();
88 mLineEditReplace->setShowSearchIcon( true );
89 mLineEditReplace->setPlaceholderText( tr( "Replace…" ) );
90 layoutFind->addWidget( mLineEditReplace, 1, mShowReplaceBarButton ? 1 : 0 );
91
92 QHBoxLayout *findButtonLayout = new QHBoxLayout();
93 findButtonLayout->setContentsMargins( 0, 0, 0, 0 );
94 findButtonLayout->setSpacing( 1 );
95 mCaseSensitiveButton = new QToolButton();
96 mCaseSensitiveButton->setToolTip( tr( "Case Sensitive" ) );
97 mCaseSensitiveButton->setCheckable( true );
98 mCaseSensitiveButton->setAutoRaise( true );
99 mCaseSensitiveButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconSearchCaseSensitive.svg" ) ) );
100 findButtonLayout->addWidget( mCaseSensitiveButton );
101
102 mWholeWordButton = new QToolButton( );
103 mWholeWordButton->setToolTip( tr( "Whole Word" ) );
104 mWholeWordButton->setCheckable( true );
105 mWholeWordButton->setAutoRaise( true );
106 mWholeWordButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconSearchWholeWord.svg" ) ) );
107 findButtonLayout->addWidget( mWholeWordButton );
108
109 mRegexButton = new QToolButton( );
110 mRegexButton->setToolTip( tr( "Use Regular Expressions" ) );
111 mRegexButton->setCheckable( true );
112 mRegexButton->setAutoRaise( true );
113 mRegexButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconSearchRegex.svg" ) ) );
114 findButtonLayout->addWidget( mRegexButton );
115
116 mWrapAroundButton = new QToolButton();
117 mWrapAroundButton->setToolTip( tr( "Wrap Around" ) );
118 mWrapAroundButton->setCheckable( true );
119 mWrapAroundButton->setAutoRaise( true );
120 mWrapAroundButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconSearchWrapAround.svg" ) ) );
121 findButtonLayout->addWidget( mWrapAroundButton );
122
123 mFindPrevButton = new QToolButton();
124 mFindPrevButton->setEnabled( false );
125 mFindPrevButton->setToolTip( tr( "Find Previous" ) );
126 mFindPrevButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "console/iconSearchPrevEditorConsole.svg" ) ) );
127 mFindPrevButton->setAutoRaise( true );
128 findButtonLayout->addWidget( mFindPrevButton );
129
130 mFindNextButton = new QToolButton();
131 mFindNextButton->setEnabled( false );
132 mFindNextButton->setToolTip( tr( "Find Next" ) );
133 mFindNextButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "console/iconSearchNextEditorConsole.svg" ) ) );
134 mFindNextButton->setAutoRaise( true );
135 findButtonLayout->addWidget( mFindNextButton );
136
137 connect( mLineEditFind, &QLineEdit::returnPressed, this, &QgsCodeEditorWidget::findNext );
138 connect( mLineEditFind, &QLineEdit::textChanged, this, &QgsCodeEditorWidget::textSearchChanged );
139 connect( mFindNextButton, &QToolButton::clicked, this, &QgsCodeEditorWidget::findNext );
140 connect( mFindPrevButton, &QToolButton::clicked, this, &QgsCodeEditorWidget::findPrevious );
141 connect( mCaseSensitiveButton, &QToolButton::toggled, this, &QgsCodeEditorWidget::updateSearch );
142 connect( mWholeWordButton, &QToolButton::toggled, this, &QgsCodeEditorWidget::updateSearch );
143 connect( mRegexButton, &QToolButton::toggled, this, &QgsCodeEditorWidget::updateSearch );
144 connect( mWrapAroundButton, &QCheckBox::toggled, this, &QgsCodeEditorWidget::updateSearch );
145
146 QShortcut *findShortcut = new QShortcut( QKeySequence::StandardKey::Find, mEditor );
147 findShortcut->setContext( Qt::ShortcutContext::WidgetWithChildrenShortcut );
148 connect( findShortcut, &QShortcut::activated, this, &QgsCodeEditorWidget::triggerFind );
149
150 QShortcut *findNextShortcut = new QShortcut( QKeySequence::StandardKey::FindNext, this );
151 findNextShortcut->setContext( Qt::ShortcutContext::WidgetWithChildrenShortcut );
152 connect( findNextShortcut, &QShortcut::activated, this, &QgsCodeEditorWidget::findNext );
153
154 QShortcut *findPreviousShortcut = new QShortcut( QKeySequence::StandardKey::FindPrevious, this );
155 findPreviousShortcut->setContext( Qt::ShortcutContext::WidgetWithChildrenShortcut );
156 connect( findPreviousShortcut, &QShortcut::activated, this, &QgsCodeEditorWidget::findPrevious );
157
158 if ( !mEditor->isReadOnly() )
159 {
160 QShortcut *replaceShortcut = new QShortcut( QKeySequence::StandardKey::Replace, this );
161 replaceShortcut->setContext( Qt::ShortcutContext::WidgetWithChildrenShortcut );
162 connect( replaceShortcut, &QShortcut::activated, this, [ = ]
163 {
164 // shortcut toggles bar visibility
165 const bool show = mLineEditReplace->isHidden();
166 setReplaceBarVisible( show );
167
168 // ensure search bar is also visible
169 if ( show )
171 } );
172 }
173
174 // escape on editor hides the find bar
175 QShortcut *closeFindShortcut = new QShortcut( Qt::Key::Key_Escape, this );
176 closeFindShortcut->setContext( Qt::ShortcutContext::WidgetWithChildrenShortcut );
177 connect( closeFindShortcut, &QShortcut::activated, this, [this]
178 {
180 mEditor->setFocus();
181 } );
182
183 layoutFind->addLayout( findButtonLayout, 0, mShowReplaceBarButton ? 2 : 1 );
184
185 QHBoxLayout *replaceButtonLayout = new QHBoxLayout();
186 replaceButtonLayout->setContentsMargins( 0, 0, 0, 0 );
187 replaceButtonLayout->setSpacing( 1 );
188
189 mReplaceButton = new QToolButton();
190 mReplaceButton->setText( tr( "Replace" ) );
191 mReplaceButton->setEnabled( false );
192 connect( mReplaceButton, &QToolButton::clicked, this, &QgsCodeEditorWidget::replace );
193 replaceButtonLayout->addWidget( mReplaceButton );
194
195 mReplaceAllButton = new QToolButton();
196 mReplaceAllButton->setText( tr( "Replace All" ) );
197 mReplaceAllButton->setEnabled( false );
198 connect( mReplaceAllButton, &QToolButton::clicked, this, &QgsCodeEditorWidget::replaceAll );
199 replaceButtonLayout->addWidget( mReplaceAllButton );
200
201 layoutFind->addLayout( replaceButtonLayout, 1, mShowReplaceBarButton ? 2 : 1 );
202
203 QToolButton *closeFindButton = new QToolButton( this );
204 closeFindButton->setToolTip( tr( "Close" ) );
205 closeFindButton->setMinimumWidth( QgsGuiUtils::scaleIconSize( 44 ) );
206 closeFindButton->setStyleSheet(
207 "QToolButton { border:none; background-color: rgba(0, 0, 0, 0); }"
208 "QToolButton::menu-button { border:none; background-color: rgba(0, 0, 0, 0); }" );
209 closeFindButton->setCursor( Qt::PointingHandCursor );
210 closeFindButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconClose.svg" ) ) );
211
212 const int iconSize = std::max( 18.0, Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 0.9 );
213 closeFindButton->setIconSize( QSize( iconSize, iconSize ) );
214 closeFindButton->setFixedSize( QSize( iconSize, iconSize ) );
215 connect( closeFindButton, &QAbstractButton::clicked, this, [this]
216 {
218 mEditor->setFocus();
219 } );
220 layoutFind->addWidget( closeFindButton, 0, mShowReplaceBarButton ? 3 : 2 );
221
222 layoutFind->setColumnStretch( mShowReplaceBarButton ? 1 : 0, 1 );
223
224 mFindWidget->setLayout( layoutFind );
225 vl->addWidget( mFindWidget );
226 mFindWidget->hide();
227
228 setReplaceBarVisible( false );
229
230 setLayout( vl );
231
232 mHighlightController = std::make_unique< QgsScrollBarHighlightController >();
233 mHighlightController->setScrollArea( mEditor );
234}
235
236void QgsCodeEditorWidget::resizeEvent( QResizeEvent *event )
237{
238 QgsPanelWidget::resizeEvent( event );
239 updateHighlightController();
240}
241
242void QgsCodeEditorWidget::showEvent( QShowEvent *event )
243{
244 QgsPanelWidget::showEvent( event );
245 updateHighlightController();
246}
247
248bool QgsCodeEditorWidget::eventFilter( QObject *obj, QEvent *event )
249{
250 if ( event->type() == QEvent::FocusIn )
251 {
252 if ( !mFilePath.isEmpty() )
253 {
254 if ( !QFile::exists( mFilePath ) )
255 {
256 // file deleted externally
257 if ( mMessageBar )
258 {
259 mMessageBar->pushCritical( QString(), tr( "The file <b>\"%1\"</b> has been deleted or is not accessible" ).arg( QDir::toNativeSeparators( mFilePath ) ) );
260 }
261 }
262 else
263 {
264 const QFileInfo fi( mFilePath );
265 if ( mLastModified != fi.lastModified() )
266 {
267 // TODO - we should give users a choice of how to react to this, eg "ignore changes"
268 // note -- we intentionally don't call loadFile here -- we want this action to be undo-able
269 QFile file( mFilePath );
270 if ( file.open( QFile::ReadOnly ) )
271 {
272 const QString content = file.readAll();
273
274 // don't clear, instead perform undoable actions:
275 mEditor->beginUndoAction();
276 mEditor->selectAll();
277 mEditor->removeSelectedText();
278 mEditor->insert( content );
279 mEditor->setModified( false );
280 mEditor->recolor();
281 mEditor->endUndoAction();
282
283 mLastModified = fi.lastModified();
285 }
286 }
287 }
288 }
289 }
290 return QgsPanelWidget::eventFilter( obj, event );
291}
292
294
296{
297 return !mFindWidget->isHidden();
298}
299
301{
302 return mMessageBar;
303}
304
309
310void QgsCodeEditorWidget::addWarning( int lineNumber, const QString &warning )
311{
312 mEditor->addWarning( lineNumber, warning );
313
314 mHighlightController->addHighlight(
316 HighlightCategory::Warning,
317 lineNumber,
318 QColor( 255, 0, 0 ),
320 )
321 );
322}
323
325{
326 mEditor->clearWarnings();
327
328 mHighlightController->removeHighlights(
329 HighlightCategory::Warning
330 );
331}
332
334{
335 addSearchHighlights();
336 mFindWidget->show();
337
338 if ( mEditor->isReadOnly() )
339 {
340 setReplaceBarVisible( false );
341 }
342
343 emit searchBarToggled( true );
344}
345
347{
348 clearSearchHighlights();
349 mFindWidget->hide();
350 emit searchBarToggled( false );
351}
352
354{
355 if ( visible )
357 else
359}
360
362{
363 if ( visible )
364 {
365 mReplaceAllButton->show();
366 mReplaceButton->show();
367 mLineEditReplace->show();
368 }
369 else
370 {
371 mReplaceAllButton->hide();
372 mReplaceButton->hide();
373 mLineEditReplace->hide();
374 }
375 if ( mShowReplaceBarButton )
376 mShowReplaceBarButton->setChecked( visible );
377}
378
380{
381 clearSearchHighlights();
382 mLineEditFind->setFocus();
383 if ( mEditor->hasSelectedText() )
384 {
385 mBlockSearching++;
386 mLineEditFind->setText( mEditor->selectedText().trimmed() );
387 mBlockSearching--;
388 }
389 mLineEditFind->selectAll();
391}
392
393bool QgsCodeEditorWidget::loadFile( const QString &path )
394{
395 if ( !QFile::exists( path ) )
396 return false;
397
398 QFile file( path );
399 if ( file.open( QFile::ReadOnly ) )
400 {
401 const QString content = file.readAll();
402 mEditor->setText( content );
403 mEditor->setModified( false );
404 mEditor->recolor();
405 mLastModified = QFileInfo( path ).lastModified();
406 setFilePath( path );
407 return true;
408 }
409 return false;
410}
411
412void QgsCodeEditorWidget::setFilePath( const QString &path )
413{
414 if ( mFilePath == path )
415 return;
416
417 mFilePath = path;
418 emit filePathChanged( mFilePath );
419}
420
422{
423 if ( mFilePath.isEmpty() )
424 return false;
425
426 const QDir dir = QFileInfo( mFilePath ).dir();
427
428 bool useFallback = true;
429
430 QString externalEditorCommand;
431 switch ( mEditor->language() )
432 {
434 externalEditorCommand = QgsCodeEditorPython::settingExternalPythonEditorCommand->value();
435 break;
436
447 break;
448 }
449
450 int currentLine, currentColumn;
451 mEditor->getCursorPosition( &currentLine, &currentColumn );
452 if ( line < 0 )
453 line = currentLine;
454 if ( column < 0 )
455 column = currentColumn;
456
457 if ( !externalEditorCommand.isEmpty() )
458 {
459 externalEditorCommand = externalEditorCommand.replace( QStringLiteral( "<file>" ), mFilePath );
460 externalEditorCommand = externalEditorCommand.replace( QStringLiteral( "<line>" ), QString::number( line + 1 ) );
461 externalEditorCommand = externalEditorCommand.replace( QStringLiteral( "<col>" ), QString::number( column + 1 ) );
462
463 const QStringList commandParts = QProcess::splitCommand( externalEditorCommand );
464 if ( QProcess::startDetached( commandParts.at( 0 ), commandParts.mid( 1 ), dir.absolutePath() ) )
465 {
466 return true;
467 }
468 }
469
470 const QString editorCommand = qgetenv( "EDITOR" );
471 if ( !editorCommand.isEmpty() )
472 {
473 const QFileInfo fi( editorCommand );
474 if ( fi.exists( ) )
475 {
476 const QString command = fi.fileName();
477 const bool isTerminalEditor = command.compare( QLatin1String( "nano" ), Qt::CaseInsensitive ) == 0
478 || command.contains( QLatin1String( "vim" ), Qt::CaseInsensitive );
479
480 if ( !isTerminalEditor && QProcess::startDetached( editorCommand, {mFilePath}, dir.absolutePath() ) )
481 {
482 useFallback = false;
483 }
484 }
485 }
486
487 if ( useFallback )
488 {
489 QDesktopServices::openUrl( QUrl::fromLocalFile( mFilePath ) );
490 }
491 return true;
492}
493
494bool QgsCodeEditorWidget::findNext()
495{
496 return findText( true, false );
497}
498
499void QgsCodeEditorWidget::findPrevious()
500{
501 findText( false, false );
502}
503
504void QgsCodeEditorWidget::textSearchChanged( const QString &text )
505{
506 if ( !text.isEmpty() )
507 {
508 updateSearch();
509 }
510 else
511 {
512 clearSearchHighlights();
513 mLineEditFind->setStyleSheet( QString() );
514 }
515}
516
517void QgsCodeEditorWidget::updateSearch()
518{
519 if ( mBlockSearching )
520 return;
521
522 clearSearchHighlights();
523 addSearchHighlights();
524
525 findText( true, true );
526}
527
528void QgsCodeEditorWidget::replace()
529{
530 if ( mEditor->isReadOnly() )
531 return;
532
533 replaceSelection();
534
535 clearSearchHighlights();
536 addSearchHighlights();
537 findNext();
538}
539
540void QgsCodeEditorWidget::replaceSelection()
541{
542 const long selectionStart = mEditor->SendScintilla( QsciScintilla::SCI_GETSELECTIONSTART );
543 const long selectionEnd = mEditor->SendScintilla( QsciScintilla::SCI_GETSELECTIONEND );
544 if ( selectionEnd - selectionStart <= 0 )
545 return;
546
547 const QString replacement = mLineEditReplace->text();
548
549 mEditor->SendScintilla( QsciScintilla::SCI_SETTARGETRANGE, selectionStart, selectionEnd );
550
551 if ( mRegexButton->isChecked() )
552 mEditor->SendScintilla( QsciScintilla::SCI_REPLACETARGETRE, replacement.size(), replacement.toLocal8Bit().constData() );
553 else
554 mEditor->SendScintilla( QsciScintilla::SCI_REPLACETARGET, replacement.size(), replacement.toLocal8Bit().constData() );
555
556 // set the cursor to the end of the replaced text
557 const long postReplacementEnd = mEditor->SendScintilla( QsciScintilla::SCI_GETTARGETEND );
558 mEditor->SendScintilla( QsciScintilla::SCI_SETCURRENTPOS, postReplacementEnd );
559}
560
561void QgsCodeEditorWidget::replaceAll()
562{
563 if ( mEditor->isReadOnly() )
564 return;
565
566 if ( !findText( true, true ) )
567 {
568 return;
569 }
570
571 mEditor->SendScintilla( QsciScintilla::SCI_BEGINUNDOACTION );
572 replaceSelection();
573
574 while ( findText( true, false ) )
575 {
576 replaceSelection();
577 }
578
579 mEditor->SendScintilla( QsciScintilla::SCI_ENDUNDOACTION );
580 clearSearchHighlights();
581}
582
583void QgsCodeEditorWidget::addSearchHighlights()
584{
585 const QString searchString = mLineEditFind->text();
586 if ( searchString.isEmpty() )
587 return;
588
589 const long originalStartPos = mEditor->SendScintilla( QsciScintilla::SCI_GETTARGETSTART );
590 const long originalEndPos = mEditor->SendScintilla( QsciScintilla::SCI_GETTARGETEND );
591 long startPos = 0;
592 long docEnd = mEditor->length();
593
594 updateHighlightController();
595
596 int searchFlags = 0;
597 const bool isRegEx = mRegexButton->isChecked();
598 const bool isCaseSensitive = mCaseSensitiveButton->isChecked();
599 const bool isWholeWordOnly = mWholeWordButton->isChecked();
600 if ( isRegEx )
601 searchFlags |= QsciScintilla::SCFIND_REGEXP | QsciScintilla::SCFIND_CXX11REGEX;
602 if ( isCaseSensitive )
603 searchFlags |= QsciScintilla::SCFIND_MATCHCASE;
604 if ( isWholeWordOnly )
605 searchFlags |= QsciScintilla::SCFIND_WHOLEWORD;
606 mEditor->SendScintilla( QsciScintilla::SCI_SETSEARCHFLAGS, searchFlags );
607 int matchCount = 0;
608 while ( true )
609 {
610 mEditor->SendScintilla( QsciScintilla::SCI_SETTARGETRANGE, startPos, docEnd );
611 const int fstart = mEditor->SendScintilla( QsciScintilla::SCI_SEARCHINTARGET, searchString.length(), searchString.toLocal8Bit().constData() );
612 if ( fstart < 0 )
613 break;
614
615 matchCount++;
616 const int matchLength = mEditor->SendScintilla( QsciScintilla::SCI_GETTARGETTEXT, 0, static_cast< void * >( nullptr ) );
617
618 startPos = fstart + matchLength;
619
620 mEditor->SendScintilla( QsciScintilla::SCI_SETINDICATORCURRENT, QgsCodeEditor::SEARCH_RESULT_INDICATOR );
621 mEditor->SendScintilla( QsciScintilla::SCI_INDICATORFILLRANGE, fstart, matchLength );
622
623 int thisLine = 0;
624 int thisIndex = 0;
625 mEditor->lineIndexFromPosition( fstart, &thisLine, &thisIndex );
626 mHighlightController->addHighlight( QgsScrollBarHighlight( SearchMatch, thisLine, QColor( 0, 200, 0 ), QgsScrollBarHighlight::Priority::HighPriority ) );
627 }
628
629 mEditor->SendScintilla( QsciScintilla::SCI_SETTARGETRANGE, originalStartPos, originalEndPos );
630
631 searchMatchCountChanged( matchCount );
632}
633
634void QgsCodeEditorWidget::clearSearchHighlights()
635{
636 long docStart = 0;
637 long docEnd = mEditor->length();
638 mEditor->SendScintilla( QsciScintilla::SCI_SETINDICATORCURRENT, QgsCodeEditor::SEARCH_RESULT_INDICATOR );
639 mEditor->SendScintilla( QsciScintilla::SCI_INDICATORCLEARRANGE, docStart, docEnd - docStart );
640
641 mHighlightController->removeHighlights( SearchMatch );
642
643 searchMatchCountChanged( 0 );
644}
645
646bool QgsCodeEditorWidget::findText( bool forward, bool findFirst )
647{
648 const QString searchString = mLineEditFind->text();
649 if ( searchString.isEmpty() )
650 return false;
651
652 int lineFrom = 0;
653 int indexFrom = 0;
654 int lineTo = 0;
655 int indexTo = 0;
656 mEditor->getSelection( &lineFrom, &indexFrom, &lineTo, &indexTo );
657
658 int line = 0;
659 int index = 0;
660 if ( !findFirst )
661 {
662 mEditor->getCursorPosition( &line, &index );
663 }
664 if ( !forward )
665 {
666 line = lineFrom;
667 index = indexFrom;
668 }
669
670 const bool isRegEx = mRegexButton->isChecked();
671 const bool wrapAround = mWrapAroundButton->isChecked();
672 const bool isCaseSensitive = mCaseSensitiveButton->isChecked();
673 const bool isWholeWordOnly = mWholeWordButton->isChecked();
674
675 const bool found = mEditor->findFirst( searchString, isRegEx, isCaseSensitive, isWholeWordOnly, wrapAround, forward,
676 line, index, true, true, isRegEx );
677
678 if ( !found )
679 {
680 const QString styleError = QStringLiteral( "QLineEdit {background-color: #d65253; color: #ffffff;}" );
681 mLineEditFind->setStyleSheet( styleError );
682 }
683 else
684 {
685 mLineEditFind->setStyleSheet( QString() );
686 }
687 return found;
688}
689
690void QgsCodeEditorWidget::searchMatchCountChanged( int matchCount )
691{
692 mReplaceButton->setEnabled( matchCount > 0 );
693 mReplaceAllButton->setEnabled( matchCount > 0 );
694 mFindNextButton->setEnabled( matchCount > 0 );
695 mFindPrevButton->setEnabled( matchCount > 0 );
696}
697
698void QgsCodeEditorWidget::updateHighlightController()
699{
700 mHighlightController->setLineHeight( QFontMetrics( mEditor->font() ).lineSpacing() );
701 mHighlightController->setVisibleRange( mEditor->viewport()->rect().height() );
702}
703
@ QgisExpression
QGIS expressions.
@ Batch
Windows batch files.
@ JavaScript
JavaScript.
@ Bash
Bash scripts.
@ Unknown
Unknown/other language.
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:5162
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
void setFilePath(const QString &path)
Sets the widget's associated file path.
QgsScrollBarHighlightController * scrollbarHighlightController()
Returns the scrollbar highlight controller, which can be used to add highlights in the code editor sc...
bool isSearchBarVisible() const
Returns true if the search bar is visible.
void showEvent(QShowEvent *event) override
void addWarning(int lineNumber, const QString &warning)
Adds a warning message and indicator to the specified a lineNumber.
void setReplaceBarVisible(bool visible)
Sets whether the replace bar is visible.
void loadedExternalChanges()
Emitted when the widget loads in text from the associated file to bring in changes made externally to...
QgsMessageBar * messageBar()
Returns the message bar associated with the widget, to use for user feedback.
void triggerFind()
Triggers a find operation, using the default behavior.
bool openInExternalEditor(int line=-1, int column=-1)
Attempts to opens the script from the editor in an external text editor.
void hideSearchBar()
Hides the search bar.
void showSearchBar()
Shows the search bar.
void searchBarToggled(bool visible)
Emitted when the visibility of the search bar is changed.
void setSearchBarVisible(bool visible)
Sets whether the search bar is visible.
void filePathChanged(const QString &path)
Emitted when the widget's associated file path is changed.
QgsCodeEditor * editor()
Returns the wrapped code editor.
void clearWarnings()
Clears all warning messages from the editor.
QgsCodeEditorWidget(QgsCodeEditor *editor, QgsMessageBar *messageBar=nullptr, QWidget *parent=nullptr)
Constructor for QgsCodeEditorWidget, wrapping the specified editor widget.
bool eventFilter(QObject *obj, QEvent *event) override
bool loadFile(const QString &path)
Loads the file at the specified path into the widget, replacing the code editor's content with that f...
void resizeEvent(QResizeEvent *event) override
~QgsCodeEditorWidget() override
A text editor based on QScintilla2.
static constexpr int SEARCH_RESULT_INDICATOR
Indicator index for search results.
void clearWarnings()
Clears all warning messages from the editor.
virtual Qgis::ScriptLanguage language() const
Returns the associated scripting language.
void addWarning(int lineNumber, const QString &warning)
Adds a warning message and indicator to the specified a lineNumber.
QLineEdit subclass with built in support for clearing the widget's value and handling custom null val...
void setShowSearchIcon(bool visible)
Define if a search icon shall be shown on the left of the image when no text is entered.
A bar for displaying non-blocking messages to the user.
void pushCritical(const QString &title, const QString &message)
Pushes a critical warning message that must be manually dismissed by the user.
Base class for any widget that can be shown as a inline panel.
Adds highlights (colored markers) to a scrollbar.
Encapsulates the details of a highlight in a scrollbar, used alongside QgsScrollBarHighlightControlle...
@ HighestPriority
Highest priority, rendered above all other highlights.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...