diff --git a/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditorwidget.sip.in b/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditorwidget.sip.in index 438655cb8b39..694c0c81e110 100644 --- a/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditorwidget.sip.in +++ b/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditorwidget.sip.in @@ -10,6 +10,7 @@ + class QgsCodeEditorWidget : QgsPanelWidget { %Docstring(signature="appended") @@ -50,6 +51,8 @@ feedback, otherwise an integrated message bar will be used. virtual void showEvent( QShowEvent *event ); + virtual bool eventFilter( QObject *obj, QEvent *event ); + QgsCodeEditor *editor(); %Docstring diff --git a/python/console/console_editor.py b/python/console/console_editor.py index 15af87bec511..36110378dded 100644 --- a/python/console/console_editor.py +++ b/python/console/console_editor.py @@ -93,9 +93,6 @@ def __init__(self, self.tab_widget: EditorTabWidget = tab_widget self.code_editor_widget: Optional[QgsCodeEditorWidget] = None - # recent modification time - self.lastModified = 0 - self.setMinimumHeight(120) self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) @@ -126,6 +123,12 @@ def __init__(self, self.modificationChanged.connect(self.editor_tab.modified) self.modificationAttempted.connect(self.fileReadOnly) + def set_code_editor_widget(self, widget: QgsCodeEditorWidget): + self.code_editor_widget = widget + self.code_editor_widget.loadedExternalChanges.connect( + self.loaded_external_changes + ) + def settingsEditor(self): # Set Python lexer self.initializeLexer() @@ -399,25 +402,8 @@ def syntaxCheck(self): return True - def focusInEvent(self, e): - if self.code_editor_widget.filePath(): - if not QFileInfo(self.code_editor_widget.filePath()).exists(): - msgText = QCoreApplication.translate('PythonConsole', - 'The file "{0}" has been deleted or is not accessible').format(self.code_editor_widget.filePath()) - self.showMessage(msgText, - level=Qgis.MessageLevel.Critical) - return - if self.code_editor_widget.filePath() and self.lastModified != QFileInfo(self.code_editor_widget.filePath()).lastModified(): - self.beginUndoAction() - self.selectAll() - self.removeSelectedText() - self.insert(Path(self.code_editor_widget.filePath()).read_text(encoding='utf-8')) - self.setModified(False) - self.endUndoAction() - - self.tab_widget.listObject(self.tab_widget.currentWidget()) - self.lastModified = QFileInfo(self.code_editor_widget.filePath()).lastModified() - super().focusInEvent(e) + def loaded_external_changes(self): + self.tab_widget.listObject(self.tab_widget.currentWidget()) def fileReadOnly(self): msgText = QCoreApplication.translate('PythonConsole', @@ -536,7 +522,7 @@ def __init__(self, self._editor_code_widget = QgsCodeEditorWidget( self._editor ) - self._editor.code_editor_widget = self._editor_code_widget + self._editor.set_code_editor_widget(self._editor_code_widget) self._editor_code_widget.searchBarToggled.connect( self.search_bar_toggled ) diff --git a/python/gui/auto_generated/codeeditors/qgscodeeditorwidget.sip.in b/python/gui/auto_generated/codeeditors/qgscodeeditorwidget.sip.in index 2ef672050241..694c0c81e110 100644 --- a/python/gui/auto_generated/codeeditors/qgscodeeditorwidget.sip.in +++ b/python/gui/auto_generated/codeeditors/qgscodeeditorwidget.sip.in @@ -51,6 +51,8 @@ feedback, otherwise an integrated message bar will be used. virtual void showEvent( QShowEvent *event ); + virtual bool eventFilter( QObject *obj, QEvent *event ); + QgsCodeEditor *editor(); %Docstring diff --git a/src/gui/codeeditors/qgscodeeditorwidget.cpp b/src/gui/codeeditors/qgscodeeditorwidget.cpp index 49869cc4dc66..4ca1a5f14e34 100644 --- a/src/gui/codeeditors/qgscodeeditorwidget.cpp +++ b/src/gui/codeeditors/qgscodeeditorwidget.cpp @@ -42,6 +42,9 @@ QgsCodeEditorWidget::QgsCodeEditorWidget( { Q_ASSERT( mEditor ); + mEditor->installEventFilter( this ); + installEventFilter( this ); + QVBoxLayout *vl = new QVBoxLayout(); vl->setContentsMargins( 0, 0, 0, 0 ); vl->setSpacing( 0 ); @@ -242,6 +245,51 @@ void QgsCodeEditorWidget::showEvent( QShowEvent *event ) updateHighlightController(); } +bool QgsCodeEditorWidget::eventFilter( QObject *obj, QEvent *event ) +{ + if ( event->type() == QEvent::FocusIn ) + { + if ( !mFilePath.isEmpty() ) + { + if ( !QFile::exists( mFilePath ) ) + { + // file deleted externally + if ( mMessageBar ) + { + mMessageBar->pushCritical( QString(), tr( "The file \"%1\" has been deleted or is not accessible" ).arg( QDir::toNativeSeparators( mFilePath ) ) ); + } + } + else + { + const QFileInfo fi( mFilePath ); + if ( mLastModified != fi.lastModified() ) + { + // TODO - we should give users a choice of how to react to this, eg "ignore changes" + // note -- we intentionally don't call loadFile here -- we want this action to be undo-able + QFile file( mFilePath ); + if ( file.open( QFile::ReadOnly ) ) + { + const QString content = file.readAll(); + + // don't clear, instead perform undoable actions: + mEditor->beginUndoAction(); + mEditor->selectAll(); + mEditor->removeSelectedText(); + mEditor->insert( content ); + mEditor->setModified( false ); + mEditor->recolor(); + mEditor->endUndoAction(); + + mLastModified = fi.lastModified(); + emit loadedExternalChanges(); + } + } + } + } + } + return QgsPanelWidget::eventFilter( obj, event ); +} + QgsCodeEditorWidget::~QgsCodeEditorWidget() = default; bool QgsCodeEditorWidget::isSearchBarVisible() const diff --git a/src/gui/codeeditors/qgscodeeditorwidget.h b/src/gui/codeeditors/qgscodeeditorwidget.h index 80b6f77f6741..5a61a4d569d4 100644 --- a/src/gui/codeeditors/qgscodeeditorwidget.h +++ b/src/gui/codeeditors/qgscodeeditorwidget.h @@ -66,6 +66,7 @@ class GUI_EXPORT QgsCodeEditorWidget : public QgsPanelWidget void resizeEvent( QResizeEvent *event ) override; void showEvent( QShowEvent *event ) override; + bool eventFilter( QObject *obj, QEvent *event ) override; /** * Returns the wrapped code editor.