diff --git a/abstract_client.h b/abstract_client.h --- a/abstract_client.h +++ b/abstract_client.h @@ -392,9 +392,10 @@ **/ virtual bool hasTransientPlacementHint() const; /** - * @returns The recommended position of the transient in parent coordinates + * Only valid is hasTransientPlacementHint is true + * @returns The position the transient wishes to position itself **/ - virtual QPoint transientPlacementHint() const; + virtual QRect transientPlacement(const QRect &bounds) const; const AbstractClient* transientFor() const; AbstractClient* transientFor(); /** diff --git a/abstract_client.cpp b/abstract_client.cpp --- a/abstract_client.cpp +++ b/abstract_client.cpp @@ -1169,9 +1169,11 @@ return false; } -QPoint AbstractClient::transientPlacementHint() const +QRect AbstractClient::transientPlacement(const QRect &bounds) const { - return QPoint(); + Q_UNUSED(bounds); + Q_ASSERT(false); + return QRect(); } bool AbstractClient::hasTransient(const AbstractClient *c, bool indirect) const diff --git a/autotests/integration/kwin_wayland_test.h b/autotests/integration/kwin_wayland_test.h --- a/autotests/integration/kwin_wayland_test.h +++ b/autotests/integration/kwin_wayland_test.h @@ -24,6 +24,7 @@ // Qt #include +#include namespace KWayland { @@ -43,7 +44,6 @@ class ShellSurface; class ShmPool; class Surface; -class XdgShellSurface; } } @@ -138,7 +138,7 @@ KWayland::Client::XdgShellSurface *createXdgShellV5Surface(KWayland::Client::Surface *surface, QObject *parent = nullptr); KWayland::Client::XdgShellSurface *createXdgShellV6Surface(KWayland::Client::Surface *surface, QObject *parent = nullptr); KWayland::Client::XdgShellSurface *createXdgShellStableSurface(KWayland::Client::Surface *surface, QObject *parent = nullptr); - +KWayland::Client::XdgShellPopup *createXdgShellStablePopup(KWayland::Client::Surface *surface, KWayland::Client::XdgShellSurface *parentSurface, const KWayland::Client::XdgPositioner &positioner, QObject *parent = nullptr); /** * Creates a shared memory buffer of @p size in @p color and attaches it to the @p surface. diff --git a/autotests/integration/test_helpers.cpp b/autotests/integration/test_helpers.cpp --- a/autotests/integration/test_helpers.cpp +++ b/autotests/integration/test_helpers.cpp @@ -480,6 +480,19 @@ return s; } +XdgShellPopup *createXdgShellStablePopup(Surface *surface, XdgShellSurface *parentSurface, const XdgPositioner &positioner, QObject *parent) +{ + if (!s_waylandConnection.xdgShellStable) { + return nullptr; + } + auto s = s_waylandConnection.xdgShellStable->createPopup(surface, parentSurface, positioner, parent); + if (!s->isValid()) { + delete s; + return nullptr; + } + return s; +} + QObject *createShellSurface(ShellSurfaceType type, KWayland::Client::Surface *surface, QObject *parent) { switch (type) { diff --git a/autotests/integration/transient_no_input_test.cpp b/autotests/integration/transient_no_input_test.cpp --- a/autotests/integration/transient_no_input_test.cpp +++ b/autotests/integration/transient_no_input_test.cpp @@ -103,7 +103,6 @@ QVERIFY(transientClient != c); QCOMPARE(transientClient->geometry(), QRect(c->x() + 10, c->y() + 20, 200, 20)); QVERIFY(transientClient->isTransient()); - QCOMPARE(transientClient->transientPlacementHint(), QPoint(10, 20)); QVERIFY(!transientClient->wantsInput()); // workspace's active window should not have changed diff --git a/autotests/integration/transient_placement.cpp b/autotests/integration/transient_placement.cpp --- a/autotests/integration/transient_placement.cpp +++ b/autotests/integration/transient_placement.cpp @@ -40,9 +40,11 @@ #include #include #include +#include #include #include + namespace KWin { @@ -59,9 +61,12 @@ void testSimplePosition(); void testDecorationPosition_data(); void testDecorationPosition(); + void testXdgPopup_data(); + void testXdgPopup(); private: - AbstractClient *showWindow(const QSize &size, bool decorated = false, KWayland::Client::Surface *parent = nullptr, const QPoint &offset = QPoint()); + AbstractClient *showWlShellWindow(const QSize &size, bool decorated = false, KWayland::Client::Surface *parent = nullptr, const QPoint &offset = QPoint()); + KWayland::Client::Surface *surfaceForClient(AbstractClient *c) const; }; @@ -97,7 +102,7 @@ Test::destroyWaylandConnection(); } -AbstractClient *TransientPlacementTest::showWindow(const QSize &size, bool decorated, KWayland::Client::Surface *parent, const QPoint &offset) +AbstractClient *TransientPlacementTest::showWlShellWindow(const QSize &size, bool decorated, KWayland::Client::Surface *parent, const QPoint &offset) { using namespace KWayland::Client; #define VERIFY(statement) \ @@ -171,14 +176,14 @@ // there are no further constraints like window too large to fit screen, cascading transients, etc // some test cases also verify that the transient fits on the screen QFETCH(QSize, parentSize); - AbstractClient *parent = showWindow(parentSize); + AbstractClient *parent = showWlShellWindow(parentSize); QVERIFY(parent->clientPos().isNull()); QVERIFY(!parent->isDecorated()); QFETCH(QPoint, parentPosition); parent->move(parentPosition); QFETCH(QSize, transientSize); QFETCH(QPoint, transientOffset); - AbstractClient *transient = showWindow(transientSize, false, surfaceForClient(parent), transientOffset); + AbstractClient *transient = showWlShellWindow(transientSize, false, surfaceForClient(parent), transientOffset); QVERIFY(transient); QVERIFY(!transient->isDecorated()); QVERIFY(transient->hasTransientPlacementHint()); @@ -203,22 +208,176 @@ // this test verifies that a transient window is correctly placed if the parent window has a // server side decoration QFETCH(QSize, parentSize); - AbstractClient *parent = showWindow(parentSize, true); + AbstractClient *parent = showWlShellWindow(parentSize, true); QVERIFY(!parent->clientPos().isNull()); QVERIFY(parent->isDecorated()); QFETCH(QPoint, parentPosition); parent->move(parentPosition); QFETCH(QSize, transientSize); QFETCH(QPoint, transientOffset); - AbstractClient *transient = showWindow(transientSize, false, surfaceForClient(parent), transientOffset); + AbstractClient *transient = showWlShellWindow(transientSize, false, surfaceForClient(parent), transientOffset); QVERIFY(transient); QVERIFY(!transient->isDecorated()); QVERIFY(transient->hasTransientPlacementHint()); QFETCH(QRect, expectedGeometry); expectedGeometry.translate(parent->clientPos()); QCOMPARE(transient->geometry(), expectedGeometry); } +void TransientPlacementTest::testXdgPopup_data() +{ + using namespace KWayland::Client; + + QTest::addColumn("parentSize"); + QTest::addColumn("parentPosition"); + QTest::addColumn("positioner"); + QTest::addColumn("expectedGeometry"); + + // window in the middle, plenty of room either side: Changing anchor + + // parent window is 500,500, starting at 300,300, anchorRect is therefore between 350->750 in both dirs + XdgPositioner positioner(QSize(200,200), QRect(50,50, 400,400)); + positioner.setGravity(Qt::BottomEdge | Qt::RightEdge); + + positioner.setAnchorEdge(Qt::Edges()); + QTest::newRow("anchorCentre") << QSize(500, 500) << QPoint(300,300) << positioner << QRect(550, 550, 200, 200); + positioner.setAnchorEdge(Qt::TopEdge | Qt::LeftEdge); + QTest::newRow("anchorTopLeft") << QSize(500, 500) << QPoint(300,300) << positioner << QRect(350,350, 200, 200); + positioner.setAnchorEdge(Qt::TopEdge); + QTest::newRow("anchorTop") << QSize(500, 500) << QPoint(300,300) << positioner << QRect(550, 350, 200, 200); + positioner.setAnchorEdge(Qt::TopEdge | Qt::RightEdge); + QTest::newRow("anchorTopRight") << QSize(500, 500) << QPoint(300,300) << positioner << QRect(750, 350, 200, 200); + positioner.setAnchorEdge(Qt::RightEdge); + QTest::newRow("anchorRight") << QSize(500, 500) << QPoint(300,300) << positioner << QRect(750, 550, 200, 200); + positioner.setAnchorEdge(Qt::BottomEdge | Qt::RightEdge); + QTest::newRow("anchorBottomRight") << QSize(500,500) << QPoint(300,300) << positioner << QRect(750, 750, 200, 200); + positioner.setAnchorEdge(Qt::BottomEdge); + QTest::newRow("anchorBottom") << QSize(500, 500) << QPoint(300,300) << positioner << QRect(550, 750, 200, 200); + positioner.setAnchorEdge(Qt::BottomEdge | Qt::LeftEdge); + QTest::newRow("anchorBottomLeft") << QSize(500, 500) << QPoint(300,300) << positioner << QRect(350, 750, 200, 200); + positioner.setAnchorEdge(Qt::LeftEdge); + QTest::newRow("anchorLeft") << QSize(500, 500) << QPoint(300,300) << positioner << QRect(350, 550, 200, 200); + + // ---------------------------------------------------------------- + // window in the middle, plenty of room either side: Changing gravity around the bottom right anchor + positioner.setAnchorEdge(Qt::BottomEdge | Qt::RightEdge); + positioner.setGravity(Qt::Edges()); + QTest::newRow("gravityCentre") << QSize(500, 500) << QPoint(300, 300) << positioner << QRect(650, 650, 200, 200); + positioner.setGravity(Qt::TopEdge | Qt::LeftEdge); + QTest::newRow("gravityTopLeft") << QSize(500, 500) << QPoint(300, 300) << positioner << QRect(550, 550, 200, 200); + positioner.setGravity(Qt::TopEdge); + QTest::newRow("gravityTop") << QSize(500, 500) << QPoint(300, 300) << positioner << QRect(650, 550, 200, 200); + positioner.setGravity(Qt::TopEdge | Qt::RightEdge); + QTest::newRow("gravityTopRight") << QSize(500, 500) << QPoint(300, 300) << positioner << QRect(750, 550, 200, 200); + positioner.setGravity(Qt::RightEdge); + QTest::newRow("gravityRight") << QSize(500, 500) << QPoint(300, 300) << positioner << QRect(750, 650, 200, 200); + positioner.setGravity(Qt::BottomEdge | Qt::RightEdge); + QTest::newRow("gravityBottomRight") << QSize(500, 500) << QPoint(300, 300) << positioner << QRect(750, 750, 200, 200); + positioner.setGravity(Qt::BottomEdge); + QTest::newRow("gravityBottom") << QSize(500, 500) << QPoint(300, 300) << positioner << QRect(650, 750, 200, 200); + positioner.setGravity(Qt::BottomEdge | Qt::LeftEdge); + QTest::newRow("gravityBottomLeft") << QSize(500, 500) << QPoint(300, 300) << positioner << QRect(550, 750, 200, 200); + positioner.setGravity(Qt::LeftEdge); + QTest::newRow("gravityLeft") << QSize(500, 500) << QPoint(300, 300) << positioner << QRect(550, 650, 200, 200); + + // ---------------------------------------------------------------- + //constrain and slide + //popup is still 200,200. window moved near edge of screen, popup always comes out towards the screen edge + positioner.setConstraints(XdgPositioner::Constraint::SlideX | XdgPositioner::Constraint::SlideY); + + positioner.setAnchorEdge(Qt::TopEdge); + positioner.setGravity(Qt::TopEdge); + QTest::newRow("constraintSlideTop") << QSize(500, 500) << QPoint(80, 80) << positioner << QRect(80 + 250 - 100, 0, 200, 200); + + positioner.setAnchorEdge(Qt::LeftEdge); + positioner.setGravity(Qt::LeftEdge); + QTest::newRow("constraintSlideLeft") << QSize(500, 500) << QPoint(80, 80) << positioner << QRect(0, 80 + 250 - 100, 200, 200); + + positioner.setAnchorEdge(Qt::RightEdge); + positioner.setGravity(Qt::RightEdge); + QTest::newRow("constraintSlideRight") << QSize(500, 500) << QPoint(700, 80) << positioner << QRect(1280 - 200, 80 + 250 - 100, 200, 200); + + positioner.setAnchorEdge(Qt::BottomEdge); + positioner.setGravity(Qt::BottomEdge); + QTest::newRow("constraintSlideBottom") << QSize(500, 500) << QPoint(80, 500) << positioner << QRect(80 + 250 - 100, 1024 - 200, 200, 200); + + positioner.setAnchorEdge(Qt::BottomEdge | Qt::RightEdge); + positioner.setGravity(Qt::BottomEdge| Qt::RightEdge); + QTest::newRow("constraintSlideBottomRight") << QSize(500, 500) << QPoint(700, 1000) << positioner << QRect(1280 - 200, 1024 - 200, 200, 200); + + + // ---------------------------------------------------------------- + // contrain and flip + positioner.setConstraints(XdgPositioner::Constraint::FlipX | XdgPositioner::Constraint::FlipY); + + positioner.setAnchorEdge(Qt::TopEdge); + positioner.setGravity(Qt::TopEdge); + QTest::newRow("constraintFlipTop") << QSize(500, 500) << QPoint(80, 80) << positioner << QRect(230, 80 + 500 - 50, 200, 200); + + positioner.setAnchorEdge(Qt::LeftEdge); + positioner.setGravity(Qt::LeftEdge); + QTest::newRow("constraintFlipLeft") << QSize(500, 500) << QPoint(80, 80) << positioner << QRect(80 + 500 - 50, 230, 200, 200); + + positioner.setAnchorEdge(Qt::RightEdge); + positioner.setGravity(Qt::RightEdge); + QTest::newRow("constraintFlipRight") << QSize(500, 500) << QPoint(700, 80) << positioner << QRect(700 + 50 - 200, 230, 200, 200); + + positioner.setAnchorEdge(Qt::BottomEdge); + positioner.setGravity(Qt::BottomEdge); + QTest::newRow("constraintFlipBottom") << QSize(500, 500) << QPoint(80, 500) << positioner << QRect(230, 500 + 50 - 200, 200, 200); + + positioner.setAnchorEdge(Qt::BottomEdge | Qt::RightEdge); + positioner.setGravity(Qt::BottomEdge| Qt::RightEdge); + QTest::newRow("constraintFlipBottomRight") << QSize(500, 500) << QPoint(700, 500) << positioner << QRect(700 + 50 - 200, 500 + 50 - 200, 200, 200); + + positioner.setAnchorEdge(Qt::TopEdge); + positioner.setGravity(Qt::RightEdge); + //as popup is positioned in the middle of the parent we need a massive popup to be able to overflow + positioner.setInitialSize(QSize(400, 400)); + QTest::newRow("constraintFlipRightNoAnchor") << QSize(500, 500) << QPoint(700, 80) << positioner << QRect(700 + 250 - 400, 330, 400, 400); + + positioner.setAnchorEdge(Qt::RightEdge); + positioner.setGravity(Qt::TopEdge); + positioner.setInitialSize(QSize(300, 200)); + QTest::newRow("constraintFlipRightNoGravity") << QSize(500, 500) << QPoint(700, 80) << positioner << QRect(700 + 50 - 150, 130, 300, 200); +} + +void TransientPlacementTest::testXdgPopup() +{ + using namespace KWayland::Client; + + // this test verifies that the position of a transient window is taken from the passed position + // there are no further constraints like window too large to fit screen, cascading transients, etc + // some test cases also verify that the transient fits on the screen + QFETCH(QSize, parentSize); + + //create parent + Surface *surface = Test::createSurface(Test::waylandCompositor()); + QVERIFY(surface); + auto parentShellSurface = Test::createXdgShellStableSurface(surface, Test::waylandCompositor()); + QVERIFY(parentShellSurface); + auto parent = Test::renderAndWaitForShown(surface, parentSize, Qt::blue); + QVERIFY(parent); + + QVERIFY(!parent->isDecorated()); + QFETCH(QPoint, parentPosition); + parent->move(parentPosition); + + //create popup + QFETCH(XdgPositioner, positioner); + + Surface *transientSurface = Test::createSurface(Test::waylandCompositor()); + QVERIFY(transientSurface); + + Test::createXdgShellStablePopup(transientSurface, parentShellSurface, positioner, Test::waylandCompositor()); + auto transient = Test::renderAndWaitForShown(transientSurface, positioner.initialSize(), Qt::red); + QVERIFY(transient); + + QVERIFY(!transient->isDecorated()); + QVERIFY(transient->hasTransientPlacementHint()); + QTEST(transient->geometry(), "expectedGeometry"); +} + } WAYLANDTEST_MAIN(KWin::TransientPlacementTest) diff --git a/placement.cpp b/placement.cpp --- a/placement.cpp +++ b/placement.cpp @@ -498,39 +498,14 @@ void Placement::placeTransient(AbstractClient *c) { - const QPoint target = c->transientFor()->pos() + c->transientFor()->clientPos() + c->transientPlacementHint(); - c->move(target); const QRect screen = screens()->geometry(c->transientFor()->screen()); - // TODO: work around Qt's transient placement of sub-menus, see https://bugreports.qt.io/browse/QTBUG-51640 -#define CHECK \ - if (screen.contains(c->geometry())) { \ - return; \ - } - CHECK - if (screen.x() + screen.width() < c->x() + c->width()) { - // overlaps on right - c->move(screen.x() + screen.width() - c->width(), c->y()); - CHECK - } - if (screen.y() + screen.height() < c->y() + c->height()) { - // overlaps on bottom - c->move(c->x(), screen.y() + screen.height() - c->height()); - CHECK - } - if (screen.y() > c->y()) { - // top is not on screen - c->move(c->x(), screen.y()); - CHECK - } - if (screen.x() > c->x()) { - // left is not on screen - c->move(screen.x(), c->y()); - CHECK + const QPoint popupPos = c->transientPlacement(screen).topLeft(); + c->move(popupPos); + + // potentially a client could set no constraint adjustments + if (!screen.contains(c->geometry())) { + c->keepInArea(screen); } -#undef CHECK - // so far the sanitizing didn't help, let's move back to orig target position and use keepInArea - c->move(target); - c->keepInArea(screen); } void Placement::placeDialog(AbstractClient* c, QRect& area, Policy nextPlacement) diff --git a/shell_client.h b/shell_client.h --- a/shell_client.h +++ b/shell_client.h @@ -147,7 +147,7 @@ bool isTransient() const override; bool hasTransientPlacementHint() const override; - QPoint transientPlacementHint() const override; + QRect transientPlacement(const QRect &bounds) const override; QMatrix4x4 inputTransformation() const override; @@ -210,6 +210,7 @@ void updateMaximizeMode(MaximizeMode maximizeMode); // called on surface commit and processes all m_pendingConfigureRequests up to m_lastAckedConfigureReqest void updatePendingGeometry(); + QPoint popupOffset(const QRect anchorRect, const Qt::Edges anchorEdge, const Qt::Edges gravity) const; static void deleteClient(ShellClient *c); KWayland::Server::ShellSurfaceInterface *m_shellSurface; diff --git a/shell_client.cpp b/shell_client.cpp --- a/shell_client.cpp +++ b/shell_client.cpp @@ -54,6 +54,7 @@ #include #include #include +#include #include #include @@ -1575,18 +1576,179 @@ bool ShellClient::hasTransientPlacementHint() const { return isTransient() && transientFor() != nullptr && - (m_shellSurface || m_xdgShellPopup); + (m_shellSurface || m_xdgShellPopup); } -QPoint ShellClient::transientPlacementHint() const +QRect ShellClient::transientPlacement(const QRect &bounds) const { + QRect anchorRect; + Qt::Edges anchorEdge; + Qt::Edges gravity; + QPoint offset; + PositionerConstraints constraintAdjustments; + + const QPoint parentClientPos = transientFor()->pos() + transientFor()->clientPos(); + QRect popupPosition; + + // returns if a target is within the supplied bounds, optional edges argument states which side to check + auto inBounds = [bounds](const QRect &target, Qt::Edges edges = Qt::LeftEdge | Qt::RightEdge | Qt::TopEdge | Qt::BottomEdge) -> bool { + if (edges & Qt::LeftEdge && target.left() < bounds.left()) { + return false; + } + if (edges & Qt::TopEdge && target.top() < bounds.top()) { + return false; + } + if (edges & Qt::RightEdge && target.right() > bounds.right()) { + //normal QRect::right issue cancels out + return false; + } + if (edges & Qt::BottomEdge && target.bottom() > bounds.bottom()) { + return false; + } + return true; + }; + if (m_shellSurface) { - return m_shellSurface->transientOffset(); + anchorRect = QRect(m_shellSurface->transientOffset(), QSize(1,1)); + anchorEdge = Qt::TopEdge | Qt::LeftEdge; + gravity = Qt::BottomEdge | Qt::RightEdge; //our single point represents the top left of the popup + constraintAdjustments = (PositionerConstraint::SlideX | PositionerConstraint::SlideY); + } else if (m_xdgShellPopup) { + anchorRect = m_xdgShellPopup->anchorRect(); + anchorEdge = m_xdgShellPopup->anchorEdge(); + gravity = m_xdgShellPopup->gravity(); + offset= m_xdgShellPopup->anchorOffset(); + constraintAdjustments = m_xdgShellPopup->constraintAdjustments(); + } else { + Q_ASSERT(false); } - if (m_xdgShellPopup) { - return m_xdgShellPopup->transientOffset(); + + //initial position + popupPosition = QRect(popupOffset(anchorRect, anchorEdge, gravity) + offset + parentClientPos, geometry().size()); + + //if that fits, we don't need to do anything + if (inBounds(popupPosition)) { + return popupPosition; + } + //otherwise apply constraint adjustment per axis in order XDG Shell Popup states + + if (constraintAdjustments & PositionerConstraint::SlideX) { + if (!inBounds(popupPosition, Qt::LeftEdge)) { + popupPosition.setX(bounds.x()); + } + if (!inBounds(popupPosition, Qt::RightEdge)) { + popupPosition.setX(bounds.x() + bounds.width() - geometry().width()); + } + } + if (constraintAdjustments & PositionerConstraint::FlipX) { + if (!inBounds(popupPosition, Qt::LeftEdge | Qt::RightEdge)) { + //flip both edges (if either bit is set, XOR both) + auto flippedAnchorEdge = anchorEdge; + if (flippedAnchorEdge & (Qt::LeftEdge | Qt::RightEdge)) { + flippedAnchorEdge ^= (Qt::LeftEdge | Qt::RightEdge); + } + auto flippedGravity = gravity; + if (flippedGravity & (Qt::LeftEdge | Qt::RightEdge)) { + flippedGravity ^= (Qt::LeftEdge | Qt::RightEdge); + } + auto flippedPopupPosition = QRect(popupOffset(anchorRect, flippedAnchorEdge, flippedGravity) + offset + parentClientPos, geometry().size()); + + //if it still doesn't fit we should resize the unflipped version + if (inBounds(flippedPopupPosition, Qt::LeftEdge | Qt::RightEdge)) { + popupPosition.setX(flippedPopupPosition.x()); + } + } + } + if (constraintAdjustments & PositionerConstraint::ResizeX) { + //TODO + //but we need to sort out when this is run as resize should only happen before first configure + } + + if (constraintAdjustments & PositionerConstraint::SlideY) { + if (!inBounds(popupPosition, Qt::TopEdge)) { + popupPosition.setY(bounds.y()); + } + if (!inBounds(popupPosition, Qt::BottomEdge)) { + popupPosition.setY(bounds.y() + bounds.height() - geometry().height()); + } + } + if (constraintAdjustments & PositionerConstraint::FlipY) { + if (!inBounds(popupPosition, Qt::TopEdge | Qt::BottomEdge)) { + //flip both edges (if either bit is set, XOR both) + auto flippedAnchorEdge = anchorEdge; + if (flippedAnchorEdge & (Qt::TopEdge | Qt::BottomEdge)) { + flippedAnchorEdge ^= (Qt::TopEdge | Qt::BottomEdge); + } + auto flippedGravity = gravity; + if (flippedGravity & (Qt::TopEdge | Qt::BottomEdge)) { + flippedGravity ^= (Qt::TopEdge | Qt::BottomEdge); + } + auto flippedPopupPosition = QRect(popupOffset(anchorRect, flippedAnchorEdge, flippedGravity) + offset + parentClientPos, geometry().size()); + + //if it still doesn't fit we should resize the unflipped version + if (inBounds(flippedPopupPosition, Qt::TopEdge | Qt::BottomEdge)) { + popupPosition.setY(flippedPopupPosition.y()); + } + } } - return QPoint(); + if (constraintAdjustments & PositionerConstraint::ResizeY) { + //TODO + } + + return popupPosition; +} + +QPoint ShellClient::popupOffset(const QRect anchorRect, const Qt::Edges anchorEdge, const Qt::Edges gravity) const +{ + const QSize popupSize = geometry().size(); + QPoint anchorPoint; + switch(anchorEdge & (Qt::LeftEdge | Qt::RightEdge)) { + case Qt::LeftEdge: + anchorPoint.setX(anchorRect.x()); + break; + case Qt::RightEdge: + anchorPoint.setX(anchorRect.x() + anchorRect.width()); + break; + default: + anchorPoint.setX(qRound(anchorRect.x() + anchorRect.width() / 2.0)); + } + switch(anchorEdge & (Qt::TopEdge | Qt::BottomEdge)) { + case Qt::TopEdge: + anchorPoint.setY(anchorRect.y()); + break; + case Qt::BottomEdge: + anchorPoint.setY(anchorRect.y() + anchorRect.height()); + break; + default: + anchorPoint.setY(qRound(anchorRect.y() + anchorRect.height() / 2.0)); + } + + // calculate where the top left point of the popup will end up with the applied gravity + // gravity indicates direction. i.e if gravitating towards the top the popup's bottom edge + // will next to the anchor point + QPoint popupPosAdjust; + switch(gravity & (Qt::LeftEdge | Qt::RightEdge)) { + case Qt::LeftEdge: + popupPosAdjust.setX(-popupSize.width()); + break; + case Qt::RightEdge: + popupPosAdjust.setX(0); + break; + default: + popupPosAdjust.setX(qRound(-popupSize.width() / 2.0)); + } + switch(gravity & (Qt::TopEdge | Qt::BottomEdge)) { + case Qt::TopEdge: + popupPosAdjust.setY(-popupSize.height()); + break; + case Qt::BottomEdge: + popupPosAdjust.setY(0); + break; + default: + popupPosAdjust.setY(qRound(-popupSize.height() / 2.0)); + } + + return anchorPoint + popupPosAdjust; } bool ShellClient::isWaitingForMoveResizeSync() const