diff options
-rw-r--r-- | src/libs/utils/checkablemessagebox.cpp | 8 | ||||
-rw-r--r-- | src/libs/utils/checkablemessagebox.h | 4 | ||||
-rw-r--r-- | src/plugins/coreplugin/fancyactionbar.cpp | 2 | ||||
-rw-r--r-- | src/plugins/coreplugin/fancytabwidget.cpp | 5 | ||||
-rw-r--r-- | src/plugins/coreplugin/locator/locator.cpp | 1 | ||||
-rw-r--r-- | src/plugins/coreplugin/outputpanemanager.cpp | 1 | ||||
-rw-r--r-- | src/plugins/coreplugin/progressmanager/progressmanager.cpp | 1 | ||||
-rw-r--r-- | src/plugins/debugger/debuggerplugin.cpp | 1 | ||||
-rw-r--r-- | src/plugins/projectexplorer/projectexplorer.cpp | 3 | ||||
-rw-r--r-- | src/plugins/welcome/images/border.png | bin | 0 -> 852 bytes | |||
-rw-r--r-- | src/plugins/welcome/introductionwidget.cpp | 399 | ||||
-rw-r--r-- | src/plugins/welcome/introductionwidget.h | 82 | ||||
-rw-r--r-- | src/plugins/welcome/welcome.pro | 6 | ||||
-rw-r--r-- | src/plugins/welcome/welcome.qrc | 1 | ||||
-rw-r--r-- | src/plugins/welcome/welcomeplugin.cpp | 15 | ||||
-rw-r--r-- | src/tools/icons/qtcreatoricons.svg | 33 |
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 Binary files differnew file mode 100644 index 0000000000..f19494d9d2 --- /dev/null +++ b/src/plugins/welcome/images/border.png 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<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 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" |