Plugins: Add skeleton for new ExtensionManager plugin
This initial commit is merely a mockup of a ExtensionManager concept. Change-Id: I19b2285667678a86097c043cc27a554545559eff Reviewed-by: Alessandro Portale <alessandro.portale@qt.io> Reviewed-by: hjk <hjk@qt.io>
@@ -4,6 +4,7 @@ add_subdirectory(coreplugin)
|
||||
# Level 1: (only depends of Level 0)
|
||||
add_subdirectory(texteditor)
|
||||
add_subdirectory(serialterminal)
|
||||
add_subdirectory(extensionmanager)
|
||||
add_subdirectory(helloworld)
|
||||
add_subdirectory(imageviewer)
|
||||
add_subdirectory(marketplace)
|
||||
|
||||
12
src/plugins/extensionmanager/CMakeLists.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
add_qtc_plugin(ExtensionManager
|
||||
PLUGIN_DEPENDS Core
|
||||
SOURCES
|
||||
extensionmanager.qrc
|
||||
extensionmanagerconstants.h
|
||||
extensionmanagerplugin.cpp
|
||||
extensionmanagertr.h
|
||||
extensionmanagerwidget.cpp
|
||||
extensionmanagerwidget.h
|
||||
extensionsbrowser.cpp
|
||||
extensionsbrowser.h
|
||||
)
|
||||
20
src/plugins/extensionmanager/ExtensionManager.json.in
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"Name" : "ExtensionManager",
|
||||
"Version" : "${IDE_VERSION}",
|
||||
"CompatVersion" : "${IDE_VERSION_COMPAT}",
|
||||
"Vendor" : "The Qt Company Ltd",
|
||||
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",
|
||||
"License" : [ "Commercial Usage",
|
||||
"",
|
||||
"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.",
|
||||
"",
|
||||
"GNU General Public License Usage",
|
||||
"",
|
||||
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
|
||||
],
|
||||
"Category" : "Core",
|
||||
"Description" : "Extension Manager",
|
||||
"Experimental": true,
|
||||
"Url" : "http://www.qt.io",
|
||||
${IDE_PLUGIN_DEPENDENCIES}
|
||||
}
|
||||
18
src/plugins/extensionmanager/extensionmanager.qbs
Normal file
@@ -0,0 +1,18 @@
|
||||
import qbs 1.0
|
||||
|
||||
QtcPlugin {
|
||||
name: "ExtensionManager"
|
||||
|
||||
Depends { name: "Core" }
|
||||
|
||||
files: [
|
||||
"extensionmanager.qrc",
|
||||
"extensionmanagerconstants.h",
|
||||
"extensionmanagerplugin.cpp",
|
||||
"extensionmanagertr.h",
|
||||
"extensionmanagerwidget.cpp",
|
||||
"extensionmanagerwidget.h",
|
||||
"extensionsbrowser.cpp",
|
||||
"extensionsbrowser.h",
|
||||
]
|
||||
}
|
||||
10
src/plugins/extensionmanager/extensionmanager.qrc
Normal file
@@ -0,0 +1,10 @@
|
||||
<RCC>
|
||||
<qresource prefix="/extensionmanager">
|
||||
<file>images/extensionsmall.png</file>
|
||||
<file>images/extensionsmall@2x.png</file>
|
||||
<file>images/mode_extensionmanager_mask.png</file>
|
||||
<file>images/mode_extensionmanager_mask@2x.png</file>
|
||||
<file>images/packsmall.png</file>
|
||||
<file>images/packsmall@2x.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
11
src/plugins/extensionmanager/extensionmanagerconstants.h
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace ExtensionManager::Constants {
|
||||
|
||||
const char MODE_EXTENSIONMANAGER[] = "ExtensionManager";
|
||||
const char C_EXTENSIONMANAGER[] = "ExtensionManager";
|
||||
|
||||
} // ExtensionManager::Constant
|
||||
91
src/plugins/extensionmanager/extensionmanagerplugin.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "extensionmanagertr.h"
|
||||
|
||||
#include "extensionmanagerconstants.h"
|
||||
#include "extensionmanagerwidget.h"
|
||||
|
||||
#include <coreplugin/actionmanager/actioncontainer.h>
|
||||
#include <coreplugin/actionmanager/actionmanager.h>
|
||||
#include <coreplugin/actionmanager/command.h>
|
||||
#include <coreplugin/coreconstants.h>
|
||||
#include <coreplugin/icontext.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/imode.h>
|
||||
#include <coreplugin/modemanager.h> // TODO: Remove!
|
||||
|
||||
#include <extensionsystem/iplugin.h>
|
||||
#include <extensionsystem/pluginspec.h>
|
||||
|
||||
#include <utils/icon.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/styledbar.h>
|
||||
|
||||
#include <QAction>
|
||||
#include <QMainWindow>
|
||||
|
||||
using namespace ExtensionSystem;
|
||||
using namespace Core;
|
||||
using namespace Utils;
|
||||
|
||||
namespace ExtensionManager::Internal {
|
||||
|
||||
class ExtensionManagerMode final : public IMode
|
||||
{
|
||||
public:
|
||||
ExtensionManagerMode()
|
||||
{
|
||||
setObjectName("ExtensionManagerMode");
|
||||
setId(Constants::C_EXTENSIONMANAGER);
|
||||
setContext(Context(Constants::MODE_EXTENSIONMANAGER));
|
||||
setDisplayName(Tr::tr("Extensions"));
|
||||
const Icon FLAT({{":/extensionmanager/images/mode_extensionmanager_mask.png",
|
||||
Theme::IconsBaseColor}});
|
||||
const Icon FLAT_ACTIVE({{":/extensionmanager/images/mode_extensionmanager_mask.png",
|
||||
Theme::IconsModeWelcomeActiveColor}});
|
||||
setIcon(Utils::Icon::modeIcon(FLAT, FLAT, FLAT_ACTIVE));
|
||||
setPriority(72);
|
||||
|
||||
using namespace Layouting;
|
||||
auto widget = Column {
|
||||
new StyledBar,
|
||||
new ExtensionManagerWidget,
|
||||
noMargin, spacing(0),
|
||||
}.emerge();
|
||||
|
||||
setWidget(widget);
|
||||
}
|
||||
|
||||
~ExtensionManagerMode() { delete widget(); }
|
||||
};
|
||||
|
||||
class ExtensionManagerPlugin final : public ExtensionSystem::IPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "ExtensionManager.json")
|
||||
|
||||
public:
|
||||
~ExtensionManagerPlugin() final
|
||||
{
|
||||
delete m_mode;
|
||||
}
|
||||
|
||||
void initialize() final
|
||||
{
|
||||
m_mode = new ExtensionManagerMode;
|
||||
}
|
||||
|
||||
bool delayedInitialize() final
|
||||
{
|
||||
ModeManager::activateMode(m_mode->id()); // TODO: Remove!
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
ExtensionManagerMode *m_mode = nullptr;
|
||||
};
|
||||
|
||||
} // ExtensionManager::Internal
|
||||
|
||||
#include "extensionmanagerplugin.moc"
|
||||
15
src/plugins/extensionmanager/extensionmanagertr.h
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
namespace ExtensionManager {
|
||||
|
||||
struct Tr
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(QtC::ExtensionManager)
|
||||
};
|
||||
|
||||
} // ExtensionManager
|
||||
305
src/plugins/extensionmanager/extensionmanagerwidget.cpp
Normal file
@@ -0,0 +1,305 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "extensionmanagertr.h"
|
||||
|
||||
#include "extensionmanagerconstants.h"
|
||||
#include "extensionmanagerwidget.h"
|
||||
#include "extensionsbrowser.h"
|
||||
#include "utils/algorithm.h"
|
||||
|
||||
#include <coreplugin/coreconstants.h>
|
||||
#include <coreplugin/icontext.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/iwelcomepage.h>
|
||||
#include <coreplugin/modemanager.h> // TODO: Remove!
|
||||
#include <coreplugin/welcomepagehelper.h>
|
||||
|
||||
#include <extensionsystem/pluginspec.h>
|
||||
|
||||
#include <utils/icon.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/stylehelper.h>
|
||||
#include <utils/utilsicons.h>
|
||||
|
||||
#include <QAction>
|
||||
#include <QTextBrowser>
|
||||
|
||||
using namespace Core;
|
||||
using namespace Utils;
|
||||
|
||||
namespace ExtensionManager::Internal {
|
||||
|
||||
static QWidget *createVr(QWidget *parent = nullptr)
|
||||
{
|
||||
auto vr = new QWidget(parent);
|
||||
vr->setFixedWidth(1);
|
||||
setBackgroundColor(vr, Theme::Token_Stroke_Subtle);
|
||||
return vr;
|
||||
}
|
||||
|
||||
class CollapsingWidget : public QWidget
|
||||
{
|
||||
public:
|
||||
explicit CollapsingWidget(QWidget *parent = nullptr)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
|
||||
}
|
||||
|
||||
void setWidth(int width)
|
||||
{
|
||||
m_width = width;
|
||||
setVisible(width > 0);
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
QSize sizeHint() const override
|
||||
{
|
||||
return {m_width, 0};
|
||||
}
|
||||
|
||||
private:
|
||||
int m_width = 100;
|
||||
};
|
||||
|
||||
ExtensionManagerWidget::ExtensionManagerWidget()
|
||||
{
|
||||
m_leftColumn = new ExtensionsBrowser;
|
||||
|
||||
auto descriptionColumns = new QWidget;
|
||||
|
||||
m_secondarDescriptionWidget = new CollapsingWidget;
|
||||
|
||||
m_primaryDescription = new QTextBrowser;
|
||||
m_primaryDescription->setOpenExternalLinks(true);
|
||||
m_primaryDescription->setFrameStyle(QFrame::NoFrame);
|
||||
|
||||
m_secondaryDescription = new QTextBrowser;
|
||||
m_secondaryDescription->setFrameStyle(QFrame::NoFrame);
|
||||
|
||||
using namespace Layouting;
|
||||
Row {
|
||||
createVr(),
|
||||
m_secondaryDescription,
|
||||
noMargin(), spacing(0),
|
||||
}.attachTo(m_secondarDescriptionWidget);
|
||||
|
||||
Row {
|
||||
createVr(),
|
||||
Row {
|
||||
m_primaryDescription,
|
||||
noMargin(),
|
||||
},
|
||||
m_secondarDescriptionWidget,
|
||||
noMargin(), spacing(0),
|
||||
}.attachTo(descriptionColumns);
|
||||
|
||||
Row {
|
||||
Space(WelcomePageHelpers::HSpacing),
|
||||
m_leftColumn,
|
||||
descriptionColumns,
|
||||
noMargin(), spacing(0),
|
||||
}.attachTo(this);
|
||||
|
||||
setBackgroundColor(this, Theme::Token_Background_Default);
|
||||
|
||||
connect(m_leftColumn, &ExtensionsBrowser::itemSelected,
|
||||
this, &ExtensionManagerWidget::updateView);
|
||||
connect(this, &ResizeSignallingWidget::resized, this,
|
||||
[this] (const QSize &size, const QSize &oldSize) {
|
||||
const int intendedLeftColumnWidth = size.width() - 580;
|
||||
m_leftColumn->adjustToWidth(intendedLeftColumnWidth);
|
||||
const bool secondaryDescriptionVisible = size.width() > 970;
|
||||
const int secondaryDescriptionWidth = secondaryDescriptionVisible ? 264 : 0;
|
||||
m_secondarDescriptionWidget->setWidth(secondaryDescriptionWidth);
|
||||
});
|
||||
updateView({}, {});
|
||||
}
|
||||
|
||||
void ExtensionManagerWidget::updateView(const QModelIndex ¤t,
|
||||
[[maybe_unused]] const QModelIndex &previous)
|
||||
{
|
||||
const QString h5Css =
|
||||
StyleHelper::fontToCssProperties(StyleHelper::uiFont(StyleHelper::UiElementH5))
|
||||
+ "; margin-top: 28px;";
|
||||
const QString h6Css =
|
||||
StyleHelper::fontToCssProperties(StyleHelper::uiFont(StyleHelper::UiElementH6))
|
||||
+ "; margin-top: 28px;";
|
||||
const QString h6CapitalCss =
|
||||
StyleHelper::fontToCssProperties(StyleHelper::uiFont(StyleHelper::UiElementH6Capital))
|
||||
+ QString::fromLatin1("; color: %1;")
|
||||
.arg(creatorTheme()->color(Theme::Token_Text_Muted).name());
|
||||
const QString bodyStyle = QString::fromLatin1("color: %1; background-color: %2;"
|
||||
"margin-left: %3px; margin-right: %3px;")
|
||||
.arg(creatorTheme()->color(Theme::Token_Text_Default).name())
|
||||
.arg(creatorTheme()->color(Theme::Token_Background_Muted).name())
|
||||
.arg(WelcomePageHelpers::HSpacing);
|
||||
const QString htmlStart = QString(R"(
|
||||
<html>
|
||||
<body style="%1">
|
||||
)").arg(bodyStyle);
|
||||
const QString htmlEnd = QString(R"(
|
||||
</body></html>
|
||||
)");
|
||||
|
||||
if (!current.isValid()) {
|
||||
const QString emptyHtml = htmlStart + htmlEnd;
|
||||
m_primaryDescription->setText(emptyHtml);
|
||||
m_secondaryDescription->setText(emptyHtml);
|
||||
return;
|
||||
}
|
||||
|
||||
const ItemData data = itemData(current);
|
||||
const bool isPack = data.type == ItemTypePack;
|
||||
const ExtensionSystem::PluginSpec *extension = data.plugins.first();
|
||||
|
||||
{
|
||||
const QString shortDescription =
|
||||
isPack ? QLatin1String("Short description for pack ") + data.name
|
||||
: extension->description();
|
||||
QString longDescription =
|
||||
isPack ? QLatin1String("Some longer text that describes the purpose and functionality "
|
||||
"of the extensions that are part of pack ") + data.name
|
||||
: extension->longDescription();
|
||||
longDescription.replace("\n", "<br/>");
|
||||
const QString location = isPack ? extension->location() : extension->filePath();
|
||||
|
||||
QString description = htmlStart;
|
||||
|
||||
description.append(QString(R"(
|
||||
<div style="%1"><br/>%2</div>
|
||||
<p>%3</p>
|
||||
)").arg(h5Css)
|
||||
.arg(shortDescription)
|
||||
.arg(longDescription));
|
||||
|
||||
description.append(QString(R"(
|
||||
<div style="%1">%2</div>
|
||||
<p>%3</p>
|
||||
)").arg(h6Css)
|
||||
.arg(Tr::tr("Get started"))
|
||||
.arg(Tr::tr("Install the extension from above. Installation starts automatically. "
|
||||
"You can always uninstall the extension afterwards.")));
|
||||
|
||||
description.append(QString(R"(
|
||||
<div style="%1">%2</div>
|
||||
<p>
|
||||
<a href="%3">%4 ></a>
|
||||
<br/>
|
||||
<a href="%5">%6 ></a>
|
||||
</p>
|
||||
)").arg(h6Css)
|
||||
.arg(Tr::tr("More information"))
|
||||
.arg(Tr::tr("Online Documentation"))
|
||||
.arg("https://doc.qt.io/qtcreator/")
|
||||
.arg(Tr::tr("Tutorials"))
|
||||
.arg("https://doc.qt.io/qtcreator/creator-tutorials.html"));
|
||||
|
||||
const QString examplesBoxCss =
|
||||
QString::fromLatin1("height: 168px; background-color: %1; ")
|
||||
.arg(creatorTheme()->color(Theme::Token_Background_Default).name());
|
||||
description.append(QString(R"(
|
||||
<div style="%1">%2</div>
|
||||
<p style="%3">
|
||||
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
|
||||
</p>
|
||||
)").arg(h6CapitalCss)
|
||||
.arg(Tr::tr("Examples"))
|
||||
.arg(examplesBoxCss));
|
||||
|
||||
const QString captionStrongCss = StyleHelper::fontToCssProperties(
|
||||
StyleHelper::uiFont(StyleHelper::UiElementCaptionStrong));
|
||||
description.append(QString(R"(
|
||||
<div style="%1">%2</div>
|
||||
<p>
|
||||
<table>
|
||||
<tr><td style="%3">%4</td><td>%5</td></tr>
|
||||
<tr><td style="%3">%6</td><td>%7</td></tr>
|
||||
<tr><td style="%3">%8</td><td>%9</td></tr>
|
||||
</table>
|
||||
</p>
|
||||
)").arg(h6Css)
|
||||
.arg(Tr::tr("Extension library details"))
|
||||
.arg(captionStrongCss)
|
||||
.arg(Tr::tr("Size"))
|
||||
.arg("547 MB")
|
||||
.arg(Tr::tr("Version"))
|
||||
.arg(extension->version())
|
||||
.arg(Tr::tr("Location"))
|
||||
.arg(location));
|
||||
|
||||
description.append(htmlEnd);
|
||||
m_primaryDescription->setText(description);
|
||||
}
|
||||
|
||||
{
|
||||
QString description = htmlStart;
|
||||
|
||||
description.append(QString(R"(
|
||||
<p style="%1"><br/>%2</p>
|
||||
)").arg(h6CapitalCss)
|
||||
.arg(Tr::tr("Extension details")));
|
||||
|
||||
description.append(QString(R"(
|
||||
<div style="%1">%2</div>
|
||||
<p>%3</p>
|
||||
)").arg(h6Css)
|
||||
.arg(Tr::tr("Released"))
|
||||
.arg("23.5.2023"));
|
||||
|
||||
const QString tagTemplate = QString(R"(
|
||||
<td style="border: 1px solid %1; padding: 3px; ">%2</td>
|
||||
)").arg(creatorTheme()->color(Theme::Token_Stroke_Subtle).name());
|
||||
const QStringList tags = Utils::transform(data.tags,
|
||||
[&tagTemplate] (const QString &tag) {
|
||||
return tagTemplate.arg(tag);
|
||||
});
|
||||
description.append(QString(R"(
|
||||
<div style="%1">%2</div>
|
||||
<p>%3</p>
|
||||
)").arg(h6Css)
|
||||
.arg(Tr::tr("Related tags"))
|
||||
.arg(tags.join(" ")));
|
||||
|
||||
description.append(QString(R"(
|
||||
<div style="%1">%2</div>
|
||||
<p>
|
||||
macOS<br/>
|
||||
Windows<br/>
|
||||
Linux
|
||||
</p>
|
||||
)").arg(h6Css)
|
||||
.arg(Tr::tr("Platforms")));
|
||||
|
||||
QStringList dependencies;
|
||||
for (const ExtensionSystem::PluginSpec *spec : data.plugins) {
|
||||
dependencies.append(Utils::transform(spec->dependencies(),
|
||||
&ExtensionSystem::PluginDependency::toString));
|
||||
}
|
||||
dependencies.removeDuplicates();
|
||||
dependencies.sort();
|
||||
description.append(QString(R"(
|
||||
<div style="%1">%2</div>
|
||||
<p>%3</p>
|
||||
)").arg(h6Css)
|
||||
.arg(Tr::tr("Dependencies"))
|
||||
.arg(dependencies.isEmpty() ? "-" : dependencies.join("<br/>")));
|
||||
|
||||
if (isPack) {
|
||||
const QStringList extensions = Utils::transform(data.plugins,
|
||||
&ExtensionSystem::PluginSpec::name);
|
||||
description.append(QString(R"(
|
||||
<div style="%1">%2</div>
|
||||
<p>%3</p>
|
||||
)").arg(h6Css)
|
||||
.arg(Tr::tr("Exentsions in pack"))
|
||||
.arg(extensions.join("<br/>")));
|
||||
}
|
||||
|
||||
description.append(htmlEnd);
|
||||
m_secondaryDescription->setText(description);
|
||||
}
|
||||
}
|
||||
|
||||
} // ExtensionManager::Internal
|
||||
33
src/plugins/extensionmanager/extensionmanagerwidget.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "extensionmanagertr.h"
|
||||
|
||||
#include "extensionmanagerconstants.h"
|
||||
|
||||
#include <coreplugin/welcomepagehelper.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QTextBrowser;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace ExtensionManager::Internal {
|
||||
|
||||
class CollapsingWidget;
|
||||
class ExtensionsBrowser;
|
||||
|
||||
class ExtensionManagerWidget final : public Core::ResizeSignallingWidget
|
||||
{
|
||||
public:
|
||||
explicit ExtensionManagerWidget();
|
||||
|
||||
private:
|
||||
void updateView(const QModelIndex ¤t, [[maybe_unused]] const QModelIndex &previous);
|
||||
|
||||
ExtensionsBrowser *m_leftColumn;
|
||||
CollapsingWidget *m_secondarDescriptionWidget;
|
||||
QTextBrowser *m_primaryDescription;
|
||||
QTextBrowser *m_secondaryDescription;
|
||||
};
|
||||
|
||||
} // ExtensionManager::Internal
|
||||
524
src/plugins/extensionmanager/extensionsbrowser.cpp
Normal file
@@ -0,0 +1,524 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "extensionmanagertr.h"
|
||||
|
||||
#include "extensionsbrowser.h"
|
||||
|
||||
#include <coreplugin/coreconstants.h>
|
||||
#include <coreplugin/icontext.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/welcomepagehelper.h>
|
||||
|
||||
#include <extensionsystem/iplugin.h>
|
||||
#include <extensionsystem/pluginspec.h>
|
||||
#include <extensionsystem/pluginview.h>
|
||||
#include <extensionsystem/pluginmanager.h>
|
||||
|
||||
#include <utils/fancylineedit.h>
|
||||
#include <utils/icon.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/stylehelper.h>
|
||||
|
||||
#include <QItemDelegate>
|
||||
#include <QLabel>
|
||||
#include <QListView>
|
||||
#include <QMessageBox>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QStandardItemModel>
|
||||
#include <QStyle>
|
||||
|
||||
using namespace ExtensionSystem;
|
||||
using namespace Core;
|
||||
using namespace Utils;
|
||||
|
||||
namespace ExtensionManager::Internal {
|
||||
|
||||
Q_LOGGING_CATEGORY(browserLog, "qtc.extensionmanager.browser", QtWarningMsg)
|
||||
|
||||
using PluginSpecList = QList<const PluginSpec *>;
|
||||
using Tags = QStringList;
|
||||
|
||||
constexpr QSize itemSize = {330, 86};
|
||||
constexpr int gapSize = 2 * WelcomePageHelpers::GridItemGap;
|
||||
constexpr QSize cellSize = {itemSize.width() + gapSize, itemSize.height() + gapSize};
|
||||
|
||||
enum Role {
|
||||
RoleName = Qt::UserRole,
|
||||
RoleItemType,
|
||||
RoleTags,
|
||||
RolePluginSpecs,
|
||||
RoleSearchText,
|
||||
};
|
||||
|
||||
ItemData itemData(const QModelIndex &index)
|
||||
{
|
||||
return {
|
||||
index.data(RoleName).toString(),
|
||||
index.data(RoleItemType).value<ItemType>(),
|
||||
index.data(RoleTags).toStringList(),
|
||||
index.data(RolePluginSpecs).value<PluginSpecList>(),
|
||||
};
|
||||
}
|
||||
|
||||
void setBackgroundColor(QWidget *widget, Theme::Color colorRole)
|
||||
{
|
||||
QPalette palette = creatorTheme()->palette();
|
||||
palette.setColor(QPalette::Window,
|
||||
creatorTheme()->color(colorRole));
|
||||
widget->setPalette(palette);
|
||||
widget->setBackgroundRole(QPalette::Window);
|
||||
widget->setAutoFillBackground(true);
|
||||
}
|
||||
|
||||
static QColor colorForExtensionName(const QString &name)
|
||||
{
|
||||
const size_t hash = qHash(name);
|
||||
return QColor::fromHsv(hash % 360, 180, 110);
|
||||
}
|
||||
|
||||
static QStandardItemModel *extensionsModel()
|
||||
{
|
||||
// The new extensions concept renames plugins to extensions and adds "packs" which are
|
||||
// groups of extensions.
|
||||
//
|
||||
// TODO: The "meta data" here which is injected into the model is only a place holder that
|
||||
// helps exploring the upcoming extensions concept.
|
||||
//
|
||||
// Before this loses the WIP prefix, we should at least have a concrete idea of how the data
|
||||
// is structured and where it lives. Ideally, it continues to reside exclusively in the
|
||||
// extension meta data.
|
||||
//
|
||||
// The grouping of extensions into packs could be done via extension tag. Extensions and will
|
||||
// receive tags and if possible screen shots.
|
||||
// Packs will also have a complete set of meta data. That could be an accumulation of the data
|
||||
// of the contained extensions. Or simply the data from the "first" extension in a pack.
|
||||
|
||||
static const char tagBuildTools[] = "Build Tools";
|
||||
static const char tagCodeAnalyzing[] = "Code Analyzing";
|
||||
static const char tagConnectivity[] = "Connectivity";
|
||||
static const char tagCore[] = "Core";
|
||||
static const char tagCpp[] = "C++";
|
||||
static const char tagEditorConvenience[] = "Editor Convenience";
|
||||
static const char tagEditor[] = "Editor";
|
||||
static const char tagEssentials[] = "Essentials";
|
||||
static const char tagGlsl[] = "GLSL";
|
||||
static const char tagPackageManager[] = "Package Manager";
|
||||
static const char tagPlatformSupport[] = "Platform Support";
|
||||
static const char tagProgrammingLanguage[] = "Programming Language";
|
||||
static const char tagPython[] = "Python";
|
||||
static const char tagQml[] = "QML";
|
||||
static const char tagQuick[] = "Quick";
|
||||
static const char tagService[] = "Service";
|
||||
static const char tagTestAutomation[] = "Test Automation";
|
||||
static const char tagUiEditor[] = "Visual UI Editor" ;
|
||||
static const char tagVersionControl[] = "Version Control";
|
||||
static const char tagVisualEditor[] = "Visual editor";
|
||||
static const char tagWidgets[] = "Widgets";
|
||||
|
||||
static const char tagTagUndefined[] = "Tag undefined";
|
||||
|
||||
static const struct {
|
||||
const QString name;
|
||||
const QStringList extensions;
|
||||
const Tags tags;
|
||||
} packs[] = {
|
||||
{"Core",
|
||||
{"Core", "Help", "ProjectExplorer", "TextEditor", "Welcome", "GenericProjectManager",
|
||||
"QtSupport"},
|
||||
{tagCore}
|
||||
},
|
||||
{"Core (from Installer)",
|
||||
{"LicenseChecker", "Marketplace", "UpdateInfo"},
|
||||
{tagCore}
|
||||
},
|
||||
{"Essentials",
|
||||
{"Bookmarks", "BinEditor", "Debugger", "DiffEditor", "ImageViewer", "Macros",
|
||||
"LanguageClient", "ResourceEditor"},
|
||||
{tagEssentials}
|
||||
},
|
||||
{"C++ Language support",
|
||||
{"ClangCodeModel", "ClangFormat", "ClassView", "CppEditor"},
|
||||
{tagProgrammingLanguage, tagCpp}
|
||||
},
|
||||
{"QML Language Support (Qt Quick libraries)",
|
||||
{"QmlJSEditor", "QmlJSTools", "QmlPreview", "QmlProfiler", "QmlProjectManager"},
|
||||
{tagProgrammingLanguage, tagQml}
|
||||
},
|
||||
{"Visual QML UI Editor",
|
||||
{"QmlDesigner", "QmlDesignerBase"},
|
||||
{tagUiEditor, tagQml, tagQuick}
|
||||
},
|
||||
{"Visual C++ Widgets UI Editor",
|
||||
{"Designer"},
|
||||
{tagUiEditor, tagCpp, tagWidgets}
|
||||
},
|
||||
};
|
||||
|
||||
static const struct {
|
||||
const QString name;
|
||||
const Tags tags;
|
||||
} extensions[] = {
|
||||
{"GLSLEditor", {tagProgrammingLanguage, tagGlsl}},
|
||||
{"Nim", {tagProgrammingLanguage}},
|
||||
{"Python", {tagProgrammingLanguage, tagPython}},
|
||||
{"Haskell", {tagProgrammingLanguage}},
|
||||
|
||||
{"ModelEditor", {tagVisualEditor}},
|
||||
{"ScxmlEditor", {tagVisualEditor}},
|
||||
|
||||
{"Bazaar", {tagVersionControl}},
|
||||
{"CVS", {tagVersionControl}},
|
||||
{"ClearCase", {tagVersionControl}},
|
||||
{"Fossil", {tagVersionControl}},
|
||||
{"Git", {tagVersionControl}},
|
||||
{"Mercurial", {tagVersionControl}},
|
||||
{"Perforce", {tagVersionControl}},
|
||||
{"Subversion", {tagVersionControl}},
|
||||
{"VcsBase", {tagVersionControl}},
|
||||
{"GitLab", {tagVersionControl, tagService}},
|
||||
|
||||
{"AutoTest", {tagTestAutomation}},
|
||||
{"Squish", {tagTestAutomation}},
|
||||
{"Coco", {tagTestAutomation}},
|
||||
|
||||
{"Vcpkg", {tagPackageManager}},
|
||||
{"Conan", {tagPackageManager}},
|
||||
|
||||
{"Copilot", {tagEditorConvenience}},
|
||||
{"EmacsKeys", {tagEditorConvenience}},
|
||||
{"FakeVim", {tagEditorConvenience}},
|
||||
{"Terminal", {tagEditorConvenience}},
|
||||
{"Todo", {tagEditorConvenience}},
|
||||
{"CodePaster", {tagEditorConvenience}},
|
||||
{"Beautifier", {tagEditorConvenience}},
|
||||
|
||||
{"SerialTerminal", {tagConnectivity}},
|
||||
|
||||
{"SilverSearcher", {tagEditor}},
|
||||
|
||||
{"AutotoolsProjectManager", {tagBuildTools}},
|
||||
{"CMakeProjectManager", {tagBuildTools}},
|
||||
{"CompilationDatabaseProjectManager", {tagBuildTools}},
|
||||
{"IncrediBuild", {tagBuildTools}},
|
||||
{"MesonProjectManager", {tagBuildTools}},
|
||||
{"QbsProjectManager", {tagBuildTools}},
|
||||
{"QmakeProjectManager", {tagBuildTools}},
|
||||
|
||||
{"Axivion", {tagCodeAnalyzing}},
|
||||
{"ClangTools", {tagCodeAnalyzing}},
|
||||
{"Cppcheck", {tagCodeAnalyzing}},
|
||||
{"CtfVisualizer", {tagCodeAnalyzing}},
|
||||
{"PerfProfiler", {tagCodeAnalyzing}},
|
||||
{"Valgrind", {tagCodeAnalyzing}},
|
||||
|
||||
{"Android", {tagPlatformSupport}},
|
||||
{"BareMetal", {tagPlatformSupport}},
|
||||
{"Boot2Qt", {tagPlatformSupport}},
|
||||
{"Ios", {tagPlatformSupport}},
|
||||
{"McuSupport", {tagPlatformSupport}},
|
||||
{"Qnx", {tagPlatformSupport}},
|
||||
{"RemoteLinux", {tagPlatformSupport}},
|
||||
{"SafeRenderer", {tagPlatformSupport}},
|
||||
{"VxWorks", {tagPlatformSupport}},
|
||||
{"WebAssembly", {tagPlatformSupport}},
|
||||
{"Docker", {tagPlatformSupport}},
|
||||
|
||||
// Missing in Kimmo's excel sheet:
|
||||
{"CompilerExplorer", {tagTagUndefined}},
|
||||
{"ExtensionManager", {tagTagUndefined}},
|
||||
{"ScreenRecorder", {tagTagUndefined}},
|
||||
};
|
||||
|
||||
QList<QStandardItem*> items;
|
||||
QStringList expectedExtensions;
|
||||
QStringList unexpectedExtensions;
|
||||
QHash<const QString, const PluginSpec*> installedPlugins;
|
||||
for (const PluginSpec *ps : PluginManager::plugins()) {
|
||||
installedPlugins.insert(ps->name(), ps);
|
||||
unexpectedExtensions.append(ps->name());
|
||||
}
|
||||
|
||||
const auto handleExtension = [&] (const ItemData &extension, bool addToBrowser) {
|
||||
if (!installedPlugins.contains(extension.name)) {
|
||||
expectedExtensions.append(extension.name);
|
||||
return false;
|
||||
}
|
||||
unexpectedExtensions.removeOne(extension.name);
|
||||
|
||||
if (addToBrowser) {
|
||||
QStandardItem *item = new QStandardItem;
|
||||
const PluginSpecList pluginSpecs = {installedPlugins.value(extension.name)};
|
||||
item->setData(ItemTypeExtension, RoleItemType);
|
||||
item->setData(QVariant::fromValue(extension.tags), RoleTags);
|
||||
item->setData(QVariant::fromValue<PluginSpecList>(pluginSpecs), RolePluginSpecs);
|
||||
item->setData(extension.name, RoleName);
|
||||
items.append(item);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const bool addPackedExtensionsToBrowser = true; // TODO: Determine how we want this. As setting?
|
||||
for (const auto &pack : packs) {
|
||||
PluginSpecList pluginSpecs;
|
||||
for (const QString &extension : pack.extensions) {
|
||||
const ItemData extensionData = {extension, {}, pack.tags, {}};
|
||||
if (!handleExtension(extensionData, addPackedExtensionsToBrowser))
|
||||
continue;
|
||||
pluginSpecs.append(installedPlugins.value(extension));
|
||||
}
|
||||
if (pluginSpecs.isEmpty())
|
||||
continue;
|
||||
|
||||
QStandardItem *item = new QStandardItem;
|
||||
item->setData(ItemTypePack, RoleItemType);
|
||||
item->setData(QVariant::fromValue(pack.tags), RoleTags);
|
||||
item->setData(QVariant::fromValue<PluginSpecList>(pluginSpecs), RolePluginSpecs);
|
||||
item->setData(pack.name, RoleName);
|
||||
items.append(item);
|
||||
}
|
||||
|
||||
for (const auto &extension : extensions) {
|
||||
const ItemData extensionData = {extension.name, {}, extension.tags, {}};
|
||||
handleExtension(extensionData, true);
|
||||
}
|
||||
|
||||
QStandardItemModel *result = new QStandardItemModel;
|
||||
for (auto item : items) {
|
||||
QStringList searchTexts;
|
||||
searchTexts.append(item->data(RoleName).toString());
|
||||
searchTexts.append(item->data(RoleTags).toStringList());
|
||||
const PluginSpecList pluginSpecs = item->data(RolePluginSpecs).value<PluginSpecList>();
|
||||
for (auto pluginSpec : pluginSpecs) {
|
||||
searchTexts.append(pluginSpec->name());
|
||||
searchTexts.append(pluginSpec->description());
|
||||
searchTexts.append(pluginSpec->longDescription());
|
||||
searchTexts.append(pluginSpec->category());
|
||||
searchTexts.append(pluginSpec->copyright());
|
||||
}
|
||||
searchTexts.removeDuplicates();
|
||||
item->setData(searchTexts.join(" "), RoleSearchText);
|
||||
|
||||
item->setDragEnabled(false);
|
||||
item->setEditable(false);
|
||||
|
||||
result->appendRow(item);
|
||||
}
|
||||
|
||||
if (browserLog().isDebugEnabled()) {
|
||||
if (!expectedExtensions.isEmpty())
|
||||
qCDebug(browserLog) << "Expected extensions/plugins are not installed:"
|
||||
<< expectedExtensions.join(", ");
|
||||
if (!unexpectedExtensions.isEmpty())
|
||||
qCDebug(browserLog) << "Unexpected extensions/plugins are installed:"
|
||||
<< unexpectedExtensions.join(", ");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
class ExtensionItemDelegate : public QItemDelegate
|
||||
{
|
||||
public:
|
||||
explicit ExtensionItemDelegate(QObject *parent = nullptr)
|
||||
: QItemDelegate(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
|
||||
const override
|
||||
{
|
||||
painter->save();
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
const ItemData data = itemData(index);
|
||||
const bool isPack = data.type == ItemTypePack;
|
||||
const QRectF itemRect(option.rect.topLeft(), itemSize);
|
||||
{
|
||||
const bool selected = option.state & QStyle::State_Selected;
|
||||
const bool hovered = option.state & QStyle::State_MouseOver;
|
||||
constexpr qreal strokeWidth = 1;
|
||||
constexpr qreal shrink = strokeWidth / 2;
|
||||
const QRectF itemRectAdjusted = itemRect.adjusted(shrink, shrink, -shrink, -shrink);
|
||||
constexpr qreal rounding = 4.5;
|
||||
QPainterPath itemOutlinePath;
|
||||
itemOutlinePath.addRoundedRect(itemRectAdjusted, rounding, rounding);
|
||||
const QColor fillColor = creatorTheme()->color(hovered ? Theme::Token_Background_Hover
|
||||
: Theme::Token_Background_Muted);
|
||||
const QColor strokeColor = creatorTheme()->color(selected ? Theme::Token_Stroke_Strong
|
||||
: Theme::Token_Stroke_Subtle);
|
||||
painter->setBrush(fillColor);
|
||||
painter->setPen(strokeColor);
|
||||
painter->drawPath(itemOutlinePath);
|
||||
}
|
||||
{
|
||||
constexpr QRectF bigCircle(16, 16, 48, 48);
|
||||
constexpr double gradientMargin = 0.14645;
|
||||
const QRectF bigCircleLocal = bigCircle.translated(itemRect.topLeft());
|
||||
QPainterPath bigCirclePath;
|
||||
bigCirclePath.addEllipse(bigCircleLocal);
|
||||
QLinearGradient gradient(bigCircleLocal.topLeft(), bigCircleLocal.bottomRight());
|
||||
const QColor startColor = isPack ? qRgb(0x1e, 0x99, 0x6e)
|
||||
: colorForExtensionName(data.name);
|
||||
const QColor endColor = isPack ? qRgb(0x07, 0x6b, 0x6d) : startColor.lighter(150);
|
||||
gradient.setColorAt(gradientMargin, startColor);
|
||||
gradient.setColorAt(1 - gradientMargin, endColor);
|
||||
painter->fillPath(bigCirclePath, gradient);
|
||||
|
||||
static const QIcon packIcon =
|
||||
Icon({{":/extensionmanager/images/packsmall.png",
|
||||
Theme::Token_Text_Default}}, Icon::Tint).icon();
|
||||
static const QIcon extensionIcon =
|
||||
Icon({{":/extensionmanager/images/extensionsmall.png",
|
||||
Theme::Token_Text_Default}}, Icon::Tint).icon();
|
||||
QRectF iconRect(0, 0, 32, 32);
|
||||
iconRect.moveCenter(bigCircleLocal.center());
|
||||
(isPack ? packIcon : extensionIcon).paint(painter, iconRect.toRect());
|
||||
}
|
||||
if (isPack) {
|
||||
constexpr QRectF smallCircle(47, 50, 18, 18);
|
||||
constexpr qreal strokeWidth = 1;
|
||||
constexpr qreal shrink = strokeWidth / 2;
|
||||
constexpr QRectF smallCircleAdjusted = smallCircle.adjusted(shrink, shrink,
|
||||
-shrink, -shrink);
|
||||
const QRectF smallCircleLocal = smallCircleAdjusted.translated(itemRect.topLeft());
|
||||
const QColor fillColor = creatorTheme()->color(Theme::Token_Background_Hover);
|
||||
const QColor strokeColor = creatorTheme()->color(Theme::Token_Stroke_Subtle);
|
||||
painter->setBrush(fillColor);
|
||||
painter->setPen(strokeColor);
|
||||
painter->drawEllipse(smallCircleLocal);
|
||||
|
||||
painter->setFont(StyleHelper::uiFont(StyleHelper::UiElementCaptionStrong));
|
||||
const QColor textColor = creatorTheme()->color(Theme::Token_Text_Default);
|
||||
painter->setPen(textColor);
|
||||
painter->drawText(smallCircleLocal, QString::number(data.plugins.count()),
|
||||
QTextOption(Qt::AlignCenter));
|
||||
}
|
||||
{
|
||||
constexpr int textX = 80;
|
||||
constexpr int rightMargin = 2 * WelcomePageHelpers::ItemGap;
|
||||
constexpr int maxTextWidth = itemSize.width() - textX - rightMargin;
|
||||
constexpr Qt::TextElideMode elideMode = Qt::ElideRight;
|
||||
|
||||
constexpr int titleY = 30;
|
||||
const QPointF titleOrigin(itemRect.topLeft() + QPointF(textX, titleY));
|
||||
painter->setPen(creatorTheme()->color(Theme::Token_Text_Default));
|
||||
painter->setFont(StyleHelper::uiFont(StyleHelper::UiElementH6));
|
||||
const QString titleElided = painter->fontMetrics().elidedText(
|
||||
data.name, elideMode, maxTextWidth);
|
||||
painter->drawText(titleOrigin, titleElided);
|
||||
|
||||
constexpr int copyrightY = 52;
|
||||
const QPointF copyrightOrigin(itemRect.topLeft() + QPointF(textX, copyrightY));
|
||||
painter->setPen(creatorTheme()->color(Theme::Token_Text_Muted));
|
||||
painter->setFont(StyleHelper::uiFont(StyleHelper::UiElementCaptionStrong));
|
||||
const QString copyrightElided = painter->fontMetrics().elidedText(
|
||||
data.plugins.first()->copyright(), elideMode, maxTextWidth);
|
||||
painter->drawText(copyrightOrigin, copyrightElided);
|
||||
|
||||
constexpr int tagsY = 70;
|
||||
const QPointF tagsOrigin(itemRect.topLeft() + QPointF(textX, tagsY));
|
||||
const QString tags = data.tags.join(", ");
|
||||
painter->setPen(creatorTheme()->color(Theme::Token_Text_Default));
|
||||
painter->setFont(StyleHelper::uiFont(StyleHelper::UiElementCaption));
|
||||
const QString tagsElided = painter->fontMetrics().elidedText(
|
||||
tags, elideMode, maxTextWidth);
|
||||
painter->drawText(tagsOrigin, tagsElided);
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
QSize sizeHint([[maybe_unused]] const QStyleOptionViewItem &option,
|
||||
[[maybe_unused]] const QModelIndex &index) const override
|
||||
{
|
||||
return cellSize;
|
||||
}
|
||||
};
|
||||
|
||||
ExtensionsBrowser::ExtensionsBrowser()
|
||||
{
|
||||
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
|
||||
|
||||
auto manageLabel = new QLabel(Tr::tr("Manage Extensions"));
|
||||
manageLabel->setFont(StyleHelper::uiFont(StyleHelper::UiElementH1));
|
||||
|
||||
m_searchBox = new Core::SearchBox;
|
||||
m_searchBox->setFixedWidth(itemSize.width());
|
||||
|
||||
m_updateButton = new WelcomePageButton;
|
||||
m_updateButton->setText(Tr::tr("Install..."));
|
||||
|
||||
m_filterProxyModel = new QSortFilterProxyModel(this);
|
||||
m_filterProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
m_filterProxyModel->setFilterRole(RoleSearchText);
|
||||
m_filterProxyModel->setSortRole(RoleItemType);
|
||||
|
||||
m_extensionsView = new QListView;
|
||||
m_extensionsView->setFrameStyle(QFrame::NoFrame);
|
||||
m_extensionsView->setItemDelegate(new ExtensionItemDelegate(this));
|
||||
m_extensionsView->setResizeMode(QListView::Adjust);
|
||||
m_extensionsView->setSelectionMode(QListView::SingleSelection);
|
||||
m_extensionsView->setUniformItemSizes(true);
|
||||
m_extensionsView->setViewMode(QListView::IconMode);
|
||||
m_extensionsView->setModel(m_filterProxyModel);
|
||||
m_extensionsView->setMouseTracking(true);
|
||||
|
||||
using namespace Layouting;
|
||||
Column {
|
||||
Space(15),
|
||||
manageLabel,
|
||||
Space(15),
|
||||
Row { m_searchBox, st, m_updateButton, Space(extraListViewWidth() + gapSize) },
|
||||
Space(gapSize),
|
||||
m_extensionsView,
|
||||
noMargin(), spacing(0),
|
||||
}.attachTo(this);
|
||||
|
||||
setBackgroundColor(this, Theme::Token_Background_Default);
|
||||
setBackgroundColor(m_extensionsView, Theme::Token_Background_Default);
|
||||
setBackgroundColor(m_extensionsView->viewport(), Theme::Token_Background_Default);
|
||||
|
||||
auto updateModel = [this] {
|
||||
m_model.reset(extensionsModel());
|
||||
m_filterProxyModel->setSourceModel(m_model.data());
|
||||
m_filterProxyModel->sort(0);
|
||||
|
||||
if (m_selectionModel == nullptr) {
|
||||
m_selectionModel = new QItemSelectionModel(m_filterProxyModel, m_extensionsView);
|
||||
m_extensionsView->setSelectionModel(m_selectionModel);
|
||||
connect(m_extensionsView->selectionModel(), &QItemSelectionModel::currentChanged,
|
||||
this, &ExtensionsBrowser::itemSelected);
|
||||
}
|
||||
};
|
||||
|
||||
connect(ExtensionSystem::PluginManager::instance(),
|
||||
&ExtensionSystem::PluginManager::pluginsChanged, this, updateModel);
|
||||
connect(m_searchBox->m_lineEdit, &Utils::FancyLineEdit::textChanged,
|
||||
m_filterProxyModel, &QSortFilterProxyModel::setFilterWildcard);
|
||||
}
|
||||
|
||||
void ExtensionsBrowser::adjustToWidth(const int width)
|
||||
{
|
||||
const int widthForItems = width - extraListViewWidth();
|
||||
m_columnsCount = qMax(1, qFloor(widthForItems / cellSize.width()));
|
||||
m_updateButton->setVisible(m_columnsCount > 1);
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
QSize ExtensionsBrowser::sizeHint() const
|
||||
{
|
||||
const int columsWidth = m_columnsCount * cellSize.width();
|
||||
return { columsWidth + extraListViewWidth(), 0};
|
||||
}
|
||||
|
||||
int ExtensionsBrowser::extraListViewWidth() const
|
||||
{
|
||||
// TODO: Investigate "transient" scrollbar, just for this list view.
|
||||
return m_extensionsView->style()->pixelMetric(QStyle::PM_ScrollBarExtent)
|
||||
+ 1; // Needed
|
||||
}
|
||||
|
||||
} // ExtensionManager::Internal
|
||||
72
src/plugins/extensionmanager/extensionsbrowser.h
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utils/theme/theme.h>
|
||||
|
||||
#include <QStandardItemModel>
|
||||
#include <QWidget>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QItemSelectionModel;
|
||||
class QListView;
|
||||
class QSortFilterProxyModel;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace ExtensionSystem
|
||||
{
|
||||
class PluginSpec;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
class SearchBox;
|
||||
class WelcomePageButton;
|
||||
}
|
||||
|
||||
namespace ExtensionManager::Internal {
|
||||
|
||||
using PluginSpecList = QList<const ExtensionSystem::PluginSpec *>;
|
||||
using Tags = QStringList;
|
||||
|
||||
enum ItemType {
|
||||
ItemTypePack,
|
||||
ItemTypeExtension,
|
||||
};
|
||||
|
||||
struct ItemData {
|
||||
const QString name;
|
||||
const ItemType type = ItemTypeExtension;
|
||||
const Tags tags;
|
||||
const PluginSpecList plugins;
|
||||
};
|
||||
|
||||
ItemData itemData(const QModelIndex &index);
|
||||
void setBackgroundColor(QWidget *widget, Utils::Theme::Color colorRole);
|
||||
|
||||
class ExtensionsBrowser final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ExtensionsBrowser();
|
||||
|
||||
void adjustToWidth(const int width);
|
||||
QSize sizeHint() const override;
|
||||
|
||||
signals:
|
||||
void itemSelected(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
|
||||
private:
|
||||
int extraListViewWidth() const; // Space for scrollbar, etc.
|
||||
|
||||
QScopedPointer<QStandardItemModel> m_model;
|
||||
Core::SearchBox *m_searchBox;
|
||||
Core::WelcomePageButton *m_updateButton;
|
||||
QListView *m_extensionsView;
|
||||
QItemSelectionModel *m_selectionModel = nullptr;
|
||||
QSortFilterProxyModel *m_filterProxyModel;
|
||||
int m_columnsCount = 2;
|
||||
};
|
||||
|
||||
} // ExtensionManager::Internal
|
||||
BIN
src/plugins/extensionmanager/images/extensionsmall.png
Normal file
|
After Width: | Height: | Size: 164 B |
BIN
src/plugins/extensionmanager/images/extensionsmall@2x.png
Normal file
|
After Width: | Height: | Size: 196 B |
|
After Width: | Height: | Size: 210 B |
|
After Width: | Height: | Size: 259 B |
BIN
src/plugins/extensionmanager/images/packsmall.png
Normal file
|
After Width: | Height: | Size: 271 B |
BIN
src/plugins/extensionmanager/images/packsmall@2x.png
Normal file
|
After Width: | Height: | Size: 454 B |
@@ -37,6 +37,7 @@ Project {
|
||||
"designer/designer.qbs",
|
||||
"diffeditor/diffeditor.qbs",
|
||||
"docker/docker.qbs",
|
||||
"extensionmanager/extensionmanager.qbs",
|
||||
"fakevim/fakevim.qbs",
|
||||
"fossil/fossil.qbs",
|
||||
"emacskeys/emacskeys.qbs",
|
||||
|
||||
@@ -645,6 +645,14 @@
|
||||
width="16"
|
||||
id="backgroundRect"
|
||||
style="display:inline;fill:#ffffff" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#backgroundRect"
|
||||
id="backgroundRect_32"
|
||||
width="100%"
|
||||
height="100%"
|
||||
transform="matrix(2,0,0,2,0,-492)" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
@@ -3741,6 +3749,43 @@
|
||||
d="m 550,427 v 28 h 32 v -28 z m 18,15.388 V 447 h -4 v -4.612 c -1.172,-0.7 -2,-1.92 -2,-3.388 0,-2.212 1.792,-4 4,-4 2.208,0 4,1.788 4,4 0,1.464 -0.828,2.688 -2,3.388 z"
|
||||
id="lockbody-1" />
|
||||
</g>
|
||||
<g
|
||||
id="src/plugins/extensionmanager/images/packsmall">
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#backgroundRect_32"
|
||||
id="use2597"
|
||||
transform="translate(32,28)"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<path
|
||||
id="path6795"
|
||||
d="m 16,423.5 v 11 M 26,418 16,423.5 6,418 m 0,11.5 10,5.5 10,-5.5 v -11 L 16,413 6,418.5 Z"
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linejoin:round"
|
||||
sodipodi:nodetypes="cccccccccccc" />
|
||||
</g>
|
||||
<g
|
||||
id="src/plugins/extensionmanager/images/extensionsmall">
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#backgroundRect_32"
|
||||
id="use2600"
|
||||
transform="translate(64,28)"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<path
|
||||
id="path4530"
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linejoin:round"
|
||||
d="M 49.5,422 H 56 Z M 38,424 h 5.5 z m 11.5,2 H 56 Z"
|
||||
sodipodi:nodetypes="ccccccccc" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1"
|
||||
d="m 50,420 h -3.5 c -1.5,0 -4,0.5 -4,4 0,3.5 2.5,4 4,4 H 50 c 1.5,0 2,-1 2,-1.5 v -5 C 52,421 51.5,420 50,420 Z"
|
||||
id="path4682"
|
||||
sodipodi:nodetypes="cczccccc" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
@@ -8501,6 +8546,48 @@
|
||||
d="m 359,45 c -5.522,0 -10,4.478 -10,10 0,5.522 4.478,10 10,10 5.522,0 10,-4.478 10,-10 0,-5.522 -4.478,-10 -10,-10 z m 1.136,16.182 c -0.274,0.269 -0.604,0.402 -0.989,0.402 -0.187,0 -0.364,-0.035 -0.533,-0.105 -0.169,-0.07 -0.318,-0.163 -0.446,-0.28 -0.129,-0.117 -0.23,-0.257 -0.307,-0.42 -0.076,-0.163 -0.113,-0.338 -0.113,-0.525 0,-0.373 0.137,-0.694 0.411,-0.962 0.273,-0.268 0.603,-0.402 0.988,-0.402 0.374,0 0.7,0.128 0.98,0.385 0.28,0.257 0.42,0.572 0.42,0.945 0,0.372 -0.138,0.693 -0.411,0.962 z m 2.738,-7.98 c -0.104,0.292 -0.245,0.56 -0.42,0.805 -0.175,0.245 -0.376,0.479 -0.604,0.7 -0.228,0.222 -0.47,0.455 -0.727,0.7 -0.163,0.151 -0.303,0.286 -0.42,0.402 -0.116,0.117 -0.213,0.236 -0.288,0.359 -0.076,0.122 -0.132,0.262 -0.167,0.42 -0.034,0.157 -0.052,0.347 -0.052,0.569 v 0.665 h -2.101 V 56.86 c 0,-0.256 0.015,-0.475 0.044,-0.656 0.029,-0.181 0.081,-0.344 0.157,-0.49 0.076,-0.146 0.175,-0.286 0.298,-0.42 0.122,-0.134 0.271,-0.295 0.446,-0.481 l 1.348,-1.365 c 0.291,-0.292 0.438,-0.67 0.438,-1.137 0,-0.455 -0.149,-0.825 -0.446,-1.111 -0.298,-0.286 -0.674,-0.429 -1.129,-0.429 -0.49,0 -0.893,0.166 -1.208,0.499 -0.314,0.333 -0.495,0.738 -0.542,1.216 l -2.24,-0.175 c 0.07,-0.56 0.216,-1.059 0.438,-1.496 0.222,-0.438 0.511,-0.808 0.866,-1.111 0.355,-0.303 0.768,-0.534 1.234,-0.691 0.466,-0.158 0.979,-0.236 1.54,-0.236 0.524,0 1.012,0.076 1.461,0.228 0.449,0.152 0.84,0.371 1.172,0.656 0.333,0.286 0.593,0.642 0.779,1.067 0.186,0.426 0.28,0.913 0.28,1.461 10e-4,0.382 -0.052,0.721 -0.157,1.013 z"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="src/plugins/extensionmanager/images/mode_extensionmanager_mask">
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#backgroundRect"
|
||||
id="use2"
|
||||
transform="matrix(2.125,0,0,2.125,102,-552.5)"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<g
|
||||
id="g12500"
|
||||
style="fill:none;stroke:#000000;stroke-width:2"
|
||||
transform="translate(1,1)">
|
||||
<rect
|
||||
id="rect5239"
|
||||
width="6"
|
||||
height="6"
|
||||
x="75"
|
||||
y="381"
|
||||
ry="1.5" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#rect5239"
|
||||
id="use6202"
|
||||
transform="translate(12)"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#rect5239"
|
||||
id="use6284"
|
||||
transform="translate(0,12)"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<path
|
||||
id="path12068"
|
||||
d="m 90,392 v 8 m -4,-4 h 8" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-595,297)"
|
||||
style="display:inline"
|
||||
|
||||
|
Before Width: | Height: | Size: 366 KiB After Width: | Height: | Size: 368 KiB |
@@ -22,6 +22,9 @@ int main(int argc, char *argv[])
|
||||
{ StyleHelper::UiElementH2, "H2" },
|
||||
{ StyleHelper::UiElementH3, "H3" },
|
||||
{ StyleHelper::UiElementH4, "H4" },
|
||||
{ StyleHelper::UiElementH5, "H5" },
|
||||
{ StyleHelper::UiElementH6, "H6" },
|
||||
{ StyleHelper::UiElementH6Capital, "H6 CAPITAL" },
|
||||
{ StyleHelper::UiElementCaptionStrong, "Caption strong" },
|
||||
{ StyleHelper::UiElementCaption, "Caption" },
|
||||
};
|
||||
|
||||