/****************************************************************************
**
** 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
#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->setText(
tr("Would you like to take a quick UI tour? This tour highlights important user "
"interface elements and shows how they are used. To take the tour later, "
"select 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(":/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 = {
{QLatin1String("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 "
"analyzers. |
"
"Projects: | Manage project settings. |
"
"Help: | Browse the help database. |
"
"
")},
{QLatin1String("KitSelector.Button"),
tr("Kit Selector"),
tr("Select the active project or project configuration."),
{}},
{QLatin1String("Run.Button"),
tr("Run Button"),
tr("Run the active project. By default this builds the project first."),
{}},
{QLatin1String("Debug.Button"),
tr("Debug Button"),
tr("Run the active project in a debugger."),
{}},
{QLatin1String("Build.Button"), tr("Build Button"), tr("Build the active project."), {}},
{QLatin1String("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
"
"
")},
{QLatin1String("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."),
{}},
{QLatin1String("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::ControlModifier | Qt::AltModifier | Qt::ShiftModifier | Qt::MetaModifier))
== Qt::NoModifier) {
const Qt::Key backKey = QGuiApplication::isLeftToRight() ? Qt::Key_Left : Qt::Key_Right;
if (ke->key() == backKey) {
if (m_step > 0)
setStep(m_step - 1);
} else {
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