diff --git a/src/libs/utils/checkablemessagebox.cpp b/src/libs/utils/checkablemessagebox.cpp index 70413886faa..857711fd8a5 100644 --- a/src/libs/utils/checkablemessagebox.cpp +++ b/src/libs/utils/checkablemessagebox.cpp @@ -277,7 +277,7 @@ QMessageBox::StandardButton CheckableMessageBox::dialogButtonBoxToMessageBoxButt return static_cast(int(db)); } -bool askAgain(QSettings *settings, const QString &settingsSubKey) +bool CheckableMessageBox::shouldAskAgain(QSettings *settings, const QString &settingsSubKey) { if (QTC_GUARD(settings)) { settings->beginGroup(QLatin1String(kDoNotAskAgainKey)); @@ -309,7 +309,7 @@ void initDoNotAskAgainMessageBox(CheckableMessageBox &messageBox, const QString messageBox.setDefaultButton(defaultButton); } -void doNotAskAgain(QSettings *settings, const QString &settingsSubKey) +void CheckableMessageBox::doNotAskAgain(QSettings *settings, const QString &settingsSubKey) { if (!settings) return; @@ -337,7 +337,7 @@ CheckableMessageBox::doNotAskAgainQuestion(QWidget *parent, const QString &title QDialogButtonBox::StandardButton acceptButton) { - if (!askAgain(settings, settingsSubKey)) + if (!shouldAskAgain(settings, settingsSubKey)) return acceptButton; CheckableMessageBox messageBox(parent); @@ -365,7 +365,7 @@ CheckableMessageBox::doNotShowAgainInformation(QWidget *parent, const QString &t QDialogButtonBox::StandardButton defaultButton) { - if (!askAgain(settings, settingsSubKey)) + if (!shouldAskAgain(settings, settingsSubKey)) return defaultButton; CheckableMessageBox messageBox(parent); diff --git a/src/libs/utils/checkablemessagebox.h b/src/libs/utils/checkablemessagebox.h index 3367ce02b16..c6359f72cd2 100644 --- a/src/libs/utils/checkablemessagebox.h +++ b/src/libs/utils/checkablemessagebox.h @@ -117,6 +117,10 @@ public: QAbstractButton *clickedButton() const; QDialogButtonBox::StandardButton clickedStandardButton() const; + // check and set "ask again" status + static bool shouldAskAgain(QSettings *settings, const QString &settingsSubKey); + static void doNotAskAgain(QSettings *settings, const QString &settingsSubKey); + // Conversion convenience static QMessageBox::StandardButton dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton); static void resetAllDoNotAskAgainQuestions(QSettings *settings); diff --git a/src/plugins/coreplugin/fancyactionbar.cpp b/src/plugins/coreplugin/fancyactionbar.cpp index b57573ef880..8d496ea3d24 100644 --- a/src/plugins/coreplugin/fancyactionbar.cpp +++ b/src/plugins/coreplugin/fancyactionbar.cpp @@ -373,6 +373,8 @@ void FancyActionBar::addProjectSelector(QAction *action) void FancyActionBar::insertAction(int index, QAction *action) { auto *button = new FancyToolButton(action, this); + if (!action->objectName().isEmpty()) + button->setObjectName(action->objectName() + ".Button"); // used for UI introduction button->setIconsOnly(m_iconsOnly); m_actionsLayout->insertWidget(index, button); } diff --git a/src/plugins/coreplugin/fancytabwidget.cpp b/src/plugins/coreplugin/fancytabwidget.cpp index 4fad26bc12c..ad1d72b1f13 100644 --- a/src/plugins/coreplugin/fancytabwidget.cpp +++ b/src/plugins/coreplugin/fancytabwidget.cpp @@ -458,6 +458,7 @@ FancyTabWidget::FancyTabWidget(QWidget *parent) : QWidget(parent) { m_tabBar = new FancyTabBar(this); + m_tabBar->setObjectName("ModeSelector"); // used for UI introduction m_selectionWidget = new QWidget(this); auto selectionLayout = new QVBoxLayout; @@ -473,7 +474,8 @@ FancyTabWidget::FancyTabWidget(QWidget *parent) layout->addWidget(fancyButton); selectionLayout->addWidget(bar); - selectionLayout->addWidget(m_tabBar, 1); + selectionLayout->addWidget(m_tabBar); + selectionLayout->addStretch(1); m_selectionWidget->setLayout(selectionLayout); m_selectionWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); @@ -551,6 +553,7 @@ void FancyTabWidget::paintEvent(QPaintEvent *event) const QRectF boderRect = QRectF(rect).adjusted(0.5, 0.5, -0.5, -0.5); if (creatorTheme()->flag(Theme::FlatToolBars)) { + painter.fillRect(event->rect(), StyleHelper::baseColor()); painter.setPen(StyleHelper::toolBarBorderColor()); painter.drawLine(boderRect.topRight(), boderRect.bottomRight()); } else { diff --git a/src/plugins/coreplugin/locator/locator.cpp b/src/plugins/coreplugin/locator/locator.cpp index bdc1664c263..17f8fbbd862 100644 --- a/src/plugins/coreplugin/locator/locator.cpp +++ b/src/plugins/coreplugin/locator/locator.cpp @@ -123,6 +123,7 @@ void Locator::initialize() mtools->addAction(cmd); auto locatorWidget = LocatorManager::createLocatorInputWidget(ICore::mainWindow()); + locatorWidget->setObjectName("LocatorInput"); // used for UI introduction StatusBarManager::addStatusBarWidget(locatorWidget, StatusBarManager::First, Context("LocatorWidget")); connect(ICore::instance(), &ICore::saveSettingsRequested, this, &Locator::saveSettings); diff --git a/src/plugins/coreplugin/outputpanemanager.cpp b/src/plugins/coreplugin/outputpanemanager.cpp index 00e1c9699e5..bda60b58123 100644 --- a/src/plugins/coreplugin/outputpanemanager.cpp +++ b/src/plugins/coreplugin/outputpanemanager.cpp @@ -232,6 +232,7 @@ OutputPaneManager::OutputPaneManager(QWidget *parent) : setLayout(mainlayout); m_buttonsWidget = new QWidget; + m_buttonsWidget->setObjectName("OutputPaneButtons"); // used for UI introduction m_buttonsWidget->setLayout(new QHBoxLayout); m_buttonsWidget->layout()->setContentsMargins(5,0,0,0); m_buttonsWidget->layout()->setSpacing( diff --git a/src/plugins/coreplugin/progressmanager/progressmanager.cpp b/src/plugins/coreplugin/progressmanager/progressmanager.cpp index 06c825ae80c..725721852f5 100644 --- a/src/plugins/coreplugin/progressmanager/progressmanager.cpp +++ b/src/plugins/coreplugin/progressmanager/progressmanager.cpp @@ -300,6 +300,7 @@ void ProgressManagerPrivate::init() readSettings(); m_statusBarWidget = new QWidget; + m_statusBarWidget->setObjectName("ProgressInfo"); // used for UI introduction auto layout = new QHBoxLayout(m_statusBarWidget); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp index c79cecab504..04b1b8d3414 100644 --- a/src/plugins/debugger/debuggerplugin.cpp +++ b/src/plugins/debugger/debuggerplugin.cpp @@ -1120,6 +1120,7 @@ bool DebuggerPluginPrivate::initialize(const QStringList &arguments, m_visibleStartAction.setAttribute(ProxyAction::UpdateIcon); m_visibleStartAction.setAction(&m_startAction); + m_visibleStartAction.setObjectName("Debug"); // used for UI introduction ModeManager::addAction(&m_visibleStartAction, Constants::P_ACTION_DEBUG); m_undisturbableAction.setIcon(interruptIcon(false)); diff --git a/src/plugins/projectexplorer/projectexplorer.cpp b/src/plugins/projectexplorer/projectexplorer.cpp index 068b4f720b8..d303f6f620e 100644 --- a/src/plugins/projectexplorer/projectexplorer.cpp +++ b/src/plugins/projectexplorer/projectexplorer.cpp @@ -984,6 +984,7 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er // Add to mode bar dd->m_modeBarBuildAction = new Utils::ProxyAction(this); + dd->m_modeBarBuildAction->setObjectName("Build"); // used for UI introduction dd->m_modeBarBuildAction->initialize(cmd->action()); dd->m_modeBarBuildAction->setAttribute(Utils::ProxyAction::UpdateText); dd->m_modeBarBuildAction->setAction(cmd->action()); @@ -1027,6 +1028,7 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+R"))); mbuild->addAction(cmd, Constants::G_BUILD_RUN); + cmd->action()->setObjectName("Run"); // used for UI introduction ModeManager::addAction(cmd->action(), Constants::P_ACTION_RUN); // Run without deployment action @@ -1213,6 +1215,7 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er // target selector dd->m_projectSelectorAction = new QAction(this); + dd->m_projectSelectorAction->setObjectName("KitSelector"); // used for UI introduction dd->m_projectSelectorAction->setCheckable(true); dd->m_projectSelectorAction->setEnabled(false); QWidget *mainWindow = ICore::mainWindow(); diff --git a/src/plugins/welcome/images/border.png b/src/plugins/welcome/images/border.png new file mode 100644 index 00000000000..f19494d9d21 Binary files /dev/null and b/src/plugins/welcome/images/border.png differ diff --git a/src/plugins/welcome/introductionwidget.cpp b/src/plugins/welcome/introductionwidget.cpp new file mode 100644 index 00000000000..407f69dfab4 --- /dev/null +++ b/src/plugins/welcome/introductionwidget.cpp @@ -0,0 +1,399 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "introductionwidget.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace Utils; + +const char kTakeTourSetting[] = "TakeUITour"; + +namespace Welcome { +namespace Internal { + +void IntroductionWidget::askUserAboutIntroduction(QWidget *parent, QSettings *settings) +{ + if (!CheckableMessageBox::shouldAskAgain(settings, kTakeTourSetting)) + return; + auto messageBox = new CheckableMessageBox(parent); + messageBox->setWindowTitle(tr("Take a UI Tour")); + messageBox->setIconPixmap(QMessageBox::standardIcon(QMessageBox::Question)); + messageBox->setText( + tr("Do you want to take a quick UI tour? This shows where the most important user " + "interface elements are, and how they are used, and will only take a minute. You can " + "also take the tour later by selecting Help > UI Tour.")); + messageBox->setCheckBoxVisible(true); + messageBox->setCheckBoxText(CheckableMessageBox::msgDoNotAskAgain()); + messageBox->setChecked(true); + messageBox->setStandardButtons(QDialogButtonBox::Cancel); + QPushButton *tourButton = messageBox->addButton(tr("Take UI Tour"), QDialogButtonBox::AcceptRole); + connect(messageBox, &QDialog::finished, parent, [parent, settings, messageBox, tourButton]() { + if (messageBox->isChecked()) + CheckableMessageBox::doNotAskAgain(settings, kTakeTourSetting); + if (messageBox->clickedButton() == tourButton) { + auto intro = new IntroductionWidget(parent); + intro->show(); + } + messageBox->deleteLater(); + }); + messageBox->show(); +} + +IntroductionWidget::IntroductionWidget(QWidget *parent) + : QWidget(parent), + m_borderImage(std::make_unique(":/welcome/images/border.png")) +{ + setFocusPolicy(Qt::StrongFocus); + setFocus(); + parent->installEventFilter(this); + + QPalette p = palette(); + p.setColor(QPalette::Foreground, QColor(220, 220, 220)); + setPalette(p); + + m_textWidget = new QWidget(this); + auto layout = new QVBoxLayout; + m_textWidget->setLayout(layout); + + m_stepText = new QLabel(this); + m_stepText->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_stepText->setWordWrap(true); + m_stepText->setTextFormat(Qt::RichText); + // why is palette not inherited??? + m_stepText->setPalette(palette()); + m_stepText->setOpenExternalLinks(true); + m_stepText->installEventFilter(this); + layout->addWidget(m_stepText); + + m_continueLabel = new QLabel(this); + m_continueLabel->setAlignment(Qt::AlignCenter); + m_continueLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + m_continueLabel->setWordWrap(true); + auto fnt = font(); + fnt.setPointSizeF(fnt.pointSizeF() * 1.5); + m_continueLabel->setFont(fnt); + m_continueLabel->setPalette(palette()); + layout->addWidget(m_continueLabel); + m_bodyCss = "font-size: 16px;"; + m_items = { + {"ModeSelector", + tr("Mode Selector"), + tr("Select different modes depending on the task at hand."), + tr("

" + "" + "" + "" + "" + "" + "" + "
Welcome:Open examples, tutorials, and " + "recent sessions and projects.
Edit:Work with code and navigate your project.
Design:Work with UI designs for Qt Widgets or Qt Quick.
Debug:Analyze your application with a debugger or other " + "analyzer.
Projects:Manage project settings.
Help:Browse the help database.

")}, + {"KitSelector.Button", + tr("Kit Selector"), + tr("Select the active project or project configuration."), + ""}, + {"Run.Button", + tr("Run Button"), + tr("Run the active project. By default this builds the project first."), + ""}, + {"Debug.Button", tr("Debug Button"), tr("Run the active project in a debugger."), ""}, + {"Build.Button", tr("Build Button"), tr("Build the active project."), ""}, + {"LocatorInput", + tr("Locator"), + tr("Type here to open a file from any open project."), + tr("Or:" + "
    " + "
  • type c<space><pattern> to jump to a class definition
  • " + "
  • type f<space><pattern> to open a file from the file " + "system
  • " + "
  • click on the magnifier icon for a complete list of possible options
  • " + "
")}, + {"OutputPaneButtons", + tr("Output Panes"), + tr("Find compile and application output here, " + "as well as a list of configuration and build issues, " + "and the panel for global searches."), + ""}, + {"ProgressInfo", + tr("Progress Indicator"), + tr("Progress information about running tasks is shown here."), + ""}, + {{}, + tr("Escape to Editor"), + tr("Pressing the Escape key brings you back to the editor. Press it " + "multiple times to also hide output panes and context help, giving the editor more " + "space."), + ""}, + {{}, + tr("The End"), + tr("You have now completed the UI tour. To learn more about the highlighted " + "controls, see User " + "Interface."), + ""}}; + setStep(0); + resizeToParent(); +} + +bool IntroductionWidget::event(QEvent *e) +{ + if (e->type() == QEvent::ShortcutOverride) { + e->accept(); + return true; + } + return QWidget::event(e); +} + +bool IntroductionWidget::eventFilter(QObject *obj, QEvent *ev) +{ + if (obj == parent() && ev->type() == QEvent::Resize) + resizeToParent(); + else if (obj == m_stepText && ev->type() == QEvent::MouseButtonRelease) + step(); + return QWidget::eventFilter(obj, ev); +} + +const int SPOTLIGHTMARGIN = 18; +const int POINTER_SIZE = 30; +const int POINTER_WIDTH = 3; + +static int margin(const QRect &small, const QRect &big, Qt::Alignment side) +{ + switch (side) { + case Qt::AlignRight: + return qMax(0, big.right() - small.right()); + case Qt::AlignTop: + return qMax(0, small.top() - big.top()); + case Qt::AlignBottom: + return qMax(0, big.bottom() - small.bottom()); + case Qt::AlignLeft: + return qMax(0, small.x() - big.x()); + default: + QTC_ASSERT(false, return 0); + } +} + +static int oppositeMargin(const QRect &small, const QRect &big, Qt::Alignment side) +{ + switch (side) { + case Qt::AlignRight: + return margin(small, big, Qt::AlignLeft); + case Qt::AlignTop: + return margin(small, big, Qt::AlignBottom); + case Qt::AlignBottom: + return margin(small, big, Qt::AlignTop); + case Qt::AlignLeft: + return margin(small, big, Qt::AlignRight); + default: + QTC_ASSERT(false, return 100000); + } +} + +static const QVector pointerPolygon(const QRect &anchorRect, const QRect &fullRect) +{ + // Put the arrow opposite to the smallest margin, + // with priority right, top, bottom, left. + // Not very sophisticated but sufficient for current use cases. + QVector alignments{Qt::AlignRight, Qt::AlignTop, Qt::AlignBottom, Qt::AlignLeft}; + Utils::sort(alignments, [anchorRect, fullRect](Qt::Alignment a, Qt::Alignment b) { + return oppositeMargin(anchorRect, fullRect, a) < oppositeMargin(anchorRect, fullRect, b); + }); + const auto alignIt = std::find_if(alignments.cbegin(), + alignments.cend(), + [anchorRect, fullRect](Qt::Alignment align) { + return margin(anchorRect, fullRect, align) > POINTER_SIZE; + }); + if (alignIt == alignments.cend()) + return {{}}; // no side with sufficient space found + const qreal arrowHeadWidth = POINTER_SIZE/3.; + if (*alignIt == Qt::AlignRight) { + const qreal middleY = anchorRect.center().y(); + const qreal startX = anchorRect.right() + POINTER_SIZE; + const qreal endX = anchorRect.right() + POINTER_WIDTH; + return {{QVector{{startX, middleY}, {endX, middleY}}}, + QVector{{endX + arrowHeadWidth, middleY - arrowHeadWidth}, + {endX, middleY}, + {endX + arrowHeadWidth, middleY + arrowHeadWidth}}}; + } + if (*alignIt == Qt::AlignTop) { + const qreal middleX = anchorRect.center().x(); + const qreal startY = anchorRect.y() - POINTER_SIZE; + const qreal endY = anchorRect.y() - POINTER_WIDTH; + return {{QVector{{middleX, startY}, {middleX, endY}}}, + QVector{{middleX - arrowHeadWidth, endY - arrowHeadWidth}, + {middleX, endY}, + {middleX + arrowHeadWidth, endY - arrowHeadWidth}}}; + } + if (*alignIt == Qt::AlignBottom) { + const qreal middleX = anchorRect.center().x(); + const qreal startY = anchorRect.y() + POINTER_WIDTH; + const qreal endY = anchorRect.y() + POINTER_SIZE; + return {{QVector{{middleX, startY}, {middleX, endY}}}, + QVector{{middleX - arrowHeadWidth, endY + arrowHeadWidth}, + {middleX, endY}, + {middleX + arrowHeadWidth, endY + arrowHeadWidth}}}; + } + + // Qt::AlignLeft + const qreal middleY = anchorRect.center().y(); + const qreal startX = anchorRect.x() - POINTER_WIDTH; + const qreal endX = anchorRect.x() - POINTER_SIZE; + return {{QVector{{startX, middleY}, {endX, middleY}}}, + QVector{{endX - arrowHeadWidth, middleY - arrowHeadWidth}, + {endX, middleY}, + {endX - arrowHeadWidth, middleY + arrowHeadWidth}}}; +} + +void IntroductionWidget::paintEvent(QPaintEvent *) +{ + QPainter p(this); + p.setOpacity(.87); + const QColor backgroundColor = Qt::black; + if (m_stepPointerAnchor) { + const QPoint anchorPos = m_stepPointerAnchor->mapTo(parentWidget(), {0, 0}); + const QRect anchorRect(anchorPos, m_stepPointerAnchor->size()); + const QRect spotlightRect = anchorRect.adjusted(-SPOTLIGHTMARGIN, + -SPOTLIGHTMARGIN, + SPOTLIGHTMARGIN, + SPOTLIGHTMARGIN); + + // darken the background to create a spotlighted area + if (spotlightRect.left() > 0) { + p.fillRect(0, 0, spotlightRect.left(), height(), backgroundColor); + } + if (spotlightRect.top() > 0) { + p.fillRect(spotlightRect.left(), + 0, + width() - spotlightRect.left(), + spotlightRect.top(), + backgroundColor); + } + if (spotlightRect.right() < width() - 1) { + p.fillRect(spotlightRect.right() + 1, + spotlightRect.top(), + width() - spotlightRect.right() - 1, + height() - spotlightRect.top(), + backgroundColor); + } + if (spotlightRect.bottom() < height() - 1) { + p.fillRect(spotlightRect.left(), + spotlightRect.bottom() + 1, + spotlightRect.width(), + height() - spotlightRect.bottom() - 1, + backgroundColor); + } + + // smooth borders of the spotlighted area by gradients + StyleHelper::drawCornerImage(*m_borderImage, + &p, + spotlightRect, + SPOTLIGHTMARGIN, + SPOTLIGHTMARGIN, + SPOTLIGHTMARGIN, + SPOTLIGHTMARGIN); + + // draw pointer + const QColor qtGreen(65, 205, 82); + p.setOpacity(1.); + p.setPen(QPen(QBrush(qtGreen), + POINTER_WIDTH, + Qt::SolidLine, + Qt::RoundCap, + Qt::MiterJoin)); + p.setRenderHint(QPainter::Antialiasing); + for (const QPolygonF &poly : pointerPolygon(spotlightRect, rect())) + p.drawPolyline(poly); + } else { + p.fillRect(rect(), backgroundColor); + } +} + +void IntroductionWidget::keyPressEvent(QKeyEvent *ke) +{ + if (ke->key() == Qt::Key_Escape) + finish(); + else if (ke->modifiers() == Qt::NoModifier) + step(); +} + +void IntroductionWidget::mouseReleaseEvent(QMouseEvent *me) +{ + me->accept(); + step(); +} + +void IntroductionWidget::finish() +{ + hide(); + deleteLater(); +} + +void IntroductionWidget::step() +{ + if (m_step >= m_items.size() - 1) + finish(); + else + setStep(m_step + 1); +} + +void IntroductionWidget::setStep(uint index) +{ + QTC_ASSERT(index < m_items.size(), return); + m_step = index; + m_continueLabel->setText(tr("UI Introduction %1/%2 >").arg(m_step + 1).arg(m_items.size())); + const Item &item = m_items.at(m_step); + m_stepText->setText("" + "

" + item.title + + "

" + item.brief + "

" + item.description + ""); + const QString anchorObjectName = m_items.at(m_step).pointerAnchorObjectName; + if (!anchorObjectName.isEmpty()) { + m_stepPointerAnchor = parentWidget()->findChild(anchorObjectName); + QTC_CHECK(m_stepPointerAnchor); + } else { + m_stepPointerAnchor.clear(); + } + update(); +} + +void IntroductionWidget::resizeToParent() +{ + QTC_ASSERT(parentWidget(), return); + setGeometry(QRect(QPoint(0, 0), parentWidget()->size())); + m_textWidget->setGeometry(QRect(width()/4, height()/4, width()/2, height()/2)); +} + +} // namespace Internal +} // namespace Welcome diff --git a/src/plugins/welcome/introductionwidget.h b/src/plugins/welcome/introductionwidget.h new file mode 100644 index 00000000000..69212e77623 --- /dev/null +++ b/src/plugins/welcome/introductionwidget.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE +class QLabel; +class QSettings; +QT_END_NAMESPACE + +namespace Welcome { +namespace Internal { + +struct Item +{ + QString pointerAnchorObjectName; + QString title; + QString brief; + QString description; +}; + +class IntroductionWidget : public QWidget +{ + Q_OBJECT +public: + explicit IntroductionWidget(QWidget *parent = nullptr); + + static void askUserAboutIntroduction(QWidget *parent, QSettings *settings); + +protected: + bool event(QEvent *e) override; + bool eventFilter(QObject *obj, QEvent *ev) override; + void paintEvent(QPaintEvent *ev) override; + void keyPressEvent(QKeyEvent *ke) override; + void mouseReleaseEvent(QMouseEvent *me) override; + +private: + void finish(); + void step(); + void setStep(uint index); + void resizeToParent(); + + QWidget *m_textWidget; + QLabel *m_stepText; + QLabel *m_continueLabel; + std::unique_ptr m_borderImage; + QString m_bodyCss; + std::vector m_items; + QPointer m_stepPointerAnchor; + uint m_step = 0; +}; + +} // namespace Internal +} // namespace Welcome diff --git a/src/plugins/welcome/welcome.pro b/src/plugins/welcome/welcome.pro index 612739a692f..7123b4abb6c 100644 --- a/src/plugins/welcome/welcome.pro +++ b/src/plugins/welcome/welcome.pro @@ -1,8 +1,12 @@ include(../../qtcreatorplugin.pri) -SOURCES += welcomeplugin.cpp +SOURCES += welcomeplugin.cpp \ + introductionwidget.cpp DEFINES += WELCOME_LIBRARY RESOURCES += welcome.qrc + +HEADERS += \ + introductionwidget.h diff --git a/src/plugins/welcome/welcome.qrc b/src/plugins/welcome/welcome.qrc index 28e8d22d1c8..d5b825d7e16 100644 --- a/src/plugins/welcome/welcome.qrc +++ b/src/plugins/welcome/welcome.qrc @@ -22,5 +22,6 @@ images/new@2x.png images/expandarrow.png images/expandarrow@2x.png + images/border.png diff --git a/src/plugins/welcome/welcomeplugin.cpp b/src/plugins/welcome/welcomeplugin.cpp index aa804bbe6d8..b70639fb4e2 100644 --- a/src/plugins/welcome/welcomeplugin.cpp +++ b/src/plugins/welcome/welcomeplugin.cpp @@ -23,11 +23,14 @@ ** ****************************************************************************/ +#include "introductionwidget.h" + #include #include #include +#include #include #include #include @@ -139,6 +142,18 @@ public: { m_welcomeMode->initPlugins(); ModeManager::activateMode(m_welcomeMode->id()); + auto introAction = new QAction(tr("UI Tour"), this); + connect(introAction, &QAction::triggered, this, []() { + auto intro = new IntroductionWidget(ICore::mainWindow()); + intro->show(); + }); + Command *cmd = ActionManager::registerAction(introAction, "Welcome.UITour"); + ActionContainer *mhelp = ActionManager::actionContainer(Core::Constants::M_HELP); + if (QTC_GUARD(mhelp)) + mhelp->addAction(cmd, Core::Constants::G_HELP_HELP); + connect(ICore::instance(), &ICore::coreOpened, this, []() { + IntroductionWidget::askUserAboutIntroduction(ICore::mainWindow(), ICore::settings()); + }, Qt::QueuedConnection); } WelcomeMode *m_welcomeMode = nullptr; diff --git a/src/tools/icons/qtcreatoricons.svg b/src/tools/icons/qtcreatoricons.svg index cc05ffbe7fb..ab26dd5fedc 100644 --- a/src/tools/icons/qtcreatoricons.svg +++ b/src/tools/icons/qtcreatoricons.svg @@ -18,6 +18,22 @@ sodipodi:docname="qtcreatoricons.svg"> + + + + + @@ -527,6 +543,16 @@ y1="109.69" x2="-191.87" y2="110.83" /> + +