forked from qt-creator/qt-creator
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 <alessandro.portale@qt.io> Reviewed-by: Tobias Hunger <tobias.hunger@qt.io> Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
This commit is contained in:
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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);
|
||||
|
@@ -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(
|
||||
|
@@ -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);
|
||||
|
@@ -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));
|
||||
|
@@ -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();
|
||||
|
BIN
src/plugins/welcome/images/border.png
Normal file
BIN
src/plugins/welcome/images/border.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 852 B |
399
src/plugins/welcome/introductionwidget.cpp
Normal file
399
src/plugins/welcome/introductionwidget.cpp
Normal file
@@ -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<space><pattern></code> to jump to a class definition</li>"
|
||||
"<li>type <code>f<space><pattern></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
|
82
src/plugins/welcome/introductionwidget.h
Normal file
82
src/plugins/welcome/introductionwidget.h
Normal file
@@ -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
|
@@ -1,8 +1,12 @@
|
||||
|
||||
include(../../qtcreatorplugin.pri)
|
||||
|
||||
SOURCES += welcomeplugin.cpp
|
||||
SOURCES += welcomeplugin.cpp \
|
||||
introductionwidget.cpp
|
||||
|
||||
DEFINES += WELCOME_LIBRARY
|
||||
|
||||
RESOURCES += welcome.qrc
|
||||
|
||||
HEADERS += \
|
||||
introductionwidget.h
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
|
@@ -18,6 +18,22 @@
|
||||
sodipodi:docname="qtcreatoricons.svg">
|
||||
<defs
|
||||
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">
|
||||
@@ -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"
|
||||
|
Before Width: | Height: | Size: 353 KiB After Width: | Height: | Size: 354 KiB |
Reference in New Issue
Block a user