aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorhjk <hjk@qt.io>2017-01-06 21:41:46 +0100
committerhjk <hjk@qt.io>2017-01-13 15:38:14 +0000
commit16944277d2aa2cdab194ec05de6c8e2d5ee814cd (patch)
tree65be67b94d693eaf5874880aa60241015acc8c6c /src
parent36d1a75118ad31861b64272716d189090e0b4e4d (diff)
Welcome: Start some performance improvements
This is essentially a widgets based re-implementation of the current design. It is still using the QAIM based interface layer between to the real data and display even though this is not needed with this approach. Removal of this layer would further reduce code size and cycle counts. For now: old new Load time 215ms 182ms delete 22ms 2ms Change-Id: I90d779a60a47a78399eaad0f1bc032d39f3ae3c0 Reviewed-by: Eike Ziller <eike.ziller@qt.io> Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/plugins/coreplugin/iwelcomepage.cpp168
-rw-r--r--src/plugins/coreplugin/iwelcomepage.h39
-rw-r--r--src/plugins/projectexplorer/projectwelcomepage.cpp466
-rw-r--r--src/plugins/projectexplorer/projectwelcomepage.h14
-rw-r--r--src/plugins/qtsupport/exampleslistmodel.cpp5
-rw-r--r--src/plugins/qtsupport/gettingstartedwelcomepage.cpp728
-rw-r--r--src/plugins/qtsupport/gettingstartedwelcomepage.h31
-rw-r--r--src/plugins/qtsupport/qtsupportplugin.cpp9
-rw-r--r--src/plugins/welcome/Welcome.json.in2
-rw-r--r--src/plugins/welcome/welcome.pro9
-rw-r--r--src/plugins/welcome/welcome.qbs7
-rw-r--r--src/plugins/welcome/welcome_dependencies.pri1
-rw-r--r--src/plugins/welcome/welcomeplugin.cpp502
-rw-r--r--src/plugins/welcome/welcomeplugin.h55
14 files changed, 1435 insertions, 601 deletions
diff --git a/src/plugins/coreplugin/iwelcomepage.cpp b/src/plugins/coreplugin/iwelcomepage.cpp
index 2b14353847a..78a7f554a4f 100644
--- a/src/plugins/coreplugin/iwelcomepage.cpp
+++ b/src/plugins/coreplugin/iwelcomepage.cpp
@@ -25,8 +25,19 @@
#include "iwelcomepage.h"
+#include "icore.h"
+
+#include <utils/icon.h>
+#include <utils/theme/theme.h>
+
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QMetaEnum>
+#include <QPixmap>
#include <QUrl>
+using namespace Utils;
+
namespace Core {
IWelcomePage::IWelcomePage()
@@ -37,4 +48,159 @@ IWelcomePage::~IWelcomePage()
{
}
-} // namespace Utils
+int IWelcomePage::screenDependHeightDistance()
+{
+ return std::min(50, std::max(16, ICore::mainWindow()->height() / 30));
+}
+
+static QPalette buttonPalette(bool isActive, bool isCursorInside, bool forText)
+{
+ QPalette pal;
+ Theme *theme = Utils::creatorTheme();
+ if (isActive) {
+ if (forText) {
+ pal.setColor(QPalette::Background, theme->color(Theme::Welcome_ForegroundPrimaryColor));
+ pal.setColor(QPalette::Foreground, theme->color(Theme::Welcome_ForegroundPrimaryColor));
+ pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_BackgroundColor));
+ } else {
+ pal.setColor(QPalette::Background, theme->color(Theme::Welcome_ForegroundPrimaryColor));
+ pal.setColor(QPalette::Foreground, theme->color(Theme::Welcome_ForegroundPrimaryColor));
+ }
+ } else {
+ if (isCursorInside) {
+ if (forText) {
+ pal.setColor(QPalette::Background, theme->color(Theme::Welcome_HoverColor));
+ pal.setColor(QPalette::Foreground, theme->color(Theme::Welcome_HoverColor));
+ pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_TextColor));
+ } else {
+ pal.setColor(QPalette::Background, theme->color(Theme::Welcome_HoverColor));
+ pal.setColor(QPalette::Foreground, theme->color(Theme::Welcome_ForegroundSecondaryColor));
+ }
+ } else {
+ if (forText) {
+ pal.setColor(QPalette::Background, theme->color(Theme::Welcome_ForegroundPrimaryColor));
+ pal.setColor(QPalette::Foreground, theme->color(Theme::Welcome_BackgroundColor));
+ pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_TextColor));
+ } else {
+ pal.setColor(QPalette::Background, theme->color(Theme::Welcome_BackgroundColor));
+ pal.setColor(QPalette::Foreground, theme->color(Theme::Welcome_ForegroundSecondaryColor));
+ }
+ }
+ }
+ return pal;
+}
+
+class WelcomePageButtonPrivate
+{
+public:
+ WelcomePageButtonPrivate(WelcomePageButton *parent) : q(parent) {}
+ bool isActive() const;
+ void doUpdate(bool cursorInside);
+
+ WelcomePageButton *q;
+ QHBoxLayout *m_layout;
+ QLabel *m_label;
+ QLabel *m_icon = nullptr;
+
+ std::function<void()> onClicked;
+ std::function<bool()> activeChecker;
+};
+
+WelcomePageButton::WelcomePageButton(QWidget *parent)
+ : QFrame(parent), d(new WelcomePageButtonPrivate(this))
+{
+ setAutoFillBackground(true);
+ setFrameShape(QFrame::Box);
+ setFrameShadow(QFrame::Plain);
+ setPalette(buttonPalette(false, false, false));
+
+ QFont f = font();
+ f.setPixelSize(15);
+ d->m_label = new QLabel(this);
+ d->m_label->setFont(f);
+ d->m_label->setPalette(buttonPalette(false, false, true));
+
+ d->m_layout = new QHBoxLayout;
+ d->m_layout->setContentsMargins(13, 5, 20, 5);
+ d->m_layout->setSpacing(0);
+ d->m_layout->addWidget(d->m_label);
+ setLayout(d->m_layout);
+}
+
+WelcomePageButton::~WelcomePageButton()
+{
+ delete d;
+}
+
+void WelcomePageButton::mousePressEvent(QMouseEvent *)
+{
+ if (d->onClicked)
+ d->onClicked();
+}
+
+void WelcomePageButton::enterEvent(QEvent *)
+{
+ d->doUpdate(true);
+}
+
+void WelcomePageButton::leaveEvent(QEvent *)
+{
+ d->doUpdate(false);
+}
+
+bool WelcomePageButtonPrivate::isActive() const
+{
+ return activeChecker && activeChecker();
+}
+
+void WelcomePageButtonPrivate::doUpdate(bool cursorInside)
+{
+ const bool active = isActive();
+ q->setPalette(buttonPalette(active, cursorInside, false));
+ const QPalette lpal = buttonPalette(active, cursorInside, true);
+ m_label->setPalette(lpal);
+ if (m_icon)
+ m_icon->setPalette(lpal);
+ q->update();
+}
+
+void WelcomePageButton::setText(const QString &text)
+{
+ d->m_label->setText(text);
+}
+
+void WelcomePageButton::setIcon(const QPixmap &pixmap)
+{
+ if (!d->m_icon) {
+ d->m_icon = new QLabel(this);
+ d->m_layout->insertWidget(0, d->m_icon);
+ d->m_layout->insertSpacing(1, 10);
+ }
+ d->m_icon->setPixmap(pixmap);
+}
+
+void WelcomePageButton::setActiveChecker(const std::function<bool ()> &value)
+{
+ d->activeChecker = value;
+}
+
+void WelcomePageButton::recheckActive()
+{
+ bool isActive = d->isActive();
+ d->doUpdate(isActive);
+}
+
+void WelcomePageButton::click()
+{
+ if (d->onClicked)
+ d->onClicked();
+}
+
+void WelcomePageButton::setOnClicked(const std::function<void ()> &value)
+{
+ d->onClicked = value;
+ if (d->isActive())
+ click();
+}
+
+} // namespace Core
diff --git a/src/plugins/coreplugin/iwelcomepage.h b/src/plugins/coreplugin/iwelcomepage.h
index 521af86f6ae..36bbabd270a 100644
--- a/src/plugins/coreplugin/iwelcomepage.h
+++ b/src/plugins/coreplugin/iwelcomepage.h
@@ -29,10 +29,14 @@
#include "id.h"
+#include <QFrame>
#include <QObject>
-#include <QUrl>
-QT_FORWARD_DECLARE_CLASS(QQmlEngine)
+#include <functional>
+
+QT_BEGIN_NAMESPACE
+class QPixmap;
+QT_END_NAMESPACE
namespace Core {
@@ -41,20 +45,41 @@ class CORE_EXPORT IWelcomePage : public QObject
Q_OBJECT
Q_PROPERTY(QString title READ title CONSTANT)
- Q_PROPERTY(QUrl pageLocation READ pageLocation CONSTANT)
Q_PROPERTY(int priority READ priority CONSTANT)
- Q_PROPERTY(bool hasSearchBar READ hasSearchBar CONSTANT)
public:
IWelcomePage();
virtual ~IWelcomePage();
- virtual QUrl pageLocation() const = 0;
virtual QString title() const = 0;
virtual int priority() const { return 0; }
- virtual void facilitateQml(QQmlEngine *) {}
- virtual bool hasSearchBar() const { return false; }
virtual Core::Id id() const = 0;
+ virtual QWidget *createWidget() const = 0;
+
+ static int screenDependHeightDistance();
+};
+
+class WelcomePageButtonPrivate;
+
+class CORE_EXPORT WelcomePageButton : public QFrame
+{
+public:
+ WelcomePageButton(QWidget *parent);
+ ~WelcomePageButton();
+
+ void mousePressEvent(QMouseEvent *) override;
+ void enterEvent(QEvent *) override;
+ void leaveEvent(QEvent *) override;
+
+ void setText(const QString &text);
+ void setIcon(const QPixmap &pixmap);
+ void setOnClicked(const std::function<void ()> &value);
+ void setActiveChecker(const std::function<bool ()> &value);
+ void recheckActive();
+ void click();
+
+private:
+ WelcomePageButtonPrivate *d;
};
} // Core
diff --git a/src/plugins/projectexplorer/projectwelcomepage.cpp b/src/plugins/projectexplorer/projectwelcomepage.cpp
index 07794ef07c8..a26249475c3 100644
--- a/src/plugins/projectexplorer/projectwelcomepage.cpp
+++ b/src/plugins/projectexplorer/projectwelcomepage.cpp
@@ -24,18 +24,36 @@
****************************************************************************/
#include "projectwelcomepage.h"
+#include "session.h"
#include "sessionmodel.h"
#include "projectexplorer.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/iwizardfactory.h>
+#include <utils/algorithm.h>
#include <utils/fileutils.h>
#include <utils/stringutils.h>
-#include <utils/algorithm.h>
+#include <utils/theme/theme.h>
-#include <QQmlContext>
-#include <QQmlEngine>
+#include <QAbstractItemDelegate>
+#include <QAction>
+#include <QBoxLayout>
+#include <QDir>
+#include <QFileInfo>
+#include <QHeaderView>
+#include <QHelpEvent>
+#include <QLabel>
+#include <QPainter>
+#include <QToolTip>
+#include <QTreeView>
+
+using namespace Core;
+using namespace Utils;
namespace ProjectExplorer {
namespace Internal {
@@ -88,30 +106,12 @@ void ProjectModel::resetProjects()
///////////////////
-void ProjectWelcomePage::facilitateQml(QQmlEngine *engine)
-{
- m_sessionModel = new SessionModel(this);
- m_projectModel = new ProjectModel(this);
-
- QQmlContext *ctx = engine->rootContext();
- ctx->setContextProperty(QLatin1String("sessionList"), m_sessionModel);
- ctx->setContextProperty(QLatin1String("projectList"), m_projectModel);
- ctx->setContextProperty(QLatin1String("projectWelcomePage"), this);
-}
-
-QUrl ProjectWelcomePage::pageLocation() const
-{
- // normalize paths so QML doesn't freak out if it's wrongly capitalized on Windows
- const QString resourcePath = Utils::FileUtils::normalizePathName(Core::ICore::resourcePath());
- return QUrl::fromLocalFile(resourcePath + QLatin1String("/welcomescreen/develop.qml"));
-}
-
Core::Id ProjectWelcomePage::id() const
{
return "Develop";
}
-void ProjectWelcomePage::reloadWelcomeScreenData()
+void ProjectWelcomePage::reloadWelcomeScreenData() const
{
if (m_sessionModel)
m_sessionModel->resetSessions();
@@ -129,5 +129,427 @@ void ProjectWelcomePage::openProject()
ProjectExplorerPlugin::openOpenProjectDialog();
}
+///////////////////
+
+static QColor themeColor(Theme::Color role)
+{
+ return Utils::creatorTheme()->color(role);
+}
+
+static QFont sizedFont(int size, const QWidget *widget, bool underline = false)
+{
+ QFont f = widget->font();
+ f.setPixelSize(size);
+ f.setUnderline(underline);
+ return f;
+}
+
+static QPixmap pixmap(const QString &id, const Theme::Color &color)
+{
+ const QString fileName = QString(":/welcome/images/%1.png").arg(id);
+ return Icon({{fileName, color}}, Icon::Tint).pixmap();
+}
+
+class SessionDelegate : public QAbstractItemDelegate
+{
+public:
+ SessionDelegate() {
+ const int actionsCount = 9;
+ Context welcomeContext(Core::Constants::C_WELCOME_MODE);
+
+ const Id sessionBase = "Welcome.OpenSession";
+ for (int i = 1; i <= actionsCount; ++i) {
+ auto act = new QAction(tr("Open Session #%1").arg(i), this);
+ Command *cmd = ActionManager::registerAction(act, sessionBase.withSuffix(i), welcomeContext);
+ cmd->setDefaultKeySequence(QKeySequence((UseMacShortcuts ? tr("Ctrl+Meta+%1") : tr("Ctrl+Alt+%1")).arg(i)));
+ m_sessionShortcuts.append(cmd->keySequence().toString(QKeySequence::NativeText));
+
+// connect(act, &QAction::triggered, this, [this, i] { openSessionTriggered(i-1); });
+ connect(cmd, &Command::keySequenceChanged, this, [this, i, cmd] {
+ m_sessionShortcuts[i-1] = cmd->keySequence().toString(QKeySequence::NativeText);
+// emit sessionsShortcutsChanged(m_sessionShortcuts);
+ });
+ }
+ }
+
+ void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const final
+ {
+ static const QPixmap arrowUp = pixmap("expandarrow",Theme::Welcome_ForegroundSecondaryColor);
+ static const QPixmap arrowDown = QPixmap::fromImage(arrowUp.toImage().mirrored(false, true));
+ static const QPixmap sessionIcon = pixmap("session", Theme::Welcome_ForegroundSecondaryColor);
+
+ const QRect rc = option.rect;
+ const QString sessionName = idx.data(Qt::DisplayRole).toString();
+
+ const QPoint mousePos = option.widget->mapFromGlobal(QCursor::pos());
+ //const bool hovered = option.state & QStyle::State_MouseOver;
+ const bool hovered = option.rect.contains(mousePos);
+ const bool expanded = m_expandedSessions.contains(sessionName);
+ painter->fillRect(rc, hovered || expanded ? hoverColor : backgroundColor);
+
+ const int x = rc.x();
+ const int x1 = x + 36;
+ const int y = rc.y();
+ const int firstBase = y + 18;
+
+ painter->drawPixmap(x + 11, y + 5, sessionIcon);
+
+ if (hovered || expanded)
+ painter->drawPixmap(rc.right() - 16, y + 5, expanded ? arrowDown : arrowUp);
+
+ if (idx.row() < 9) {
+ painter->setPen(foregroundColor2);
+ painter->setFont(sizedFont(10, option.widget));
+ painter->drawText(x + 3, firstBase, QString::number(idx.row() + 1));
+ }
+
+ const bool isLastSession = idx.data(SessionModel::LastSessionRole).toBool();
+ const bool isActiveSession = idx.data(SessionModel::ActiveSessionRole).toBool();
+ const bool isDefaultVirgin = SessionManager::isDefaultVirgin();
+
+ QString fullSessionName = sessionName;
+ if (isLastSession && isDefaultVirgin)
+ fullSessionName = ProjectWelcomePage::tr("%1 (last session)").arg(fullSessionName);
+ if (isActiveSession && !isDefaultVirgin)
+ fullSessionName = ProjectWelcomePage::tr("%1 (current session)").arg(fullSessionName);
+
+ const QRect switchRect = QRect(x, y, rc.width() - 20, firstBase + 3 - y);
+ const bool switchActive = switchRect.contains(mousePos);
+ painter->setPen(linkColor);
+ painter->setFont(sizedFont(12, option.widget, switchActive));
+ painter->drawText(x1, firstBase, fullSessionName);
+ if (switchActive)
+ m_activeSwitchToRect = switchRect;
+
+ if (expanded) {
+ painter->setPen(textColor);
+ painter->setFont(sizedFont(12, option.widget));
+ const QStringList projects = SessionManager::projectsForSessionName(sessionName);
+ int yy = firstBase + 25;
+ QFontMetrics fm(option.widget->font());
+ for (const QString &project : projects) {
+ // Project name.
+ QFileInfo fi(project);
+ QString completeBase = fi.completeBaseName();
+ painter->setPen(textColor);
+ painter->drawText(x1, yy, completeBase);
+ yy += 18;
+
+ // Project path.
+ QString pathWithTilde = Utils::withTildeHomePath(QDir::toNativeSeparators(project));
+ painter->setPen(foregroundColor1);
+ painter->drawText(x1, yy, fm.elidedText(pathWithTilde, Qt::ElideMiddle, rc.width() - 40));
+ yy += 22;
+ }
+
+ yy += 5;
+ int xx = x1;
+ const QStringList actions = {
+ ProjectWelcomePage::tr("Clone"),
+ ProjectWelcomePage::tr("Rename"),
+ ProjectWelcomePage::tr("Delete")
+ };
+ for (int i = 0; i < 3; ++i) {
+ const QString &action = actions.at(i);
+ const int ww = fm.width(action);
+ const QRect actionRect(xx, yy - 10, ww, 15);
+ const bool isActive = actionRect.contains(mousePos);
+ painter->setPen(linkColor);
+ painter->setFont(sizedFont(12, option.widget, isActive));
+ painter->drawText(xx, yy, action);
+ if (i < 2) {
+ xx += ww + 14;
+ int pp = xx - 7;
+ painter->setPen(textColor);
+ painter->drawLine(pp, yy - 10, pp, yy);
+ }
+ if (isActive)
+ m_activeActionRects[i] = actionRect;
+ }
+ }
+ }
+
+ QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &idx) const final
+ {
+ int h = 30;
+ QString sessionName = idx.data(Qt::DisplayRole).toString();
+ if (m_expandedSessions.contains(sessionName)) {
+ QStringList projects = SessionManager::projectsForSessionName(sessionName);
+ h += projects.size() * 40 + 35;
+ }
+ return QSize(380, h);
+ }
+
+ bool helpEvent(QHelpEvent *ev, QAbstractItemView *view,
+ const QStyleOptionViewItem &option, const QModelIndex &idx) final
+ {
+ const int y = ev->pos().y();
+ if (y > option.rect.bottom() - 20)
+ return false;
+
+ QString sessionShortcut;
+ if (idx.row() < m_sessionShortcuts.size())
+ sessionShortcut = m_sessionShortcuts.at(idx.row());
+
+ QString sessionName = idx.data(Qt::DisplayRole).toString();
+ QString tooltipText;
+ if (sessionShortcut.isEmpty())
+ tooltipText = ProjectWelcomePage::tr("Opens session \"%1\"").arg(sessionName);
+ else
+ tooltipText = ProjectWelcomePage::tr("Opens session \"%1\" (%2)").arg(sessionName).arg(sessionShortcut);
+
+ if (tooltipText.isEmpty())
+ return false;
+
+ QToolTip::showText(ev->globalPos(), tooltipText, view);
+ return true;
+ }
+
+ bool editorEvent(QEvent *ev, QAbstractItemModel *model,
+ const QStyleOptionViewItem &option, const QModelIndex &idx) final
+ {
+ if (ev->type() == QEvent::MouseButtonRelease) {
+ const QPoint pos = static_cast<QMouseEvent *>(ev)->pos();
+ const QRect rc(option.rect.right() - 20, option.rect.top(), 20, 30);
+ const QString sessionName = idx.data(Qt::DisplayRole).toString();
+ if (rc.contains(pos)) {
+ // The expand/collapse "button".
+ if (m_expandedSessions.contains(sessionName))
+ m_expandedSessions.removeOne(sessionName);
+ else
+ m_expandedSessions.append(sessionName);
+ model->layoutChanged({ QPersistentModelIndex(idx) });
+ return false;
+ }
+ // One of the action links?
+ const auto sessionModel = qobject_cast<SessionModel *>(model);
+ QTC_ASSERT(sessionModel, return false);
+ if (m_activeSwitchToRect.contains(pos))
+ sessionModel->switchToSession(sessionName);
+ else if (m_activeActionRects[0].contains(pos))
+ sessionModel->cloneSession(sessionName);
+ else if (m_activeActionRects[1].contains(pos))
+ sessionModel->renameSession(sessionName);
+ else if (m_activeActionRects[2].contains(pos))
+ sessionModel->deleteSession(sessionName);
+ return true;
+ }
+ if (ev->type() == QEvent::MouseMove) {
+ model->layoutChanged({ QPersistentModelIndex(idx) }); // Somewhat brutish.
+ //update(option.rect);
+ return true;
+ }
+ return false;
+ }
+
+private:
+ const QColor hoverColor = themeColor(Theme::Welcome_HoverColor);
+ const QColor textColor = themeColor(Theme::Welcome_TextColor);
+ const QColor linkColor = themeColor(Theme::Welcome_LinkColor);
+ const QColor backgroundColor = themeColor(Theme::Welcome_BackgroundColor);
+ const QColor foregroundColor1 = themeColor(Theme::Welcome_ForegroundPrimaryColor); // light-ish.
+ const QColor foregroundColor2 = themeColor(Theme::Welcome_ForegroundSecondaryColor); // blacker.
+
+ QStringList m_sessionShortcuts;
+ QStringList m_expandedSessions;
+
+ mutable QRect m_activeSwitchToRect;
+ mutable QRect m_activeActionRects[3];
+};
+
+class ProjectDelegate : public QAbstractItemDelegate
+{
+public:
+ ProjectDelegate()
+ {
+ const int actionsCount = 9;
+ Context welcomeContext(Core::Constants::C_WELCOME_MODE);
+
+ const Id projectBase = "Welcome.OpenRecentProject";
+ for (int i = 1; i <= actionsCount; ++i) {
+ auto act = new QAction(tr("Open Recent Project #%1").arg(i), this);
+ Command *cmd = ActionManager::registerAction(act, projectBase.withSuffix(i), welcomeContext);
+ cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+Shift+%1").arg(i)));
+ m_recentProjectsShortcuts.append(cmd->keySequence().toString(QKeySequence::NativeText));
+
+// connect(act, &QAction::triggered, this, [this, i] { openRecentProjectTriggered(i-1); });
+ connect(cmd, &Command::keySequenceChanged, this, [this, i, cmd] {
+ m_recentProjectsShortcuts[i - 1] = cmd->keySequence().toString(QKeySequence::NativeText);
+ });
+ }
+ }
+
+ void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const final
+ {
+ QRect rc = option.rect;
+
+ const bool hovered = option.widget->isActiveWindow() && option.state & QStyle::State_MouseOver;
+ QColor color = themeColor(hovered ? Theme::Welcome_HoverColor : Theme::Welcome_BackgroundColor);
+ painter->fillRect(rc, color);
+
+ const int x = rc.x();
+ const int y = rc.y();
+ const int firstBase = y + 15;
+ const int secondBase = firstBase + 15;
+
+ painter->drawPixmap(x + 11, y + 3, pixmap("project", Theme::Welcome_ForegroundSecondaryColor));
+
+ QString projectName = idx.data(Qt::DisplayRole).toString();
+ QString projectPath = idx.data(Qt::UserRole + 1).toString();
+
+ painter->setPen(themeColor(Theme::Welcome_ForegroundSecondaryColor));
+ painter->setFont(sizedFont(10, option.widget));
+
+ if (idx.row() < 9)
+ painter->drawText(x + 3, firstBase, QString::number(idx.row() + 1));
+
+ painter->setPen(themeColor(Theme::Welcome_LinkColor));
+ painter->setFont(sizedFont(12, option.widget, hovered));
+ painter->drawText(x + 36, firstBase, projectName);
+
+ painter->setPen(themeColor(Theme::Welcome_ForegroundPrimaryColor));
+ painter->setFont(sizedFont(12, option.widget));
+ QString pathWithTilde = Utils::withTildeHomePath(QDir::toNativeSeparators(projectPath));
+ painter->drawText(x + 36, secondBase, pathWithTilde);
+ }
+
+ QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &idx) const final
+ {
+ QString projectName = idx.data(Qt::DisplayRole).toString();
+ QString projectPath = idx.data(Qt::UserRole + 1).toString();
+ QFontMetrics fm(sizedFont(13, option.widget));
+ int width = std::max(fm.width(projectName), fm.width(projectPath)) + 36;
+ return QSize(width, 48);
+ }
+
+ bool editorEvent(QEvent *ev, QAbstractItemModel *,
+ const QStyleOptionViewItem &, const QModelIndex &idx) final
+ {
+ if (ev->type() == QEvent::MouseButtonRelease) {
+ QString projectFile = idx.data(Qt::UserRole + 1).toString();
+ ProjectExplorerPlugin::openProjectWelcomePage(projectFile);
+ return true;
+ }
+ return false;
+ }
+
+ QStringList m_recentProjectsShortcuts;
+};
+
+class TreeView : public QTreeView
+{
+public:
+ TreeView(QWidget *parent)
+ : QTreeView(parent)
+ {
+ header()->hide();
+ setMouseTracking(true); // To enable hover.
+ setIndentation(0);
+ setSelectionMode(QAbstractItemView::NoSelection);
+ setFrameShape(QFrame::NoFrame);
+ setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
+ setFocusPolicy(Qt::NoFocus);
+
+ QPalette pal; // Needed for classic theme (only).
+ pal.setColor(QPalette::Base, themeColor(Theme::Welcome_BackgroundColor));
+ viewport()->setPalette(pal);
+ }
+
+ void leaveEvent(QEvent *) final
+ {
+ QHoverEvent hev(QEvent::HoverLeave, QPointF(), QPointF());
+ viewportEvent(&hev); // Seemingly needed to kill the hover paint.
+ }
+};
+
+class SessionsPage : public QWidget
+{
+public:
+ SessionsPage(ProjectWelcomePage *projectWelcomePage)
+ {
+ // FIXME: Remove once facilitateQml() is gone.
+ if (!projectWelcomePage->m_sessionModel)
+ projectWelcomePage->m_sessionModel = new SessionModel(this);
+ if (!projectWelcomePage->m_projectModel)
+ projectWelcomePage->m_projectModel = new ProjectModel(this);
+
+ auto newButton = new WelcomePageButton(this);
+ newButton->setText(ProjectWelcomePage::tr("New Project"));
+ newButton->setIcon(pixmap("new", Theme::Welcome_ForegroundSecondaryColor));
+ newButton->setOnClicked([] { ProjectExplorerPlugin::openNewProjectDialog(); });
+
+ auto openButton = new WelcomePageButton(this);
+ openButton->setText(ProjectWelcomePage::tr("Open Project"));
+ openButton->setIcon(pixmap("open", Theme::Welcome_ForegroundSecondaryColor));
+ openButton->setOnClicked([] { ProjectExplorerPlugin::openOpenProjectDialog(); });
+
+ auto sessionsLabel = new QLabel(this);
+ sessionsLabel->setFont(sizedFont(15, this));
+ sessionsLabel->setText(ProjectWelcomePage::tr("Sessions"));
+
+ auto recentProjectsLabel = new QLabel(this);
+ recentProjectsLabel->setFont(sizedFont(15, this));
+ recentProjectsLabel->setText(ProjectWelcomePage::tr("Recent Projects"));
+
+ auto sessionsList = new TreeView(this);
+ sessionsList->setModel(projectWelcomePage->m_sessionModel);
+ sessionsList->header()->setSectionHidden(1, true); // The "last modified" column.
+ sessionsList->setItemDelegate(&m_sessionDelegate);
+ sessionsList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+
+ auto projectsList = new TreeView(this);
+ projectsList->setUniformRowHeights(true);
+ projectsList->setModel(projectWelcomePage->m_projectModel);
+ projectsList->setItemDelegate(&m_projectDelegate);
+ projectsList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+
+ const int d = IWelcomePage::screenDependHeightDistance();
+
+ auto hbox11 = new QHBoxLayout;
+ hbox11->setContentsMargins(0, 0, 0, 0);
+ hbox11->addWidget(newButton);
+ hbox11->addStretch(1);
+
+ auto hbox21 = new QHBoxLayout;
+ hbox21->setContentsMargins(0, 0, 0, 0);
+ hbox21->addWidget(openButton);
+ hbox21->addStretch(1);
+
+ auto vbox1 = new QVBoxLayout;
+ vbox1->setContentsMargins(0, 0, 0, 0);
+ vbox1->addStrut(200);
+ vbox1->addItem(hbox11);
+ vbox1->addSpacing(d);
+ vbox1->addWidget(sessionsLabel);
+ vbox1->addSpacing(d + 5);
+ vbox1->addWidget(sessionsList);
+
+ auto vbox2 = new QVBoxLayout;
+ vbox2->setContentsMargins(0, 0, 0, 0);
+ vbox2->addItem(hbox21);
+ vbox2->addSpacing(d);
+ vbox2->addWidget(recentProjectsLabel);
+ vbox2->addSpacing(d + 5);
+ vbox2->addWidget(projectsList);
+
+ auto hbox = new QHBoxLayout(this);
+ hbox->setContentsMargins(30, 27, 27, 27);
+ hbox->addItem(vbox1);
+ hbox->addSpacing(d);
+ hbox->addItem(vbox2);
+ hbox->setStretchFactor(vbox2, 2);
+ }
+
+ SessionDelegate m_sessionDelegate;
+ ProjectDelegate m_projectDelegate;
+};
+
+QWidget *ProjectWelcomePage::createWidget() const
+{
+ return new SessionsPage(const_cast<ProjectWelcomePage *>(this));
+}
+
} // namespace Internal
} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/projectwelcomepage.h b/src/plugins/projectexplorer/projectwelcomepage.h
index 6c5bd8faad9..805bac2054f 100644
--- a/src/plugins/projectexplorer/projectwelcomepage.h
+++ b/src/plugins/projectexplorer/projectwelcomepage.h
@@ -25,18 +25,15 @@
#pragma once
-#include <QAbstractListModel>
-
#include <coreplugin/iwelcomepage.h>
-QT_BEGIN_NAMESPACE
-class QQmlEngine;
-QT_END_NAMESPACE
+#include <QAbstractListModel>
namespace ProjectExplorer {
namespace Internal {
class SessionModel;
+class SessionsPage;
class ProjectModel : public QAbstractListModel
{
@@ -60,14 +57,12 @@ class ProjectWelcomePage : public Core::IWelcomePage
public:
ProjectWelcomePage() = default;
- void facilitateQml(QQmlEngine *engine) override;
- QUrl pageLocation() const override;
- QWidget *page() { return nullptr; }
QString title() const override { return tr("Projects"); }
int priority() const override { return 20; }
Core::Id id() const override;
+ QWidget *createWidget() const override;
- void reloadWelcomeScreenData();
+ void reloadWelcomeScreenData() const;
public slots:
void newProject();
@@ -78,6 +73,7 @@ signals:
void manageSessions();
private:
+ friend class SessionsPage;
SessionModel *m_sessionModel = nullptr;
ProjectModel *m_projectModel = nullptr;
};
diff --git a/src/plugins/qtsupport/exampleslistmodel.cpp b/src/plugins/qtsupport/exampleslistmodel.cpp
index ac2fd0d36ab..45c8f9199b4 100644
--- a/src/plugins/qtsupport/exampleslistmodel.cpp
+++ b/src/plugins/qtsupport/exampleslistmodel.cpp
@@ -629,10 +629,8 @@ QString prefixForItem(const ExampleItem &item)
QVariant ExamplesListModel::data(const QModelIndex &index, int role) const
{
- if (!index.isValid() || index.row()+1 > m_exampleItems.count()) {
- qDebug() << Q_FUNC_INFO << "invalid index requested";
+ if (!index.isValid() || index.row()+1 > m_exampleItems.count())
return QVariant();
- }
ExampleItem item = m_exampleItems.at(index.row());
switch (role)
@@ -674,7 +672,6 @@ QVariant ExamplesListModel::data(const QModelIndex &index, int role) const
case IsHighlighted:
return item.isHighlighted;
default:
- qDebug() << Q_FUNC_INFO << "role type not supported";
return QVariant();
}
}
diff --git a/src/plugins/qtsupport/gettingstartedwelcomepage.cpp b/src/plugins/qtsupport/gettingstartedwelcomepage.cpp
index a09e0217401..6f8f6027119 100644
--- a/src/plugins/qtsupport/gettingstartedwelcomepage.cpp
+++ b/src/plugins/qtsupport/gettingstartedwelcomepage.cpp
@@ -39,26 +39,30 @@
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/project.h>
-#include <QMutex>
-#include <QThread>
-#include <QMutexLocker>
-#include <QPointer>
-#include <QWaitCondition>
-#include <QDir>
+#include <QApplication>
#include <QBuffer>
+#include <QCloseEvent>
+#include <QComboBox>
+#include <QDesktopServices>
+#include <QDialogButtonBox>
+#include <QDir>
+#include <QGridLayout>
+#include <QHeaderView>
+#include <QIdentityProxyModel>
#include <QImage>
#include <QImageReader>
-#include <QGridLayout>
#include <QLabel>
-#include <QDialogButtonBox>
-#include <QPushButton>
#include <QMessageBox>
-#include <QApplication>
-#include <QQuickImageProvider>
-#include <QQmlEngine>
-#include <QQmlContext>
-#include <QDesktopServices>
+#include <QPainter>
+#include <QPixmapCache>
+#include <QPointer>
+#include <QPushButton>
+#include <QStyledItemDelegate>
+#include <QTableView>
+#include <QTime>
+#include <QTimer>
+using namespace Core;
using namespace Utils;
namespace QtSupport {
@@ -66,223 +70,40 @@ namespace Internal {
const char C_FALLBACK_ROOT[] = "ProjectsFallbackRoot";
-QPointer<ExamplesListModel> &examplesModelStatic()
-{
- static QPointer<ExamplesListModel> s_examplesModel;
- return s_examplesModel;
-}
-
-class Fetcher : public QObject
-{
- Q_OBJECT
-
-public:
- Fetcher() : QObject(), m_shutdown(false)
- {
- connect(Core::ICore::instance(), &Core::ICore::coreAboutToClose, this, &Fetcher::shutdown);
- }
-
- void wait()
- {
- if (QThread::currentThread() == QApplication::instance()->thread())
- return;
- if (m_shutdown)
- return;
-
- m_waitcondition.wait(&m_mutex, 4000);
- }
-
- QByteArray data()
- {
- QMutexLocker lock(&m_dataMutex);
- return m_fetchedData;
- }
-
- void clearData()
- {
- QMutexLocker lock(&m_dataMutex);
- m_fetchedData.clear();
- }
-
- bool asynchronousFetchData(const QUrl &url)
- {
- QMutexLocker lock(&m_mutex);
-
- if (!QMetaObject::invokeMethod(this,
- "fetchData",
- Qt::AutoConnection,
- Q_ARG(QUrl, url))) {
- return false;
- }
-
- wait();
- return true;
- }
-
-
-public slots:
- void fetchData(const QUrl &url)
- {
- if (m_shutdown)
- return;
-
- QMutexLocker lock(&m_mutex);
-
- if (Core::HelpManager::instance()) {
- QMutexLocker dataLock(&m_dataMutex);
- m_fetchedData = Core::HelpManager::fileData(url);
- }
- m_waitcondition.wakeAll();
- }
-
-private:
- void shutdown()
- {
- m_shutdown = true;
- }
-
-public:
- QByteArray m_fetchedData;
- QWaitCondition m_waitcondition;
- QMutex m_mutex; //This mutex synchronises the wait() and wakeAll() on the wait condition.
- //We have to ensure that wakeAll() is called always after wait().
-
- QMutex m_dataMutex; //This mutex synchronises the access of m_fectedData.
- //If the wait condition timeouts we otherwise get a race condition.
- bool m_shutdown;
-};
-
-class HelpImageProvider : public QQuickImageProvider
-{
-public:
- HelpImageProvider()
- : QQuickImageProvider(QQuickImageProvider::Image)
- {
- }
-
- // gets called by declarative in separate thread
- QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize)
- {
- QMutexLocker lock(&m_mutex);
-
- QUrl url = QUrl::fromEncoded(id.toLatin1());
-
-
- if (!m_fetcher.asynchronousFetchData(url) || m_fetcher.data().isEmpty()) {
- if (size) {
- size->setWidth(0);
- size->setHeight(0);
- }
- return QImage();
- }
-
- QByteArray data = m_fetcher.data();
- QBuffer imgBuffer(&data);
- imgBuffer.open(QIODevice::ReadOnly);
- QImageReader reader(&imgBuffer);
- QImage img = reader.read();
-
- m_fetcher.clearData();
- img = ScreenshotCropper::croppedImage(img, id, requestedSize);
- if (size)
- *size = img.size();
- return img;
+const int itemWidth = 240;
+const int itemHeight = 240;
+const int itemGap = 10;
+const int tagsSeparatorY = itemHeight - 60;
- }
-private:
- Fetcher m_fetcher;
- QMutex m_mutex;
-};
-
-ExamplesWelcomePage::ExamplesWelcomePage()
- : m_engine(0), m_showExamples(false)
-{
-}
-
-void ExamplesWelcomePage::setShowExamples(bool showExamples)
+ExamplesWelcomePage::ExamplesWelcomePage(bool showExamples)
+ : m_showExamples(showExamples)
{
- m_showExamples = showExamples;
}
QString ExamplesWelcomePage::title() const
{
- if (m_showExamples)
- return tr("Examples");
- else
- return tr("Tutorials");
+ return m_showExamples ? tr("Examples") : tr("Tutorials");
}
- int ExamplesWelcomePage::priority() const
- {
- if (m_showExamples)
- return 30;
- else
- return 40;
- }
-
- bool ExamplesWelcomePage::hasSearchBar() const
- {
- if (m_showExamples)
- return true;
- else
- return false;
- }
-
-QUrl ExamplesWelcomePage::pageLocation() const
+int ExamplesWelcomePage::priority() const
{
- // normalize paths so QML doesn't freak out if it's wrongly capitalized on Windows
- const QString resourcePath = Utils::FileUtils::normalizePathName(Core::ICore::resourcePath());
- if (m_showExamples)
- return QUrl::fromLocalFile(resourcePath + QLatin1String("/welcomescreen/examples.qml"));
- else
- return QUrl::fromLocalFile(resourcePath + QLatin1String("/welcomescreen/tutorials.qml"));
+ return m_showExamples ? 30 : 40;
}
-void ExamplesWelcomePage::facilitateQml(QQmlEngine *engine)
-{
- m_engine = engine;
- m_engine->addImageProvider(QLatin1String("helpimage"), new HelpImageProvider);
- ExamplesListModelFilter *proxy = new ExamplesListModelFilter(examplesModel(), this);
-
- proxy->setDynamicSortFilter(true);
- proxy->sort(0);
- proxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
-
- QQmlContext *rootContenxt = m_engine->rootContext();
- if (m_showExamples) {
- proxy->setShowTutorialsOnly(false);
- rootContenxt->setContextProperty(QLatin1String("examplesModel"), proxy);
- rootContenxt->setContextProperty(QLatin1String("exampleSetModel"), proxy->exampleSetModel());
- } else {
- rootContenxt->setContextProperty(QLatin1String("tutorialsModel"), proxy);
- }
- rootContenxt->setContextProperty(QLatin1String("gettingStarted"), this);
-}
-
-Core::Id ExamplesWelcomePage::id() const
+Id ExamplesWelcomePage::id() const
{
return m_showExamples ? "Examples" : "Tutorials";
}
void ExamplesWelcomePage::openHelpInExtraWindow(const QUrl &help)
{
- Core::HelpManager::handleHelpRequest(help, Core::HelpManager::ExternalHelpAlways);
-}
-
-void ExamplesWelcomePage::openHelp(const QUrl &help)
-{
- Core::HelpManager::handleHelpRequest(help, Core::HelpManager::HelpModeAlways);
-}
-
-void ExamplesWelcomePage::openUrl(const QUrl &url)
-{
- QDesktopServices::openUrl(url);
+ HelpManager::handleHelpRequest(help, HelpManager::ExternalHelpAlways);
}
QString ExamplesWelcomePage::copyToAlternativeLocation(const QFileInfo& proFileInfo, QStringList &filesToOpen, const QStringList& dependencies)
{
const QString projectDir = proFileInfo.canonicalPath();
- QDialog d(Core::ICore::mainWindow());
+ QDialog d(ICore::mainWindow());
QGridLayout *lay = new QGridLayout(&d);
QLabel *descrLbl = new QLabel;
d.setWindowTitle(tr("Copy Project to writable Location?"));
@@ -305,9 +126,9 @@ QString ExamplesWelcomePage::copyToAlternativeLocation(const QFileInfo& proFileI
txt->setBuddy(chooser);
chooser->setExpectedKind(PathChooser::ExistingDirectory);
chooser->setHistoryCompleter(QLatin1String("Qt.WritableExamplesDir.History"));
- QSettings *settings = Core::ICore::settings();
+ QSettings *settings = ICore::settings();
chooser->setPath(settings->value(QString::fromLatin1(C_FALLBACK_ROOT),
- Core::DocumentManager::projectsDirectory()).toString());
+ DocumentManager::projectsDirectory()).toString());
lay->addWidget(txt, 1, 0);
lay->addWidget(chooser, 1, 1);
enum { Copy = QDialog::Accepted + 1, Keep = QDialog::Accepted + 2 };
@@ -327,7 +148,7 @@ QString ExamplesWelcomePage::copyToAlternativeLocation(const QFileInfo& proFileI
QDir toDirWithExamplesDir(destBaseDir);
if (toDirWithExamplesDir.cd(exampleDirName)) {
toDirWithExamplesDir.cdUp(); // step out, just to not be in the way
- QMessageBox::warning(Core::ICore::mainWindow(), tr("Cannot Use Location"),
+ QMessageBox::warning(ICore::mainWindow(), tr("Cannot Use Location"),
tr("The specified location already exists. "
"Please specify a valid location."),
QMessageBox::Ok, QMessageBox::NoButton);
@@ -347,7 +168,7 @@ QString ExamplesWelcomePage::copyToAlternativeLocation(const QFileInfo& proFileI
targetFile.appendPath(QDir(dependency).dirName());
if (!FileUtils::copyRecursively(FileName::fromString(dependency), targetFile,
&error)) {
- QMessageBox::warning(Core::ICore::mainWindow(), tr("Cannot Copy Project"), error);
+ QMessageBox::warning(ICore::mainWindow(), tr("Cannot Copy Project"), error);
// do not fail, just warn;
}
}
@@ -355,7 +176,7 @@ QString ExamplesWelcomePage::copyToAlternativeLocation(const QFileInfo& proFileI
return targetDir + QLatin1Char('/') + proFileInfo.fileName();
} else {
- QMessageBox::warning(Core::ICore::mainWindow(), tr("Cannot Copy Project"), error);
+ QMessageBox::warning(ICore::mainWindow(), tr("Cannot Copy Project"), error);
}
}
@@ -363,7 +184,6 @@ QString ExamplesWelcomePage::copyToAlternativeLocation(const QFileInfo& proFileI
if (code == Keep)
return proFileInfo.absoluteFilePath();
return QString();
-
}
void ExamplesWelcomePage::openProject(const QString &projectFile,
@@ -402,23 +222,485 @@ void ExamplesWelcomePage::openProject(const QString &projectFile,
ProjectExplorer::ProjectExplorerPlugin::OpenProjectResult result =
ProjectExplorer::ProjectExplorerPlugin::instance()->openProject(proFile);
if (result) {
- Core::ICore::openFiles(filesToOpen);
- Core::ModeManager::activateMode(Core::Constants::MODE_EDIT);
+ ICore::openFiles(filesToOpen);
+ ModeManager::activateMode(Core::Constants::MODE_EDIT);
if (help.isValid())
openHelpInExtraWindow(help.toString());
- Core::ModeManager::activateMode(ProjectExplorer::Constants::MODE_SESSION);
+ ModeManager::activateMode(ProjectExplorer::Constants::MODE_SESSION);
} else {
ProjectExplorer::ProjectExplorerPlugin::showOpenProjectError(result);
}
}
-ExamplesListModel *ExamplesWelcomePage::examplesModel() const
+//////////////////////////////
+
+static QColor themeColor(Theme::Color role)
+{
+ return Utils::creatorTheme()->color(role);
+}
+
+static QFont sizedFont(int size, const QWidget *widget, bool underline = false)
+{
+ QFont f = widget->font();
+ f.setPixelSize(size);
+ f.setUnderline(underline);
+ return f;
+}
+
+static QString resourcePath()
+{
+ // normalize paths so QML doesn't freak out if it's wrongly capitalized on Windows
+ return FileUtils::normalizePathName(ICore::resourcePath());
+}
+
+class SearchBox : public QFrame
+{
+public:
+ SearchBox(const QString &placeHolderText, QWidget *parent)
+ : QFrame(parent)
+ {
+ setFrameShape(QFrame::Box);
+ setFrameShadow(QFrame::Plain);
+
+ QPalette pal;
+ pal.setColor(QPalette::Base, themeColor(Theme::Welcome_BackgroundColor));
+
+ m_lineEdit = new QLineEdit;
+ m_lineEdit->setPlaceholderText(placeHolderText);
+ m_lineEdit->setFrame(false);
+ m_lineEdit->setFont(sizedFont(14, this));
+ m_lineEdit->setAttribute(Qt::WA_MacShowFocusRect, false);
+ m_lineEdit->setPalette(pal);
+
+ auto box = new QHBoxLayout(this);
+ box->setContentsMargins(15, 3, 15, 3);
+ box->addWidget(m_lineEdit);
+ }
+
+ QLineEdit *m_lineEdit;
+};
+
+class GridView : public QTableView
{
- if (examplesModelStatic())
- return examplesModelStatic().data();
+public:
+ GridView(QWidget *parent)
+ : QTableView(parent)
+ {
+ horizontalHeader()->hide();
+ horizontalHeader()->setDefaultSectionSize(itemWidth);
+ verticalHeader()->hide();
+ verticalHeader()->setDefaultSectionSize(itemHeight);
+ setMouseTracking(true); // To enable hover.
+ setSelectionMode(QAbstractItemView::NoSelection);
+ setFrameShape(QFrame::NoFrame);
+ setGridStyle(Qt::NoPen);
+
+ QPalette pal;
+ pal.setColor(QPalette::Base, themeColor(Theme::Welcome_BackgroundColor));
+ setPalette(pal); // Makes a difference on Mac.
+ }
- examplesModelStatic() = new ExamplesListModel(const_cast<ExamplesWelcomePage*>(this));
- return examplesModelStatic().data();
+ void leaveEvent(QEvent *) final
+ {
+ QHoverEvent hev(QEvent::HoverLeave, QPointF(), QPointF());
+ viewportEvent(&hev); // Seemingly needed to kill the hover paint.
+ }
+};
+
+class GridProxyModel : public QIdentityProxyModel
+{
+public:
+ GridProxyModel()
+ {}
+
+ void setColumnCount(int columnCount)
+ {
+ if (columnCount == m_columnCount)
+ return;
+ QTC_ASSERT(columnCount >= 1, columnCount = 1);
+ m_columnCount = columnCount;
+ layoutChanged();
+ }
+
+ int rowCount(const QModelIndex &parent) const final
+ {
+ if (parent.isValid())
+ return 0;
+ int rows = sourceModel()->rowCount(QModelIndex());
+ return (rows + m_columnCount - 1) / m_columnCount;
+ }
+
+ int columnCount(const QModelIndex &parent) const final
+ {
+ if (parent.isValid())
+ return 0;
+ return m_columnCount;
+ }
+
+ QModelIndex index(int row, int column, const QModelIndex &) const final
+ {
+ return createIndex(row, column, nullptr);
+ }
+
+ QModelIndex parent(const QModelIndex &) const final
+ {
+ return QModelIndex();
+ }
+
+ QModelIndex mapToSource(const QModelIndex &proxyIndex) const final
+ {
+ if (!proxyIndex.isValid())
+ return QModelIndex();
+ int sourceRow = proxyIndex.row() * m_columnCount + proxyIndex.column();
+ return sourceModel()->index(sourceRow, 0);
+ }
+
+ QModelIndex mapFromSource(const QModelIndex &sourceIndex) const final
+ {
+ if (!sourceIndex.isValid())
+ return QModelIndex();
+ QTC_CHECK(sourceIndex.column() == 0);
+ int proxyRow = sourceIndex.row() / m_columnCount;
+ int proxyColumn = sourceIndex.row() % m_columnCount;
+ return index(proxyRow, proxyColumn, QModelIndex());
+ }
+
+private:
+ int m_columnCount = 1;
+};
+
+class ExampleDelegate : public QStyledItemDelegate
+{
+ Q_OBJECT
+
+public:
+ void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const final
+ {
+ const QRect rc = option.rect;
+ const QString name = index.data(Name).toString();
+
+ // Quick hack for empty items in the last row.
+ if (name.isEmpty())
+ return;
+
+ const int d = 10;
+ const int x = rc.x() + d;
+ const int y = rc.y() + d;
+ const int w = rc.width() - 2 * d - itemGap;
+ const int h = rc.height() - 2 * d;
+ const bool hovered = option.state & QStyle::State_MouseOver;
+
+ const int tagsBase = tagsSeparatorY + 10;
+ const int shiftY = tagsSeparatorY - 20;
+ const int nameY = tagsSeparatorY - 20;
+
+ const QRect textRect = QRect(x, y + nameY, w, h);
+
+ QTextOption wrapped;
+ wrapped.setWrapMode(QTextOption::WordWrap);
+ int offset = 0;
+ if (hovered) {
+ if (index != m_previousIndex) {
+ m_previousIndex = index;
+ m_startTime.start();
+ m_currentArea = rc;
+ m_currentWidget = qobject_cast<QAbstractItemView *>(
+ const_cast<QWidget *>(option.widget));
+ }
+ offset = m_startTime.elapsed() * itemHeight / 200; // Duration 200 ms.
+ if (offset < shiftY)
+ QTimer::singleShot(5, this, &ExampleDelegate::goon);
+ else if (offset > shiftY)
+ offset = shiftY;
+ } else {
+ m_previousIndex = QModelIndex();
+ }
+
+ const QFontMetrics fm(option.widget->font());
+ const QRect shiftedTextRect = textRect.adjusted(0, -offset, 0, -offset);
+
+ // The pixmap.
+ if (offset == 0) {
+ const QString imageUrl = index.data(ImageUrl).toString();
+ const bool isVideo = index.data(IsVideo).toBool();
+ const QSize requestSize(188, 145);
+
+ QPixmap pm;
+ if (QPixmap *foundPixmap = m_pixmapCache.find(imageUrl)) {
+ pm = *foundPixmap;
+ } else {
+ pm.load(imageUrl);
+ if (pm.isNull())
+ pm.load(resourcePath() + "/welcomescreen/widgets/" + imageUrl);
+ if (pm.isNull()) {
+ // FIXME: Make async
+ QByteArray fetchedData = HelpManager::fileData(imageUrl);
+ QBuffer imgBuffer(&fetchedData);
+ imgBuffer.open(QIODevice::ReadOnly);
+ QImageReader reader(&imgBuffer);
+ QImage img = reader.read();
+ img = ScreenshotCropper::croppedImage(img, imageUrl, requestSize);
+ pm = QPixmap::fromImage(img);
+ }
+ m_pixmapCache.insert(imageUrl, pm);
+ }
+
+ QRect inner(x + 11, y - offset, requestSize.width(), requestSize.height());
+ QRect pixmapRect = inner;
+ if (!pm.isNull()) {
+ painter->setPen(foregroundColor2);
+ if (isVideo)
+ pixmapRect = inner.adjusted(6, 10, -6, -25);
+ QPoint pixmapPos = pixmapRect.center();
+ pixmapPos.rx() -= pm.width() / 2;
+ pixmapPos.ry() -= pm.height() / 2;
+ painter->drawPixmap(pixmapPos, pm);
+ if (isVideo) {
+ painter->setFont(sizedFont(13, option.widget));
+ QRect lenRect(x, y + 120, w, 20);
+ QString videoLen = index.data(VideoLength).toString();
+ lenRect = fm.boundingRect(lenRect, Qt::AlignHCenter, videoLen);
+ painter->drawText(lenRect.adjusted(0, 0, 5, 0), videoLen);
+ }
+ } else {
+ // The description text as fallback.
+ QRect descRect = pixmapRect.adjusted(6, 10, -6, -10);
+ QString desc = index.data(Description).toString();
+ painter->setPen(foregroundColor2);
+ painter->setFont(sizedFont(11, option.widget));
+ painter->drawText(descRect, desc, wrapped);
+ }
+ painter->setPen(foregroundColor1);
+ painter->drawRect(pixmapRect.adjusted(-1, -1, -1, -1));
+ }
+
+ // The title of the example.
+ painter->setPen(foregroundColor1);
+ painter->setFont(sizedFont(13, option.widget));
+ QRectF nameRect;
+ if (offset) {
+ nameRect = painter->boundingRect(shiftedTextRect, name, wrapped);
+ painter->drawText(nameRect, name, wrapped);
+ } else {
+ nameRect = QRect(x, y + nameY, x + w, y + nameY + 20);
+ QString elidedName = fm.elidedText(name, Qt::ElideRight, w - 20);
+ painter->drawText(nameRect, elidedName);
+ }
+
+ // The separator line below the example title.
+ if (offset) {
+ int ll = nameRect.bottom() + 5;
+ painter->setPen(lightColor);
+ painter->drawLine(x, ll, x + w, ll);
+ }
+
+ // The description text.
+ if (offset) {
+ int dd = nameRect.height() + 10;
+ QRect descRect = shiftedTextRect.adjusted(0, dd, 0, dd);
+ QString desc = index.data(Description).toString();
+ painter->setPen(foregroundColor2);
+ painter->setFont(sizedFont(11, option.widget));
+ painter->drawText(descRect, desc, wrapped);
+ }
+
+ // Separator line between text and 'Tags:' section
+ painter->setPen(lightColor);
+ painter->drawLine(x, y + tagsSeparatorY, x + w, y + tagsSeparatorY);
+
+ // The 'Tags:' section
+ const int tagsHeight = h - tagsBase;
+ const QFont tagsFont = sizedFont(10, option.widget);
+ const QFontMetrics tagsFontMetrics(tagsFont);
+ QRect tagsLabelRect = QRect(x, y + tagsBase, 30, tagsHeight - 2);
+ painter->setPen(foregroundColor2);
+ painter->setFont(tagsFont);
+ painter->drawText(tagsLabelRect, ExamplesWelcomePage::tr("Tags:"));
+
+ painter->setPen(themeColor(Theme::Welcome_LinkColor));
+ const QStringList tags = index.data(Tags).toStringList();
+ m_currentTagRects.clear();
+ int xx = 0;
+ int yy = y + tagsBase;
+ for (const QString tag : tags) {
+ const int ww = tagsFontMetrics.width(tag) + 5;
+ if (xx + ww > w - 30) {
+ yy += 15;
+ xx = 0;
+ }
+ const QRect tagRect(xx + x + 30, yy, ww, 15);
+ painter->drawText(tagRect, tag);
+ m_currentTagRects.append({ tag, tagRect });
+ xx += ww;
+ }
+
+ // Box it when hovered.
+ if (hovered) {
+ painter->setPen(lightColor);
+ painter->drawRect(rc.adjusted(0, 0, -1, -1));
+ }
+ }
+
+ void goon()
+ {
+ if (m_currentWidget)
+ m_currentWidget->viewport()->update(m_currentArea);
+ }
+
+ bool editorEvent(QEvent *ev, QAbstractItemModel *model,
+ const QStyleOptionViewItem &option, const QModelIndex &idx) final
+ {
+ if (ev->type() == QEvent::MouseButtonRelease) {
+ auto mev = static_cast<QMouseEvent *>(ev);
+ if (idx.isValid()) {
+ const QPoint pos = mev->pos();
+ if (pos.y() > option.rect.y() + tagsSeparatorY) {
+ //const QStringList tags = idx.data(Tags).toStringList();
+ for (auto it : m_currentTagRects) {
+ if (it.second.contains(pos))
+ emit tagClicked(it.first);
+ }
+ } else {
+ if (idx.data(IsVideo).toBool())
+ QDesktopServices::openUrl(idx.data(VideoUrl).toUrl());
+ else if (idx.data(HasSourceCode).toBool())
+ ExamplesWelcomePage::openProject(idx.data(ProjectPath).toString(),
+ idx.data(FilesToOpen).toStringList(),
+ idx.data(MainFile).toString(),
+ idx.data(DocUrl).toUrl(),
+ idx.data(Dependencies).toStringList(),
+ idx.data(Platforms).toStringList());
+ else
+ ExamplesWelcomePage::openHelpInExtraWindow(idx.data(DocUrl).toUrl());
+ }
+ }
+ }
+ return QAbstractItemDelegate::editorEvent(ev, model, option, idx);
+ }
+
+signals:
+ void tagClicked(const QString &tag);
+
+private:
+ const QColor lightColor = QColor(221, 220, 220); // color: "#dddcdc"
+ const QColor backgroundColor = themeColor(Theme::Welcome_BackgroundColor);
+ const QColor foregroundColor1 = themeColor(Theme::Welcome_ForegroundPrimaryColor); // light-ish.
+ const QColor foregroundColor2 = themeColor(Theme::Welcome_ForegroundSecondaryColor); // blacker.
+
+ mutable QPersistentModelIndex m_previousIndex;
+ mutable QTime m_startTime;
+ mutable QRect m_currentArea;
+ mutable QPointer<QAbstractItemView> m_currentWidget;
+ mutable QVector<QPair<QString, QRect>> m_currentTagRects;
+ mutable QPixmapCache m_pixmapCache;
+};
+
+class ExamplesPageWidget : public QWidget
+{
+public:
+ ExamplesPageWidget(bool isExamples)
+ : m_isExamples(isExamples)
+ {
+ static ExamplesListModel *s_examplesModel = new ExamplesListModel(this);
+ m_examplesModel = s_examplesModel;
+
+ m_filteredModel = new ExamplesListModelFilter(m_examplesModel, this);
+ m_filteredModel->setDynamicSortFilter(true);
+ m_filteredModel->sort(0);
+ m_filteredModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
+
+ auto vbox = new QVBoxLayout(this);
+ vbox->setContentsMargins(30, 27, 20, 20);
+ if (m_isExamples) {
+ m_filteredModel->setShowTutorialsOnly(false);
+ m_qtVersionSelector = new QComboBox(this);
+ m_qtVersionSelector->setMinimumWidth(itemWidth);
+ m_qtVersionSelector->setMaximumWidth(itemWidth);
+ m_searchBox = new SearchBox(tr("Search in Examples..."), this);
+ auto hbox = new QHBoxLayout;
+ hbox->setSpacing(17);
+ hbox->addWidget(m_qtVersionSelector);
+ hbox->addWidget(m_searchBox);
+ vbox->addItem(hbox);
+ connect(m_qtVersionSelector, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated),
+ this, &ExamplesPageWidget::updateComboBox);
+ } else {
+ m_filteredModel->setShowTutorialsOnly(true);
+ m_searchBox = new SearchBox(tr("Search in Tutorials..."), this);
+ vbox->addWidget(m_searchBox);
+ }
+
+ m_gridModel.setSourceModel(m_filteredModel);
+
+ m_gridView = new GridView(this);
+ m_gridView->setModel(&m_gridModel);
+ m_gridView->setItemDelegate(&m_exampleDelegate);
+ vbox->addWidget(m_gridView);
+
+ connect(&m_exampleDelegate, &ExampleDelegate::tagClicked,
+ this, &ExamplesPageWidget::onTagClicked);
+ connect(m_filteredModel, &ExamplesListModelFilter::filterTagsChanged,
+ this, &ExamplesPageWidget::updateStuff);
+ connect(m_filteredModel, &ExamplesListModelFilter::searchStringChanged,
+ this, &ExamplesPageWidget::updateStuff);
+ connect(m_filteredModel, &ExamplesListModelFilter::exampleSetIndexChanged,
+ this, &ExamplesPageWidget::updateStuff);
+
+ connect(m_searchBox->m_lineEdit, &QLineEdit::textChanged,
+ m_filteredModel, &ExamplesListModelFilter::setSearchString);
+ }
+
+ int bestColumnCount() const
+ {
+ return qMax(1, (width() - 30) / (itemWidth + itemGap));
+ }
+
+ void resizeEvent(QResizeEvent *ev) final
+ {
+ QWidget::resizeEvent(ev);
+ m_gridModel.setColumnCount(bestColumnCount());
+ }
+
+ void onTagClicked(const QString &tag)
+ {
+ QString text = m_searchBox->m_lineEdit->text();
+ m_searchBox->m_lineEdit->setText(text + QString("tag:\"%1\" ").arg(tag));
+ }
+
+ void updateStuff()
+ {
+ if (m_isExamples) {
+ QTC_ASSERT(m_examplesModel, return);
+ const QList<BaseQtVersion *> qtVersions = m_examplesModel->qtVersions();
+ const QList<ExamplesListModel::ExtraExampleSet> extraExampleSets = m_examplesModel->extraExampleSets();
+ QStringList list;
+ for (BaseQtVersion *qtVersion : qtVersions)
+ list.append(qtVersion->displayName());
+ m_qtVersionSelector->clear();
+ m_qtVersionSelector->addItems(list);
+ }
+ }
+
+ void updateComboBox(int index)
+ {
+ QTC_ASSERT(m_isExamples, return);
+ QTC_ASSERT(m_examplesModel, return);
+ m_examplesModel->selectExampleSet(index);
+ }
+
+ const bool m_isExamples;
+ ExampleDelegate m_exampleDelegate;
+ QPointer<ExamplesListModel> m_examplesModel;
+ ExamplesListModelFilter *m_filteredModel;
+ SearchBox *m_searchBox;
+ QComboBox *m_qtVersionSelector = nullptr;
+ GridView *m_gridView;
+ GridProxyModel m_gridModel;
+};
+
+QWidget *ExamplesWelcomePage::createWidget() const
+{
+ return new ExamplesPageWidget(m_showExamples);
}
} // namespace Internal
diff --git a/src/plugins/qtsupport/gettingstartedwelcomepage.h b/src/plugins/qtsupport/gettingstartedwelcomepage.h
index 689e75fb4a9..8a78ceffe9d 100644
--- a/src/plugins/qtsupport/gettingstartedwelcomepage.h
+++ b/src/plugins/qtsupport/gettingstartedwelcomepage.h
@@ -30,7 +30,6 @@
#include <QStringList>
QT_BEGIN_NAMESPACE
-class QQmlEngine;
class QFileInfo;
QT_END_NAMESPACE
@@ -44,29 +43,21 @@ class ExamplesWelcomePage : public Core::IWelcomePage
Q_OBJECT
public:
- ExamplesWelcomePage();
+ explicit ExamplesWelcomePage(bool showExamples);
- void setShowExamples(bool showExamples);
- QUrl pageLocation() const;
- QString title() const;
- int priority() const;
- bool hasSearchBar() const;
- void facilitateQml(QQmlEngine *);
- Core::Id id() const;
- Q_INVOKABLE void openUrl(const QUrl &url);
+ QString title() const final;
+ int priority() const final;
+ Core::Id id() const final;
+ QWidget *createWidget() const final;
-public slots:
- void openHelpInExtraWindow(const QUrl &help);
- void openHelp(const QUrl &help);
- void openProject(const QString& projectFile, const QStringList& additionalFilesToOpen,
- const QString &mainFile, const QUrl& help, const QStringList &dependencies,
- const QStringList &platforms);
+ static void openHelpInExtraWindow(const QUrl &help);
+ static void openProject(const QString& projectFile, const QStringList& additionalFilesToOpen,
+ const QString &mainFile, const QUrl &help,
+ const QStringList &dependencies, const QStringList &platforms);
private:
- ExamplesListModel *examplesModel() const;
- QString copyToAlternativeLocation(const QFileInfo &fileInfo, QStringList &filesToOpen, const QStringList &dependencies);
- QQmlEngine *m_engine;
- bool m_showExamples;
+ static QString copyToAlternativeLocation(const QFileInfo &fileInfo, QStringList &filesToOpen, const QStringList &dependencies);
+ const bool m_showExamples;
};
} // namespace Internal
diff --git a/src/plugins/qtsupport/qtsupportplugin.cpp b/src/plugins/qtsupport/qtsupportplugin.cpp
index 62c8820ee3d..4d2453bbd4a 100644
--- a/src/plugins/qtsupport/qtsupportplugin.cpp
+++ b/src/plugins/qtsupport/qtsupportplugin.cpp
@@ -76,13 +76,8 @@ bool QtSupportPlugin::initialize(const QStringList &arguments, QString *errorMes
addAutoReleasedObject(new CodeGenSettingsPage);
addAutoReleasedObject(new QtOptionsPage);
- ExamplesWelcomePage *welcomePage;
- welcomePage = new ExamplesWelcomePage;
- addAutoReleasedObject(welcomePage);
- welcomePage->setShowExamples(true);
-
- welcomePage = new ExamplesWelcomePage;
- addAutoReleasedObject(welcomePage);
+ addAutoReleasedObject(new ExamplesWelcomePage(true)); // Examples
+ addAutoReleasedObject(new ExamplesWelcomePage(false)); // Tutorials
ProjectExplorer::KitManager::registerKitInformation(new QtKitInformation);
diff --git a/src/plugins/welcome/Welcome.json.in b/src/plugins/welcome/Welcome.json.in
index 60f79a59fe7..f9cfb3f18eb 100644
--- a/src/plugins/welcome/Welcome.json.in
+++ b/src/plugins/welcome/Welcome.json.in
@@ -13,7 +13,7 @@
\"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\" : \"Qt Creator\",
- \"Description\" : \"Default Welcome Screen Plugin.\",
+ \"Description\" : \"Secondary Welcome Screen Plugin.\",
\"Url\" : \"http://www.qt.io\",
$$dependencyList
}
diff --git a/src/plugins/welcome/welcome.pro b/src/plugins/welcome/welcome.pro
index 9fec1225511..612739a692f 100644
--- a/src/plugins/welcome/welcome.pro
+++ b/src/plugins/welcome/welcome.pro
@@ -1,15 +1,8 @@
-QT += quick quickwidgets
-
-QML_IMPORT_PATH=../../../share/qtcreator/welcomescreen
-
include(../../qtcreatorplugin.pri)
-HEADERS += welcomeplugin.h
-
SOURCES += welcomeplugin.cpp
DEFINES += WELCOME_LIBRARY
-RESOURCES += \
- welcome.qrc
+RESOURCES += welcome.qrc
diff --git a/src/plugins/welcome/welcome.qbs b/src/plugins/welcome/welcome.qbs
index 2a29e749aeb..74dd1d04e4f 100644
--- a/src/plugins/welcome/welcome.qbs
+++ b/src/plugins/welcome/welcome.qbs
@@ -3,14 +3,13 @@ import qbs 1.0
QtcPlugin {
name: "Welcome"
- Depends { name: "Qt"; submodules: ["widgets", "network", "quick", "quickwidgets"] }
+ Depends { name: "Qt"; submodules: ["widgets", "network" ] }
Depends { name: "Utils" }
Depends { name: "Core" }
files: [
- "welcomeplugin.cpp",
- "welcomeplugin.h",
- "welcome.qrc",
+ "welcome2plugin.cpp",
+ "welcome2plugin.h",
]
}
diff --git a/src/plugins/welcome/welcome_dependencies.pri b/src/plugins/welcome/welcome_dependencies.pri
index c9a5fc67e18..af40b7285fe 100644
--- a/src/plugins/welcome/welcome_dependencies.pri
+++ b/src/plugins/welcome/welcome_dependencies.pri
@@ -4,3 +4,4 @@ QTC_LIB_DEPENDS += \
utils
QTC_PLUGIN_DEPENDS += \
coreplugin
+
diff --git a/src/plugins/welcome/welcomeplugin.cpp b/src/plugins/welcome/welcomeplugin.cpp
index c10c5ffab2a..2c6f88956a9 100644
--- a/src/plugins/welcome/welcomeplugin.cpp
+++ b/src/plugins/welcome/welcomeplugin.cpp
@@ -23,13 +23,11 @@
**
****************************************************************************/
-#include "welcomeplugin.h"
-
+#include <extensionsystem/iplugin.h>
#include <extensionsystem/pluginmanager.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
-
#include <coreplugin/coreconstants.h>
#include <coreplugin/icore.h>
#include <coreplugin/imode.h>
@@ -43,278 +41,318 @@
#include <utils/hostosinfo.h>
#include <utils/qtcassert.h>
#include <utils/styledbar.h>
-
+#include <utils/treemodel.h>
#include <utils/theme/theme.h>
-#include <QVBoxLayout>
-#include <QMessageBox>
-
-#include <QDir>
-#include <QQmlPropertyMap>
-#include <QQuickImageProvider>
+#include <QDesktopServices>
+#include <QHeaderView>
+#include <QLabel>
+#include <QMouseEvent>
+#include <QPainter>
+#include <QStackedWidget>
#include <QTimer>
-
-#include <QtQuickWidgets/QQuickWidget>
-#include <QtQml/QQmlContext>
-#include <QtQml/QQmlEngine>
-
-enum { debug = 0 };
+#include <QVBoxLayout>
using namespace Core;
using namespace ExtensionSystem;
using namespace Utils;
-static const char currentPageSettingsKeyC[] = "WelcomeTab";
-
namespace Welcome {
namespace Internal {
-static QString applicationDirPath()
+class SideBar;
+
+const int lrPadding = 34;
+const char currentPageSettingsKeyC[] = "Welcome2Tab";
+
+static QColor themeColor(Theme::Color role)
+{
+ return Utils::creatorTheme()->color(role);
+}
+
+static QFont sizedFont(int size, const QWidget *widget, bool underline = false)
{
- // normalize paths so QML doesn't freak out if it's wrongly capitalized on Windows
- return FileUtils::normalizePathName(QCoreApplication::applicationDirPath());
+ QFont f = widget->font();
+ f.setPixelSize(size);
+ f.setUnderline(underline);
+ return f;
}
-static QString resourcePath()
+static QPalette lightText()
{
- // normalize paths so QML doesn't freak out if it's wrongly capitalized on Windows
- return FileUtils::normalizePathName(ICore::resourcePath());
+ QPalette pal;
+ pal.setColor(QPalette::Foreground, themeColor(Theme::Welcome_ForegroundPrimaryColor));
+ pal.setColor(QPalette::WindowText, themeColor(Theme::Welcome_ForegroundPrimaryColor));
+ return pal;
}
-class WelcomeImageIconProvider : public QQuickImageProvider
+class WelcomeMode : public IMode
{
+ Q_OBJECT
public:
- WelcomeImageIconProvider()
- : QQuickImageProvider(Pixmap)
- {
- }
+ WelcomeMode();
+ ~WelcomeMode();
- QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override
- {
- Q_UNUSED(requestedSize)
+ void initPlugins();
- QString maskFile;
- Theme::Color themeColor = Theme::Welcome_ForegroundPrimaryColor;
+ Q_INVOKABLE bool openDroppedFiles(const QList<QUrl> &urls);
- const QStringList elements = id.split(QLatin1Char('/'));
+private:
+ void addPage(IWelcomePage *page);
- if (!elements.empty())
- maskFile = elements.first();
+ QWidget *m_modeWidget;
+ QStackedWidget *m_pageStack;
+ SideBar *m_sideBar;
+ QList<IWelcomePage *> m_pluginList;
+ QList<WelcomePageButton *> m_pageButtons;
+ Id m_activePage;
+};
- if (elements.count() >= 2) {
- const static QMetaObject &m = Theme::staticMetaObject;
- const static QMetaEnum e = m.enumerator(m.indexOfEnumerator("Color"));
- bool success = false;
- int value = e.keyToValue(elements.at(1).toLatin1(), &success);
- if (success)
- themeColor = Theme::Color(value);
- }
+class WelcomePlugin : public ExtensionSystem::IPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Welcome.json")
- const QString fileName = QString::fromLatin1(":/welcome/images/%1.png").arg(maskFile);
- const Icon icon({{fileName, themeColor}}, Icon::Tint);
- const QPixmap result = icon.pixmap();
- if (size)
- *size = result.size();
- return result;
+public:
+ WelcomePlugin() {}
+
+ bool initialize(const QStringList &, QString *) final
+ {
+ m_welcomeMode = new WelcomeMode;
+ addAutoReleasedObject(m_welcomeMode);
+ return true;
+ }
+
+ void extensionsInitialized() final
+ {
+ m_welcomeMode->initPlugins();
+ ModeManager::activateMode(m_welcomeMode->id());
}
+
+ WelcomeMode *m_welcomeMode = nullptr;
};
-class WelcomeMode : public IMode
+class IconAndLink : public QWidget
{
- Q_OBJECT
- Q_PROPERTY(int activePlugin READ activePlugin WRITE setActivePlugin NOTIFY activePluginChanged)
- Q_PROPERTY(QStringList recentProjectsShortcuts READ recentProjectsShortcuts NOTIFY recentProjectsShortcutsChanged)
- Q_PROPERTY(QStringList sessionsShortcuts READ sessionsShortcuts NOTIFY sessionsShortcutsChanged)
public:
- WelcomeMode();
- ~WelcomeMode();
+ IconAndLink(const QString &iconSource,
+ const QString &title,
+ const QString &openUrl,
+ QWidget *parent)
+ : QWidget(parent), m_iconSource(iconSource), m_title(title), m_openUrl(openUrl)
+ {
+ setAutoFillBackground(true);
- void activated();
- void initPlugins();
- int activePlugin() const { return m_activePlugin; }
+ const QString fileName = QString(":/welcome/images/%1.png").arg(iconSource);
+ const Icon icon({{ fileName, Theme::Welcome_ForegroundPrimaryColor }}, Icon::Tint);
- QStringList recentProjectsShortcuts() const { return m_recentProjectsShortcuts; }
- QStringList sessionsShortcuts() const { return m_sessionsShortcuts; }
+ m_icon = new QLabel;
+ m_icon->setPixmap(icon.pixmap());
- Q_INVOKABLE bool openDroppedFiles(const QList<QUrl> &urls);
+ m_label = new QLabel(title);
+ m_label->setFont(sizedFont(11, m_label, false));
-public slots:
- void setActivePlugin(int pos)
+ auto layout = new QHBoxLayout;
+ layout->setContentsMargins(lrPadding, 0, lrPadding, 0);
+ layout->addWidget(m_icon);
+ layout->addSpacing(2);
+ layout->addWidget(m_label);
+ layout->addStretch(1);
+ setLayout(layout);
+ }
+
+ void enterEvent(QEvent *) override
{
- if (m_activePlugin != pos) {
- m_activePlugin = pos;
- emit activePluginChanged(pos);
- }
+ QPalette pal;
+ pal.setColor(QPalette::Background, themeColor(Theme::Welcome_HoverColor));
+ setPalette(pal);
+ m_label->setFont(sizedFont(11, m_label, true));
+ update();
}
-signals:
- void activePluginChanged(int pos);
+ void leaveEvent(QEvent *) override
+ {
+ QPalette pal;
+ pal.setColor(QPalette::Background, themeColor(Theme::Welcome_BackgroundColor));
+ setPalette(pal);
+ m_label->setFont(sizedFont(11, m_label, false));
+ update();
+ }
- void openSessionTriggered(int index);
- void openRecentProjectTriggered(int index);
+ void mousePressEvent(QMouseEvent *) override
+ {
+ QDesktopServices::openUrl(m_openUrl);
+ }
- void recentProjectsShortcutsChanged(QStringList recentProjectsShortcuts);
- void sessionsShortcutsChanged(QStringList sessionsShortcuts);
+ QString m_iconSource;
+ QString m_title;
+ QString m_openUrl;
-private:
- void welcomePluginAdded(QObject*);
- void sceneGraphError(QQuickWindow::SceneGraphError, const QString &message);
- void facilitateQml(QQmlEngine *engine);
- void addPages(QQmlEngine *engine, const QList<IWelcomePage *> &pages);
- void addKeyboardShortcuts();
+ QLabel *m_icon;
+ QLabel *m_label;
+};
- QWidget *m_modeWidget;
- QQuickWidget *m_welcomePage;
- QMap<Id, IWelcomePage *> m_idPageMap;
- QList<IWelcomePage *> m_pluginList;
- int m_activePlugin;
- QStringList m_recentProjectsShortcuts;
- QStringList m_sessionsShortcuts;
+class SideBar : public QWidget
+{
+ Q_OBJECT
+public:
+ SideBar(QWidget *parent)
+ : QWidget(parent)
+ {
+ setAutoFillBackground(true);
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setPalette(themeColor(Theme::Welcome_BackgroundColor));
+
+ auto vbox = new QVBoxLayout(this);
+ vbox->setSpacing(0);
+ vbox->setContentsMargins(0, 27, 0, 0);
+
+ int sd = IWelcomePage::screenDependHeightDistance();
+
+ {
+ auto l = m_pluginButtons = new QVBoxLayout;
+ l->setContentsMargins(lrPadding, 0, lrPadding, 0);
+ l->setSpacing(sd + 3);
+ vbox->addItem(l);
+ vbox->addSpacing(62);
+ }
+
+ {
+ auto l = new QVBoxLayout;
+ l->setContentsMargins(lrPadding, 0, lrPadding, 0);
+ l->setSpacing(sd - 8);
+
+ auto newLabel = new QLabel(tr("New to Qt?"), this);
+ newLabel->setFont(sizedFont(18, this));
+ l->addWidget(newLabel);
+
+ auto learnLabel = new QLabel(tr("Learn how to develop your own applications "
+ "and explore Qt Creator."), this);
+ learnLabel->setMaximumWidth(200);
+ learnLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ learnLabel->setWordWrap(true);
+ learnLabel->setFont(sizedFont(12, this));
+ learnLabel->setPalette(lightText());
+ l->addWidget(learnLabel);
+
+ l->addSpacing(12);
+
+ auto getStartedButton = new WelcomePageButton(this);
+ getStartedButton->setText(tr("Get Started Now"));
+ getStartedButton->setOnClicked([] {
+ QDesktopServices::openUrl(QString("qthelp://org.qt-project.qtcreator/doc/index.html"));
+ });
+ l->addWidget(getStartedButton);
+
+ vbox->addItem(l);
+ vbox->addSpacing(77);
+ }
+
+ {
+ auto l = new QVBoxLayout;
+ l->setContentsMargins(0, 0, 0, 0);
+ l->setSpacing(sd + 3);
+ l->addWidget(new IconAndLink("qtaccount", tr("Qt Account"), "https://account.qt.io", this));
+ l->addWidget(new IconAndLink("community", tr("Online Community"), "http://forum.qt.io", this));
+ l->addWidget(new IconAndLink("blogs", tr("Blogs"), "http://planet.qt.io", this));
+ l->addWidget(new IconAndLink("userguide", tr("User Guide"),
+ "qthelp://org.qt-project.qtcreator/doc/index.html", this));
+ vbox->addItem(l);
+ }
+
+ vbox->addStretch(1);
+ }
+
+ QVBoxLayout *m_pluginButtons = nullptr;
};
WelcomeMode::WelcomeMode()
- : m_activePlugin(0)
{
setDisplayName(tr("Welcome"));
- const Utils::Icon MODE_WELCOME_CLASSIC(
- QLatin1String(":/welcome/images/mode_welcome.png"));
- const Utils::Icon MODE_WELCOME_FLAT({
- {QLatin1String(":/welcome/images/mode_welcome_mask.png"), Utils::Theme::IconsBaseColor}});
- const Utils::Icon MODE_WELCOME_FLAT_ACTIVE({
- {QLatin1String(":/welcome/images/mode_welcome_mask.png"), Utils::Theme::IconsModeWelcomeActiveColor}});
+ const Icon CLASSIC(":/welcome/images/mode_welcome.png");
+ const Icon FLAT({{":/welcome/images/mode_welcome_mask.png",
+ Theme::IconsBaseColor}});
+ const Icon FLAT_ACTIVE({{":/welcome/images/mode_welcome_mask.png",
+ Theme::IconsModeWelcomeActiveColor}});
+ setIcon(Icon::modeIcon(CLASSIC, FLAT, FLAT_ACTIVE));
- setIcon(Utils::Icon::modeIcon(MODE_WELCOME_CLASSIC,
- MODE_WELCOME_FLAT, MODE_WELCOME_FLAT_ACTIVE));
setPriority(Constants::P_MODE_WELCOME);
setId(Constants::MODE_WELCOME);
- setContextHelpId(QLatin1String("Qt Creator Manual"));
+ setContextHelpId("Qt Creator Manual");
setContext(Context(Constants::C_WELCOME_MODE));
- m_modeWidget = new QWidget;
- m_modeWidget->setObjectName(QLatin1String("WelcomePageModeWidget"));
- QVBoxLayout *layout = new QVBoxLayout(m_modeWidget);
- layout->setMargin(0);
- layout->setSpacing(0);
+ QPalette palette = creatorTheme()->palette();
+ palette.setColor(QPalette::Background, themeColor(Theme::Welcome_BackgroundColor));
- m_welcomePage = new QQuickWidget;
- m_welcomePage->setResizeMode(QQuickWidget::SizeRootObjectToView);
+ m_modeWidget = new QWidget;
+ m_modeWidget->setPalette(palette);
- m_welcomePage->setObjectName(QLatin1String("WelcomePage"));
+ m_sideBar = new SideBar(m_modeWidget);
- connect(m_welcomePage, &QQuickWidget::sceneGraphError,
- this, &WelcomeMode::sceneGraphError);
+ auto divider = new QWidget(m_modeWidget);
+ divider->setMaximumWidth(1);
+ divider->setMinimumWidth(1);
+ divider->setAutoFillBackground(true);
+ divider->setPalette(themeColor(Theme::Welcome_DividerColor));
- StyledBar *styledBar = new StyledBar(m_modeWidget);
- styledBar->setObjectName(QLatin1String("WelcomePageStyledBar"));
- layout->addWidget(styledBar);
+ m_pageStack = new QStackedWidget(m_modeWidget);
+ m_pageStack->setAutoFillBackground(true);
- m_welcomePage->setParent(m_modeWidget);
- layout->addWidget(m_welcomePage);
+ auto hbox = new QHBoxLayout;
+ hbox->addWidget(m_sideBar);
+ hbox->addWidget(divider);
+ hbox->addWidget(m_pageStack);
+ hbox->setStretchFactor(m_pageStack, 10);
- addKeyboardShortcuts();
+ auto layout = new QVBoxLayout(m_modeWidget);
+ layout->setMargin(0);
+ layout->setSpacing(0);
+ layout->addWidget(new StyledBar(m_modeWidget));
+ layout->addItem(hbox);
setWidget(m_modeWidget);
}
-void WelcomeMode::addKeyboardShortcuts()
-{
- const int actionsCount = 9;
- Context welcomeContext(Core::Constants::C_WELCOME_MODE);
-
- const Id sessionBase = "Welcome.OpenSession";
- for (int i = 1; i <= actionsCount; ++i) {
- auto act = new QAction(tr("Open Session #%1").arg(i), this);
- Command *cmd = ActionManager::registerAction(act, sessionBase.withSuffix(i), welcomeContext);
- cmd->setDefaultKeySequence(QKeySequence((UseMacShortcuts ? tr("Ctrl+Meta+%1") : tr("Ctrl+Alt+%1")).arg(i)));
- m_sessionsShortcuts.append(cmd->keySequence().toString(QKeySequence::NativeText));
-
- connect(act, &QAction::triggered, this, [this, i] { openSessionTriggered(i-1); });
- connect(cmd, &Command::keySequenceChanged, this, [this, i, cmd] {
- m_sessionsShortcuts[i-1] = cmd->keySequence().toString(QKeySequence::NativeText);
- emit sessionsShortcutsChanged(m_sessionsShortcuts);
- });
- }
-
- const Id projectBase = "Welcome.OpenRecentProject";
- for (int i = 1; i <= actionsCount; ++i) {
- auto act = new QAction(tr("Open Recent Project #%1").arg(i), this);
- Command *cmd = ActionManager::registerAction(act, projectBase.withSuffix(i), welcomeContext);
- cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+Shift+%1").arg(i)));
- m_recentProjectsShortcuts.append(cmd->keySequence().toString(QKeySequence::NativeText));
-
- connect(act, &QAction::triggered, this, [this, i] { openRecentProjectTriggered(i-1); });
- connect(cmd, &Command::keySequenceChanged, this, [this, i, cmd] {
- m_recentProjectsShortcuts[i-1] = cmd->keySequence().toString(QKeySequence::NativeText);
- emit recentProjectsShortcutsChanged(m_recentProjectsShortcuts);
- });
- }
-}
-
WelcomeMode::~WelcomeMode()
{
QSettings *settings = ICore::settings();
- settings->setValue(QLatin1String(currentPageSettingsKeyC), activePlugin());
+ settings->setValue(currentPageSettingsKeyC, m_activePage.toSetting());
delete m_modeWidget;
}
-void WelcomeMode::sceneGraphError(QQuickWindow::SceneGraphError, const QString &message)
-{
- QMessageBox *messageBox =
- new QMessageBox(QMessageBox::Warning,
- tr("Welcome Mode Load Error"), message,
- QMessageBox::Close, m_modeWidget);
- messageBox->setModal(false);
- messageBox->setAttribute(Qt::WA_DeleteOnClose);
- messageBox->show();
-}
-
-void WelcomeMode::facilitateQml(QQmlEngine *engine)
-{
- QStringList importPathList = engine->importPathList();
- importPathList.append(resourcePath() + QLatin1String("/welcomescreen"));
- engine->addImageProvider(QLatin1String("icons"), new WelcomeImageIconProvider);
- if (!debug)
- engine->setOutputWarningsToStandardError(false);
-
- QString pluginPath = applicationDirPath();
- if (HostOsInfo::isMacHost())
- pluginPath += QLatin1String("/../PlugIns");
- else
- pluginPath += QLatin1String("/../" IDE_LIBRARY_BASENAME "/qtcreator");
- importPathList.append(QDir::cleanPath(pluginPath));
- engine->setImportPathList(importPathList);
-
- QQmlContext *ctx = engine->rootContext();
- ctx->setContextProperty(QLatin1String("welcomeMode"), this);
- ctx->setContextProperty(QLatin1String("creatorTheme"), Utils::creatorTheme()->values());
- ctx->setContextProperty(QLatin1String("useNativeText"), true);
-}
-
void WelcomeMode::initPlugins()
{
QSettings *settings = ICore::settings();
- setActivePlugin(settings->value(QLatin1String(currentPageSettingsKeyC)).toInt());
+ m_activePage = Id::fromSetting(settings->value(currentPageSettingsKeyC));
- QQmlEngine *engine = m_welcomePage->engine();
- facilitateQml(engine);
+ const QList<IWelcomePage *> availablePages = PluginManager::getObjects<IWelcomePage>();
+ for (IWelcomePage *page : availablePages)
+ addPage(page);
- QList<IWelcomePage *> availablePages = PluginManager::getObjects<IWelcomePage>();
- addPages(engine, availablePages);
// make sure later added pages are made available too:
- connect(PluginManager::instance(), &PluginManager::objectAdded, engine, [this, engine](QObject *obj) {
+ connect(PluginManager::instance(), &PluginManager::objectAdded, this, [this](QObject *obj) {
if (IWelcomePage *page = qobject_cast<IWelcomePage*>(obj))
- addPages(engine, QList<IWelcomePage *>() << page);
+ addPage(page);
});
- QString path = resourcePath() + QLatin1String("/welcomescreen/welcomescreen.qml");
-
- // finally, load the root page
- m_welcomePage->setSource(QUrl::fromLocalFile(path));
+ if (!m_activePage.isValid() && !m_pageButtons.isEmpty()) {
+ m_activePage = m_pluginList.at(0)->id();
+ m_pageButtons.at(0)->click();
+ }
}
bool WelcomeMode::openDroppedFiles(const QList<QUrl> &urls)
{
+// DropArea {
+// anchors.fill: parent
+// keys: ["text/uri-list"]
+// onDropped: {
+// if ((drop.supportedActions & Qt.CopyAction != 0)
+// && welcomeMode.openDroppedFiles(drop.urls))
+// drop.accept(Qt.CopyAction);
+// }
+// }
const QList<QUrl> localUrls = Utils::filtered(urls, &QUrl::isLocalFile);
if (!localUrls.isEmpty()) {
QTimer::singleShot(0, [localUrls]() {
@@ -325,54 +363,38 @@ bool WelcomeMode::openDroppedFiles(const QList<QUrl> &urls)
return false;
}
-void WelcomeMode::addPages(QQmlEngine *engine, const QList<IWelcomePage *> &pages)
+void WelcomeMode::addPage(IWelcomePage *page)
{
- QList<IWelcomePage *> addedPages = pages;
- Utils::sort(addedPages, &IWelcomePage::priority);
- // insert into m_pluginList, keeping m_pluginList sorted by priority
- auto addIt = addedPages.cbegin();
- auto currentIt = m_pluginList.begin();
- while (addIt != addedPages.cend()) {
- IWelcomePage *page = *addIt;
- QTC_ASSERT(!m_idPageMap.contains(page->id()), ++addIt; continue);
- while (currentIt != m_pluginList.end() && (*currentIt)->priority() <= page->priority())
- ++currentIt;
- // currentIt is now either end() or a page with higher value
- currentIt = m_pluginList.insert(currentIt, page);
- m_idPageMap.insert(page->id(), page);
- page->facilitateQml(engine);
- ++currentIt;
- ++addIt;
+ int idx;
+ int pagePriority = page->priority();
+ for (idx = 0; idx != m_pluginList.size(); ++idx) {
+ if (m_pluginList.at(idx)->priority() >= pagePriority)
+ break;
}
- // update model through reset
- QQmlContext *ctx = engine->rootContext();
- ctx->setContextProperty(QLatin1String("pagesModel"), QVariant::fromValue(
- Utils::transform(m_pluginList, // transform into QList<QObject *>
- [](IWelcomePage *page) -> QObject * {
- return page;
- })));
-}
-
-WelcomePlugin::WelcomePlugin()
- : m_welcomeMode(0)
-{
-}
-
-bool WelcomePlugin::initialize(const QStringList & /* arguments */, QString *errorMessage)
-{
- if (!Utils::HostOsInfo::canCreateOpenGLContext(errorMessage))
- return false;
-
- m_welcomeMode = new WelcomeMode;
- addAutoReleasedObject(m_welcomeMode);
-
- return true;
-}
-
-void WelcomePlugin::extensionsInitialized()
-{
- m_welcomeMode->initPlugins();
- ModeManager::activateMode(m_welcomeMode->id());
+ auto pageButton = new WelcomePageButton(m_sideBar);
+ auto pageId = page->id();
+ pageButton->setText(page->title());
+ pageButton->setActiveChecker([this, pageId] { return m_activePage == pageId; });
+
+ m_pluginList.append(page);
+ m_pageButtons.append(pageButton);
+
+ m_sideBar->m_pluginButtons->insertWidget(idx, pageButton);
+
+ QWidget *stackPage = page->createWidget();
+ stackPage->setAutoFillBackground(true);
+ m_pageStack->insertWidget(idx, stackPage);
+
+ auto onClicked = [this, page, pageId, stackPage] {
+ m_activePage = pageId;
+ m_pageStack->setCurrentWidget(stackPage);
+ for (WelcomePageButton *pageButton : m_pageButtons)
+ pageButton->recheckActive();
+ };
+
+ pageButton->setOnClicked(onClicked);
+ if (pageId == m_activePage)
+ onClicked();
}
} // namespace Internal
diff --git a/src/plugins/welcome/welcomeplugin.h b/src/plugins/welcome/welcomeplugin.h
deleted file mode 100644
index fe05ee8a04c..00000000000
--- a/src/plugins/welcome/welcomeplugin.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of Qt Creator.
-**
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the 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. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file 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 file. 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.
-**
-****************************************************************************/
-
-#pragma once
-
-#include <extensionsystem/iplugin.h>
-
-QT_BEGIN_NAMESPACE
-class QQmlEngine;
-QT_END_NAMESPACE
-
-namespace Welcome {
-namespace Internal {
-
-class WelcomeMode;
-
-class WelcomePlugin : public ExtensionSystem::IPlugin
-{
- Q_OBJECT
- Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Welcome.json")
-
-public:
- WelcomePlugin();
-
- virtual bool initialize(const QStringList &arguments, QString *errorMessage);
- virtual void extensionsInitialized();
-
-private:
- WelcomeMode *m_welcomeMode;
-};
-
-} // namespace Welcome
-} // namespace Internal