summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/libs/utils/checkablemessagebox.cpp8
-rw-r--r--src/libs/utils/checkablemessagebox.h4
-rw-r--r--src/plugins/coreplugin/fancyactionbar.cpp2
-rw-r--r--src/plugins/coreplugin/fancytabwidget.cpp5
-rw-r--r--src/plugins/coreplugin/locator/locator.cpp1
-rw-r--r--src/plugins/coreplugin/outputpanemanager.cpp1
-rw-r--r--src/plugins/coreplugin/progressmanager/progressmanager.cpp1
-rw-r--r--src/plugins/debugger/debuggerplugin.cpp1
-rw-r--r--src/plugins/projectexplorer/projectexplorer.cpp3
-rw-r--r--src/plugins/welcome/images/border.pngbin0 -> 852 bytes
-rw-r--r--src/plugins/welcome/introductionwidget.cpp399
-rw-r--r--src/plugins/welcome/introductionwidget.h82
-rw-r--r--src/plugins/welcome/welcome.pro6
-rw-r--r--src/plugins/welcome/welcome.qrc1
-rw-r--r--src/plugins/welcome/welcomeplugin.cpp15
-rw-r--r--src/tools/icons/qtcreatoricons.svg33
16 files changed, 556 insertions, 6 deletions
diff --git a/src/libs/utils/checkablemessagebox.cpp b/src/libs/utils/checkablemessagebox.cpp
index 70413886fa..857711fd8a 100644
--- a/src/libs/utils/checkablemessagebox.cpp
+++ b/src/libs/utils/checkablemessagebox.cpp
@@ -277,7 +277,7 @@ QMessageBox::StandardButton CheckableMessageBox::dialogButtonBoxToMessageBoxButt
return static_cast<QMessageBox::StandardButton>(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 3367ce02b1..c6359f72cd 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 b57573ef88..8d496ea3d2 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 4fad26bc12..ad1d72b1f1 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 bdc1664c26..17f8fbbd86 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 00e1c9699e..bda60b5812 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 06c825ae80..725721852f 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 c79cecab50..04b1b8d341 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 068b4f720b..d303f6f620 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 0000000000..f19494d9d2
--- /dev/null
+++ b/src/plugins/welcome/images/border.png
Binary files differ
diff --git a/src/plugins/welcome/introductionwidget.cpp b/src/plugins/welcome/introductionwidget.cpp
new file mode 100644
index 0000000000..407f69dfab
--- /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 <utils/algorithm.h>
+#include <utils/checkablemessagebox.h>
+#include <utils/qtcassert.h>
+#include <utils/stylehelper.h>
+
+#include <QEvent>
+#include <QKeyEvent>
+#include <QLabel>
+#include <QPainter>
+#include <QPushButton>
+#include <QVBoxLayout>
+
+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<QImage>(":/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("<p style=\"margin-top: 30px\"><table>"
+ "<tr><td style=\"padding-right: 20px\">Welcome:</td><td>Open examples, tutorials, and "
+ "recent sessions and projects.</td></tr>"
+ "<tr><td>Edit:</td><td>Work with code and navigate your project.</td></tr>"
+ "<tr><td>Design:</td><td>Work with UI designs for Qt Widgets or Qt Quick.</td></tr>"
+ "<tr><td>Debug:</td><td>Analyze your application with a debugger or other "
+ "analyzer.</td></tr>"
+ "<tr><td>Projects:</td><td>Manage project settings.</td></tr>"
+ "<tr><td>Help:</td><td>Browse the help database.</td></tr>"
+ "</table></p>")},
+ {"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:"
+ "<ul>"
+ "<li>type <code>c&lt;space&gt;&lt;pattern&gt;</code> to jump to a class definition</li>"
+ "<li>type <code>f&lt;space&gt;&lt;pattern&gt;</code> to open a file from the file "
+ "system</li>"
+ "<li>click on the magnifier icon for a complete list of possible options</li>"
+ "</ul>")},
+ {"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 <a style=\"color: #41CD52\" "
+ "href=\"qthelp://org.qt-project.qtcreator/doc/creator-quick-tour.html\">User "
+ "Interface</a>."),
+ ""}};
+ 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<QPolygonF> 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<Qt::Alignment> 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<QPointF>{{startX, middleY}, {endX, middleY}}},
+ QVector<QPointF>{{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<QPointF>{{middleX, startY}, {middleX, endY}}},
+ QVector<QPointF>{{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<QPointF>{{middleX, startY}, {middleX, endY}}},
+ QVector<QPointF>{{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<QPointF>{{startX, middleY}, {endX, middleY}}},
+ QVector<QPointF>{{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("<html><body style=\"" + m_bodyCss + "\">" + "<h1>" + item.title
+ + "</h1><p>" + item.brief + "</p>" + item.description + "</body></html>");
+ const QString anchorObjectName = m_items.at(m_step).pointerAnchorObjectName;
+ if (!anchorObjectName.isEmpty()) {
+ m_stepPointerAnchor = parentWidget()->findChild<QWidget *>(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 0000000000..69212e7762
--- /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 <QImage>
+#include <QPointer>
+#include <QWidget>
+
+#include <memory>
+
+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<QImage> m_borderImage;
+ QString m_bodyCss;
+ std::vector<Item> m_items;
+ QPointer<QWidget> 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 612739a692..7123b4abb6 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 28e8d22d1c..d5b825d7e1 100644
--- a/src/plugins/welcome/welcome.qrc
+++ b/src/plugins/welcome/welcome.qrc
@@ -22,5 +22,6 @@
<file>images/new@2x.png</file>
<file>images/expandarrow.png</file>
<file>images/expandarrow@2x.png</file>
+ <file>images/border.png</file>
</qresource>
</RCC>
diff --git a/src/plugins/welcome/welcomeplugin.cpp b/src/plugins/welcome/welcomeplugin.cpp
index aa804bbe6d..b70639fb4e 100644
--- a/src/plugins/welcome/welcomeplugin.cpp
+++ b/src/plugins/welcome/welcomeplugin.cpp
@@ -23,11 +23,14 @@
**
****************************************************************************/
+#include "introductionwidget.h"
+
#include <extensionsystem/iplugin.h>
#include <extensionsystem/pluginmanager.h>
#include <app/app_version.h>
+#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/coreconstants.h>
@@ -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 cc05ffbe7f..ab26dd5fed 100644
--- a/src/tools/icons/qtcreatoricons.svg
+++ b/src/tools/icons/qtcreatoricons.svg
@@ -20,6 +20,22 @@
id="defs4">
<linearGradient
inkscape:collect="always"
+ id="linearGradient3970">
+ <stop
+ style="stop-color:#000000;stop-opacity:0"
+ offset="0"
+ id="stop3966" />
+ <stop
+ id="stop3974"
+ offset="0.35135135"
+ style="stop-color:#000000;stop-opacity:0" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1"
+ offset="1"
+ id="stop3968" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
id="linearGradient9821">
<stop
style="stop-color:#000000;stop-opacity:1"
@@ -527,6 +543,16 @@
y1="109.69"
x2="-191.87"
y2="110.83" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3970"
+ id="radialGradient3972"
+ cx="548.5"
+ cy="91.5"
+ fx="548.5"
+ fy="91.5"
+ r="18.5"
+ gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
@@ -741,6 +767,13 @@
d="m 108.63,21.74 h -2 V 9 h 2 v 7.36 l 1.11,-0.11 2.13,-3.51 h 2.19 l -2.59,4.26 2.74,4.78 H 112 l -2.2,-3.78 -1.17,0.12 z m -6.41,0 V 9 h 2 v 12.74 z m -2.57,-6.22 v 4.1 a 0.86,0.86 0 0 0 0.2,0.58 1,1 0 0 0 0.59,0.25 l -0.06,1.49 A 3.8,3.8 0 0 1 98,21.28 6.91,6.91 0 0 1 95.1,21.94 c -1.79,0 -2.68,-1 -2.68,-2.86 a 2.44,2.44 0 0 1 0.73,-2 4,4 0 0 1 2.24,-0.74 l 2.32,-0.2 V 15.5 a 1.33,1.33 0 0 0 -0.31,-1 1.35,1.35 0 0 0 -0.93,-0.29 c -0.77,0 -1.73,0 -2.88,0.14 H 93.01 L 92.93,13 a 15.72,15.72 0 0 1 3.61,-0.46 3.31,3.31 0 0 1 2.38,0.71 3.05,3.05 0 0 1 0.73,2.27 z m -4,2.23 a 1.21,1.21 0 0 0 -1.24,1.35 c 0,0.83 0.37,1.24 1.1,1.24 a 7,7 0 0 0 1.91,-0.29 l 0.32,-0.11 V 17.55 Z M 84,11.2 V 9.41 h 9 v 1.79 h -3.49 v 10.54 h -2 V 11.2 Z m -4.62,3.22 h -2.49 v 4 a 4.12,4.12 0 0 0 0.17,1.46 c 0.1,0.24 0.38,0.36 0.82,0.36 l 1.48,-0.06 0.09,1.57 A 11,11 0 0 1 77.61,21.98 2.55,2.55 0 0 1 75.52,21.28 4.41,4.41 0 0 1 75,18.59 V 14.42 H 73.8 V 12.74 H 75 v -2.61 h 1.94 v 2.61 h 2.49 z m -11.94,7.52 c -1.87,0 -3.17,-0.5 -3.91,-1.51 a 8.16,8.16 0 0 1 -1.11,-4.78 8.43,8.43 0 0 1 1.13,-4.85 c 0.75,-1.06 2,-1.58 3.89,-1.58 1.89,0 3.15,0.52 3.89,1.57 a 8.41,8.41 0 0 1 1.12,4.85 11.08,11.08 0 0 1 -0.45,3.49 4,4 0 0 1 -1.5,2 l 1.5,2.47 -1.86,0.86 -1.6,-2.63 a 3.68,3.68 0 0 1 -1.1,0.11 z m -2.34,-2.8 a 3.13,3.13 0 0 0 4.68,0 7.43,7.43 0 0 0 0.6,-3.5 7.78,7.78 0 0 0 -0.62,-3.58 3,3 0 0 0 -4.64,0 7.56,7.56 0 0 0 -0.63,3.56 7.47,7.47 0 0 0 0.61,3.52 z"
inkscape:connector-curvature="0" />
</g>
+ <rect
+ style="fill:url(#radialGradient3972)"
+ id="src/plugins/welcome/images/border"
+ width="37"
+ height="37"
+ x="530"
+ y="73" />
</g>
<g
inkscape:groupmode="layer"