aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/coreplugin/plugindialog.cpp
diff options
context:
space:
mode:
authorEike Ziller <eike.ziller@qt.io>2020-01-21 14:17:05 +0100
committerEike Ziller <eike.ziller@qt.io>2020-03-23 07:39:06 +0000
commita040bebe5d61837178ae0aebddd5f8a571ff2307 (patch)
treed91bba3483db3e17497afa6c8910b51972afdb66 /src/plugins/coreplugin/plugindialog.cpp
parent6a07e2c341ba22474f47611852898736bd314f2b (diff)
Add "Install Plugin" button
Shows a wizard. Select a library file or zip file, and if you want to install in user location or Qt Creator install. For zip files it requires "unzip", "7z" or "cmake" in the PATH. Change-Id: I191079046cbd2cb6ab181bc044a00488af41b349 Reviewed-by: David Schulz <david.schulz@qt.io>
Diffstat (limited to 'src/plugins/coreplugin/plugindialog.cpp')
-rw-r--r--src/plugins/coreplugin/plugindialog.cpp320
1 files changed, 312 insertions, 8 deletions
diff --git a/src/plugins/coreplugin/plugindialog.cpp b/src/plugins/coreplugin/plugindialog.cpp
index f3dceb73a8..466850863b 100644
--- a/src/plugins/coreplugin/plugindialog.cpp
+++ b/src/plugins/coreplugin/plugindialog.cpp
@@ -29,28 +29,237 @@
#include "dialogs/restartdialog.h"
-#include <extensionsystem/pluginmanager.h>
-#include <extensionsystem/pluginview.h>
+#include <app/app_version.h>
+
#include <extensionsystem/plugindetailsview.h>
#include <extensionsystem/pluginerrorview.h>
+#include <extensionsystem/pluginmanager.h>
#include <extensionsystem/pluginspec.h>
+#include <extensionsystem/pluginview.h>
+#include <utils/algorithm.h>
+#include <utils/environment.h>
#include <utils/fancylineedit.h>
+#include <utils/infolabel.h>
+#include <utils/mimetypes/mimedatabase.h>
+#include <utils/pathchooser.h>
+#include <utils/qtcassert.h>
+#include <utils/synchronousprocess.h>
+#include <utils/wizard.h>
+#include <utils/wizardpage.h>
-#include <QVBoxLayout>
-#include <QHBoxLayout>
+#include <QButtonGroup>
#include <QCheckBox>
+#include <QDebug>
#include <QDialog>
#include <QDialogButtonBox>
-#include <QPushButton>
+#include <QDir>
+#include <QFileInfo>
+#include <QHBoxLayout>
#include <QLabel>
-#include <QDebug>
+#include <QPushButton>
+#include <QRadioButton>
+#include <QVBoxLayout>
+
+using namespace Utils;
namespace Core {
namespace Internal {
static bool s_isRestartRequired = false;
+const char kPath[] = "Path";
+const char kApplicationInstall[] = "ApplicationInstall";
+
+static bool hasLibSuffix(const QString &path)
+{
+ return HostOsInfo().isWindowsHost() && path.endsWith(".dll", Qt::CaseInsensitive)
+ || HostOsInfo().isLinuxHost() && QFileInfo(path).completeSuffix().startsWith(".so")
+ || HostOsInfo().isMacHost() && path.endsWith(".dylib");
+}
+
+static bool isZipFile(const QString &path)
+{
+ const QList<MimeType> mimeType = mimeTypesForFileName(path);
+ return anyOf(mimeType, [](const MimeType &mt) { return mt.inherits("application/zip"); });
+}
+
+struct Tool
+{
+ FilePath executable;
+ QStringList arguments;
+};
+
+static Utils::optional<Tool> unzipTool(const FilePath &src, const FilePath &dest)
+{
+ const FilePath unzip = Utils::Environment::systemEnvironment().searchInPath(
+ Utils::HostOsInfo::withExecutableSuffix("unzip"));
+ if (!unzip.isEmpty())
+ return Tool{unzip, {"-o", src.toString(), "-d", dest.toString()}};
+
+ const FilePath sevenzip = Utils::Environment::systemEnvironment().searchInPath(
+ Utils::HostOsInfo::withExecutableSuffix("7z"));
+ if (!sevenzip.isEmpty())
+ return Tool{sevenzip, {"x", QString("-o") + dest.toString(), "-y", src.toString()}};
+ return Utils::nullopt;
+
+ const FilePath cmake = Utils::Environment::systemEnvironment().searchInPath(
+ Utils::HostOsInfo::withExecutableSuffix("cmake"));
+ if (!cmake.isEmpty())
+ return Tool{cmake, {"-E", "tar", "xvf", src.toString()}};
+}
+
+class SourcePage : public WizardPage
+{
+public:
+ SourcePage(QWidget *parent)
+ : WizardPage(parent)
+ {
+ setTitle(PluginDialog::tr("Source"));
+ auto vlayout = new QVBoxLayout;
+ setLayout(vlayout);
+
+ auto label = new QLabel(
+ "<p>"
+ + PluginDialog::tr(
+ "Choose source location. This can be a plugin library file or a zip file.")
+ + "</p>");
+ label->setWordWrap(true);
+ vlayout->addWidget(label);
+
+ auto path = new PathChooser;
+ path->setExpectedKind(PathChooser::Any);
+ vlayout->addWidget(path);
+ registerFieldWithName(kPath, path, "path", SIGNAL(pathChanged(QString)));
+ connect(path, &PathChooser::pathChanged, this, &SourcePage::updateWarnings);
+
+ m_info = new InfoLabel;
+ m_info->setType(InfoLabel::Error);
+ m_info->setVisible(false);
+ vlayout->addWidget(m_info);
+ }
+
+ void updateWarnings()
+ {
+ m_info->setVisible(!isComplete());
+ emit completeChanged();
+ }
+
+ bool isComplete() const
+ {
+ const QString path = field(kPath).toString();
+ if (!QFile::exists(path)) {
+ m_info->setText(PluginDialog::tr("File does not exist."));
+ return false;
+ }
+ if (hasLibSuffix(path))
+ return true;
+
+ if (!isZipFile(path)) {
+ m_info->setText(PluginDialog::tr("File format not supported."));
+ return false;
+ }
+ if (!unzipTool({}, {})) {
+ m_info->setText(
+ PluginDialog::tr("Could not find unzip, 7z, or cmake executable in PATH."));
+ return false;
+ }
+ return true;
+ }
+
+ InfoLabel *m_info = nullptr;
+};
+
+class InstallLocationPage : public WizardPage
+{
+public:
+ InstallLocationPage(QWidget *parent)
+ : WizardPage(parent)
+ {
+ setTitle(PluginDialog::tr("Install Location"));
+ auto vlayout = new QVBoxLayout;
+ setLayout(vlayout);
+
+ auto label = new QLabel("<p>" + PluginDialog::tr("Choose install location.") + "</p>");
+ label->setWordWrap(true);
+ vlayout->addWidget(label);
+ vlayout->addSpacing(10);
+
+ auto localInstall = new QRadioButton(PluginDialog::tr("User plugins"));
+ localInstall->setChecked(true);
+ auto localLabel = new QLabel(
+ PluginDialog::tr("The plugin will be available to all compatible %1 "
+ "installations, but only for the current user.")
+ .arg(Constants::IDE_DISPLAY_NAME));
+ localLabel->setWordWrap(true);
+ localLabel->setAttribute(Qt::WA_MacSmallSize, true);
+
+ vlayout->addWidget(localInstall);
+ vlayout->addWidget(localLabel);
+ vlayout->addSpacing(10);
+
+ auto appInstall = new QRadioButton(
+ PluginDialog::tr("%1 installation").arg(Constants::IDE_DISPLAY_NAME));
+ auto appLabel = new QLabel(
+ PluginDialog::tr("The plugin will be available only to this %1 "
+ "installation, but for all users that can access it.")
+ .arg(Constants::IDE_DISPLAY_NAME));
+ appLabel->setWordWrap(true);
+ appLabel->setAttribute(Qt::WA_MacSmallSize, true);
+ vlayout->addWidget(appInstall);
+ vlayout->addWidget(appLabel);
+
+ auto group = new QButtonGroup(this);
+ group->addButton(localInstall);
+ group->addButton(appInstall);
+
+ registerFieldWithName(kApplicationInstall, this);
+ setField(kApplicationInstall, false);
+ connect(appInstall, &QRadioButton::toggled, this, [this](bool toggled) {
+ setField(kApplicationInstall, toggled);
+ });
+ }
+};
+
+static FilePath pluginInstallPath(QWizard *wizard)
+{
+ return FilePath::fromString(wizard->field(kApplicationInstall).toBool()
+ ? ICore::pluginPath()
+ : ICore::userPluginPath());
+}
+
+static FilePath pluginFilePath(QWizard *wizard)
+{
+ return FilePath::fromVariant(wizard->field(kPath));
+}
+
+class SummaryPage : public WizardPage
+{
+public:
+ SummaryPage(QWidget *parent)
+ : WizardPage(parent)
+ {
+ setTitle(PluginDialog::tr("Summary"));
+
+ auto vlayout = new QVBoxLayout;
+ setLayout(vlayout);
+
+ m_summaryLabel = new QLabel(this);
+ m_summaryLabel->setWordWrap(true);
+ vlayout->addWidget(m_summaryLabel);
+ }
+
+ void initializePage()
+ {
+ m_summaryLabel->setText(PluginDialog::tr("\"%1\" will be installed into \"%2\".")
+ .arg(pluginFilePath(wizard()).toUserOutput(),
+ pluginInstallPath(wizard()).toUserOutput()));
+ }
+
+private:
+ QLabel *m_summaryLabel;
+};
+
PluginDialog::PluginDialog(QWidget *parent)
: QDialog(parent),
m_view(new ExtensionSystem::PluginView(this))
@@ -78,6 +287,7 @@ PluginDialog::PluginDialog(QWidget *parent)
m_detailsButton = new QPushButton(tr("Details"), this);
m_errorDetailsButton = new QPushButton(tr("Error Details"), this);
m_closeButton = new QPushButton(tr("Close"), this);
+ m_installButton = new QPushButton(tr("Install Plugin..."), this);
m_detailsButton->setEnabled(false);
m_errorDetailsButton->setEnabled(false);
m_closeButton->setEnabled(true);
@@ -90,6 +300,7 @@ PluginDialog::PluginDialog(QWidget *parent)
auto hl = new QHBoxLayout;
hl->addWidget(m_detailsButton);
hl->addWidget(m_errorDetailsButton);
+ hl->addWidget(m_installButton);
hl->addSpacing(10);
hl->addWidget(m_restartRequired);
hl->addStretch(5);
@@ -110,8 +321,8 @@ PluginDialog::PluginDialog(QWidget *parent)
[this] { openDetails(m_view->currentPlugin()); });
connect(m_errorDetailsButton, &QAbstractButton::clicked,
this, &PluginDialog::openErrorDetails);
- connect(m_closeButton, &QAbstractButton::clicked,
- this, &PluginDialog::closeDialog);
+ connect(m_installButton, &QAbstractButton::clicked, this, &PluginDialog::showInstallWizard);
+ connect(m_closeButton, &QAbstractButton::clicked, this, &PluginDialog::closeDialog);
updateButtons();
}
@@ -126,6 +337,99 @@ void PluginDialog::closeDialog()
accept();
}
+static bool copyPluginFile(const FilePath &src, const FilePath &dest)
+{
+ const FilePath destFile = dest.pathAppended(src.fileName());
+ if (QFile::exists(destFile.toString())) {
+ QMessageBox box(QMessageBox::Question,
+ PluginDialog::tr("Overwrite File"),
+ PluginDialog::tr("The file \"%1\" exists. Overwrite?")
+ .arg(destFile.toUserOutput()),
+ QMessageBox::Cancel,
+ ICore::dialogParent());
+ QPushButton *acceptButton = box.addButton(PluginDialog::tr("Overwrite"), QMessageBox::AcceptRole);
+ box.setDefaultButton(acceptButton);
+ box.exec();
+ if (box.clickedButton() != acceptButton)
+ return false;
+ QFile::remove(destFile.toString());
+ }
+ QDir(dest.toString()).mkpath(".");
+ if (!QFile::copy(src.toString(), destFile.toString())) {
+ QMessageBox::warning(ICore::dialogParent(),
+ PluginDialog::tr("Failed to Write File"),
+ PluginDialog::tr("Failed to write file \"%1\".")
+ .arg(destFile.toUserOutput()));
+ return false;
+ }
+ return true;
+}
+
+static bool unzip(const FilePath &src, const FilePath &dest)
+{
+ const Utils::optional<Tool> tool = unzipTool(src, dest);
+ QTC_ASSERT(tool, return false);
+ const QString workingDirectory = dest.toFileInfo().absoluteFilePath();
+ QDir(workingDirectory).mkpath(".");
+ QMessageBox box(QMessageBox::Information,
+ PluginDialog::tr("Unzipping File"),
+ PluginDialog::tr("Unzipping \"%1\" to \"%2\".")
+ .arg(src.toUserOutput(), dest.toUserOutput()),
+ QMessageBox::Ok | QMessageBox::Cancel,
+ ICore::dialogParent());
+ box.button(QMessageBox::Ok)->setEnabled(false);
+ box.setDetailedText(
+ PluginDialog::tr("Running %1\nin \"%2\".\n\n", "Running <cmd> in <workingdirectory>")
+ .arg(CommandLine(tool->executable, tool->arguments).toUserOutput(), workingDirectory));
+ QProcess process;
+ process.setProcessChannelMode(QProcess::MergedChannels);
+ QObject::connect(&process, &QProcess::readyReadStandardOutput, &box, [&box, &process]() {
+ box.setDetailedText(box.detailedText() + QString::fromUtf8(process.readAllStandardOutput()));
+ });
+ QObject::connect(&process,
+ QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
+ [&box](int, QProcess::ExitStatus) {
+ box.button(QMessageBox::Ok)->setEnabled(true);
+ box.button(QMessageBox::Cancel)->setEnabled(false);
+ });
+ QObject::connect(&box, &QMessageBox::rejected, &process, [&process] {
+ SynchronousProcess::stopProcess(process);
+ });
+ process.setProgram(tool->executable.toString());
+ process.setArguments(tool->arguments);
+ process.setWorkingDirectory(workingDirectory);
+ process.start(QProcess::ReadOnly);
+ box.exec();
+ return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
+}
+
+void PluginDialog::showInstallWizard()
+{
+ Wizard wizard(ICore::dialogParent());
+ wizard.setWindowTitle(tr("Install Plugin"));
+
+ auto filePage = new SourcePage(&wizard);
+ wizard.addPage(filePage);
+
+ auto installLocationPage = new InstallLocationPage(&wizard);
+ wizard.addPage(installLocationPage);
+
+ auto summaryPage = new SummaryPage(&wizard);
+ wizard.addPage(summaryPage);
+
+ if (wizard.exec()) {
+ const FilePath path = pluginFilePath(&wizard);
+ const FilePath installPath = pluginInstallPath(&wizard);
+ if (hasLibSuffix(path.toString())) {
+ if (copyPluginFile(path, installPath))
+ updateRestartRequired();
+ } else if (isZipFile(path.toString())) {
+ if (unzip(path, installPath))
+ updateRestartRequired();
+ }
+ }
+}
+
void PluginDialog::updateRestartRequired()
{
// just display the notice all the time after once changing something