mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-08-23 23:16:31 +00:00
Add a project explorer to the python scripting window
* This displays the current UI extension files, some built-in examples, and recently opened loose files
This commit is contained in:
@@ -394,6 +394,22 @@ DECLARE_REFLECTION_STRUCT(BugReport);
|
||||
"type: List[str]"); \
|
||||
CONFIG_SETTING(public, QVariantList, rdcarray<rdcstr>, Python_StubDirs) \
|
||||
\
|
||||
DOCUMENT( \
|
||||
"``True`` if there should be a prompt when reloading script file changes from disk, even " \
|
||||
"if the script has not been changed in the editor. Otherwise changes are reloaded " \
|
||||
"silently.\n" \
|
||||
"\n" \
|
||||
"Defaults to ``False``." \
|
||||
"" \
|
||||
":type: bool"); \
|
||||
CONFIG_SETTING_VAL(public, bool, bool, Python_PromptReloadUnchanged, false) \
|
||||
\
|
||||
DOCUMENT( \
|
||||
"List of files recently opened in the python scripting window.\n" \
|
||||
"\n:" \
|
||||
"type: List[str]"); \
|
||||
CONFIG_SETTING(public, QVariantList, rdcarray<rdcstr>, Python_RecentFiles) \
|
||||
\
|
||||
DOCUMENT( \
|
||||
"``True`` if when loading a capture that was originally captured on a remote device but " \
|
||||
"uses an API that can be supported locally, should be loaded locally without prompting to " \
|
||||
|
||||
@@ -309,6 +309,7 @@ SettingsDialog::SettingsDialog(ICaptureContext &ctx, QWidget *parent)
|
||||
|
||||
ui->Python_DebugEnabled->setChecked(m_Ctx.Config().Python_DebugEnabled);
|
||||
ui->Python_LaunchVSCode->setChecked(m_Ctx.Config().Python_LaunchVSCode);
|
||||
ui->Python_PromptReloadUnchanged->setChecked(m_Ctx.Config().Python_PromptReloadUnchanged);
|
||||
|
||||
ui->Python_DebugPyDir->setText(m_Ctx.Config().Python_DebugPyDir);
|
||||
|
||||
@@ -886,6 +887,13 @@ void SettingsDialog::on_Python_LaunchVSCode_toggled(bool checked)
|
||||
m_Ctx.Config().Save();
|
||||
}
|
||||
|
||||
void SettingsDialog::on_Python_PromptReloadUnchanged_toggled(bool checked)
|
||||
{
|
||||
m_Ctx.Config().Python_PromptReloadUnchanged = ui->Python_PromptReloadUnchanged->isChecked();
|
||||
|
||||
m_Ctx.Config().Save();
|
||||
}
|
||||
|
||||
// texture viewer
|
||||
void SettingsDialog::on_TextureViewer_PerTexSettings_toggled(bool checked)
|
||||
{
|
||||
|
||||
@@ -87,6 +87,7 @@ private slots:
|
||||
void on_Python_DebugPyDir_textEdited(const QString &dir);
|
||||
void on_Python_DebugEnabled_toggled(bool checked);
|
||||
void on_Python_LaunchVSCode_toggled(bool checked);
|
||||
void on_Python_PromptReloadUnchanged_toggled(bool checked);
|
||||
|
||||
// texture viewer
|
||||
void on_TextureViewer_PerTexSettings_toggled(bool checked);
|
||||
|
||||
@@ -770,7 +770,25 @@ If disabled, debugging will do nothing and wait for a debugger connection.</stri
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="Python_PromptReloadUnchangedLabel">
|
||||
<property name="text">
|
||||
<string>Prompt for reload unmodified scripts with changes from disk</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QCheckBox" name="Python_PromptReloadUnchanged">
|
||||
<property name="toolTip">
|
||||
<string>If a script open in the python editor has changes on disk but has not been modified in the UI,
|
||||
prompt when reloading from disk.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<spacer name="verticalSpacer_9">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "PythonShell.h"
|
||||
#include <QAbstractItemView>
|
||||
#include <QCompleter>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QFontDatabase>
|
||||
#include <QKeyEvent>
|
||||
#include <QMenu>
|
||||
@@ -32,6 +33,7 @@
|
||||
#include <QStringListModel>
|
||||
#include <QTimer>
|
||||
#include "Code/QRDUtils.h"
|
||||
#include "Code/Resources.h"
|
||||
#include "Code/ScintillaSyntax.h"
|
||||
#include "Code/pyrenderdoc/PythonContext.h"
|
||||
#include "Widgets/Extended/RDToolTip.h"
|
||||
@@ -1123,18 +1125,78 @@ PythonShell::PythonShell(ICaptureContext &ctx, QWidget *parent)
|
||||
on_clear_clicked();
|
||||
on_newScript_clicked();
|
||||
|
||||
ScintillaEdit *editor = m_Scintillas[0];
|
||||
|
||||
ui->saveScript->setEnabled(false);
|
||||
|
||||
ToolWindowManagerArea *editorTabs = ui->docking->areaOf(editor);
|
||||
QObject::connect(editorTabs, &QTabWidget::currentChanged, this, &PythonShell::editorTab_Changed);
|
||||
setupTabs();
|
||||
|
||||
ui->docking->addToolWindow(ui->replGroup, ToolWindowManager::AreaReference(
|
||||
ToolWindowManager::BottomOf, editorTabs, 0.3f));
|
||||
ui->docking->addToolWindow(
|
||||
ui->replGroup, ToolWindowManager::AreaReference(ToolWindowManager::BottomOf,
|
||||
ui->docking->areaOf(m_Scintillas[0]), 0.3f));
|
||||
ui->docking->setToolWindowProperties(
|
||||
ui->replGroup, ToolWindowManager::HideCloseButton | ToolWindowManager::DisallowFloatWindow);
|
||||
|
||||
ui->projectExplorer->setWindowTitle(tr("Project Explorer"));
|
||||
ui->projectExplorer->setColumns({tr("Name")});
|
||||
ui->projectExplorer->hideGridLines();
|
||||
|
||||
m_Watcher = new QFileSystemWatcher({}, this);
|
||||
|
||||
QObject::connect(m_Watcher, &QFileSystemWatcher::fileChanged, this, &PythonShell::openFileModified);
|
||||
QObject::connect(m_Watcher, &QFileSystemWatcher::directoryChanged, this,
|
||||
&PythonShell::updateExtensionProjects);
|
||||
|
||||
ui->projectExplorer->beginUpdate();
|
||||
|
||||
m_Examples = new RDTreeWidgetItem({lit("Examples")});
|
||||
m_Examples->setSelectable(false);
|
||||
m_Examples->setBold(true);
|
||||
m_Examples->setIcon(0, Icons::help());
|
||||
|
||||
const QPair<QString, QString> examples[] = {
|
||||
{tr("Show a Buffer"), lit("Example will go here")},
|
||||
};
|
||||
|
||||
for(const QPair<QString, QString> &example : examples)
|
||||
{
|
||||
RDTreeWidgetItem *ex = new RDTreeWidgetItem({example.first});
|
||||
|
||||
QFile file(example.second);
|
||||
|
||||
file.open(QFile::ReadOnly);
|
||||
ex->setData(0, Qt::UserRole, QString::fromUtf8(file.readAll()));
|
||||
file.close();
|
||||
|
||||
m_Examples->addChild(ex);
|
||||
}
|
||||
|
||||
m_UIExtensions = new RDTreeWidgetItem({lit("UI Extensions")});
|
||||
m_UIExtensions->setSelectable(false);
|
||||
m_UIExtensions->setBold(true);
|
||||
m_UIExtensions->setIcon(0, Icons::plugin());
|
||||
|
||||
m_RecentFiles = new RDTreeWidgetItem({lit("Recent files")});
|
||||
m_RecentFiles->setSelectable(false);
|
||||
m_RecentFiles->setBold(true);
|
||||
m_RecentFiles->setIcon(0, Icons::page_white_edit());
|
||||
|
||||
ui->projectExplorer->addTopLevelItem(m_Examples);
|
||||
ui->projectExplorer->addTopLevelItem(m_UIExtensions);
|
||||
ui->projectExplorer->addTopLevelItem(m_RecentFiles);
|
||||
|
||||
updateRecentFiles(false);
|
||||
updateExtensionProjects();
|
||||
|
||||
ui->projectExplorer->endUpdate();
|
||||
|
||||
ui->projectExplorer->expandItem(m_RecentFiles);
|
||||
|
||||
ui->docking->addToolWindow(ui->projectExplorer, ToolWindowManager::AreaReference(
|
||||
ToolWindowManager::LeftOf,
|
||||
ui->docking->areaOf(m_Scintillas[0]), 0.2f));
|
||||
ui->docking->setToolWindowProperties(
|
||||
ui->projectExplorer,
|
||||
ToolWindowManager::HideCloseButton | ToolWindowManager::DisallowFloatWindow);
|
||||
|
||||
ui->docking->addToolWindow(ui->outputGroup,
|
||||
ToolWindowManager::AreaReference(ToolWindowManager::AddTo,
|
||||
ui->docking->areaOf(ui->replGroup)));
|
||||
@@ -1219,6 +1281,9 @@ void PythonShell::doSyntaxCheck()
|
||||
if(!editor)
|
||||
return;
|
||||
|
||||
if(editor->lexer() != SCLEX_PYTHON)
|
||||
return;
|
||||
|
||||
// don't syntax check while the user still seems to be editing, e.g. with autocomplete or a
|
||||
// function tooltip active. The syntax check timer will be restarted when these go away
|
||||
if(editor->autoCActive() || m_FuncTip)
|
||||
@@ -1249,6 +1314,151 @@ void PythonShell::editorTab_Changed(int index)
|
||||
ui->saveScript->setEnabled(getEditorFilename(editor) != QString());
|
||||
}
|
||||
|
||||
void PythonShell::openFileModified(const QString &path)
|
||||
{
|
||||
for(ScintillaEdit *edit : m_Scintillas)
|
||||
{
|
||||
if(getEditorFilename(edit) == path)
|
||||
{
|
||||
// delay slightly to avoid reading while the file is being written or if it was deleted before
|
||||
// being written as some editors do
|
||||
QTimer::singleShot(150, [this, edit, path]() {
|
||||
bool mod = isEditorModified(edit);
|
||||
|
||||
// re-add the path, it may have been removed if the file was deleted
|
||||
m_Watcher->addPath(path);
|
||||
|
||||
if(!mod && !m_Ctx.Config().Python_PromptReloadUnchanged)
|
||||
{
|
||||
// unconditionally reload
|
||||
}
|
||||
else
|
||||
{
|
||||
QString prompt = tr("Reload from disk and overwrite your changes?");
|
||||
if(!mod)
|
||||
prompt = tr("Reload from disk?");
|
||||
|
||||
QMessageBox::StandardButton response = RDDialog::question(
|
||||
this, tr("%1 has been modified on disk").arg(QFileInfo(path).fileName()),
|
||||
tr("%1 has been modified on disk. %2").arg(QFileInfo(path).fileName()).arg(prompt));
|
||||
|
||||
if(response == QMessageBox::No)
|
||||
return;
|
||||
}
|
||||
|
||||
QFile f(path);
|
||||
if(f.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
{
|
||||
edit->setText(f.readAll().data());
|
||||
markEditorModified(edit, false);
|
||||
return;
|
||||
}
|
||||
|
||||
RDDialog::critical(this, tr("Error reloading script"),
|
||||
tr("Couldn't open %1.\n%2").arg(path).arg(f.errorString()));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PythonShell::editorTab_Menu(const QPoint &pos)
|
||||
{
|
||||
ToolWindowManagerArea *editorTabs = ui->docking->areaOf(m_Scintillas[0]);
|
||||
|
||||
int tabIndex = editorTabs->tabBar()->tabAt(pos);
|
||||
|
||||
if(tabIndex == -1)
|
||||
return;
|
||||
|
||||
if(editorTabs->count() == 1)
|
||||
return;
|
||||
|
||||
QAction closeTab(tr("Close tab"), this);
|
||||
QAction closeOtherTabs(tr("Close other tabs"), this);
|
||||
QAction closeRightTabs(tr("Close tabs to the right"), this);
|
||||
|
||||
QMenu contextMenu(this);
|
||||
|
||||
contextMenu.addAction(&closeTab);
|
||||
contextMenu.addAction(&closeOtherTabs);
|
||||
contextMenu.addAction(&closeRightTabs);
|
||||
|
||||
QObject::connect(&closeTab, &QAction::triggered, [this, editorTabs, tabIndex]() {
|
||||
// remove the tab at this index
|
||||
delete editorTabs->widget(tabIndex);
|
||||
});
|
||||
|
||||
QObject::connect(&closeRightTabs, &QAction::triggered, [this, editorTabs, tabIndex]() {
|
||||
for(int i = editorTabs->count() - 1; i > tabIndex; i--)
|
||||
delete editorTabs->widget(i);
|
||||
});
|
||||
|
||||
QObject::connect(&closeOtherTabs, &QAction::triggered, [this, editorTabs, tabIndex]() {
|
||||
for(int i = editorTabs->count() - 1; i >= 0; i--)
|
||||
if(i != tabIndex)
|
||||
delete editorTabs->widget(i);
|
||||
});
|
||||
|
||||
RDDialog::show(&contextMenu, QCursor::pos());
|
||||
}
|
||||
|
||||
void PythonShell::updateExtensionProjects()
|
||||
{
|
||||
RDTreeViewExpansionState expansion;
|
||||
ui->projectExplorer->saveExpansion(expansion, 0);
|
||||
|
||||
ui->projectExplorer->beginUpdate();
|
||||
|
||||
m_UIExtensions->clear();
|
||||
|
||||
for(const ExtensionMetadata &ext : m_Ctx.Extensions().GetInstalledExtensions())
|
||||
{
|
||||
RDTreeWidgetItem *root = new RDTreeWidgetItem({ext.name});
|
||||
|
||||
addExtensionDirItems(root, QDir(ext.filePath));
|
||||
|
||||
m_UIExtensions->addChild(root);
|
||||
}
|
||||
|
||||
ui->projectExplorer->endUpdate();
|
||||
|
||||
ui->projectExplorer->applyExpansion(expansion, 0);
|
||||
}
|
||||
|
||||
void PythonShell::addExtensionDirItems(RDTreeWidgetItem *root, QDir dir)
|
||||
{
|
||||
m_Watcher->addPath(dir.absolutePath());
|
||||
for(QString child : dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot))
|
||||
{
|
||||
QString path = dir.absoluteFilePath(child);
|
||||
QFileInfo fileInfo(path);
|
||||
|
||||
// only show .py files, .md files (for README.md) and the extension.json
|
||||
if(fileInfo.isFile() && fileInfo.suffix().toLower() != lit("py") &&
|
||||
fileInfo.suffix().toLower() != lit("md") && child.toLower() != lit("extension.json"))
|
||||
continue;
|
||||
|
||||
RDTreeWidgetItem *item = new RDTreeWidgetItem({child});
|
||||
if(fileInfo.isDir())
|
||||
{
|
||||
// ignore some directories
|
||||
if(child.toLower() == lit("__pycache__") || child.toLower() == lit(".git") ||
|
||||
child.toLower() == lit(".vscode"))
|
||||
continue;
|
||||
|
||||
item->setIcon(0, Icons::folder());
|
||||
|
||||
addExtensionDirItems(item, QDir(path));
|
||||
}
|
||||
else
|
||||
{
|
||||
item->setData(0, Qt::UserRole, path);
|
||||
}
|
||||
|
||||
root->addChild(item);
|
||||
}
|
||||
}
|
||||
|
||||
ScintillaEdit *PythonShell::curEditor()
|
||||
{
|
||||
for(ScintillaEdit *edit : m_Scintillas)
|
||||
@@ -1260,7 +1470,7 @@ ScintillaEdit *PythonShell::curEditor()
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ScintillaEdit *PythonShell::makeEditor()
|
||||
ScintillaEdit *PythonShell::makeEditor(rdcstr filename)
|
||||
{
|
||||
ScintillaEdit *editor = new EditorWrapper(this);
|
||||
|
||||
@@ -1288,7 +1498,24 @@ ScintillaEdit *PythonShell::makeEditor()
|
||||
QObject::connect(editor, &ScintillaEdit::customContextMenuRequested, this,
|
||||
&PythonShell::editor_contextMenu);
|
||||
|
||||
ConfigureSyntax(editor, SCLEX_PYTHON);
|
||||
QString suffix;
|
||||
|
||||
if(!filename.isEmpty())
|
||||
suffix = QFileInfo(filename).suffix().toLower();
|
||||
if(suffix == lit("md"))
|
||||
{
|
||||
ConfigureSyntax(editor, SCLEX_NULL);
|
||||
editor->setWrapMode(SC_WRAP_WORD);
|
||||
}
|
||||
else if(suffix.toLower() == lit("json"))
|
||||
{
|
||||
ConfigureSyntax(editor, SCLEX_JSON);
|
||||
editor->setWrapMode(SC_WRAP_WORD);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConfigureSyntax(editor, SCLEX_PYTHON);
|
||||
}
|
||||
|
||||
editor->setTabWidth(4);
|
||||
editor->setUseTabs(false);
|
||||
@@ -1307,6 +1534,7 @@ ScintillaEdit *PythonShell::makeEditor()
|
||||
|
||||
QObject::connect(editor, &QWidget::destroyed, [this, editor]() {
|
||||
m_Scintillas.removeOne(editor);
|
||||
m_Watcher->removePath(getEditorFilename(editor));
|
||||
updateEditorCloseButton();
|
||||
});
|
||||
|
||||
@@ -1458,6 +1686,58 @@ void PythonShell::updateEditorCloseButton()
|
||||
}
|
||||
}
|
||||
|
||||
void PythonShell::addRecentFile(rdcstr filename)
|
||||
{
|
||||
// don't add recent files from UI extensions
|
||||
for(const ExtensionMetadata &ext : m_Ctx.Extensions().GetInstalledExtensions())
|
||||
{
|
||||
if(QString(QFileInfo(filename).absolutePath())
|
||||
.toLower()
|
||||
.startsWith(QDir(ext.filePath).absolutePath().toLower()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_Ctx.Config().Python_RecentFiles.removeIf([filename](const rdcstr &o) { return o == filename; });
|
||||
|
||||
if(m_Ctx.Config().Python_RecentFiles.size() == 10)
|
||||
m_Ctx.Config().Python_RecentFiles.pop_back();
|
||||
|
||||
m_Ctx.Config().Python_RecentFiles.insert(0, filename);
|
||||
|
||||
m_Ctx.Config().Save();
|
||||
|
||||
updateRecentFiles(true);
|
||||
}
|
||||
|
||||
void PythonShell::updateRecentFiles(bool added)
|
||||
{
|
||||
ui->projectExplorer->beginUpdate();
|
||||
|
||||
bool expanded = ui->projectExplorer->isItemExpanded(m_RecentFiles);
|
||||
|
||||
// expand if we're adding the first recent file
|
||||
if(m_Ctx.Config().Python_RecentFiles.size() == 1 && added)
|
||||
expanded = true;
|
||||
|
||||
m_RecentFiles->clear();
|
||||
|
||||
for(rdcstr file : m_Ctx.Config().Python_RecentFiles)
|
||||
{
|
||||
RDTreeWidgetItem *recent = new RDTreeWidgetItem({QFileInfo(file).fileName()});
|
||||
recent->setData(0, Qt::UserRole, QString(file));
|
||||
m_RecentFiles->addChild(recent);
|
||||
}
|
||||
|
||||
ui->projectExplorer->endUpdate();
|
||||
|
||||
if(expanded)
|
||||
ui->projectExplorer->expandItem(m_RecentFiles);
|
||||
else
|
||||
ui->projectExplorer->collapseItem(m_RecentFiles);
|
||||
}
|
||||
|
||||
bool PythonShell::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if(qobject_cast<ScintillaEdit *>(watched))
|
||||
@@ -1492,6 +1772,14 @@ QVariant PythonShell::persistData()
|
||||
{
|
||||
QVariantMap state = ui->docking->saveState();
|
||||
|
||||
RDTreeViewExpansionState expansion;
|
||||
ui->projectExplorer->saveExpansion(expansion, 0);
|
||||
|
||||
QVariantList expansionList;
|
||||
for(uint x : expansion)
|
||||
expansionList.append(x);
|
||||
state[lit("projectExplorerExpansion")] = expansionList;
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1501,8 +1789,25 @@ void PythonShell::setPersistData(const QVariant &persistData)
|
||||
|
||||
ui->docking->restoreState(state);
|
||||
|
||||
ui->projectExplorer->collapseAll();
|
||||
|
||||
RDTreeViewExpansionState expansion;
|
||||
for(QVariant x : state[lit("projectExplorerExpansion")].toList())
|
||||
expansion.insert(x.toUInt());
|
||||
ui->projectExplorer->applyExpansion(expansion, 0);
|
||||
|
||||
setupTabs();
|
||||
}
|
||||
|
||||
void PythonShell::setupTabs()
|
||||
{
|
||||
ToolWindowManagerArea *editorTabs = ui->docking->areaOf(m_Scintillas[0]);
|
||||
QObject::connect(editorTabs, &QTabWidget::currentChanged, this, &PythonShell::editorTab_Changed);
|
||||
|
||||
editorTabs->tabBar()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
QObject::connect(editorTabs->tabBar(), &QTabBar::customContextMenuRequested, this,
|
||||
&PythonShell::editorTab_Menu);
|
||||
}
|
||||
|
||||
PythonContext *PythonShell::GetScriptContext()
|
||||
@@ -1522,13 +1827,17 @@ bool PythonShell::LoadScriptFromFilename(rdcstr filename)
|
||||
QFile f(filename);
|
||||
if(f.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
{
|
||||
ScintillaEdit *editor = makeEditor();
|
||||
ScintillaEdit *editor = makeEditor(filename);
|
||||
|
||||
editor->setText(f.readAll().data());
|
||||
editor->emptyUndoBuffer();
|
||||
|
||||
ui->saveScript->setEnabled(true);
|
||||
|
||||
addRecentFile(filename);
|
||||
|
||||
m_Watcher->addPath(filename);
|
||||
|
||||
setEditorFilename(editor, QString(filename));
|
||||
markEditorModified(editor, false);
|
||||
return true;
|
||||
@@ -1540,7 +1849,7 @@ bool PythonShell::LoadScriptFromFilename(rdcstr filename)
|
||||
|
||||
void PythonShell::CreateNewScriptEditor(rdcstr name, rdcstr text)
|
||||
{
|
||||
ScintillaEdit *editor = makeEditor();
|
||||
ScintillaEdit *editor = makeEditor("");
|
||||
|
||||
editor->setText(text.c_str());
|
||||
editor->emptyUndoBuffer();
|
||||
@@ -1705,7 +2014,7 @@ void PythonShell::on_newScript_clicked()
|
||||
|
||||
minidocHeader = QFormatStr("# %1\n\n").arg(minidocHeader);
|
||||
|
||||
ScintillaEdit *editor = makeEditor();
|
||||
ScintillaEdit *editor = makeEditor("");
|
||||
|
||||
ui->saveScript->setEnabled(false);
|
||||
|
||||
@@ -1826,6 +2135,69 @@ void PythonShell::on_outputContext_currentIndexChanged(int idx)
|
||||
updateScriptOutput(true);
|
||||
}
|
||||
|
||||
void PythonShell::on_projectExplorer_itemActivated(RDTreeWidgetItem *item, int column)
|
||||
{
|
||||
// ignore these, just let them collapse/expand
|
||||
if(item == m_Examples || item == m_UIExtensions || item == m_RecentFiles)
|
||||
return;
|
||||
|
||||
if(item->parent() == m_Examples)
|
||||
{
|
||||
QString filename = tr("Example: ") + item->text(0);
|
||||
QString text = item->data(0, Qt::UserRole).toString();
|
||||
|
||||
for(ScintillaEdit *edit : m_Scintillas)
|
||||
{
|
||||
if(getEditorFilename(edit) == filename)
|
||||
{
|
||||
ToolWindowManager::raiseToolWindow(edit);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CreateNewScriptEditor(filename, text);
|
||||
}
|
||||
else
|
||||
{
|
||||
// recent file or UI extension, the user role contains the path
|
||||
QString filename = item->data(0, Qt::UserRole).toString();
|
||||
|
||||
// directories in UI extensions have no filename to activate
|
||||
if(filename.isEmpty())
|
||||
return;
|
||||
|
||||
for(ScintillaEdit *edit : m_Scintillas)
|
||||
{
|
||||
if(getEditorFilename(edit) == filename)
|
||||
{
|
||||
ToolWindowManager::raiseToolWindow(edit);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(!QFileInfo(filename).exists())
|
||||
{
|
||||
QMessageBox::StandardButton response = RDDialog::question(
|
||||
this, tr("'%1' does not exist").arg(QFileInfo(filename).fileName()),
|
||||
tr("File not found at path:\n%1\n\nRemove from recent files list?").arg(filename));
|
||||
|
||||
if(response == QMessageBox::Yes)
|
||||
{
|
||||
rdcstr f = filename;
|
||||
m_Ctx.Config().Python_RecentFiles.removeIf([f](const rdcstr &o) { return o == f; });
|
||||
|
||||
m_Ctx.Config().Save();
|
||||
|
||||
GUIInvoke::defer(this, [this]() { updateRecentFiles(false); });
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
LoadScriptFromFilename(filename);
|
||||
}
|
||||
}
|
||||
|
||||
bool PythonShell::checkAllowClose()
|
||||
{
|
||||
for(ScintillaEdit *edit : m_Scintillas)
|
||||
@@ -1881,6 +2253,7 @@ bool PythonShell::saveEditorAs(ScintillaEdit *editor)
|
||||
|
||||
bool PythonShell::saveEditor(ScintillaEdit *editor, QString filename)
|
||||
{
|
||||
QString oldFilename = getEditorFilename(editor);
|
||||
if(!filename.isEmpty())
|
||||
{
|
||||
QDir dirinfo = QFileInfo(filename).dir();
|
||||
@@ -1889,10 +2262,20 @@ bool PythonShell::saveEditor(ScintillaEdit *editor, QString filename)
|
||||
QFile f(filename);
|
||||
if(f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text))
|
||||
{
|
||||
// remove the path from watching first so we don't get a notification from the write itself
|
||||
m_Watcher->removePath(oldFilename);
|
||||
|
||||
QString text = QString::fromUtf8(editor->getText(editor->textLength() + 1));
|
||||
text.remove(QLatin1Char('\r'));
|
||||
f.write(text.toUtf8());
|
||||
|
||||
addRecentFile(filename);
|
||||
|
||||
// delay a short while before starting to watch this file. This is highly unlikely to miss
|
||||
// any real writes (which would have to happen externally after we save), but prevents us
|
||||
// from identifying our own writes as an external modification.
|
||||
QTimer::singleShot(200, [this, filename]() { m_Watcher->addPath(filename); });
|
||||
|
||||
setEditorFilename(editor, filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QFrame>
|
||||
#include "Code/Interface/QRDInterface.h"
|
||||
#include "scintilla/include/qt/ScintillaEdit.h"
|
||||
@@ -34,6 +35,8 @@ class RDToolTip;
|
||||
class QTimer;
|
||||
class QCompleter;
|
||||
class QStringListModel;
|
||||
class QFileSystemWatcher;
|
||||
class RDTreeWidgetItem;
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
@@ -107,6 +110,8 @@ private slots:
|
||||
void on_abortRun_clicked();
|
||||
void on_outputContext_currentIndexChanged(int idx);
|
||||
|
||||
void on_projectExplorer_itemActivated(RDTreeWidgetItem *item, int column);
|
||||
|
||||
bool checkAllowClose();
|
||||
|
||||
// manual slots
|
||||
@@ -121,6 +126,10 @@ private slots:
|
||||
void editorTab_Changed(int index);
|
||||
void doSyntaxCheck();
|
||||
|
||||
void openFileModified(const QString &path);
|
||||
void updateExtensionProjects();
|
||||
void editorTab_Menu(const QPoint &pos);
|
||||
|
||||
private:
|
||||
Ui::PythonShell *ui;
|
||||
ICaptureContext &m_Ctx;
|
||||
@@ -134,6 +143,10 @@ private:
|
||||
bool m_ContextMenuVisible = false;
|
||||
bool m_HelpPrinting = false;
|
||||
|
||||
RDTreeWidgetItem *m_UIExtensions, *m_Examples, *m_RecentFiles;
|
||||
|
||||
QFileSystemWatcher *m_Watcher = NULL;
|
||||
|
||||
QTimer *m_SyntaxCheckTimer;
|
||||
|
||||
QCompleter *m_InteractiveCompleter;
|
||||
@@ -161,10 +174,17 @@ private:
|
||||
|
||||
QList<ScintillaEdit *> m_Scintillas;
|
||||
|
||||
void setupTabs();
|
||||
|
||||
ScintillaEdit *curEditor();
|
||||
ScintillaEdit *makeEditor();
|
||||
ScintillaEdit *makeEditor(rdcstr filename);
|
||||
void updateEditorCloseButton();
|
||||
|
||||
void addRecentFile(rdcstr filename);
|
||||
void updateRecentFiles(bool added);
|
||||
|
||||
void addExtensionDirItems(RDTreeWidgetItem *root, QDir dir);
|
||||
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
void updateScriptOutput(bool fullRefresh);
|
||||
|
||||
@@ -436,6 +436,19 @@
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="RDTreeWidget" name="projectExplorer">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>540</x>
|
||||
<y>40</y>
|
||||
<width>161</width>
|
||||
<height>141</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
@@ -453,6 +466,11 @@
|
||||
<extends>QTextEdit</extends>
|
||||
<header>Widgets/Extended/RDTextEdit.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>RDTreeWidget</class>
|
||||
<extends>QTreeWidget</extends>
|
||||
<header>Widgets/Extended/RDTreeWidget.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../Resources/resources.qrc"/>
|
||||
|
||||
Reference in New Issue
Block a user