forked from qt-creator/qt-creator
Lua: Cleanup InfoBar entries
Also changes InfoBarDisplay::update to completely disconnect the resulting widgets when an entry is removed. Change-Id: Ic32ee8a1c9ee8dcd026e4a0cb7521b07323ca892 Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
@@ -264,10 +264,20 @@ void InfoBarDisplay::infoBarDestroyed()
|
||||
// will delete the widgets itself) or setInfoBar() being called explicitly.
|
||||
}
|
||||
|
||||
static void disconnectRecursively(QObject *obj)
|
||||
{
|
||||
obj->disconnect();
|
||||
for (QObject *child : obj->children())
|
||||
disconnectRecursively(child);
|
||||
}
|
||||
|
||||
void InfoBarDisplay::update()
|
||||
{
|
||||
for (QWidget *widget : std::as_const(m_infoWidgets)) {
|
||||
widget->disconnect(this); // We want no destroyed() signal now
|
||||
// Make sure that we are no longer connect to anything (especially lambdas).
|
||||
// Otherwise a lambda might live longer than the owner of the lambda.
|
||||
disconnectRecursively(widget);
|
||||
|
||||
widget->hide(); // Late deletion can cause duplicate infos. Hide immediately to prevent it.
|
||||
widget->deleteLater();
|
||||
}
|
||||
|
@@ -4,6 +4,8 @@
|
||||
#include "../luaengine.h"
|
||||
#include "../luatr.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
@@ -135,188 +137,199 @@ void setupFetchModule()
|
||||
|
||||
std::shared_ptr<Module> module = std::make_shared<Module>();
|
||||
|
||||
registerProvider("Fetch", [mod = std::move(module)](sol::state_view lua) -> sol::object {
|
||||
const ScriptPluginSpec *pluginSpec = lua.get<ScriptPluginSpec *>("PluginSpec");
|
||||
registerProvider(
|
||||
"Fetch",
|
||||
[mod = std::move(module),
|
||||
infoBarCleaner = InfoBarCleaner()](sol::state_view lua) mutable -> sol::object {
|
||||
const ScriptPluginSpec *pluginSpec = lua.get<ScriptPluginSpec *>("PluginSpec");
|
||||
|
||||
sol::table async = lua.script("return require('async')", "_fetch_").get<sol::table>();
|
||||
sol::function wrap = async["wrap"];
|
||||
sol::table async = lua.script("return require('async')", "_fetch_").get<sol::table>();
|
||||
sol::function wrap = async["wrap"];
|
||||
|
||||
sol::table fetch = lua.create_table();
|
||||
sol::table fetch = lua.create_table();
|
||||
|
||||
auto networkReplyType = lua.new_usertype<QNetworkReply>(
|
||||
"QNetworkReply",
|
||||
"error",
|
||||
sol::property([](QNetworkReply *self) -> int { return self->error(); }),
|
||||
"readAll",
|
||||
[](QNetworkReply *r) { return r->readAll().toStdString(); },
|
||||
"__tostring",
|
||||
[](QNetworkReply *r) {
|
||||
return QString("QNetworkReply(%1 \"%2\") => %3")
|
||||
.arg(opToString(r->operation()))
|
||||
.arg(r->url().toString())
|
||||
.arg(r->error());
|
||||
});
|
||||
auto networkReplyType = lua.new_usertype<QNetworkReply>(
|
||||
"QNetworkReply",
|
||||
"error",
|
||||
sol::property([](QNetworkReply *self) -> int { return self->error(); }),
|
||||
"readAll",
|
||||
[](QNetworkReply *r) { return r->readAll().toStdString(); },
|
||||
"__tostring",
|
||||
[](QNetworkReply *r) {
|
||||
return QString("QNetworkReply(%1 \"%2\") => %3")
|
||||
.arg(opToString(r->operation()))
|
||||
.arg(r->url().toString())
|
||||
.arg(r->error());
|
||||
});
|
||||
|
||||
auto checkPermission = [mod,
|
||||
pluginName = pluginSpec->name,
|
||||
guard = pluginSpec->connectionGuard.get()](
|
||||
QString url,
|
||||
std::function<void()> fetch,
|
||||
std::function<void()> notAllowed) {
|
||||
auto isAllowed = mod->isAllowedToFetch(pluginName);
|
||||
if (isAllowed == Module::IsAllowed::Yes) {
|
||||
fetch();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAllowed == Module::IsAllowed::No) {
|
||||
notAllowed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (QApplication::activeModalWidget()) {
|
||||
// We are already showing a modal dialog,
|
||||
// so we have to use a QMessageBox instead of the info bar
|
||||
auto msgBox = new QMessageBox(
|
||||
QMessageBox::Question,
|
||||
Tr::tr("Allow Internet Access"),
|
||||
Tr::tr("Allow the extension \"%1\" to fetch from the following URL:\n%2")
|
||||
.arg(pluginName)
|
||||
.arg(url),
|
||||
QMessageBox::Yes | QMessageBox::No,
|
||||
ICore::dialogParent());
|
||||
msgBox->setCheckBox(new QCheckBox(Tr::tr("Remember choice")));
|
||||
|
||||
QObject::connect(
|
||||
msgBox, &QMessageBox::accepted, guard, [mod, fetch, pluginName, msgBox]() {
|
||||
if (msgBox->checkBox()->isChecked())
|
||||
mod->setAllowedToFetch(pluginName, Module::IsAllowed::Yes);
|
||||
fetch();
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
msgBox, &QMessageBox::rejected, guard, [mod, notAllowed, pluginName, msgBox]() {
|
||||
if (msgBox->checkBox()->isChecked())
|
||||
mod->setAllowedToFetch(pluginName, Module::IsAllowed::No);
|
||||
notAllowed();
|
||||
});
|
||||
|
||||
msgBox->show();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
InfoBarEntry entry{
|
||||
Id("Fetch").withSuffix(pluginName),
|
||||
Tr::tr("Allow the extension \"%1\" to fetch data from the internet?")
|
||||
.arg(pluginName)};
|
||||
entry.setDetailsWidgetCreator([pluginName, url] {
|
||||
const QString markdown = Tr::tr("Allow the extension \"%1\" to fetch data"
|
||||
"from the following URL:\n\n")
|
||||
.arg("**" + pluginName + "**")
|
||||
+ QString("* [%1](%1)").arg(url);
|
||||
|
||||
QLabel *list = new QLabel();
|
||||
list->setTextFormat(Qt::TextFormat::MarkdownText);
|
||||
list->setText(markdown);
|
||||
list->setMargin(StyleHelper::SpacingTokens::ExPaddingGapS);
|
||||
return list;
|
||||
});
|
||||
entry.addCustomButton(Tr::tr("Always Allow"), [mod, pluginName, fetch]() {
|
||||
mod->setAllowedToFetch(pluginName, Module::IsAllowed::Yes);
|
||||
ICore::infoBar()->removeInfo(Id("Fetch").withSuffix(pluginName));
|
||||
fetch();
|
||||
});
|
||||
entry.addCustomButton(Tr::tr("Allow Once"), [pluginName, fetch]() {
|
||||
ICore::infoBar()->removeInfo(Id("Fetch").withSuffix(pluginName));
|
||||
fetch();
|
||||
});
|
||||
|
||||
entry.setCancelButtonInfo(Tr::tr("Deny"), [mod, notAllowed, pluginName]() {
|
||||
ICore::infoBar()->removeInfo(Id("Fetch").withSuffix(pluginName));
|
||||
mod->setAllowedToFetch(pluginName, Module::IsAllowed::No);
|
||||
notAllowed();
|
||||
});
|
||||
ICore::infoBar()->addInfo(entry);
|
||||
};
|
||||
|
||||
fetch["fetch_cb"] = [checkPermission,
|
||||
pluginName = pluginSpec->name,
|
||||
guard = pluginSpec->connectionGuard.get(),
|
||||
mod](
|
||||
const sol::table &options,
|
||||
const sol::function &callback,
|
||||
const sol::this_state &thisState) {
|
||||
auto url = options.get<QString>("url");
|
||||
auto actualFetch = [guard, url, options, callback, thisState]() {
|
||||
auto method = (options.get_or<QString>("method", "GET")).toLower();
|
||||
auto headers = options.get_or<sol::table>("headers", {});
|
||||
auto data = options.get_or<QString>("body", {});
|
||||
bool convertToTable
|
||||
= options.get<std::optional<bool>>("convertToTable").value_or(false);
|
||||
|
||||
QNetworkRequest request((QUrl(url)));
|
||||
if (headers && !headers.empty()) {
|
||||
for (const auto &[k, v] : headers)
|
||||
request.setRawHeader(k.as<QString>().toUtf8(), v.as<QString>().toUtf8());
|
||||
auto checkPermission = [mod,
|
||||
&infoBarCleaner,
|
||||
pluginName = pluginSpec->name,
|
||||
guard = pluginSpec->connectionGuard.get()](
|
||||
QString url,
|
||||
std::function<void()> fetch,
|
||||
std::function<void()> notAllowed) {
|
||||
auto isAllowed = mod->isAllowedToFetch(pluginName);
|
||||
if (isAllowed == Module::IsAllowed::Yes) {
|
||||
fetch();
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkReply *reply = nullptr;
|
||||
if (method == "get")
|
||||
reply = NetworkAccessManager::instance()->get(request);
|
||||
else if (method == "post")
|
||||
reply = NetworkAccessManager::instance()->post(request, data.toUtf8());
|
||||
else
|
||||
throw std::runtime_error("Unknown method: " + method.toStdString());
|
||||
if (isAllowed == Module::IsAllowed::No) {
|
||||
notAllowed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (QApplication::activeModalWidget()) {
|
||||
// We are already showing a modal dialog,
|
||||
// so we have to use a QMessageBox instead of the info bar
|
||||
auto msgBox = new QMessageBox(
|
||||
QMessageBox::Question,
|
||||
Tr::tr("Allow Internet Access"),
|
||||
Tr::tr("Allow the extension \"%1\" to fetch from the following URL:\n%2")
|
||||
.arg(pluginName)
|
||||
.arg(url),
|
||||
QMessageBox::Yes | QMessageBox::No,
|
||||
ICore::dialogParent());
|
||||
msgBox->setCheckBox(new QCheckBox(Tr::tr("Remember choice")));
|
||||
|
||||
if (convertToTable) {
|
||||
QObject::connect(
|
||||
reply, &QNetworkReply::finished, guard, [reply, thisState, callback]() {
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
callback(QString("%1 (%2):\n%3")
|
||||
.arg(reply->errorString())
|
||||
.arg(QString::fromLatin1(
|
||||
QMetaEnum::fromType<QNetworkReply::NetworkError>()
|
||||
.valueToKey(reply->error())))
|
||||
.arg(QString::fromUtf8(reply->readAll())));
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
callback(error.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
callback(toTable(thisState, doc));
|
||||
msgBox, &QMessageBox::accepted, guard, [mod, fetch, pluginName, msgBox]() {
|
||||
if (msgBox->checkBox()->isChecked())
|
||||
mod->setAllowedToFetch(pluginName, Module::IsAllowed::Yes);
|
||||
fetch();
|
||||
});
|
||||
|
||||
} else {
|
||||
QObject::connect(
|
||||
reply, &QNetworkReply::finished, guard, [reply, callback]() {
|
||||
msgBox,
|
||||
&QMessageBox::rejected,
|
||||
guard,
|
||||
[mod, notAllowed, pluginName, msgBox]() {
|
||||
if (msgBox->checkBox()->isChecked())
|
||||
mod->setAllowedToFetch(pluginName, Module::IsAllowed::No);
|
||||
notAllowed();
|
||||
});
|
||||
|
||||
msgBox->show();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const Id infoBarId = Id("Fetch").withSuffix(pluginName);
|
||||
infoBarCleaner.infoBarEntryAdded(infoBarId);
|
||||
|
||||
InfoBarEntry entry{
|
||||
infoBarId,
|
||||
Tr::tr("Allow the extension \"%1\" to fetch data from the internet?")
|
||||
.arg(pluginName)};
|
||||
entry.setDetailsWidgetCreator([pluginName, url] {
|
||||
const QString markdown = Tr::tr("Allow the extension \"%1\" to fetch data"
|
||||
"from the following URL:\n\n")
|
||||
.arg("**" + pluginName + "**")
|
||||
+ QString("* [%1](%1)").arg(url);
|
||||
|
||||
QLabel *list = new QLabel();
|
||||
list->setTextFormat(Qt::TextFormat::MarkdownText);
|
||||
list->setText(markdown);
|
||||
list->setMargin(StyleHelper::SpacingTokens::ExPaddingGapS);
|
||||
return list;
|
||||
});
|
||||
entry.addCustomButton(Tr::tr("Always Allow"), [mod, pluginName, fetch]() {
|
||||
mod->setAllowedToFetch(pluginName, Module::IsAllowed::Yes);
|
||||
ICore::infoBar()->removeInfo(Id("Fetch").withSuffix(pluginName));
|
||||
fetch();
|
||||
});
|
||||
entry.addCustomButton(Tr::tr("Allow Once"), [pluginName, fetch]() {
|
||||
ICore::infoBar()->removeInfo(Id("Fetch").withSuffix(pluginName));
|
||||
fetch();
|
||||
});
|
||||
|
||||
entry.setCancelButtonInfo(Tr::tr("Deny"), [mod, notAllowed, pluginName]() {
|
||||
ICore::infoBar()->removeInfo(Id("Fetch").withSuffix(pluginName));
|
||||
mod->setAllowedToFetch(pluginName, Module::IsAllowed::No);
|
||||
notAllowed();
|
||||
});
|
||||
ICore::infoBar()->addInfo(entry);
|
||||
};
|
||||
|
||||
fetch["fetch_cb"] = [checkPermission,
|
||||
pluginName = pluginSpec->name,
|
||||
guard = pluginSpec->connectionGuard.get(),
|
||||
mod](
|
||||
const sol::main_table &options,
|
||||
const sol::main_function &callback,
|
||||
const sol::this_state &thisState) {
|
||||
auto url = options.get<QString>("url");
|
||||
auto actualFetch = [guard, url, options, callback, thisState]() {
|
||||
auto method = (options.get_or<QString>("method", "GET")).toLower();
|
||||
auto headers = options.get_or<sol::table>("headers", {});
|
||||
auto data = options.get_or<QString>("body", {});
|
||||
bool convertToTable
|
||||
= options.get<std::optional<bool>>("convertToTable").value_or(false);
|
||||
|
||||
QNetworkRequest request((QUrl(url)));
|
||||
if (headers && !headers.empty()) {
|
||||
for (const auto &[k, v] : headers)
|
||||
request.setRawHeader(k.as<QString>().toUtf8(), v.as<QString>().toUtf8());
|
||||
}
|
||||
|
||||
QNetworkReply *reply = nullptr;
|
||||
if (method == "get")
|
||||
reply = NetworkAccessManager::instance()->get(request);
|
||||
else if (method == "post")
|
||||
reply = NetworkAccessManager::instance()->post(request, data.toUtf8());
|
||||
else
|
||||
throw std::runtime_error("Unknown method: " + method.toStdString());
|
||||
|
||||
if (convertToTable) {
|
||||
QObject::connect(
|
||||
reply, &QNetworkReply::finished, guard, [reply, thisState, callback]() {
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
callback(
|
||||
QString("%1 (%2):\n%3")
|
||||
.arg(reply->errorString())
|
||||
.arg(QString::fromLatin1(
|
||||
QMetaEnum::fromType<QNetworkReply::NetworkError>()
|
||||
.valueToKey(reply->error())))
|
||||
.arg(QString::fromUtf8(reply->readAll())));
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
callback(error.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
callback(toTable(thisState, doc));
|
||||
});
|
||||
|
||||
} else {
|
||||
QObject::connect(reply, &QNetworkReply::finished, guard, [reply, callback]() {
|
||||
// We don't want the network reply to be deleted by the manager, but
|
||||
// by the Lua GC
|
||||
reply->setParent(nullptr);
|
||||
callback(std::unique_ptr<QNetworkReply>(reply));
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkPermission(url, actualFetch, [callback, pluginName]() {
|
||||
callback(
|
||||
Tr::tr("Fetching is not allowed for the extension \"%1\". (You can edit "
|
||||
"permissions in Preferences > Lua.)")
|
||||
.arg(pluginName));
|
||||
});
|
||||
};
|
||||
|
||||
checkPermission(url, actualFetch, [callback, pluginName]() {
|
||||
callback(Tr::tr("Fetching is not allowed for the extension \"%1\". (You can edit "
|
||||
"permissions in Preferences > Lua.)")
|
||||
.arg(pluginName));
|
||||
});
|
||||
};
|
||||
fetch["fetch"] = wrap(fetch["fetch_cb"]);
|
||||
|
||||
fetch["fetch"] = wrap(fetch["fetch_cb"]);
|
||||
|
||||
return fetch;
|
||||
});
|
||||
return fetch;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Lua::Internal
|
||||
|
@@ -4,6 +4,8 @@
|
||||
#include "../luaengine.h"
|
||||
#include "../luatr.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/progressmanager/progressmanager.h>
|
||||
#include <coreplugin/progressmanager/taskprogress.h>
|
||||
@@ -255,7 +257,9 @@ void setupInstallModule()
|
||||
};
|
||||
|
||||
registerProvider(
|
||||
"Install", [state = State()](sol::state_view lua) mutable -> sol::object {
|
||||
"Install",
|
||||
[state = State(),
|
||||
infoBarCleaner = InfoBarCleaner()](sol::state_view lua) mutable -> sol::object {
|
||||
sol::table async
|
||||
= lua.script("return require('async')", "_install_async_").get<sol::table>();
|
||||
sol::function wrap = async["wrap"];
|
||||
@@ -281,7 +285,7 @@ void setupInstallModule()
|
||||
};
|
||||
|
||||
install["install_cb"] =
|
||||
[pluginSpec, &state](
|
||||
[pluginSpec, &state, &infoBarCleaner](
|
||||
const QString &msg,
|
||||
const sol::table &installOptions,
|
||||
const sol::function &callback) {
|
||||
@@ -354,6 +358,8 @@ void setupInstallModule()
|
||||
.withSuffix(pluginSpec->name)
|
||||
.withSuffix(QString::number(qHash(installOptionsList)));
|
||||
|
||||
infoBarCleaner.infoBarEntryAdded(infoBarId);
|
||||
|
||||
InfoBarEntry entry(infoBarId, msg, InfoBarEntry::GlobalSuppression::Enabled);
|
||||
|
||||
entry.addCustomButton(Tr::tr("Install"), [install, infoBarId]() {
|
||||
|
@@ -4,6 +4,8 @@
|
||||
#include "../luaengine.h"
|
||||
#include "../luaqttypes.h"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#include <utils/async.h>
|
||||
@@ -12,6 +14,7 @@
|
||||
#include <utils/hostosinfo.h>
|
||||
#include <utils/icon.h>
|
||||
#include <utils/id.h>
|
||||
#include <utils/infobar.h>
|
||||
#include <utils/processinterface.h>
|
||||
|
||||
#include <QDesktopServices>
|
||||
@@ -318,4 +321,10 @@ void setupUtilsModule()
|
||||
});
|
||||
}
|
||||
|
||||
InfoBarCleaner::~InfoBarCleaner()
|
||||
{
|
||||
for (const auto &id : openInfoBars)
|
||||
Core::ICore::infoBar()->removeInfo(id);
|
||||
}
|
||||
|
||||
} // namespace Lua::Internal
|
||||
|
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <utils/filepath.h>
|
||||
#include <utils/icon.h>
|
||||
#include <utils/id.h>
|
||||
|
||||
#include <QMetaEnum>
|
||||
|
||||
@@ -61,4 +62,13 @@ inline QFlags<E> tableToFlags(const sol::table &table) noexcept {
|
||||
return flags;
|
||||
}
|
||||
|
||||
class InfoBarCleaner
|
||||
{
|
||||
QList<Utils::Id> openInfoBars;
|
||||
|
||||
public:
|
||||
~InfoBarCleaner();
|
||||
void infoBarEntryAdded(const Utils::Id &id) { openInfoBars.append(id); }
|
||||
};
|
||||
|
||||
} // namespace Lua::Internal
|
||||
|
Reference in New Issue
Block a user