Add registration and loading of extensions through a management window

This commit is contained in:
baldurk
2018-10-22 13:04:11 +01:00
parent 2dd293d691
commit dd3a352408
16 changed files with 953 additions and 1 deletions
+213
View File
@@ -25,6 +25,7 @@
#include "CaptureContext.h"
#include <QApplication>
#include <QDir>
#include <QDirIterator>
#include <QElapsedTimer>
#include <QFileInfo>
#include <QLabel>
@@ -32,7 +33,9 @@
#include <QMessageBox>
#include <QMetaObject>
#include <QProgressDialog>
#include <QRegularExpression>
#include <QTimer>
#include "Code/pyrenderdoc/PythonContext.h"
#include "Windows/APIInspector.h"
#include "Windows/BufferViewer.h"
#include "Windows/CommentView.h"
@@ -54,6 +57,7 @@
#include "Windows/TimelineBar.h"
#include "QRDUtils.h"
#include "RGPInterop.h"
#include "version.h"
#include "pipestate.inl"
@@ -103,6 +107,24 @@ CaptureContext::CaptureContext(QString paramFilename, QString remoteHost, uint32
m_MainWindow->takeCaptureOwnership();
}
}
{
QDir dir(configFilePath("extensions"));
if(!dir.exists())
dir.mkpath(dir.absolutePath());
}
rdcarray<ExtensionMetadata> exts = CaptureContext::GetInstalledExtensions();
for(const ExtensionMetadata &e : exts)
{
if(cfg.AlwaysLoad_Extensions.contains(e.Package))
{
qInfo() << "Automatically loading extension" << QString(e.Package);
LoadExtension(e.Package);
}
}
}
CaptureContext::~CaptureContext()
@@ -139,6 +161,197 @@ rdcstr CaptureContext::TempCaptureFilename(const rdcstr &appname)
.arg(QDateTime::currentDateTimeUtc().toString(lit("yyyy.MM.dd_HH.mm.ss"))));
}
rdcarray<ExtensionMetadata> CaptureContext::GetInstalledExtensions()
{
QString extensionFolder = configFilePath("extensions");
rdcarray<ExtensionMetadata> ret;
QDirIterator it(extensionFolder, QDirIterator::Subdirectories);
extensionFolder = extensionFolder.toLower();
while(it.hasNext())
{
QFileInfo fileinfo(it.next());
if(fileinfo.fileName().toLower() == lit("extension.json"))
{
QFile f(fileinfo.absoluteFilePath());
QString package = fileinfo.absolutePath()
.toLower()
.replace(extensionFolder, QString())
.replace(QLatin1Char('/'), QLatin1Char('.'));
while(package[0] == QLatin1Char('.'))
package.remove(0, 1);
while(package[package.size() - 1] == QLatin1Char('.'))
package.remove(package.size() - 1, 1);
if(f.exists() && f.open(QIODevice::ReadOnly | QIODevice::Text))
{
QVariantMap json = JSONToVariant(QString::fromUtf8(f.readAll()));
if(json.empty())
{
qCritical() << fileinfo.absoluteFilePath() << "is corrupt, cannot parse json";
continue;
}
ExtensionMetadata ext;
ext.Package = package;
ext.FilePath = fileinfo.absolutePath();
if(json.contains(lit("name")))
{
ext.Name = json[lit("name")].toString();
}
else
{
qCritical() << "Extension" << package << "is corrupt, no name entry";
continue;
}
ext.ExtensionAPI = 1;
if(json.contains(lit("extension_api")))
{
ext.ExtensionAPI = json[lit("extension_api")].toInt();
}
else
{
qCritical() << "Extension" << QString(ext.Name) << "is corrupt, no api version entry";
continue;
}
if(json.contains(lit("version")))
{
ext.Version = json[lit("version")].toString();
}
else
{
qCritical() << "Extension" << QString(ext.Name) << "is corrupt, no version entry";
continue;
}
if(json.contains(lit("description")))
{
ext.Description = json[lit("description")].toString();
}
else
{
qCritical() << "Extension" << QString(ext.Name) << "is corrupt, no description entry";
continue;
}
if(json.contains(lit("author")))
{
ext.Author = json[lit("author")].toString();
}
else
{
qCritical() << "Extension" << QString(ext.Name) << "is corrupt, no author entry";
continue;
}
if(json.contains(lit("url")))
{
ext.URL = json[lit("url")].toString();
}
else
{
qCritical() << "Extension" << QString(ext.Name) << "is corrupt, no URL entry";
continue;
}
if(json.contains(lit("minimum_renderdoc")))
{
QString minVer = json[lit("minimum_renderdoc")].toString();
QRegularExpression re(lit("([0-9]*).([0-9]*)"));
QRegularExpressionMatch match = re.match(minVer);
bool ok = false, badversion = false;
if(match.hasMatch())
{
int major = match.captured(1).toInt(&ok);
if(ok)
{
int minor = match.captured(2).toInt(&ok);
// if it needs a higher major version, we can't load it
if(major > RENDERDOC_VERSION_MAJOR)
badversion = true;
// if major versions are the same and it needs a higher minor, we can't load it either
if(major == RENDERDOC_VERSION_MAJOR && minor > RENDERDOC_VERSION_MINOR)
badversion = true;
}
}
if(!ok)
{
qCritical() << "Extension" << QString(ext.Name)
<< "is corrupt, minimum_renderdoc doesn't match a MAJOR.MINOR version";
continue;
}
if(badversion)
{
qInfo() << "Extension" << QString(ext.Name) << "declares minimum_renderdoc" << minVer
<< "so skipping";
continue;
}
}
ret.push_back(ext);
}
}
}
return ret;
}
bool CaptureContext::IsExtensionLoaded(rdcstr name)
{
return m_ExtensionObjects.contains(name);
}
bool CaptureContext::LoadExtension(rdcstr name)
{
bool ret = false;
PythonContext::ProcessExtensionWork([this, &ret, name]() {
for(QObject *o : m_ExtensionObjects[name])
delete o;
m_ExtensionObjects[name].clear();
ret = PythonContext::LoadExtension(*this, name);
if(ret)
{
m_ExtensionObjects[name].swap(m_PendingExtensionObjects);
}
else
{
m_ExtensionObjects.remove(name);
for(QObject *o : m_PendingExtensionObjects)
delete o;
m_PendingExtensionObjects.clear();
}
});
m_MainWindow->CleanMenus();
return ret;
}
void CaptureContext::LoadCapture(const rdcstr &captureFile, const rdcstr &origFilename,
bool temporary, bool local)
{
+12 -1
View File
@@ -52,7 +52,7 @@ class TimelineBar;
class PythonShell;
class ResourceInspector;
class CaptureContext : public ICaptureContext
class CaptureContext : public ICaptureContext, IExtensionManager
{
Q_DECLARE_TR_FUNCTIONS(CaptureContext);
@@ -65,6 +65,13 @@ public:
rdcstr TempCaptureFilename(const rdcstr &appname) override;
//////////////////////////////////////////////////////////////////////////////
// IExtensionManager
rdcarray<ExtensionMetadata> GetInstalledExtensions() override;
bool IsExtensionLoaded(rdcstr name) override;
bool LoadExtension(rdcstr name) override;
//////////////////////////////////////////////////////////////////////////////
// Control functions
@@ -99,6 +106,7 @@ public:
// Accessors
IReplayManager &Replay() override { return m_Replay; }
IExtensionManager &Extensions() override { return *this; }
bool IsCaptureLoaded() override { return m_CaptureLoaded; }
bool IsCaptureLocal() override { return m_CaptureLocal; }
bool IsCaptureTemporary() override { return m_CaptureTemporary; }
@@ -330,6 +338,9 @@ private:
QIcon *m_Icon = NULL;
QList<QObject *> m_PendingExtensionObjects;
QMap<rdcstr, QList<QObject *>> m_ExtensionObjects;
// Windows
MainWindow *m_MainWindow = NULL;
EventBrowser *m_EventBrowser = NULL;
@@ -354,6 +354,8 @@ DECLARE_REFLECTION_STRUCT(BugReport);
\
CONFIG_SETTING(public, QVariantList, rdcarray<BugReport>, CrashReport_ReportedBugs) \
\
CONFIG_SETTING(public, QVariantList, rdcarray<rdcstr>, AlwaysLoad_Extensions) \
\
CONFIG_SETTING(private, QVariantMap, rdcstrpairs, ConfigSettings) \
\
CONFIG_SETTING(private, QVariantList, rdcarray<RemoteHost>, RemoteHostList)
@@ -691,6 +693,12 @@ For more information about some of these settings that are user-facing see
A list of :class:`BugReport` detailing previously submitted bugs that we're watching for updates.
.. data:: AlwaysLoad_Extensions
A list of strings with extension packages to always load on startup, without needing manual
enabling.
)");
class PersistantConfig
{
+111
View File
@@ -1022,6 +1022,110 @@ protected:
DECLARE_REFLECTION_STRUCT(IRGPInterop);
DOCUMENT("The metadata for an extension.");
struct ExtensionMetadata
{
DOCUMENT("");
bool operator==(const ExtensionMetadata &o) const
{
return ExtensionAPI == o.ExtensionAPI && FilePath == o.FilePath && Package == o.Package &&
Name == o.Name && Version == o.Version && Author == o.Author && URL == o.URL &&
Description == o.Description;
}
bool operator<(const ExtensionMetadata &o) const
{
if(!(ExtensionAPI == o.ExtensionAPI))
return ExtensionAPI < o.ExtensionAPI;
if(!(FilePath == o.FilePath))
return FilePath < o.FilePath;
if(!(Package == o.Package))
return Package < o.Package;
if(!(Name == o.Name))
return Name < o.Name;
if(!(Version == o.Version))
return Version < o.Version;
if(!(Author == o.Author))
return Author < o.Author;
if(!(URL == o.URL))
return URL < o.URL;
if(!(Description == o.Description))
return Description < o.Description;
return false;
}
DOCUMENT("The version of the extension API that this extension is written against");
int ExtensionAPI;
DOCUMENT("The location of this package on disk");
rdcstr FilePath;
DOCUMENT("The python package for this extension, e.g. foo.bar");
rdcstr Package;
DOCUMENT("The short friendly name for the extension");
rdcstr Name;
DOCUMENT("The version of the extension");
rdcstr Version;
DOCUMENT("The author of the extension, optionally with an email contact");
rdcstr Author;
DOCUMENT("The URL for where the extension is fetched from");
rdcstr URL;
DOCUMENT("A longer description of what the extension does");
rdcstr Description;
};
DECLARE_REFLECTION_STRUCT(ExtensionMetadata);
DOCUMENT(R"(A manager for listing available and active extensions, as well as the interface for
extensions to register hooks and additional functionality.
.. function:: ExtensionCallback(context)
Not a member function - the signature for any ``ExtensionCallback`` callbacks.
Callback for extensions to register entry points with, used in many situations depending on how it
was registered.
:param CaptureContext context: The current capture context.
)");
struct IExtensionManager
{
typedef std::function<void(ICaptureContext *ctx)> ExtensionCallback;
DOCUMENT(R"(Retrieve a list of installed extensions.
:return: The list of installed extensions.
:rtype: ``list`` of :class:`ExtensionMetadata`.
)");
virtual rdcarray<ExtensionMetadata> GetInstalledExtensions() = 0;
DOCUMENT(R"(Check if an installed extension is enabled.
:param str name: The qualified name of the extension, e.g. ``foo.bar``
:return: If the extension is enabled or not.
:rtype: bool
)");
virtual bool IsExtensionLoaded(rdcstr name) = 0;
DOCUMENT(R"(Enable an extension by name. If the extension is already enabled, this will reload it.
:param str name: The qualified name of the extension, e.g. ``foo.bar``
)");
virtual bool LoadExtension(rdcstr name) = 0;
protected:
DOCUMENT("");
IExtensionManager() = default;
~IExtensionManager() = default;
};
DECLARE_REFLECTION_STRUCT(IExtensionManager);
DOCUMENT("The capture context that the python script is running in.")
struct ICaptureContext
{
@@ -1924,6 +2028,13 @@ capture's API.
)");
virtual PersistantConfig &Config() = 0;
DOCUMENT(R"(Retrieve the manager for extensions.
:return: The current extension manager.
:rtype: ExtensionManager
)");
virtual IExtensionManager &Extensions() = 0;
protected:
ICaptureContext() = default;
~ICaptureContext() = default;
@@ -506,6 +506,17 @@ bool PythonContext::LoadExtension(ICaptureContext &ctx, const rdcstr &extension)
{
qInfo() << "Reloading " << QString(extension);
// call unregister() if it exists
PyObject *unregister_func = PyObject_GetAttrString(extensions[extension], "unregister");
if(unregister_func)
{
PyObject *retval = PyObject_CallFunction(unregister_func, "");
// discard the return value, regardless of error we don't abort the reload
Py_XDECREF(retval);
}
// if the extension is a package, we need to manually reload any loaded submodules
PyObject *sysmodules = PyObject_GetAttrString(sysobj, "modules");
+1
View File
@@ -83,6 +83,7 @@ TEMPLATE_ARRAY_INSTANTIATE(rdcarray, EventBookmark)
TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ShaderProcessingTool)
TEMPLATE_ARRAY_INSTANTIATE(rdcarray, rdcstrpair)
TEMPLATE_ARRAY_INSTANTIATE(rdcarray, BugReport)
TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ExtensionMetadata)
TEMPLATE_ARRAY_INSTANTIATE_PTR(rdcarray, ICaptureViewer)
// unignore the function from above
@@ -0,0 +1,241 @@
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2018 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "ExtensionManager.h"
#include <QDesktopServices>
#include <QFileInfo>
#include <QKeyEvent>
#include <QRegularExpression>
#include "Code/Interface/QRDInterface.h"
#include "Code/Resources.h"
#include "Widgets/Extended/RDHeaderView.h"
#include "Windows/MainWindow.h"
#include "ui_ExtensionManager.h"
Q_DECLARE_METATYPE(ExtensionMetadata);
ExtensionManager::ExtensionManager(ICaptureContext &ctx)
: QDialog(NULL), ui(new Ui::ExtensionManager), m_Ctx(ctx)
{
ui->setupUi(this);
{
RDHeaderView *header = new RDHeaderView(Qt::Horizontal, this);
ui->extensions->setHeader(header);
ui->extensions->setColumns({tr("Package"), tr("Name"), tr("Loaded")});
header->setColumnStretchHints({1, 4, -1});
}
ui->name->setText(lit("---"));
ui->version->setText(lit("---"));
ui->author->setText(lit("---"));
ui->URL->setText(lit("---"));
ui->reload->setEnabled(false);
ui->alwaysLoad->setEnabled(false);
QObject::connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
QString extensionFolder = configFilePath("extensions");
m_Extensions = m_Ctx.Extensions().GetInstalledExtensions();
if(m_Extensions.isEmpty())
{
ui->extensions->addTopLevelItem(
new RDTreeWidgetItem({QString(), lit("No extensions found available"), QString()}));
ui->extensions->addTopLevelItem(new RDTreeWidgetItem(
{QString(), lit("Create packages in %1").arg(extensionFolder), QString()}));
}
else
{
for(const ExtensionMetadata &e : m_Extensions)
{
RDTreeWidgetItem *item = new RDTreeWidgetItem({e.Package, e.Name, QString()});
item->setCheckState(
2, m_Ctx.Extensions().IsExtensionLoaded(e.Package) ? Qt::Checked : Qt::Unchecked);
ui->extensions->addTopLevelItem(item);
}
ui->extensions->setCurrentItem(ui->extensions->topLevelItem(0));
}
}
ExtensionManager::~ExtensionManager()
{
delete ui;
}
void ExtensionManager::on_reload_clicked()
{
RDTreeWidgetItem *item = ui->extensions->currentItem();
if(!item)
return;
int idx = ui->extensions->indexOfTopLevelItem(item);
if(idx >= 0 && idx < m_Extensions.count())
{
const ExtensionMetadata &e = m_Extensions[idx];
if(!e.Name.isEmpty())
{
// if the load succeeds, set us as checked. Otherwise, unchecked
if(m_Ctx.Extensions().LoadExtension(e.Package))
{
item->setCheckState(2, Qt::Checked);
}
else
{
item->setCheckState(2, Qt::Unchecked);
RDDialog::critical(this, tr("Failed to load extension"),
tr("Failed to load extension '%1'.\n"
"Check the diagnostic log for python errors")
.arg(e.Name));
}
update_currentItem(item);
}
}
}
void ExtensionManager::on_openLocation_clicked()
{
if(m_Extensions.empty())
{
QDesktopServices::openUrl(QString(configFilePath("extensions")));
return;
}
RDTreeWidgetItem *item = ui->extensions->currentItem();
if(!item)
return;
int idx = ui->extensions->indexOfTopLevelItem(item);
if(idx >= 0 && idx < m_Extensions.count())
{
const ExtensionMetadata &e = m_Extensions[idx];
if(!e.Name.isEmpty())
{
QDesktopServices::openUrl(QFileInfo(e.FilePath).absoluteFilePath());
}
}
}
void ExtensionManager::on_alwaysLoad_toggled(bool checked)
{
RDTreeWidgetItem *item = ui->extensions->currentItem();
if(!item)
return;
int idx = ui->extensions->indexOfTopLevelItem(item);
if(idx >= 0 && idx < m_Extensions.count())
{
const ExtensionMetadata &e = m_Extensions[idx];
if(!e.Name.isEmpty())
{
m_Ctx.Config().AlwaysLoad_Extensions.removeOne(e.Package);
if(checked)
m_Ctx.Config().AlwaysLoad_Extensions.push_back(e.Package);
m_Ctx.Config().Save();
}
}
}
void ExtensionManager::on_extensions_currentItemChanged(RDTreeWidgetItem *item, RDTreeWidgetItem *)
{
update_currentItem(item);
}
void ExtensionManager::on_extensions_itemChanged(RDTreeWidgetItem *item, int col)
{
if(col == 2)
{
ui->extensions->setCurrentItem(item);
bool loaded = m_Ctx.Extensions().IsExtensionLoaded(item->text(0));
// if the extension is loaded, don't allow unchecking
if(loaded && item->checkState(2) != Qt::Checked)
{
item->setCheckState(2, Qt::Checked);
return;
}
// if the extension is unloaded, if we're now checked then try to load it. If
// we're unchecked allow that (it is a code-change after we failed to load)
if(!loaded)
{
if(item->checkState(2) == Qt::Checked)
on_reload_clicked();
}
}
}
void ExtensionManager::update_currentItem(RDTreeWidgetItem *item)
{
if(!item)
return;
if(item != ui->extensions->currentItem())
{
ui->extensions->setCurrentItem(item);
return;
}
int idx = ui->extensions->indexOfTopLevelItem(item);
if(idx >= 0 && idx < m_Extensions.count())
{
const ExtensionMetadata &e = m_Extensions[idx];
if(!e.Name.isEmpty())
{
QRegularExpression authRE(lit("^(.*) <(.*)>$"));
ui->name->setText(e.Name);
ui->version->setText(e.Version);
ui->URL->setText(QFormatStr("<a href=\"%1\">%1</a>").arg(e.URL));
ui->description->setText(e.Description);
QRegularExpressionMatch match = authRE.match(QString(e.Author).trimmed());
if(match.hasMatch() && match.captured(2).contains(QLatin1Char('@')))
ui->author->setText(
QFormatStr("<a href=\"mailto:%2\">%1</a>").arg(match.captured(1)).arg(match.captured(2)));
else
ui->author->setText(e.Author);
bool loaded = item->checkState(2) == Qt::Checked;
ui->reload->setEnabled(true);
ui->reload->setText(loaded ? tr("Reload") : tr("Load"));
ui->alwaysLoad->setEnabled(loaded);
ui->alwaysLoad->setChecked(m_Ctx.Config().AlwaysLoad_Extensions.contains(e.Package));
}
}
}
@@ -0,0 +1,64 @@
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2018 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#pragma once
#include <QDialog>
#include <QList>
#include <QSemaphore>
#include "Code/Interface/QRDInterface.h"
namespace Ui
{
class ExtensionManager;
}
class RDTreeWidgetItem;
struct ICaptureContext;
class MainWindow;
class ExtensionManager : public QDialog
{
Q_OBJECT
public:
explicit ExtensionManager(ICaptureContext &ctx);
~ExtensionManager();
private slots:
// automatic slots
void on_reload_clicked();
void on_openLocation_clicked();
void on_alwaysLoad_toggled(bool checked);
void on_extensions_currentItemChanged(RDTreeWidgetItem *item, RDTreeWidgetItem *);
void on_extensions_itemChanged(RDTreeWidgetItem *item, int col);
private:
void update_currentItem(RDTreeWidgetItem *item);
Ui::ExtensionManager *ui;
ICaptureContext &m_Ctx;
rdcarray<ExtensionMetadata> m_Extensions;
};
@@ -0,0 +1,244 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ExtensionManager</class>
<widget class="QDialog" name="ExtensionManager">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>556</width>
<height>544</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Extension Manager</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Extension List</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="RDTreeWidget" name="extensions">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="indentation">
<number>0</number>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Details</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="5" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>URL:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Name:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Author:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Version:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="name">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="author">
<property name="text">
<string/>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="URL">
<property name="text">
<string/>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="openLocation">
<property name="text">
<string>Open Location</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="reload">
<property name="text">
<string>Reload</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="alwaysLoad">
<property name="text">
<string>Always Load</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Description:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="version">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QTextEdit" name="description">
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::NoTextInteraction</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>RDTreeWidget</class>
<extends>QTreeView</extends>
<header>Widgets/Extended/RDTreeWidget.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+7
View File
@@ -42,6 +42,7 @@
#include "Windows/Dialogs/AboutDialog.h"
#include "Windows/Dialogs/CaptureDialog.h"
#include "Windows/Dialogs/CrashDialog.h"
#include "Windows/Dialogs/ExtensionManager.h"
#include "Windows/Dialogs/LiveCapture.h"
#include "Windows/Dialogs/RemoteManager.h"
#include "Windows/Dialogs/SettingsDialog.h"
@@ -2432,6 +2433,12 @@ void MainWindow::on_action_Attach_to_Running_Instance_triggered()
on_action_Manage_Remote_Servers_triggered();
}
void MainWindow::on_action_Manage_Extensions_triggered()
{
ExtensionManager manager(m_Ctx);
RDDialog::show(&manager);
}
void MainWindow::on_action_Manage_Remote_Servers_triggered()
{
RemoteManager *rm = new RemoteManager(m_Ctx, this);
+1
View File
@@ -131,6 +131,7 @@ private slots:
void on_action_Open_RGP_Profile_triggered();
void on_action_Create_RGP_Profile_triggered();
void on_action_Attach_to_Running_Instance_triggered();
void on_action_Manage_Extensions_triggered();
void on_action_Manage_Remote_Servers_triggered();
void on_action_Settings_triggered();
void on_action_View_Documentation_triggered();
+6
View File
@@ -57,6 +57,7 @@
<addaction name="action_Open_RGP_Profile"/>
<addaction name="separator"/>
<addaction name="action_Settings"/>
<addaction name="action_Manage_Extensions"/>
<addaction name="action_Manage_Remote_Servers"/>
<addaction name="separator"/>
</widget>
@@ -466,6 +467,11 @@
<string>Open RGP Profile</string>
</property>
</action>
<action name="action_Manage_Extensions">
<property name="text">
<string>Manage Extensions</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
+1
View File
@@ -52,6 +52,7 @@ struct CaptureContextInvoker : ICaptureContext
{
return m_Ctx.TempCaptureFilename(appname);
}
virtual IExtensionManager &Extensions() override { return m_Ctx.Extensions(); }
virtual IReplayManager &Replay() override { return m_Ctx.Replay(); }
virtual bool IsCaptureLoaded() override { return m_Ctx.IsCaptureLoaded(); }
virtual bool IsCaptureLocal() override { return m_Ctx.IsCaptureLocal(); }
+3
View File
@@ -217,6 +217,7 @@ SOURCES += Code/qrenderdoc.cpp \
Windows/Dialogs/SuggestRemoteDialog.cpp \
Windows/Dialogs/VirtualFileDialog.cpp \
Windows/Dialogs/RemoteManager.cpp \
Windows/Dialogs/ExtensionManager.cpp \
Windows/PixelHistoryView.cpp \
Widgets/PipelineFlowChart.cpp \
Windows/Dialogs/EnvironmentEditor.cpp \
@@ -291,6 +292,7 @@ HEADERS += Code/CaptureContext.h \
Windows/Dialogs/SuggestRemoteDialog.h \
Windows/Dialogs/VirtualFileDialog.h \
Windows/Dialogs/RemoteManager.h \
Windows/Dialogs/ExtensionManager.h \
Windows/PixelHistoryView.h \
Widgets/PipelineFlowChart.h \
Windows/Dialogs/EnvironmentEditor.h \
@@ -331,6 +333,7 @@ FORMS += Windows/Dialogs/AboutDialog.ui \
Windows/Dialogs/SuggestRemoteDialog.ui \
Windows/Dialogs/VirtualFileDialog.ui \
Windows/Dialogs/RemoteManager.ui \
Windows/Dialogs/ExtensionManager.ui \
Windows/PixelHistoryView.ui \
Windows/Dialogs/EnvironmentEditor.ui \
Widgets/FindReplace.ui \
+15
View File
@@ -613,6 +613,7 @@
<ClCompile Include="$(IntDir)generated\moc_EnvironmentEditor.cpp" />
<ClCompile Include="$(IntDir)generated\moc_OrderedListEditor.cpp" />
<ClCompile Include="$(IntDir)generated\moc_RemoteManager.cpp" />
<ClCompile Include="$(IntDir)generated\moc_ExtensionManager.cpp" />
<ClCompile Include="$(IntDir)generated\moc_PerformanceCounterSelection.cpp" />
<ClCompile Include="$(IntDir)generated\moc_PerformanceCounterViewer.cpp" />
<ClCompile Include="$(IntDir)generated\moc_PipelineStateViewer.cpp" />
@@ -726,6 +727,7 @@
<ClCompile Include="Windows\Dialogs\EnvironmentEditor.cpp" />
<ClCompile Include="Windows\Dialogs\PerformanceCounterSelection.cpp" />
<ClCompile Include="Windows\Dialogs\RemoteManager.cpp" />
<ClCompile Include="Windows\Dialogs\ExtensionManager.cpp" />
<ClCompile Include="Windows\Dialogs\SettingsDialog.cpp" />
<ClCompile Include="Windows\Dialogs\SuggestRemoteDialog.cpp" />
<ClCompile Include="Windows\Dialogs\TextureSaveDialog.cpp" />
@@ -934,6 +936,7 @@
<ClInclude Include="$(IntDir)generated\ui_MainWindow.h" />
<ClInclude Include="$(IntDir)generated\ui_EnvironmentEditor.h" />
<ClInclude Include="$(IntDir)generated\ui_RemoteManager.h" />
<ClInclude Include="$(IntDir)generated\ui_ExtensionManager.h" />
<ClInclude Include="$(IntDir)generated\ui_PerformanceCounterSelection.h" />
<ClInclude Include="$(IntDir)generated\ui_PipelineStateViewer.h" />
<ClInclude Include="$(IntDir)generated\ui_ResourcePreview.h" />
@@ -1209,6 +1212,12 @@
<Message>MOC %(Filename).h</Message>
<Outputs>$(IntDir)generated\moc_%(Filename).cpp</Outputs>
</CustomBuild>
<CustomBuild Include="Windows\Dialogs\ExtensionManager.h">
<AdditionalInputs>%(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe;%(AdditionalInputs)</AdditionalInputs>
<Command>"$(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe" -DUNICODE -DWIN32 -DWIN64 -D_WIN32 -D_WIN64 -DRENDERDOC_PLATFORM_WIN32 -DSCINTILLA_QT=1 -DSCI_LEXER=1 -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -D_MSC_VER=1900 -I"$(ProjectDir)." -I"$(SolutionDir)\renderdoc\api\replay" -I"$(ProjectDir)3rdparty\qt\$(Platform)\mkspecs/win32-msvc2015" -I"$(ProjectDir)3rdparty\qt\include" -I"$(ProjectDir)3rdparty\qt\include\QtWidgets" -I"$(ProjectDir)3rdparty\qt\include\QtGui" -I"$(ProjectDir)3rdparty\qt\include\QtCore" "%(Fullpath)" -o "$(IntDir)generated\moc_%(Filename).cpp"</Command>
<Message>MOC %(Filename).h</Message>
<Outputs>$(IntDir)generated\moc_%(Filename).cpp</Outputs>
</CustomBuild>
<CustomBuild Include="Windows\Dialogs\SettingsDialog.h">
<AdditionalInputs>%(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe;%(AdditionalInputs)</AdditionalInputs>
<Command>"$(ProjectDir)3rdparty\qt\$(Platform)\bin\moc.exe" -DUNICODE -DWIN32 -DWIN64 -D_WIN32 -D_WIN64 -DRENDERDOC_PLATFORM_WIN32 -DSCINTILLA_QT=1 -DSCI_LEXER=1 -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -D_MSC_VER=1900 -I"$(ProjectDir)." -I"$(SolutionDir)\renderdoc\api\replay" -I"$(ProjectDir)3rdparty\qt\$(Platform)\mkspecs/win32-msvc2015" -I"$(ProjectDir)3rdparty\qt\include" -I"$(ProjectDir)3rdparty\qt\include\QtWidgets" -I"$(ProjectDir)3rdparty\qt\include\QtGui" -I"$(ProjectDir)3rdparty\qt\include\QtCore" "%(Fullpath)" -o "$(IntDir)generated\moc_%(Filename).cpp"</Command>
@@ -1456,6 +1465,12 @@
<Message>UIC %(Filename).ui</Message>
<Outputs>$(IntDir)generated\ui_%(Filename).h</Outputs>
</CustomBuild>
<CustomBuild Include="Windows\Dialogs\ExtensionManager.ui">
<AdditionalInputs>%(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
<Command>"$(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe" "%(Fullpath)" -o "$(IntDir)generated\ui_%(Filename).h"</Command>
<Message>UIC %(Filename).ui</Message>
<Outputs>$(IntDir)generated\ui_%(Filename).h</Outputs>
</CustomBuild>
<CustomBuild Include="Windows\Dialogs\SettingsDialog.ui">
<AdditionalInputs>%(Fullpath);$(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
<Command>"$(ProjectDir)3rdparty\qt\$(Platform)\bin\uic.exe" "%(Fullpath)" -o "$(IntDir)generated\ui_%(Filename).h"</Command>
@@ -711,6 +711,12 @@
<ClCompile Include="Code\Interface\ShaderProcessingTool.cpp">
<Filter>Code\Interface</Filter>
</ClCompile>
<ClCompile Include="$(IntDir)generated\moc_ExtensionManager.cpp">
<Filter>Generated Files</Filter>
</ClCompile>
<ClCompile Include="Windows\Dialogs\ExtensionManager.cpp">
<Filter>Windows\Dialogs</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="3rdparty\flowlayout\FlowLayout.h">
@@ -1061,6 +1067,9 @@
<ClInclude Include="Code\RGPInterop.h">
<Filter>Code</Filter>
</ClInclude>
<ClInclude Include="$(IntDir)generated\ui_ExtensionManager.h">
<Filter>Generated Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="Code\pyrenderdoc\pyconversion.h">
@@ -1436,6 +1445,12 @@
<CustomBuild Include="Widgets\CollapseGroupBox.h">
<Filter>Widgets</Filter>
</CustomBuild>
<CustomBuild Include="Windows\Dialogs\ExtensionManager.ui">
<Filter>Windows\Dialogs</Filter>
</CustomBuild>
<CustomBuild Include="Windows\Dialogs\ExtensionManager.h">
<Filter>Windows\Dialogs</Filter>
</CustomBuild>
</ItemGroup>
<ItemGroup>
<Image Include="Resources\action.png">