From 8770d83fc8bd71691e7cbb5114a1c0b4b8cb4ed8 Mon Sep 17 00:00:00 2001
From: Eike Ziller
Date: Mon, 10 Dec 2018 17:10:17 +0100
Subject: [PATCH] Add general UI introduction
Adds a Help > UI Introduction, which highlights basic aspects of the Qt
Creator UI.
The pages to show are defined in a format that can later move into a
text based configuration file like JSON. It specifies an optional object
name of a widget to highlight, a title, a brief description and a longer
description (potentially with tables, lists and other simple HTML).
Fixes: QTCREATORBUG-21585
Change-Id: Idb64c87e1d752bc24437588278093a96be0eeddb
Reviewed-by: Alessandro Portale
Reviewed-by: Tobias Hunger
Reviewed-by: Leena Miettinen
---
src/libs/utils/checkablemessagebox.cpp | 8 +-
src/libs/utils/checkablemessagebox.h | 4 +
src/plugins/coreplugin/fancyactionbar.cpp | 2 +
src/plugins/coreplugin/fancytabwidget.cpp | 5 +-
src/plugins/coreplugin/locator/locator.cpp | 1 +
src/plugins/coreplugin/outputpanemanager.cpp | 1 +
.../progressmanager/progressmanager.cpp | 1 +
src/plugins/debugger/debuggerplugin.cpp | 1 +
.../projectexplorer/projectexplorer.cpp | 3 +
src/plugins/welcome/images/border.png | Bin 0 -> 852 bytes
src/plugins/welcome/introductionwidget.cpp | 399 ++++++++++++++++++
src/plugins/welcome/introductionwidget.h | 82 ++++
src/plugins/welcome/welcome.pro | 6 +-
src/plugins/welcome/welcome.qrc | 1 +
src/plugins/welcome/welcomeplugin.cpp | 15 +
src/tools/icons/qtcreatoricons.svg | 33 ++
16 files changed, 556 insertions(+), 6 deletions(-)
create mode 100644 src/plugins/welcome/images/border.png
create mode 100644 src/plugins/welcome/introductionwidget.cpp
create mode 100644 src/plugins/welcome/introductionwidget.h
diff --git a/src/libs/utils/checkablemessagebox.cpp b/src/libs/utils/checkablemessagebox.cpp
index 70413886faa..857711fd8a5 100644
--- a/src/libs/utils/checkablemessagebox.cpp
+++ b/src/libs/utils/checkablemessagebox.cpp
@@ -277,7 +277,7 @@ QMessageBox::StandardButton CheckableMessageBox::dialogButtonBoxToMessageBoxButt
return static_cast(int(db));
}
-bool askAgain(QSettings *settings, const QString &settingsSubKey)
+bool CheckableMessageBox::shouldAskAgain(QSettings *settings, const QString &settingsSubKey)
{
if (QTC_GUARD(settings)) {
settings->beginGroup(QLatin1String(kDoNotAskAgainKey));
@@ -309,7 +309,7 @@ void initDoNotAskAgainMessageBox(CheckableMessageBox &messageBox, const QString
messageBox.setDefaultButton(defaultButton);
}
-void doNotAskAgain(QSettings *settings, const QString &settingsSubKey)
+void CheckableMessageBox::doNotAskAgain(QSettings *settings, const QString &settingsSubKey)
{
if (!settings)
return;
@@ -337,7 +337,7 @@ CheckableMessageBox::doNotAskAgainQuestion(QWidget *parent, const QString &title
QDialogButtonBox::StandardButton acceptButton)
{
- if (!askAgain(settings, settingsSubKey))
+ if (!shouldAskAgain(settings, settingsSubKey))
return acceptButton;
CheckableMessageBox messageBox(parent);
@@ -365,7 +365,7 @@ CheckableMessageBox::doNotShowAgainInformation(QWidget *parent, const QString &t
QDialogButtonBox::StandardButton defaultButton)
{
- if (!askAgain(settings, settingsSubKey))
+ if (!shouldAskAgain(settings, settingsSubKey))
return defaultButton;
CheckableMessageBox messageBox(parent);
diff --git a/src/libs/utils/checkablemessagebox.h b/src/libs/utils/checkablemessagebox.h
index 3367ce02b16..c6359f72cd2 100644
--- a/src/libs/utils/checkablemessagebox.h
+++ b/src/libs/utils/checkablemessagebox.h
@@ -117,6 +117,10 @@ public:
QAbstractButton *clickedButton() const;
QDialogButtonBox::StandardButton clickedStandardButton() const;
+ // check and set "ask again" status
+ static bool shouldAskAgain(QSettings *settings, const QString &settingsSubKey);
+ static void doNotAskAgain(QSettings *settings, const QString &settingsSubKey);
+
// Conversion convenience
static QMessageBox::StandardButton dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton);
static void resetAllDoNotAskAgainQuestions(QSettings *settings);
diff --git a/src/plugins/coreplugin/fancyactionbar.cpp b/src/plugins/coreplugin/fancyactionbar.cpp
index b57573ef880..8d496ea3d24 100644
--- a/src/plugins/coreplugin/fancyactionbar.cpp
+++ b/src/plugins/coreplugin/fancyactionbar.cpp
@@ -373,6 +373,8 @@ void FancyActionBar::addProjectSelector(QAction *action)
void FancyActionBar::insertAction(int index, QAction *action)
{
auto *button = new FancyToolButton(action, this);
+ if (!action->objectName().isEmpty())
+ button->setObjectName(action->objectName() + ".Button"); // used for UI introduction
button->setIconsOnly(m_iconsOnly);
m_actionsLayout->insertWidget(index, button);
}
diff --git a/src/plugins/coreplugin/fancytabwidget.cpp b/src/plugins/coreplugin/fancytabwidget.cpp
index 4fad26bc12c..ad1d72b1f13 100644
--- a/src/plugins/coreplugin/fancytabwidget.cpp
+++ b/src/plugins/coreplugin/fancytabwidget.cpp
@@ -458,6 +458,7 @@ FancyTabWidget::FancyTabWidget(QWidget *parent)
: QWidget(parent)
{
m_tabBar = new FancyTabBar(this);
+ m_tabBar->setObjectName("ModeSelector"); // used for UI introduction
m_selectionWidget = new QWidget(this);
auto selectionLayout = new QVBoxLayout;
@@ -473,7 +474,8 @@ FancyTabWidget::FancyTabWidget(QWidget *parent)
layout->addWidget(fancyButton);
selectionLayout->addWidget(bar);
- selectionLayout->addWidget(m_tabBar, 1);
+ selectionLayout->addWidget(m_tabBar);
+ selectionLayout->addStretch(1);
m_selectionWidget->setLayout(selectionLayout);
m_selectionWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
@@ -551,6 +553,7 @@ void FancyTabWidget::paintEvent(QPaintEvent *event)
const QRectF boderRect = QRectF(rect).adjusted(0.5, 0.5, -0.5, -0.5);
if (creatorTheme()->flag(Theme::FlatToolBars)) {
+ painter.fillRect(event->rect(), StyleHelper::baseColor());
painter.setPen(StyleHelper::toolBarBorderColor());
painter.drawLine(boderRect.topRight(), boderRect.bottomRight());
} else {
diff --git a/src/plugins/coreplugin/locator/locator.cpp b/src/plugins/coreplugin/locator/locator.cpp
index bdc1664c263..17f8fbbd862 100644
--- a/src/plugins/coreplugin/locator/locator.cpp
+++ b/src/plugins/coreplugin/locator/locator.cpp
@@ -123,6 +123,7 @@ void Locator::initialize()
mtools->addAction(cmd);
auto locatorWidget = LocatorManager::createLocatorInputWidget(ICore::mainWindow());
+ locatorWidget->setObjectName("LocatorInput"); // used for UI introduction
StatusBarManager::addStatusBarWidget(locatorWidget, StatusBarManager::First,
Context("LocatorWidget"));
connect(ICore::instance(), &ICore::saveSettingsRequested, this, &Locator::saveSettings);
diff --git a/src/plugins/coreplugin/outputpanemanager.cpp b/src/plugins/coreplugin/outputpanemanager.cpp
index 00e1c9699e5..bda60b58123 100644
--- a/src/plugins/coreplugin/outputpanemanager.cpp
+++ b/src/plugins/coreplugin/outputpanemanager.cpp
@@ -232,6 +232,7 @@ OutputPaneManager::OutputPaneManager(QWidget *parent) :
setLayout(mainlayout);
m_buttonsWidget = new QWidget;
+ m_buttonsWidget->setObjectName("OutputPaneButtons"); // used for UI introduction
m_buttonsWidget->setLayout(new QHBoxLayout);
m_buttonsWidget->layout()->setContentsMargins(5,0,0,0);
m_buttonsWidget->layout()->setSpacing(
diff --git a/src/plugins/coreplugin/progressmanager/progressmanager.cpp b/src/plugins/coreplugin/progressmanager/progressmanager.cpp
index 06c825ae80c..725721852f5 100644
--- a/src/plugins/coreplugin/progressmanager/progressmanager.cpp
+++ b/src/plugins/coreplugin/progressmanager/progressmanager.cpp
@@ -300,6 +300,7 @@ void ProgressManagerPrivate::init()
readSettings();
m_statusBarWidget = new QWidget;
+ m_statusBarWidget->setObjectName("ProgressInfo"); // used for UI introduction
auto layout = new QHBoxLayout(m_statusBarWidget);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp
index c79cecab504..04b1b8d3414 100644
--- a/src/plugins/debugger/debuggerplugin.cpp
+++ b/src/plugins/debugger/debuggerplugin.cpp
@@ -1120,6 +1120,7 @@ bool DebuggerPluginPrivate::initialize(const QStringList &arguments,
m_visibleStartAction.setAttribute(ProxyAction::UpdateIcon);
m_visibleStartAction.setAction(&m_startAction);
+ m_visibleStartAction.setObjectName("Debug"); // used for UI introduction
ModeManager::addAction(&m_visibleStartAction, Constants::P_ACTION_DEBUG);
m_undisturbableAction.setIcon(interruptIcon(false));
diff --git a/src/plugins/projectexplorer/projectexplorer.cpp b/src/plugins/projectexplorer/projectexplorer.cpp
index 068b4f720b8..d303f6f620e 100644
--- a/src/plugins/projectexplorer/projectexplorer.cpp
+++ b/src/plugins/projectexplorer/projectexplorer.cpp
@@ -984,6 +984,7 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er
// Add to mode bar
dd->m_modeBarBuildAction = new Utils::ProxyAction(this);
+ dd->m_modeBarBuildAction->setObjectName("Build"); // used for UI introduction
dd->m_modeBarBuildAction->initialize(cmd->action());
dd->m_modeBarBuildAction->setAttribute(Utils::ProxyAction::UpdateText);
dd->m_modeBarBuildAction->setAction(cmd->action());
@@ -1027,6 +1028,7 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er
cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+R")));
mbuild->addAction(cmd, Constants::G_BUILD_RUN);
+ cmd->action()->setObjectName("Run"); // used for UI introduction
ModeManager::addAction(cmd->action(), Constants::P_ACTION_RUN);
// Run without deployment action
@@ -1213,6 +1215,7 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er
// target selector
dd->m_projectSelectorAction = new QAction(this);
+ dd->m_projectSelectorAction->setObjectName("KitSelector"); // used for UI introduction
dd->m_projectSelectorAction->setCheckable(true);
dd->m_projectSelectorAction->setEnabled(false);
QWidget *mainWindow = ICore::mainWindow();
diff --git a/src/plugins/welcome/images/border.png b/src/plugins/welcome/images/border.png
new file mode 100644
index 0000000000000000000000000000000000000000..f19494d9d2161a4400de30962bcdd80715251a9e
GIT binary patch
literal 852
zcmeAS@N?(olHy`uVBq!ia0y~yU{D2N4mJh`hNJTom>C$Dr9E97Ln2!D&bU~%$U(qm
z{fbVfYj6J7rph-Vw!SjK5>vZYMyt+f{+s9A{(RSha|h^VnqoJ$*6|FGN<(PtpIRK{KACG3t*o$41baXGI1cH%(Qm)M;$H>#sd`dp(N?1Tcsr(WMxy+Gv3!9$Eo
zV?y4l#+LUAmqrRsxU@8+a^?l*#Tw#QHp#Et`|&2D##5GS>`#)tXX_oTPz=p@t6ZRY
zX@8K6HN)bf_}|T;9D*YmE|9#flfb
zZ(oPbmoqB$I1A^ZRM-o%hp{rO=fA^`xBdB+e=oB>J>SCL9CcUGcGqw1zQZjZf#8!xuSmk#o`JU
z`QLhqzqEHBJip_9U(NIA>9f^ywM71W_`PyrfRz0wwS1i^{r|t6`>bEhe&?0FsEIBE
P0|SGntDnm{r-UW|uELPu
literal 0
HcmV?d00001
diff --git a/src/plugins/welcome/introductionwidget.cpp b/src/plugins/welcome/introductionwidget.cpp
new file mode 100644
index 00000000000..407f69dfab4
--- /dev/null
+++ b/src/plugins/welcome/introductionwidget.cpp
@@ -0,0 +1,399 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 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.
+**
+****************************************************************************/
+
+#include "introductionwidget.h"
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace Utils;
+
+const char kTakeTourSetting[] = "TakeUITour";
+
+namespace Welcome {
+namespace Internal {
+
+void IntroductionWidget::askUserAboutIntroduction(QWidget *parent, QSettings *settings)
+{
+ if (!CheckableMessageBox::shouldAskAgain(settings, kTakeTourSetting))
+ return;
+ auto messageBox = new CheckableMessageBox(parent);
+ messageBox->setWindowTitle(tr("Take a UI Tour"));
+ messageBox->setIconPixmap(QMessageBox::standardIcon(QMessageBox::Question));
+ messageBox->setText(
+ tr("Do you want to take a quick UI tour? This shows where the most important user "
+ "interface elements are, and how they are used, and will only take a minute. You can "
+ "also take the tour later by selecting Help > UI Tour."));
+ messageBox->setCheckBoxVisible(true);
+ messageBox->setCheckBoxText(CheckableMessageBox::msgDoNotAskAgain());
+ messageBox->setChecked(true);
+ messageBox->setStandardButtons(QDialogButtonBox::Cancel);
+ QPushButton *tourButton = messageBox->addButton(tr("Take UI Tour"), QDialogButtonBox::AcceptRole);
+ connect(messageBox, &QDialog::finished, parent, [parent, settings, messageBox, tourButton]() {
+ if (messageBox->isChecked())
+ CheckableMessageBox::doNotAskAgain(settings, kTakeTourSetting);
+ if (messageBox->clickedButton() == tourButton) {
+ auto intro = new IntroductionWidget(parent);
+ intro->show();
+ }
+ messageBox->deleteLater();
+ });
+ messageBox->show();
+}
+
+IntroductionWidget::IntroductionWidget(QWidget *parent)
+ : QWidget(parent),
+ m_borderImage(std::make_unique(":/welcome/images/border.png"))
+{
+ setFocusPolicy(Qt::StrongFocus);
+ setFocus();
+ parent->installEventFilter(this);
+
+ QPalette p = palette();
+ p.setColor(QPalette::Foreground, QColor(220, 220, 220));
+ setPalette(p);
+
+ m_textWidget = new QWidget(this);
+ auto layout = new QVBoxLayout;
+ m_textWidget->setLayout(layout);
+
+ m_stepText = new QLabel(this);
+ m_stepText->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ m_stepText->setWordWrap(true);
+ m_stepText->setTextFormat(Qt::RichText);
+ // why is palette not inherited???
+ m_stepText->setPalette(palette());
+ m_stepText->setOpenExternalLinks(true);
+ m_stepText->installEventFilter(this);
+ layout->addWidget(m_stepText);
+
+ m_continueLabel = new QLabel(this);
+ m_continueLabel->setAlignment(Qt::AlignCenter);
+ m_continueLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ m_continueLabel->setWordWrap(true);
+ auto fnt = font();
+ fnt.setPointSizeF(fnt.pointSizeF() * 1.5);
+ m_continueLabel->setFont(fnt);
+ m_continueLabel->setPalette(palette());
+ layout->addWidget(m_continueLabel);
+ m_bodyCss = "font-size: 16px;";
+ m_items = {
+ {"ModeSelector",
+ tr("Mode Selector"),
+ tr("Select different modes depending on the task at hand."),
+ tr(""
+ "Welcome: | Open examples, tutorials, and "
+ "recent sessions and projects. |
"
+ "Edit: | Work with code and navigate your project. |
"
+ "Design: | Work with UI designs for Qt Widgets or Qt Quick. |
"
+ "Debug: | Analyze your application with a debugger or other "
+ "analyzer. |
"
+ "Projects: | Manage project settings. |
"
+ "Help: | Browse the help database. |
"
+ "
")},
+ {"KitSelector.Button",
+ tr("Kit Selector"),
+ tr("Select the active project or project configuration."),
+ ""},
+ {"Run.Button",
+ tr("Run Button"),
+ tr("Run the active project. By default this builds the project first."),
+ ""},
+ {"Debug.Button", tr("Debug Button"), tr("Run the active project in a debugger."), ""},
+ {"Build.Button", tr("Build Button"), tr("Build the active project."), ""},
+ {"LocatorInput",
+ tr("Locator"),
+ tr("Type here to open a file from any open project."),
+ tr("Or:"
+ ""
+ "- type
c<space><pattern>
to jump to a class definition "
+ "- type
f<space><pattern>
to open a file from the file "
+ "system "
+ "- click on the magnifier icon for a complete list of possible options
"
+ "
")},
+ {"OutputPaneButtons",
+ tr("Output Panes"),
+ tr("Find compile and application output here, "
+ "as well as a list of configuration and build issues, "
+ "and the panel for global searches."),
+ ""},
+ {"ProgressInfo",
+ tr("Progress Indicator"),
+ tr("Progress information about running tasks is shown here."),
+ ""},
+ {{},
+ tr("Escape to Editor"),
+ tr("Pressing the Escape key brings you back to the editor. Press it "
+ "multiple times to also hide output panes and context help, giving the editor more "
+ "space."),
+ ""},
+ {{},
+ tr("The End"),
+ tr("You have now completed the UI tour. To learn more about the highlighted "
+ "controls, see User "
+ "Interface."),
+ ""}};
+ setStep(0);
+ resizeToParent();
+}
+
+bool IntroductionWidget::event(QEvent *e)
+{
+ if (e->type() == QEvent::ShortcutOverride) {
+ e->accept();
+ return true;
+ }
+ return QWidget::event(e);
+}
+
+bool IntroductionWidget::eventFilter(QObject *obj, QEvent *ev)
+{
+ if (obj == parent() && ev->type() == QEvent::Resize)
+ resizeToParent();
+ else if (obj == m_stepText && ev->type() == QEvent::MouseButtonRelease)
+ step();
+ return QWidget::eventFilter(obj, ev);
+}
+
+const int SPOTLIGHTMARGIN = 18;
+const int POINTER_SIZE = 30;
+const int POINTER_WIDTH = 3;
+
+static int margin(const QRect &small, const QRect &big, Qt::Alignment side)
+{
+ switch (side) {
+ case Qt::AlignRight:
+ return qMax(0, big.right() - small.right());
+ case Qt::AlignTop:
+ return qMax(0, small.top() - big.top());
+ case Qt::AlignBottom:
+ return qMax(0, big.bottom() - small.bottom());
+ case Qt::AlignLeft:
+ return qMax(0, small.x() - big.x());
+ default:
+ QTC_ASSERT(false, return 0);
+ }
+}
+
+static int oppositeMargin(const QRect &small, const QRect &big, Qt::Alignment side)
+{
+ switch (side) {
+ case Qt::AlignRight:
+ return margin(small, big, Qt::AlignLeft);
+ case Qt::AlignTop:
+ return margin(small, big, Qt::AlignBottom);
+ case Qt::AlignBottom:
+ return margin(small, big, Qt::AlignTop);
+ case Qt::AlignLeft:
+ return margin(small, big, Qt::AlignRight);
+ default:
+ QTC_ASSERT(false, return 100000);
+ }
+}
+
+static const QVector pointerPolygon(const QRect &anchorRect, const QRect &fullRect)
+{
+ // Put the arrow opposite to the smallest margin,
+ // with priority right, top, bottom, left.
+ // Not very sophisticated but sufficient for current use cases.
+ QVector alignments{Qt::AlignRight, Qt::AlignTop, Qt::AlignBottom, Qt::AlignLeft};
+ Utils::sort(alignments, [anchorRect, fullRect](Qt::Alignment a, Qt::Alignment b) {
+ return oppositeMargin(anchorRect, fullRect, a) < oppositeMargin(anchorRect, fullRect, b);
+ });
+ const auto alignIt = std::find_if(alignments.cbegin(),
+ alignments.cend(),
+ [anchorRect, fullRect](Qt::Alignment align) {
+ return margin(anchorRect, fullRect, align) > POINTER_SIZE;
+ });
+ if (alignIt == alignments.cend())
+ return {{}}; // no side with sufficient space found
+ const qreal arrowHeadWidth = POINTER_SIZE/3.;
+ if (*alignIt == Qt::AlignRight) {
+ const qreal middleY = anchorRect.center().y();
+ const qreal startX = anchorRect.right() + POINTER_SIZE;
+ const qreal endX = anchorRect.right() + POINTER_WIDTH;
+ return {{QVector{{startX, middleY}, {endX, middleY}}},
+ QVector{{endX + arrowHeadWidth, middleY - arrowHeadWidth},
+ {endX, middleY},
+ {endX + arrowHeadWidth, middleY + arrowHeadWidth}}};
+ }
+ if (*alignIt == Qt::AlignTop) {
+ const qreal middleX = anchorRect.center().x();
+ const qreal startY = anchorRect.y() - POINTER_SIZE;
+ const qreal endY = anchorRect.y() - POINTER_WIDTH;
+ return {{QVector{{middleX, startY}, {middleX, endY}}},
+ QVector{{middleX - arrowHeadWidth, endY - arrowHeadWidth},
+ {middleX, endY},
+ {middleX + arrowHeadWidth, endY - arrowHeadWidth}}};
+ }
+ if (*alignIt == Qt::AlignBottom) {
+ const qreal middleX = anchorRect.center().x();
+ const qreal startY = anchorRect.y() + POINTER_WIDTH;
+ const qreal endY = anchorRect.y() + POINTER_SIZE;
+ return {{QVector{{middleX, startY}, {middleX, endY}}},
+ QVector{{middleX - arrowHeadWidth, endY + arrowHeadWidth},
+ {middleX, endY},
+ {middleX + arrowHeadWidth, endY + arrowHeadWidth}}};
+ }
+
+ // Qt::AlignLeft
+ const qreal middleY = anchorRect.center().y();
+ const qreal startX = anchorRect.x() - POINTER_WIDTH;
+ const qreal endX = anchorRect.x() - POINTER_SIZE;
+ return {{QVector{{startX, middleY}, {endX, middleY}}},
+ QVector{{endX - arrowHeadWidth, middleY - arrowHeadWidth},
+ {endX, middleY},
+ {endX - arrowHeadWidth, middleY + arrowHeadWidth}}};
+}
+
+void IntroductionWidget::paintEvent(QPaintEvent *)
+{
+ QPainter p(this);
+ p.setOpacity(.87);
+ const QColor backgroundColor = Qt::black;
+ if (m_stepPointerAnchor) {
+ const QPoint anchorPos = m_stepPointerAnchor->mapTo(parentWidget(), {0, 0});
+ const QRect anchorRect(anchorPos, m_stepPointerAnchor->size());
+ const QRect spotlightRect = anchorRect.adjusted(-SPOTLIGHTMARGIN,
+ -SPOTLIGHTMARGIN,
+ SPOTLIGHTMARGIN,
+ SPOTLIGHTMARGIN);
+
+ // darken the background to create a spotlighted area
+ if (spotlightRect.left() > 0) {
+ p.fillRect(0, 0, spotlightRect.left(), height(), backgroundColor);
+ }
+ if (spotlightRect.top() > 0) {
+ p.fillRect(spotlightRect.left(),
+ 0,
+ width() - spotlightRect.left(),
+ spotlightRect.top(),
+ backgroundColor);
+ }
+ if (spotlightRect.right() < width() - 1) {
+ p.fillRect(spotlightRect.right() + 1,
+ spotlightRect.top(),
+ width() - spotlightRect.right() - 1,
+ height() - spotlightRect.top(),
+ backgroundColor);
+ }
+ if (spotlightRect.bottom() < height() - 1) {
+ p.fillRect(spotlightRect.left(),
+ spotlightRect.bottom() + 1,
+ spotlightRect.width(),
+ height() - spotlightRect.bottom() - 1,
+ backgroundColor);
+ }
+
+ // smooth borders of the spotlighted area by gradients
+ StyleHelper::drawCornerImage(*m_borderImage,
+ &p,
+ spotlightRect,
+ SPOTLIGHTMARGIN,
+ SPOTLIGHTMARGIN,
+ SPOTLIGHTMARGIN,
+ SPOTLIGHTMARGIN);
+
+ // draw pointer
+ const QColor qtGreen(65, 205, 82);
+ p.setOpacity(1.);
+ p.setPen(QPen(QBrush(qtGreen),
+ POINTER_WIDTH,
+ Qt::SolidLine,
+ Qt::RoundCap,
+ Qt::MiterJoin));
+ p.setRenderHint(QPainter::Antialiasing);
+ for (const QPolygonF &poly : pointerPolygon(spotlightRect, rect()))
+ p.drawPolyline(poly);
+ } else {
+ p.fillRect(rect(), backgroundColor);
+ }
+}
+
+void IntroductionWidget::keyPressEvent(QKeyEvent *ke)
+{
+ if (ke->key() == Qt::Key_Escape)
+ finish();
+ else if (ke->modifiers() == Qt::NoModifier)
+ step();
+}
+
+void IntroductionWidget::mouseReleaseEvent(QMouseEvent *me)
+{
+ me->accept();
+ step();
+}
+
+void IntroductionWidget::finish()
+{
+ hide();
+ deleteLater();
+}
+
+void IntroductionWidget::step()
+{
+ if (m_step >= m_items.size() - 1)
+ finish();
+ else
+ setStep(m_step + 1);
+}
+
+void IntroductionWidget::setStep(uint index)
+{
+ QTC_ASSERT(index < m_items.size(), return);
+ m_step = index;
+ m_continueLabel->setText(tr("UI Introduction %1/%2 >").arg(m_step + 1).arg(m_items.size()));
+ const Item &item = m_items.at(m_step);
+ m_stepText->setText("" + "" + item.title
+ + "
" + item.brief + "
" + item.description + "");
+ const QString anchorObjectName = m_items.at(m_step).pointerAnchorObjectName;
+ if (!anchorObjectName.isEmpty()) {
+ m_stepPointerAnchor = parentWidget()->findChild(anchorObjectName);
+ QTC_CHECK(m_stepPointerAnchor);
+ } else {
+ m_stepPointerAnchor.clear();
+ }
+ update();
+}
+
+void IntroductionWidget::resizeToParent()
+{
+ QTC_ASSERT(parentWidget(), return);
+ setGeometry(QRect(QPoint(0, 0), parentWidget()->size()));
+ m_textWidget->setGeometry(QRect(width()/4, height()/4, width()/2, height()/2));
+}
+
+} // namespace Internal
+} // namespace Welcome
diff --git a/src/plugins/welcome/introductionwidget.h b/src/plugins/welcome/introductionwidget.h
new file mode 100644
index 00000000000..69212e77623
--- /dev/null
+++ b/src/plugins/welcome/introductionwidget.h
@@ -0,0 +1,82 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 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
+#include
+#include
+
+#include
+
+QT_BEGIN_NAMESPACE
+class QLabel;
+class QSettings;
+QT_END_NAMESPACE
+
+namespace Welcome {
+namespace Internal {
+
+struct Item
+{
+ QString pointerAnchorObjectName;
+ QString title;
+ QString brief;
+ QString description;
+};
+
+class IntroductionWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit IntroductionWidget(QWidget *parent = nullptr);
+
+ static void askUserAboutIntroduction(QWidget *parent, QSettings *settings);
+
+protected:
+ bool event(QEvent *e) override;
+ bool eventFilter(QObject *obj, QEvent *ev) override;
+ void paintEvent(QPaintEvent *ev) override;
+ void keyPressEvent(QKeyEvent *ke) override;
+ void mouseReleaseEvent(QMouseEvent *me) override;
+
+private:
+ void finish();
+ void step();
+ void setStep(uint index);
+ void resizeToParent();
+
+ QWidget *m_textWidget;
+ QLabel *m_stepText;
+ QLabel *m_continueLabel;
+ std::unique_ptr m_borderImage;
+ QString m_bodyCss;
+ std::vector- m_items;
+ QPointer m_stepPointerAnchor;
+ uint m_step = 0;
+};
+
+} // namespace Internal
+} // namespace Welcome
diff --git a/src/plugins/welcome/welcome.pro b/src/plugins/welcome/welcome.pro
index 612739a692f..7123b4abb6c 100644
--- a/src/plugins/welcome/welcome.pro
+++ b/src/plugins/welcome/welcome.pro
@@ -1,8 +1,12 @@
include(../../qtcreatorplugin.pri)
-SOURCES += welcomeplugin.cpp
+SOURCES += welcomeplugin.cpp \
+ introductionwidget.cpp
DEFINES += WELCOME_LIBRARY
RESOURCES += welcome.qrc
+
+HEADERS += \
+ introductionwidget.h
diff --git a/src/plugins/welcome/welcome.qrc b/src/plugins/welcome/welcome.qrc
index 28e8d22d1c8..d5b825d7e16 100644
--- a/src/plugins/welcome/welcome.qrc
+++ b/src/plugins/welcome/welcome.qrc
@@ -22,5 +22,6 @@
images/new@2x.png
images/expandarrow.png
images/expandarrow@2x.png
+ images/border.png
diff --git a/src/plugins/welcome/welcomeplugin.cpp b/src/plugins/welcome/welcomeplugin.cpp
index aa804bbe6d8..b70639fb4e2 100644
--- a/src/plugins/welcome/welcomeplugin.cpp
+++ b/src/plugins/welcome/welcomeplugin.cpp
@@ -23,11 +23,14 @@
**
****************************************************************************/
+#include "introductionwidget.h"
+
#include
#include
#include
+#include
#include
#include
#include
@@ -139,6 +142,18 @@ public:
{
m_welcomeMode->initPlugins();
ModeManager::activateMode(m_welcomeMode->id());
+ auto introAction = new QAction(tr("UI Tour"), this);
+ connect(introAction, &QAction::triggered, this, []() {
+ auto intro = new IntroductionWidget(ICore::mainWindow());
+ intro->show();
+ });
+ Command *cmd = ActionManager::registerAction(introAction, "Welcome.UITour");
+ ActionContainer *mhelp = ActionManager::actionContainer(Core::Constants::M_HELP);
+ if (QTC_GUARD(mhelp))
+ mhelp->addAction(cmd, Core::Constants::G_HELP_HELP);
+ connect(ICore::instance(), &ICore::coreOpened, this, []() {
+ IntroductionWidget::askUserAboutIntroduction(ICore::mainWindow(), ICore::settings());
+ }, Qt::QueuedConnection);
}
WelcomeMode *m_welcomeMode = nullptr;
diff --git a/src/tools/icons/qtcreatoricons.svg b/src/tools/icons/qtcreatoricons.svg
index cc05ffbe7fb..ab26dd5fedc 100644
--- a/src/tools/icons/qtcreatoricons.svg
+++ b/src/tools/icons/qtcreatoricons.svg
@@ -18,6 +18,22 @@
sodipodi:docname="qtcreatoricons.svg">
+
+
+
+
+
@@ -527,6 +543,16 @@
y1="109.69"
x2="-191.87"
y2="110.83" />
+
+