"Fossies" - the Fresh Open Source Software Archive

Member "flightgear-2020.3.8/src/GUI/GettingStartedTipsController.cxx" (24 Mar 2021, 13489 Bytes) of package /linux/privat/flightgear-2020.3.8.tar.bz2:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "GettingStartedTipsController.cxx" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2020.3.7_vs_2020.3.8.

    1 #include "GettingStartedTipsController.hxx"
    2 
    3 #include <algorithm>
    4 
    5 #include <QSettings>
    6 #include <QDebug>
    7 #include <QQmlContext>
    8 #include <QTimer>
    9 
   10 #include <QtQml> // qmlContext
   11 
   12 #include "GettingStartedTip.hxx"
   13 #include "TipBackgroundBox.hxx"
   14 
   15 struct TipGeometryByArrowLocation
   16 {
   17     TipGeometryByArrowLocation(GettingStartedTip::Arrow a, const QRectF& g, Qt::Alignment al) :
   18         arrow(a),
   19         geometry(g),
   20         verticalAlignment(al)
   21     {
   22     }
   23 
   24     GettingStartedTip::Arrow arrow;
   25     QRectF geometry;
   26     // specify how vertical space is adjusted; is the top, bottom or center fixed
   27     Qt::Alignment verticalAlignment = Qt::AlignVCenter;
   28 };
   29 
   30 const double tipBoxWidth = 300.0;
   31 const double halfBoxWidth = tipBoxWidth * 0.5;
   32 const double arrowSideOffset = TipBackgroundBox::arrowSideOffset();
   33 const double rightSideOffset = -tipBoxWidth + arrowSideOffset;
   34 const double dummyHeight = 200.0;
   35 const double topHeightOffset = -TipBackgroundBox::arrowHeight();
   36 
   37 static std::initializer_list<TipGeometryByArrowLocation> static_tipGeometries = {
   38     {GettingStartedTip::Arrow::BottomRight, QRectF{rightSideOffset, 0.0, tipBoxWidth, dummyHeight}, Qt::AlignBottom},
   39     {GettingStartedTip::Arrow::BottomCenter, QRectF{halfBoxWidth, 0.0, tipBoxWidth, dummyHeight}, Qt::AlignBottom},
   40     {GettingStartedTip::Arrow::TopCenter, QRectF{-halfBoxWidth, 0.0, tipBoxWidth, dummyHeight}, Qt::AlignTop},
   41     {GettingStartedTip::Arrow::TopRight, QRectF{rightSideOffset, 0.0, tipBoxWidth, dummyHeight}, Qt::AlignTop},
   42     {GettingStartedTip::Arrow::TopLeft, QRectF{-arrowSideOffset, 0.0, tipBoxWidth, dummyHeight}, Qt::AlignTop},
   43     {GettingStartedTip::Arrow::LeftCenter, QRectF{0.0, 0.0, tipBoxWidth, dummyHeight}, Qt::AlignVCenter},
   44     {GettingStartedTip::Arrow::RightCenter, QRectF{-(tipBoxWidth + TipBackgroundBox::arrowHeight()), 0.0, tipBoxWidth, dummyHeight}, Qt::AlignVCenter},
   45     {GettingStartedTip::Arrow::LeftTop, QRectF{0.0, topHeightOffset, tipBoxWidth, dummyHeight}, Qt::AlignTop},
   46     {GettingStartedTip::Arrow::NoArrow, QRectF{-halfBoxWidth, 0.0, tipBoxWidth, dummyHeight}, Qt::AlignVCenter},
   47 
   48 };
   49 
   50 /**
   51  * @brief The GettingStartedTipsController::ItemPositionObserver class
   52  *
   53  * This is a helper to observe the full position (and in the future, transform if required)
   54  * of a QQuickItem, so we can update a signal when the on-screen position changes. This
   55  * is necessary to re-transform the tooltip location if the item it's 'attached' to
   56  * moves, or some ancestor does.
   57  *
   58  * At present this does not handle arbitrary scaling or rotation, but observing those
   59  * signals would also be possible.
   60  */
   61 class GettingStartedTipsController::ItemPositionObserver : public QObject
   62 {
   63     Q_OBJECT
   64 public:
   65     ItemPositionObserver(QObject* pr) :
   66         QObject(pr)
   67     {
   68         _notMovingTimeout = new QTimer(this);
   69         _notMovingTimeout->setSingleShot(true);
   70         _notMovingTimeout->setInterval(1000);
   71 
   72         connect(this, SIGNAL(itemPositionChanged()),
   73                 _notMovingTimeout, SLOT(start()));
   74 
   75         connect(_notMovingTimeout, &QTimer::timeout,
   76                 this, &ItemPositionObserver::itemNotMoving);
   77     }
   78 
   79     void setObservedItem(QQuickItem* obs)
   80     {
   81         if (obs == _observedItem)
   82             return;
   83 
   84         if (_observedItem) {
   85             for (auto o = _observedItem; o; o = o->parentItem()) {
   86                 disconnect(o, nullptr, this, nullptr);
   87             }
   88         }
   89 
   90         _observedItem = obs;
   91         if (obs) {
   92             startObserving(_observedItem);
   93         }
   94     }
   95 
   96     bool hasRecentlyMoved() const
   97     {
   98         return _notMovingTimeout->isActive();
   99     }
  100 signals:
  101     void itemPositionChanged();
  102 
  103     void itemNotMoving();
  104 private:
  105     void startObserving(QQuickItem* obs)
  106     {
  107 
  108         connect(obs, &QQuickItem::xChanged, this, &ItemPositionObserver::itemPositionChanged);
  109         connect(obs, &QQuickItem::yChanged, this, &ItemPositionObserver::itemPositionChanged);
  110         connect(obs, &QQuickItem::widthChanged, this, &ItemPositionObserver::itemPositionChanged);
  111         connect(obs, &QQuickItem::heightChanged, this, &ItemPositionObserver::itemPositionChanged);
  112 
  113         // recurse up the item hierarchy
  114         if (obs->parentItem()) {
  115             startObserving(obs->parentItem());
  116         }
  117     }
  118 
  119     QPointer<QQuickItem> _observedItem;
  120     QTimer* _notMovingTimeout = nullptr;
  121 };
  122 
  123 // static used to ensure only one controller is active at a time
  124 static QPointer<GettingStartedTipsController> static_activeController;
  125 
  126 GettingStartedTipsController::GettingStartedTipsController(QObject *parent) : QObject(parent)
  127 {
  128     // observer for the tip item
  129     _positionObserver = new ItemPositionObserver(this);
  130     connect(_positionObserver, &ItemPositionObserver::itemPositionChanged,
  131             this, &GettingStartedTipsController::tipPositionInVisualAreaChanged);
  132 
  133     // observer for the visual area (which could also be scrolled)
  134     _viewAreaObserver = new ItemPositionObserver(this);
  135     connect(_viewAreaObserver, &ItemPositionObserver::itemPositionChanged,
  136             this, &GettingStartedTipsController::tipPositionInVisualAreaChanged);
  137 
  138     connect(_positionObserver, &ItemPositionObserver::itemNotMoving,
  139             this, &GettingStartedTipsController::tipPositionInVisualAreaChanged);
  140     connect(_viewAreaObserver, &ItemPositionObserver::itemNotMoving,
  141             this, &GettingStartedTipsController::tipPositionInVisualAreaChanged);
  142 
  143     auto qqParent = qobject_cast<QQuickItem*>(parent);
  144     if (qqParent) {
  145         setVisualArea(qqParent);
  146     }
  147 }
  148 
  149 GettingStartedTipsController::~GettingStartedTipsController()
  150 {
  151 }
  152 
  153 int GettingStartedTipsController::count() const
  154 {
  155     if (_oneShotTip) {
  156         return 1;
  157     }
  158 
  159     return _tips.size();
  160 }
  161 
  162 int GettingStartedTipsController::index() const
  163 {
  164     if (_oneShotTip) {
  165         return 0;
  166     }
  167 
  168     return _index;
  169 }
  170 
  171 GettingStartedTip *GettingStartedTipsController::tip() const
  172 {
  173     if (_oneShotTip) {
  174         return _oneShotTip;
  175     }
  176 
  177     if (_tips.empty())
  178         return nullptr;
  179 
  180     if ((_index < 0) || (_index >= _tips.size()))
  181         return nullptr;
  182 
  183     return _tips.at(_index);
  184 }
  185 
  186 void GettingStartedTipsController::setVisualArea(QQuickItem *visualArea)
  187 {
  188     if (_visualArea == visualArea)
  189         return;
  190 
  191     _visualArea = visualArea;
  192     _viewAreaObserver->setObservedItem(_visualArea);
  193 
  194     emit tipPositionInVisualAreaChanged();
  195     emit visualAreaChanged(_visualArea);
  196 }
  197 
  198 void GettingStartedTipsController::setActiveTipHeight(int activeTipHeight)
  199 {
  200     if (_activeTipHeight == activeTipHeight)
  201         return;
  202 
  203     _activeTipHeight = activeTipHeight;
  204     emit activeTipHeightChanged(_activeTipHeight);
  205     emit tipGeometryChanged();
  206 }
  207 
  208 void GettingStartedTipsController::showOneShotTip(GettingStartedTip *tip)
  209 {
  210     if (_scopeActive) {
  211         return;
  212     }
  213 
  214     if (static_activeController != this) {
  215         return;
  216     }
  217 
  218     QSettings settings;
  219     settings.beginGroup("GettingStarted-DontShow");
  220     if (settings.value(tip->tipId()).toBool()) {
  221         return;
  222     }
  223 
  224     // mark the tip as shown
  225     settings.setValue(tip->tipId(), true);
  226     _oneShotTip = tip;
  227 
  228     connect(_oneShotTip, &QObject::destroyed, this, &GettingStartedTipsController::onOneShotDestroyed);
  229 
  230     currentTipUpdated();
  231     emit indexChanged(0);
  232     emit countChanged(count());
  233 }
  234 
  235 void GettingStartedTipsController::tipsWereReset()
  236 {
  237     bool a = shouldShowScope();
  238     if (a != _scopeActive) {
  239         _scopeActive = a;
  240         static_activeController = this; // we became active
  241         emit activeChanged();
  242         currentTipUpdated();
  243     }
  244 }
  245 
  246 void GettingStartedTipsController::currentTipUpdated()
  247 {
  248     _positionObserver->setObservedItem(tip());
  249 
  250     emit activeChanged();
  251     emit tipChanged();
  252     emit tipPositionInVisualAreaChanged();
  253     emit tipGeometryChanged();
  254 }
  255 
  256 bool GettingStartedTipsController::addTip(GettingStartedTip *t)
  257 {
  258     if (_tips.contains(t)) {
  259         qWarning() << Q_FUNC_INFO << "Duplicate tip" << t;
  260         return false;
  261     }
  262 
  263     // this logic is important to suppress duplicate tips inside a ListView or Repeater;
  264     // effectively, we only show a tip on the first registered instance.
  265     Q_FOREACH(GettingStartedTip* tip, _tips) {
  266         if (tip->tipId() == t->tipId()) {
  267             return false;
  268         }
  269     }
  270 
  271     _tips.append(t);
  272     // order tips by nextTip ID, if defined
  273     std::sort(_tips.begin(), _tips.end(), [](const GettingStartedTip* a, GettingStartedTip *b) {
  274        return a->nextTip() == b->tipId();
  275     });
  276 
  277     currentTipUpdated();
  278     emit countChanged(count());
  279     return true;
  280 }
  281 
  282 void GettingStartedTipsController::removeTip(GettingStartedTip *t)
  283 {
  284     const bool removedActive = (tip() == t);
  285     if (!_tips.removeOne(t)) {
  286         qWarning() << Q_FUNC_INFO << "tip not found";
  287     }
  288 
  289     if (removedActive) {
  290         _index = qMax(_index - 1, 0);
  291     }
  292 
  293     currentTipUpdated();
  294     emit countChanged(count());
  295 }
  296 
  297 void GettingStartedTipsController::onOneShotDestroyed()
  298 {
  299     if (_oneShotTip == sender()) {
  300         emit activeChanged();
  301         currentTipUpdated();
  302     }
  303 }
  304 
  305 bool GettingStartedTipsController::isActive() const
  306 {
  307     if (_oneShotTip)
  308         return true;
  309 
  310     return _scopeActive && !_tips.empty();
  311 }
  312 
  313 QPointF GettingStartedTipsController::tipPositionInVisualArea() const
  314 {
  315     auto t = tip();
  316     if (!_visualArea || !t) {
  317         return {};
  318     }
  319 
  320     return _visualArea->mapFromItem(t, QPointF{0,0});
  321 }
  322 
  323 QRectF GettingStartedTipsController::tipGeometry() const
  324 {
  325     auto t = tip();
  326     if (!t)
  327         return {};
  328 
  329     const auto arrow = t->arrow();
  330     auto it = std::find_if(static_tipGeometries.begin(), static_tipGeometries.end(),
  331                            [arrow](const TipGeometryByArrowLocation& tg)
  332     {
  333         return tg.arrow == arrow;
  334     });
  335 
  336     if (it == static_tipGeometries.end()) {
  337         qWarning() << Q_FUNC_INFO << "Missing tip geometry" << arrow;
  338         return {};
  339     }
  340 
  341     QRectF g = it->geometry;
  342     if ((arrow == GettingStartedTip::Arrow::LeftCenter) 
  343             || (arrow == GettingStartedTip::Arrow::RightCenter)
  344             || (arrow == GettingStartedTip::Arrow::LeftTop)
  345             || (arrow == GettingStartedTip::Arrow::NoArrow))
  346     {
  347         g.setHeight(_activeTipHeight);
  348     } else {
  349         g.setHeight(_activeTipHeight + TipBackgroundBox::arrowHeight());
  350     }
  351 
  352     switch (it->verticalAlignment) {
  353     case Qt::AlignBottom:
  354         g.moveBottom(0);
  355         break;
  356 
  357     case Qt::AlignTop:
  358         g.moveTop(0);
  359         break;
  360 
  361     case Qt::AlignVCenter:
  362         g.moveTop(_activeTipHeight * -0.5);
  363         break;
  364     }
  365 
  366     return g;
  367 }
  368 
  369 bool GettingStartedTipsController::tipPositionValid() const
  370 {
  371     if (!_visualArea || !isActive())
  372         return false;
  373 
  374     // hide tips when resizing the window or scrolling; it's visually distracting otherwise
  375     if (_positionObserver->hasRecentlyMoved() || _viewAreaObserver->hasRecentlyMoved()) {
  376         return false;
  377     }
  378 
  379     if (_oneShotTip)
  380         return true;
  381 
  382     return !_tips.empty();
  383 }
  384 
  385 int GettingStartedTipsController::activeTipHeight() const
  386 {
  387     return _activeTipHeight;
  388 }
  389 
  390 QRectF GettingStartedTipsController::contentGeometry() const
  391 {
  392     QRectF g(0.0, 0.0, tipBoxWidth, 200.0);
  393 
  394     auto t = tip();
  395     if (!t)
  396         return g;
  397 
  398     const auto arrow = t->arrow();
  399     if ((arrow == GettingStartedTip::Arrow::TopCenter) ||
  400             (arrow == GettingStartedTip::Arrow::TopLeft) ||
  401             (arrow == GettingStartedTip::Arrow::TopRight))
  402     {
  403         g.moveTop(TipBackgroundBox::arrowHeight());
  404     }
  405 
  406     if (arrow == GettingStartedTip::Arrow::RightCenter) {
  407         g.setWidth(tipBoxWidth - TipBackgroundBox::arrowHeight());
  408     }
  409 
  410     if (arrow == GettingStartedTip::Arrow::LeftCenter) {
  411         g.setWidth(tipBoxWidth - TipBackgroundBox::arrowHeight());
  412         g.moveLeft(TipBackgroundBox::arrowHeight());
  413     }
  414 
  415     return g;
  416 }
  417 
  418 void GettingStartedTipsController::close()
  419 {
  420     // one-shot tips handle this logic differently; we set the don't show
  421     // when the tip first appears
  422     if (_oneShotTip) {
  423         disconnect(_oneShotTip, nullptr, this, nullptr);
  424         _oneShotTip = nullptr;
  425         static_activeController.clear();
  426     } else {
  427         if (_scopeActive) {
  428             static_activeController.clear();
  429         }
  430 
  431         QSettings settings;
  432         settings.beginGroup("GettingStarted-DontShow");
  433         settings.setValue(_scopeId, true);
  434         _scopeActive = false;
  435     }
  436 
  437     emit activeChanged();
  438     currentTipUpdated();
  439 }
  440 
  441 void GettingStartedTipsController::setIndex(int index)
  442 {
  443     if (_oneShotTip) {
  444         return;
  445     }
  446 
  447     if (_index == index)
  448         return;
  449 
  450     _index = qBound(0, index, _tips.size() - 1);
  451     _positionObserver->setObservedItem(tip());
  452 
  453     emit indexChanged(_index);
  454     currentTipUpdated();
  455 }
  456 
  457 void GettingStartedTipsController::setScopeId(QString scopeId)
  458 {
  459     if (_scopeId == scopeId)
  460         return;
  461 
  462     _scopeId = scopeId;
  463     _scopeActive = shouldShowScope();
  464     if (_scopeActive) {
  465         static_activeController = this;
  466     }
  467     emit scopeIdChanged(_scopeId);
  468     emit activeChanged();
  469 }
  470 
  471 bool GettingStartedTipsController::shouldShowScope() const
  472 {
  473     if (static_activeController && (static_activeController != this)) {
  474         return false;
  475     }
  476 
  477     if (_scopeId.isEmpty())
  478         return true;
  479 
  480     QSettings settings;
  481     settings.beginGroup("GettingStarted-DontShow");
  482     return settings.value(_scopeId).toBool() == false;
  483 }
  484 
  485 
  486 #include "GettingStartedTipsController.moc"