diff --git a/autotests/decorationbuttontest.cpp b/autotests/decorationbuttontest.cpp index bafccbe..c15a2cf 100644 --- a/autotests/decorationbuttontest.cpp +++ b/autotests/decorationbuttontest.cpp @@ -1,1290 +1,1346 @@ /* * Copyright 2014 Martin Gräßlin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include #include #include #include "../src/decoratedclient.h" #include "../src/decorationsettings.h" #include "mockdecoration.h" #include "mockbridge.h" #include "mockbutton.h" #include "mockclient.h" #include "mocksettings.h" Q_DECLARE_METATYPE(Qt::MouseButton) class DecorationButtonTest : public QObject { Q_OBJECT private Q_SLOTS: void testButton(); void testChecked(); void testEnabled(); void testPressIgnore_data(); void testPressIgnore(); void testReleaseIgnore_data(); void testReleaseIgnore(); void testHoverEnterIgnore_data(); void testHoverEnterIgnore(); void testHoverLeaveIgnore_data(); void testHoverLeaveIgnore(); void testHover(); void testMouseMove_data(); void testMouseMove(); void testClose(); void testMinimize(); void testQuickHelp(); void testKeepAbove(); void testKeepBelow(); void testShade(); void testMaximize(); void testOnAllDesktops(); void testMenu(); void testMenuDoubleClick(); void testMenuPressAndHold(); + void testApplicationMenu(); }; void DecorationButtonTest::testButton() { MockBridge bridge; MockDecoration mockDecoration(&bridge); // create a custom button and verify the base settings MockButton button(KDecoration2::DecorationButtonType::Custom, &mockDecoration); QCOMPARE(button.decoration().data(), &mockDecoration); const MockButton &constRef(button); QCOMPARE(constRef.decoration().data(), &mockDecoration); QCOMPARE(button.type(), KDecoration2::DecorationButtonType::Custom); QCOMPARE(button.acceptedButtons(), Qt::MouseButtons(Qt::LeftButton)); QCOMPARE(button.isCheckable(), false); QCOMPARE(button.isChecked(), false); QCOMPARE(button.isEnabled(), true); QCOMPARE(button.isHovered(), false); QCOMPARE(button.isPressed(), false); QCOMPARE(button.isVisible(), true); QCOMPARE(button.size(), QSizeF(0, 0)); QCOMPARE(button.geometry(), QRectF()); // test setting the geometry QSignalSpy geometryChangedSpy(&button, SIGNAL(geometryChanged(QRectF))); QVERIFY(geometryChangedSpy.isValid()); // setting to default geometry shouldn't change button.setGeometry(QRectF()); QCOMPARE(button.geometry(), QRectF()); QCOMPARE(geometryChangedSpy.count(), 0); // setting to a proper geometry should change const QRectF testGeometry = QRectF(0, 0, 10, 20); button.setGeometry(testGeometry); QCOMPARE(button.geometry(), testGeometry); QCOMPARE(button.size(), testGeometry.size()); QCOMPARE(geometryChangedSpy.count(), 1); QCOMPARE(geometryChangedSpy.first().first().toRectF(), testGeometry); // test changing visibility QSignalSpy visibilityChangedSpy(&button, SIGNAL(visibilityChanged(bool))); QVERIFY(visibilityChangedSpy.isValid()); button.setVisible(true); QCOMPARE(visibilityChangedSpy.count(), 0); button.setVisible(false); QCOMPARE(button.isVisible(), false); QCOMPARE(visibilityChangedSpy.count(), 1); QCOMPARE(visibilityChangedSpy.first().first().toBool(), false); } void DecorationButtonTest::testChecked() { MockBridge bridge; MockDecoration mockDecoration(&bridge); // create a custom button and verify the base settings MockButton button(KDecoration2::DecorationButtonType::Custom, &mockDecoration); button.setGeometry(QRect(0, 0, 10, 10)); // without being checkable it should not get checked QSignalSpy checkedChangedSpy(&button, SIGNAL(checkedChanged(bool))); QVERIFY(checkedChangedSpy.isValid()); button.setChecked(true); QCOMPARE(button.isChecked(), false); QCOMPARE(checkedChangedSpy.count(), 0); // now let's set the checkable state QSignalSpy checkableChangedSpy(&button, SIGNAL(checkableChanged(bool))); QVERIFY(checkableChangedSpy.isValid()); // setting to same should not emit button.setCheckable(false); QCOMPARE(checkableChangedSpy.count(), 0); button.setCheckable(true); QCOMPARE(button.isCheckable(), true); QCOMPARE(checkableChangedSpy.count(), 1); QCOMPARE(checkableChangedSpy.first().first().toBool(), true); // now it should be possible to check the button button.setChecked(true); QCOMPARE(button.isChecked(), true); QCOMPARE(checkedChangedSpy.count(), 1); // setting again should not change button.setChecked(true); QCOMPARE(button.isChecked(), true); QCOMPARE(checkedChangedSpy.count(), 1); // and disable button.setChecked(false); QCOMPARE(button.isChecked(), false); QCOMPARE(checkedChangedSpy.count(), 2); QCOMPARE(checkedChangedSpy.first().first().toBool(), true); QCOMPARE(checkedChangedSpy.last().first().toBool(), false); // last but not least let's disable the checkable again, this should disable a checked button button.setChecked(true); QCOMPARE(button.isChecked(), true); checkedChangedSpy.clear(); QCOMPARE(checkedChangedSpy.count(), 0); button.setCheckable(false); QCOMPARE(button.isCheckable(), false); QCOMPARE(checkableChangedSpy.count(), 2); QCOMPARE(checkableChangedSpy.last().first().toBool(), false); QCOMPARE(button.isChecked(), false); QCOMPARE(checkedChangedSpy.count(), 1); } void DecorationButtonTest::testEnabled() { MockBridge bridge; MockDecoration mockDecoration(&bridge); // create a custom button and verify the base settings MockButton button(KDecoration2::DecorationButtonType::Custom, &mockDecoration); button.setGeometry(QRect(0, 0, 10, 10)); // enabling has influence on whether the button accepts events, so we need to fake events QSignalSpy enabledChangedSpy(&button, SIGNAL(enabledChanged(bool))); QVERIFY(enabledChangedSpy.isValid()); // setting to same shouldn't change button.setEnabled(true); QCOMPARE(enabledChangedSpy.count(), 0); button.setEnabled(false); QCOMPARE(button.isEnabled(), false); QCOMPARE(enabledChangedSpy.count(), 1); QCOMPARE(enabledChangedSpy.first().first().toBool(), false); // now let's send it a hover entered event QSignalSpy hoveredChangedSpy(&button, SIGNAL(hoveredChanged(bool))); QVERIFY(hoveredChangedSpy.isValid()); QHoverEvent event(QEvent::HoverEnter, QPointF(1, 1), QPointF(-1, -1)); event.setAccepted(false); button.event(&event); QCOMPARE(event.isAccepted(), false); QCOMPARE(hoveredChangedSpy.count(), 0); // if we enable the button again we should get a hovered changed signal button.setEnabled(true); QCOMPARE(enabledChangedSpy.count(), 2); QCOMPARE(enabledChangedSpy.last().first().toBool(), true); button.event(&event); QCOMPARE(event.isAccepted(), true); QCOMPARE(hoveredChangedSpy.count(), 1); QCOMPARE(hoveredChangedSpy.first().first().toBool(), true); // if we disable the button now we get a hovered disabled signal button.setEnabled(false); QCOMPARE(hoveredChangedSpy.count(), 2); QCOMPARE(hoveredChangedSpy.last().first().toBool(), false); } void DecorationButtonTest::testPressIgnore_data() { QTest::addColumn("enabled"); QTest::addColumn("visible"); QTest::addColumn("clickPos"); QTest::addColumn("mouseButton"); QTest::addColumn("expectedAccepted"); QTest::newRow("all-disabled") << false << false << QPoint(0, 0) << Qt::LeftButton << false; QTest::newRow("enabled") << true << false << QPoint(0, 0) << Qt::LeftButton << false; QTest::newRow("visible") << false << true << QPoint(0, 0) << Qt::LeftButton << false; QTest::newRow("outside") << true << true << QPoint(20, 20) << Qt::LeftButton << false; QTest::newRow("wrong-button") << true << true << QPoint(0, 0) << Qt::RightButton << false; } void DecorationButtonTest::testPressIgnore() { MockBridge bridge; MockDecoration mockDecoration(&bridge); // create a custom button and verify the base settings MockButton button(KDecoration2::DecorationButtonType::Custom, &mockDecoration); button.setGeometry(QRect(0, 0, 10, 10)); button.setAcceptedButtons(Qt::LeftButton); QSignalSpy pressedSpy(&button, SIGNAL(pressed())); QVERIFY(pressedSpy.isValid()); QSignalSpy pressedChangedSpy(&button, SIGNAL(pressedChanged(bool))); QVERIFY(pressedChangedSpy.isValid()); QFETCH(bool, enabled); QFETCH(bool, visible); button.setEnabled(enabled); button.setVisible(visible); QFETCH(QPoint, clickPos); QFETCH(Qt::MouseButton, mouseButton); QMouseEvent pressEvent(QEvent::MouseButtonPress, clickPos, mouseButton, mouseButton, Qt::NoModifier); pressEvent.setAccepted(false); button.event(&pressEvent); QTEST(pressEvent.isAccepted(), "expectedAccepted"); QCOMPARE(button.isPressed(), false); QVERIFY(pressedSpy.isEmpty()); QVERIFY(pressedChangedSpy.isEmpty()); } void DecorationButtonTest::testReleaseIgnore_data() { QTest::addColumn("enabled"); QTest::addColumn("visible"); QTest::addColumn("clickPos"); QTest::addColumn("mouseButton"); QTest::addColumn("expectedAccepted"); QTest::addColumn("expectedPressed"); QTest::addColumn("expectedPressedChanged"); QTest::newRow("all-disabled") << false << false << QPoint(0, 0) << Qt::LeftButton << false << false << 2; QTest::newRow("enabled") << true << false << QPoint(0, 0) << Qt::LeftButton << false << false << 2; QTest::newRow("visible") << false << true << QPoint(0, 0) << Qt::LeftButton << false << false << 2; QTest::newRow("outside") << true << true << QPoint(20, 20) << Qt::LeftButton << true << false << 2; QTest::newRow("wrong-button") << true << true << QPoint(0, 0) << Qt::RightButton << false << true << 1; } void DecorationButtonTest::testReleaseIgnore() { MockBridge bridge; MockDecoration mockDecoration(&bridge); // create a custom button and verify the base settings MockButton button(KDecoration2::DecorationButtonType::Custom, &mockDecoration); button.setGeometry(QRect(0, 0, 10, 10)); button.setAcceptedButtons(Qt::LeftButton); button.setEnabled(true); button.setVisible(true); QSignalSpy pressedSpy(&button, SIGNAL(pressed())); QVERIFY(pressedSpy.isValid()); QSignalSpy pressedChangedSpy(&button, SIGNAL(pressedChanged(bool))); QVERIFY(pressedChangedSpy.isValid()); QSignalSpy clickedSpy(&button, SIGNAL(clicked(Qt::MouseButton))); QVERIFY(clickedSpy.isValid()); QMouseEvent pressEvent(QEvent::MouseButtonPress, QPoint(0, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); pressEvent.setAccepted(false); button.event(&pressEvent); QCOMPARE(pressEvent.isAccepted(), true); QCOMPARE(button.isPressed(), true); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(pressedChangedSpy.count(), 1); QCOMPARE(pressedChangedSpy.last().first().toBool(), true); QFETCH(bool, enabled); QFETCH(bool, visible); button.setEnabled(enabled); button.setVisible(visible); QFETCH(QPoint, clickPos); QFETCH(Qt::MouseButton, mouseButton); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, clickPos, mouseButton, Qt::NoButton, Qt::NoModifier); releaseEvent.setAccepted(false); button.event(&releaseEvent); QTEST(releaseEvent.isAccepted(), "expectedAccepted"); QTEST(button.isPressed(), "expectedPressed"); QCOMPARE(pressedSpy.count(), 1); QTEST(pressedChangedSpy.count(), "expectedPressedChanged"); QCOMPARE(pressedChangedSpy.last().first().toBool(), button.isPressed()); QCOMPARE(clickedSpy.count(), 0); } void DecorationButtonTest::testHoverEnterIgnore_data() { QTest::addColumn("enabled"); QTest::addColumn("visible"); QTest::addColumn("enterPos"); QTest::newRow("all-disabled") << false << false << QPoint(0, 0); QTest::newRow("enabled") << true << false << QPoint(0, 0); QTest::newRow("visible") << false << true << QPoint(0, 0); QTest::newRow("outside") << true << true << QPoint(20, 20); } void DecorationButtonTest::testHoverEnterIgnore() { MockBridge bridge; MockDecoration mockDecoration(&bridge); // create a custom button and verify the base settings MockButton button(KDecoration2::DecorationButtonType::Custom, &mockDecoration); button.setGeometry(QRect(0, 0, 10, 10)); QSignalSpy pointerEnteredSpy(&button, SIGNAL(pointerEntered())); QVERIFY(pointerEnteredSpy.isValid()); QSignalSpy hoveredChangedSpy(&button, SIGNAL(hoveredChanged(bool))); QVERIFY(hoveredChangedSpy.isValid()); QFETCH(bool, enabled); QFETCH(bool, visible); button.setEnabled(enabled); button.setVisible(visible); QFETCH(QPoint, enterPos); QHoverEvent enterEvent(QEvent::HoverEnter, enterPos, QPoint()); enterEvent.setAccepted(false); button.event(&enterEvent); QCOMPARE(enterEvent.isAccepted(), false); QCOMPARE(button.isHovered(), false); QCOMPARE(pointerEnteredSpy.count(), 0); QCOMPARE(hoveredChangedSpy.count(), 0); // send a HoverLeft event should not be processed button.setEnabled(true); button.setVisible(true); QHoverEvent leftEvent(QEvent::HoverLeave, QPoint(20, 20), enterPos); leftEvent.setAccepted(false); button.event(&leftEvent); QCOMPARE(leftEvent.isAccepted(), false); } void DecorationButtonTest::testHoverLeaveIgnore_data() { QTest::addColumn("enabled"); QTest::addColumn("visible"); QTest::addColumn("leavePos"); QTest::addColumn("expectedLeaveCount"); QTest::addColumn("expectedHoverChangedCount"); QTest::newRow("all-disabled") << false << false << QPoint(20, 20) << 1 << 2; QTest::newRow("enabled") << true << false << QPoint(20, 20) << 1 << 2; QTest::newRow("visible") << false << true << QPoint(20, 20) << 1 << 2; QTest::newRow("inside") << true << true << QPoint(5, 5) << 0 << 1; } void DecorationButtonTest::testHoverLeaveIgnore() { MockBridge bridge; MockDecoration mockDecoration(&bridge); // create a custom button and verify the base settings MockButton button(KDecoration2::DecorationButtonType::Custom, &mockDecoration); button.setGeometry(QRect(0, 0, 10, 10)); button.setEnabled(true); button.setVisible(true); QSignalSpy pointerEnteredSpy(&button, SIGNAL(pointerEntered())); QVERIFY(pointerEnteredSpy.isValid()); QSignalSpy hoveredChangedSpy(&button, SIGNAL(hoveredChanged(bool))); QVERIFY(hoveredChangedSpy.isValid()); QSignalSpy pointerLeavedSpy(&button, SIGNAL(pointerLeft())); QVERIFY(pointerLeavedSpy.isValid()); QHoverEvent enterEvent(QEvent::HoverEnter, QPoint(0, 0), QPoint()); enterEvent.setAccepted(false); button.event(&enterEvent); QCOMPARE(enterEvent.isAccepted(), true); QCOMPARE(button.isHovered(), true); QCOMPARE(pointerEnteredSpy.count(), 1); QCOMPARE(hoveredChangedSpy.count(), 1); QCOMPARE(hoveredChangedSpy.last().first().toBool(), true); QFETCH(bool, enabled); QFETCH(bool, visible); button.setEnabled(enabled); button.setVisible(visible); QFETCH(QPoint, leavePos); QHoverEvent leftEvent(QEvent::HoverLeave, leavePos, QPoint(0, 0)); leftEvent.setAccepted(false); button.event(&leftEvent); QCOMPARE(leftEvent.isAccepted(), false); QCOMPARE(pointerEnteredSpy.count(), 1); QTEST(pointerLeavedSpy.count(), "expectedLeaveCount"); QTEST(hoveredChangedSpy.count(), "expectedHoverChangedCount"); QCOMPARE(hoveredChangedSpy.last().first().toBool(), button.isHovered()); } void DecorationButtonTest::testHover() { MockBridge bridge; MockDecoration mockDecoration(&bridge); // create a custom button and verify the base settings MockButton button(KDecoration2::DecorationButtonType::Custom, &mockDecoration); button.setGeometry(QRectF(0, 0, 10, 10)); button.setEnabled(true); button.setVisible(true); QSignalSpy pointerEnteredSpy(&button, SIGNAL(pointerEntered())); QVERIFY(pointerEnteredSpy.isValid()); QSignalSpy hoveredChangedSpy(&button, SIGNAL(hoveredChanged(bool))); QVERIFY(hoveredChangedSpy.isValid()); QSignalSpy pointerLeavedSpy(&button, SIGNAL(pointerLeft())); QVERIFY(pointerLeavedSpy.isValid()); QHoverEvent enterEvent(QEvent::HoverEnter, QPoint(0, 0), QPoint()); enterEvent.setAccepted(false); button.event(&enterEvent); QCOMPARE(enterEvent.isAccepted(), true); QCOMPARE(button.isHovered(), true); QCOMPARE(pointerEnteredSpy.count(), 1); QCOMPARE(hoveredChangedSpy.count(), 1); QCOMPARE(hoveredChangedSpy.last().first().toBool(), true); // send in a hovermove event - it's passed through, but not used QHoverEvent moveEvent(QEvent::HoverMove, QPoint(5, 0), QPoint(0, 0)); moveEvent.setAccepted(false); button.event(&moveEvent); QCOMPARE(moveEvent.isAccepted(), false); QHoverEvent leftEvent(QEvent::HoverLeave, QPointF(10.1, 0.0), QPointF(0, 0)); leftEvent.setAccepted(false); button.event(&leftEvent); QCOMPARE(leftEvent.isAccepted(), true); QCOMPARE(pointerEnteredSpy.count(), 1); QCOMPARE(pointerLeavedSpy.count(), 1); QCOMPARE(hoveredChangedSpy.count(), 2); QCOMPARE(hoveredChangedSpy.last().first().toBool(), false); } void DecorationButtonTest::testMouseMove_data() { QTest::addColumn("enabled"); QTest::addColumn("visible"); QTest::addColumn("movePos"); QTest::addColumn("expectedAccepted"); QTest::addColumn("expectedHovered"); QTest::addColumn("expectedChangedCount"); QTest::newRow("outside") << true << true << QPointF(10.1, 10) << true << false << 2; QTest::newRow("inside") << true << true << QPointF(5, 5) << false << true << 1; QTest::newRow("disabled") << false << true << QPointF(10, 10) << false << false << 2; QTest::newRow("invisible") << true << false << QPointF(10, 10) << false << false << 2; } void DecorationButtonTest::testMouseMove() { MockBridge bridge; MockDecoration mockDecoration(&bridge); // create a custom button and verify the base settings MockButton button(KDecoration2::DecorationButtonType::Custom, &mockDecoration); button.setGeometry(QRectF(0, 0, 10, 10)); button.setEnabled(true); button.setVisible(true); QSignalSpy hoveredChangedSpy(&button, SIGNAL(hoveredChanged(bool))); QVERIFY(hoveredChangedSpy.isValid()); QSignalSpy pointerLeavedSpy(&button, SIGNAL(pointerLeft())); QVERIFY(pointerLeavedSpy.isValid()); QHoverEvent enterEvent(QEvent::HoverEnter, QPoint(0, 0), QPoint()); enterEvent.setAccepted(false); button.event(&enterEvent); QCOMPARE(enterEvent.isAccepted(), true); QCOMPARE(button.isHovered(), true); QCOMPARE(hoveredChangedSpy.count(), 1); QCOMPARE(hoveredChangedSpy.last().first().toBool(), true); QFETCH(bool, enabled); button.setEnabled(enabled); QFETCH(bool, visible); button.setVisible(visible); QFETCH(QPointF, movePos); QMouseEvent mouseMoveEvent(QEvent::MouseMove, movePos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); mouseMoveEvent.setAccepted(false); button.event(&mouseMoveEvent); QTEST(mouseMoveEvent.isAccepted(), "expectedAccepted"); QTEST(button.isHovered(), "expectedHovered"); QTEST(hoveredChangedSpy.count(), "expectedChangedCount"); QCOMPARE(hoveredChangedSpy.last().first().toBool(), button.isHovered()); // explicit further move event outside of button QMouseEvent mouseMoveEvent2(QEvent::MouseMove, QPoint(50, 50), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); mouseMoveEvent2.setAccepted(false); button.event(&mouseMoveEvent2); QTEST(mouseMoveEvent2.isAccepted(), "expectedHovered"); QCOMPARE(button.isHovered(), false); } void DecorationButtonTest::testClose() { MockBridge bridge; MockDecoration mockDecoration(&bridge); MockClient *client = bridge.lastCreatedClient(); MockButton button(KDecoration2::DecorationButtonType::Close, &mockDecoration); button.setGeometry(QRect(0, 0, 10, 10)); QCOMPARE(button.isEnabled(), false); QCOMPARE(button.isCheckable(), false); QCOMPARE(button.isChecked(), false); QCOMPARE(button.isVisible(), true); QCOMPARE(button.acceptedButtons(), Qt::LeftButton); // if the client is closeable the button should get enabled QSignalSpy closeableChangedSpy(mockDecoration.client().data(), SIGNAL(closeableChanged(bool))); QVERIFY(closeableChangedSpy.isValid()); client->setCloseable(true); QCOMPARE(button.isEnabled(), true); QCOMPARE(closeableChangedSpy.count(), 1); QCOMPARE(closeableChangedSpy.first().first().toBool(), true); // clicking the button should trigger a request for close QSignalSpy clickedSpy(&button, SIGNAL(clicked(Qt::MouseButton))); QVERIFY(clickedSpy.isValid()); QSignalSpy pressedSpy(&button, SIGNAL(pressed())); QVERIFY(pressedSpy.isValid()); QSignalSpy releasedSpy(&button, SIGNAL(released())); QVERIFY(releasedSpy.isValid()); QSignalSpy closeRequestedSpy(client, SIGNAL(closeRequested())); QVERIFY(closeRequestedSpy.isValid()); QSignalSpy pressedChangedSpy(&button, SIGNAL(pressedChanged(bool))); QVERIFY(pressedChangedSpy.isValid()); QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(5, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); pressEvent.setAccepted(false); button.event(&pressEvent); QCOMPARE(pressEvent.isAccepted(), true); QCOMPARE(button.isPressed(), true); QCOMPARE(clickedSpy.count(), 0); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(releasedSpy.count(), 0); QCOMPARE(closeRequestedSpy.count(), 0); QCOMPARE(pressedChangedSpy.count(), 1); QCOMPARE(pressedChangedSpy.first().first().toBool(), true); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPointF(5, 5), Qt::LeftButton, Qt::NoButton, Qt::NoModifier); releaseEvent.setAccepted(false); button.event(&releaseEvent); QCOMPARE(releaseEvent.isAccepted(), true); QCOMPARE(button.isPressed(), false); QCOMPARE(clickedSpy.count(), 1); QCOMPARE(clickedSpy.first().first().value(), Qt::LeftButton); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(releasedSpy.count(), 1); QVERIFY(closeRequestedSpy.wait()); QCOMPARE(closeRequestedSpy.count(), 1); QCOMPARE(pressedChangedSpy.count(), 2); QCOMPARE(pressedChangedSpy.last().first().toBool(), false); } void DecorationButtonTest::testMinimize() { MockBridge bridge; MockDecoration mockDecoration(&bridge); MockClient *client = bridge.lastCreatedClient(); MockButton button(KDecoration2::DecorationButtonType::Minimize, &mockDecoration); button.setGeometry(QRect(0, 0, 10, 10)); QCOMPARE(button.isEnabled(), false); QCOMPARE(button.isCheckable(), false); QCOMPARE(button.isChecked(), false); QCOMPARE(button.isVisible(), true); QCOMPARE(button.acceptedButtons(), Qt::LeftButton); // if the client is minimizeable the button should get enabled QSignalSpy minimizableChangedSpy(mockDecoration.client().data(), SIGNAL(minimizeableChanged(bool))); QVERIFY(minimizableChangedSpy.isValid()); client->setMinimizable(true); QCOMPARE(button.isEnabled(), true); QCOMPARE(minimizableChangedSpy.count(), 1); QCOMPARE(minimizableChangedSpy.first().first().toBool(), true); // clicking the button should trigger a request for minimize QSignalSpy clickedSpy(&button, SIGNAL(clicked(Qt::MouseButton))); QVERIFY(clickedSpy.isValid()); QSignalSpy pressedSpy(&button, SIGNAL(pressed())); QVERIFY(pressedSpy.isValid()); QSignalSpy releasedSpy(&button, SIGNAL(released())); QVERIFY(releasedSpy.isValid()); QSignalSpy minimizeRequestedSpy(client, SIGNAL(minimizeRequested())); QVERIFY(minimizeRequestedSpy.isValid()); QSignalSpy pressedChangedSpy(&button, SIGNAL(pressedChanged(bool))); QVERIFY(pressedChangedSpy.isValid()); QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(5, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); pressEvent.setAccepted(false); button.event(&pressEvent); QCOMPARE(pressEvent.isAccepted(), true); QCOMPARE(button.isPressed(), true); QCOMPARE(clickedSpy.count(), 0); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(releasedSpy.count(), 0); QCOMPARE(minimizeRequestedSpy.count(), 0); QCOMPARE(pressedChangedSpy.count(), 1); QCOMPARE(pressedChangedSpy.first().first().toBool(), true); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPointF(5, 5), Qt::LeftButton, Qt::NoButton, Qt::NoModifier); releaseEvent.setAccepted(false); button.event(&releaseEvent); QCOMPARE(releaseEvent.isAccepted(), true); QCOMPARE(button.isPressed(), false); QCOMPARE(clickedSpy.count(), 1); QCOMPARE(clickedSpy.first().first().value(), Qt::LeftButton); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(releasedSpy.count(), 1); QVERIFY(minimizeRequestedSpy.wait()); QCOMPARE(minimizeRequestedSpy.count(), 1); QCOMPARE(pressedChangedSpy.count(), 2); QCOMPARE(pressedChangedSpy.last().first().toBool(), false); } void DecorationButtonTest::testQuickHelp() { MockBridge bridge; MockDecoration mockDecoration(&bridge); MockClient *client = bridge.lastCreatedClient(); MockButton button(KDecoration2::DecorationButtonType::ContextHelp, &mockDecoration); button.setGeometry(QRect(0, 0, 10, 10)); QCOMPARE(button.isEnabled(), true); QCOMPARE(button.isCheckable(), false); QCOMPARE(button.isChecked(), false); QCOMPARE(button.isVisible(), false); QCOMPARE(button.acceptedButtons(), Qt::LeftButton); // if the client provides quickhelp the button should get enabled QSignalSpy providesContextHelpChangedSpy(mockDecoration.client().data(), SIGNAL(providesContextHelpChanged(bool))); QVERIFY(providesContextHelpChangedSpy.isValid()); client->setProvidesContextHelp(true); QCOMPARE(button.isVisible(), true); QCOMPARE(providesContextHelpChangedSpy.count(), 1); QCOMPARE(providesContextHelpChangedSpy.first().first().toBool(), true); // clicking the button should trigger a request for minimize QSignalSpy clickedSpy(&button, SIGNAL(clicked(Qt::MouseButton))); QVERIFY(clickedSpy.isValid()); QSignalSpy pressedSpy(&button, SIGNAL(pressed())); QVERIFY(pressedSpy.isValid()); QSignalSpy releasedSpy(&button, SIGNAL(released())); QVERIFY(releasedSpy.isValid()); QSignalSpy quickhelpRequestedSpy(client, SIGNAL(quickHelpRequested())); QVERIFY(quickhelpRequestedSpy.isValid()); QSignalSpy pressedChangedSpy(&button, SIGNAL(pressedChanged(bool))); QVERIFY(pressedChangedSpy.isValid()); QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(5, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); pressEvent.setAccepted(false); button.event(&pressEvent); QCOMPARE(pressEvent.isAccepted(), true); QCOMPARE(button.isPressed(), true); QCOMPARE(clickedSpy.count(), 0); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(releasedSpy.count(), 0); QCOMPARE(quickhelpRequestedSpy.count(), 0); QCOMPARE(pressedChangedSpy.count(), 1); QCOMPARE(pressedChangedSpy.first().first().toBool(), true); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPointF(5, 5), Qt::LeftButton, Qt::NoButton, Qt::NoModifier); releaseEvent.setAccepted(false); button.event(&releaseEvent); QCOMPARE(releaseEvent.isAccepted(), true); QCOMPARE(button.isPressed(), false); QCOMPARE(clickedSpy.count(), 1); QCOMPARE(clickedSpy.first().first().value(), Qt::LeftButton); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(releasedSpy.count(), 1); QVERIFY(quickhelpRequestedSpy.wait()); QCOMPARE(quickhelpRequestedSpy.count(), 1); QCOMPARE(pressedChangedSpy.count(), 2); QCOMPARE(pressedChangedSpy.last().first().toBool(), false); } void DecorationButtonTest::testKeepAbove() { MockBridge bridge; MockDecoration mockDecoration(&bridge); MockButton button(KDecoration2::DecorationButtonType::KeepAbove, &mockDecoration); button.setGeometry(QRect(0, 0, 10, 10)); QCOMPARE(button.isEnabled(), true); QCOMPARE(button.isCheckable(), true); QCOMPARE(button.isChecked(), false); QCOMPARE(button.isVisible(), true); QCOMPARE(button.acceptedButtons(), Qt::LeftButton); // clicking the button should trigger a request for keep above changed QSignalSpy clickedSpy(&button, SIGNAL(clicked(Qt::MouseButton))); QVERIFY(clickedSpy.isValid()); QSignalSpy pressedSpy(&button, SIGNAL(pressed())); QVERIFY(pressedSpy.isValid()); QSignalSpy releasedSpy(&button, SIGNAL(released())); QVERIFY(releasedSpy.isValid()); QSignalSpy keepAboveChangedSpy(mockDecoration.client().data(), SIGNAL(keepAboveChanged(bool))); QVERIFY(keepAboveChangedSpy.isValid()); QSignalSpy pressedChangedSpy(&button, SIGNAL(pressedChanged(bool))); QVERIFY(pressedChangedSpy.isValid()); QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(5, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); pressEvent.setAccepted(false); button.event(&pressEvent); QCOMPARE(pressEvent.isAccepted(), true); QCOMPARE(button.isPressed(), true); QCOMPARE(clickedSpy.count(), 0); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(releasedSpy.count(), 0); QCOMPARE(keepAboveChangedSpy.count(), 0); QCOMPARE(pressedChangedSpy.count(), 1); QCOMPARE(pressedChangedSpy.first().first().toBool(), true); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPointF(5, 5), Qt::LeftButton, Qt::NoButton, Qt::NoModifier); releaseEvent.setAccepted(false); button.event(&releaseEvent); QCOMPARE(releaseEvent.isAccepted(), true); QCOMPARE(button.isPressed(), false); QCOMPARE(clickedSpy.count(), 1); QCOMPARE(clickedSpy.first().first().value(), Qt::LeftButton); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(releasedSpy.count(), 1); QVERIFY(keepAboveChangedSpy.wait()); QCOMPARE(keepAboveChangedSpy.count(), 1); QCOMPARE(keepAboveChangedSpy.first().first().toBool(), true); QCOMPARE(pressedChangedSpy.count(), 2); QCOMPARE(pressedChangedSpy.last().first().toBool(), false); QCOMPARE(button.isChecked(), true); // click once more should change again button.event(&pressEvent); button.event(&releaseEvent); QVERIFY(keepAboveChangedSpy.wait()); QCOMPARE(keepAboveChangedSpy.count(), 2); QCOMPARE(keepAboveChangedSpy.first().first().toBool(), true); QCOMPARE(keepAboveChangedSpy.last().first().toBool(), false); QCOMPARE(button.isChecked(), false); } void DecorationButtonTest::testKeepBelow() { MockBridge bridge; MockDecoration mockDecoration(&bridge); MockButton button(KDecoration2::DecorationButtonType::KeepBelow, &mockDecoration); button.setGeometry(QRect(0, 0, 10, 10)); QCOMPARE(button.isEnabled(), true); QCOMPARE(button.isCheckable(), true); QCOMPARE(button.isChecked(), false); QCOMPARE(button.isVisible(), true); QCOMPARE(button.acceptedButtons(), Qt::LeftButton); // clicking the button should trigger a request for keep above changed QSignalSpy clickedSpy(&button, SIGNAL(clicked(Qt::MouseButton))); QVERIFY(clickedSpy.isValid()); QSignalSpy pressedSpy(&button, SIGNAL(pressed())); QVERIFY(pressedSpy.isValid()); QSignalSpy releasedSpy(&button, SIGNAL(released())); QVERIFY(releasedSpy.isValid()); QSignalSpy keepBelowChangedSpy(mockDecoration.client().data(), SIGNAL(keepBelowChanged(bool))); QVERIFY(keepBelowChangedSpy.isValid()); QSignalSpy pressedChangedSpy(&button, SIGNAL(pressedChanged(bool))); QVERIFY(pressedChangedSpy.isValid()); QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(5, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); pressEvent.setAccepted(false); button.event(&pressEvent); QCOMPARE(pressEvent.isAccepted(), true); QCOMPARE(button.isPressed(), true); QCOMPARE(clickedSpy.count(), 0); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(releasedSpy.count(), 0); QCOMPARE(keepBelowChangedSpy.count(), 0); QCOMPARE(pressedChangedSpy.count(), 1); QCOMPARE(pressedChangedSpy.first().first().toBool(), true); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPointF(5, 5), Qt::LeftButton, Qt::NoButton, Qt::NoModifier); releaseEvent.setAccepted(false); button.event(&releaseEvent); QCOMPARE(releaseEvent.isAccepted(), true); QCOMPARE(button.isPressed(), false); QCOMPARE(clickedSpy.count(), 1); QCOMPARE(clickedSpy.first().first().value(), Qt::LeftButton); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(releasedSpy.count(), 1); QVERIFY(keepBelowChangedSpy.wait()); QCOMPARE(keepBelowChangedSpy.count(), 1); QCOMPARE(keepBelowChangedSpy.first().first().toBool(), true); QCOMPARE(pressedChangedSpy.count(), 2); QCOMPARE(pressedChangedSpy.last().first().toBool(), false); QCOMPARE(button.isChecked(), true); // click once more should change again button.event(&pressEvent); button.event(&releaseEvent); QVERIFY(keepBelowChangedSpy.wait()); QCOMPARE(keepBelowChangedSpy.count(), 2); QCOMPARE(keepBelowChangedSpy.first().first().toBool(), true); QCOMPARE(keepBelowChangedSpy.last().first().toBool(), false); QCOMPARE(button.isChecked(), false); } void DecorationButtonTest::testShade() { MockBridge bridge; MockDecoration mockDecoration(&bridge); MockClient *client = bridge.lastCreatedClient(); MockButton button(KDecoration2::DecorationButtonType::Shade, &mockDecoration); button.setGeometry(QRect(0, 0, 10, 10)); QCOMPARE(button.isEnabled(), false); QCOMPARE(button.isCheckable(), true); QCOMPARE(button.isChecked(), false); QCOMPARE(button.isVisible(), true); QCOMPARE(button.acceptedButtons(), Qt::LeftButton); // if the client is shadeable the button should get enabled QSignalSpy shadeableChangedSpy(mockDecoration.client().data(), SIGNAL(shadeableChanged(bool))); QVERIFY(shadeableChangedSpy.isValid()); client->setShadeable(true); QCOMPARE(button.isEnabled(), true); QCOMPARE(shadeableChangedSpy.count(), 1); QCOMPARE(shadeableChangedSpy.first().first().toBool(), true); // clicking the button should trigger a request for keep above changed QSignalSpy clickedSpy(&button, SIGNAL(clicked(Qt::MouseButton))); QVERIFY(clickedSpy.isValid()); QSignalSpy pressedSpy(&button, SIGNAL(pressed())); QVERIFY(pressedSpy.isValid()); QSignalSpy releasedSpy(&button, SIGNAL(released())); QVERIFY(releasedSpy.isValid()); QSignalSpy shadedChangedSpy(mockDecoration.client().data(), SIGNAL(shadedChanged(bool))); QVERIFY(shadedChangedSpy.isValid()); QSignalSpy pressedChangedSpy(&button, SIGNAL(pressedChanged(bool))); QVERIFY(pressedChangedSpy.isValid()); QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(5, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); pressEvent.setAccepted(false); button.event(&pressEvent); QCOMPARE(pressEvent.isAccepted(), true); QCOMPARE(button.isPressed(), true); QCOMPARE(clickedSpy.count(), 0); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(releasedSpy.count(), 0); QCOMPARE(shadedChangedSpy.count(), 0); QCOMPARE(pressedChangedSpy.count(), 1); QCOMPARE(pressedChangedSpy.first().first().toBool(), true); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPointF(5, 5), Qt::LeftButton, Qt::NoButton, Qt::NoModifier); releaseEvent.setAccepted(false); button.event(&releaseEvent); QCOMPARE(releaseEvent.isAccepted(), true); QCOMPARE(button.isPressed(), false); QCOMPARE(clickedSpy.count(), 1); QCOMPARE(clickedSpy.first().first().value(), Qt::LeftButton); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(releasedSpy.count(), 1); QVERIFY(shadedChangedSpy.wait()); QCOMPARE(shadedChangedSpy.count(), 1); QCOMPARE(shadedChangedSpy.first().first().toBool(), true); QCOMPARE(pressedChangedSpy.count(), 2); QCOMPARE(pressedChangedSpy.last().first().toBool(), false); QCOMPARE(button.isChecked(), true); // click once more should change again button.event(&pressEvent); button.event(&releaseEvent); QVERIFY(shadedChangedSpy.wait()); QCOMPARE(shadedChangedSpy.count(), 2); QCOMPARE(shadedChangedSpy.first().first().toBool(), true); QCOMPARE(shadedChangedSpy.last().first().toBool(), false); QCOMPARE(button.isChecked(), false); } void DecorationButtonTest::testMaximize() { MockBridge bridge; MockDecoration mockDecoration(&bridge); MockClient *client = bridge.lastCreatedClient(); MockButton button(KDecoration2::DecorationButtonType::Maximize, &mockDecoration); button.setGeometry(QRect(0, 0, 10, 10)); QCOMPARE(button.isEnabled(), false); QCOMPARE(button.isCheckable(), true); QCOMPARE(button.isChecked(), false); QCOMPARE(button.isVisible(), true); QCOMPARE(button.acceptedButtons(), Qt::LeftButton | Qt::MiddleButton | Qt::RightButton); // if the client is maximizable the button should get enabled QSignalSpy maximizableChangedSpy(mockDecoration.client().data(), SIGNAL(maximizeableChanged(bool))); QVERIFY(maximizableChangedSpy.isValid()); client->setMaximizable(true); QCOMPARE(button.isEnabled(), true); QCOMPARE(maximizableChangedSpy.count(), 1); QCOMPARE(maximizableChangedSpy.first().first().toBool(), true); // clicking the button should trigger a request for keep above changed QSignalSpy clickedSpy(&button, SIGNAL(clicked(Qt::MouseButton))); QVERIFY(clickedSpy.isValid()); QSignalSpy pressedSpy(&button, SIGNAL(pressed())); QVERIFY(pressedSpy.isValid()); QSignalSpy releasedSpy(&button, SIGNAL(released())); QVERIFY(releasedSpy.isValid()); QSignalSpy maximizedChangedSpy(mockDecoration.client().data(), SIGNAL(maximizedChanged(bool))); QVERIFY(maximizedChangedSpy.isValid()); QSignalSpy maximizedVerticallyChangedSpy(mockDecoration.client().data(), SIGNAL(maximizedVerticallyChanged(bool))); QVERIFY(maximizedVerticallyChangedSpy.isValid()); QSignalSpy maximizedHorizontallyChangedSpy(mockDecoration.client().data(), SIGNAL(maximizedHorizontallyChanged(bool))); QVERIFY(maximizedHorizontallyChangedSpy.isValid()); QSignalSpy pressedChangedSpy(&button, SIGNAL(pressedChanged(bool))); QVERIFY(pressedChangedSpy.isValid()); QMouseEvent leftPressEvent(QEvent::MouseButtonPress, QPointF(5, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); leftPressEvent.setAccepted(false); button.event(&leftPressEvent); QCOMPARE(leftPressEvent.isAccepted(), true); QCOMPARE(button.isPressed(), true); QCOMPARE(clickedSpy.count(), 0); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(releasedSpy.count(), 0); QCOMPARE(maximizedChangedSpy.count(), 0); QCOMPARE(pressedChangedSpy.count(), 1); QCOMPARE(pressedChangedSpy.first().first().toBool(), true); QMouseEvent leftReleaseEvent(QEvent::MouseButtonRelease, QPointF(5, 5), Qt::LeftButton, Qt::NoButton, Qt::NoModifier); leftReleaseEvent.setAccepted(false); button.event(&leftReleaseEvent); QCOMPARE(leftReleaseEvent.isAccepted(), true); QCOMPARE(button.isPressed(), false); QCOMPARE(clickedSpy.count(), 1); QCOMPARE(clickedSpy.first().first().value(), Qt::LeftButton); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(releasedSpy.count(), 1); QVERIFY(maximizedChangedSpy.wait()); QCOMPARE(maximizedChangedSpy.count(), 1); QCOMPARE(maximizedChangedSpy.first().first().toBool(), true); QCOMPARE(pressedChangedSpy.count(), 2); QCOMPARE(pressedChangedSpy.last().first().toBool(), false); QCOMPARE(button.isChecked(), true); // clicking again should set to restored button.event(&leftPressEvent); button.event(&leftReleaseEvent); QVERIFY(maximizedChangedSpy.wait()); QCOMPARE(maximizedChangedSpy.count(), 2); QCOMPARE(maximizedChangedSpy.first().first().toBool(), true); QCOMPARE(maximizedChangedSpy.last().first().toBool(), false); QCOMPARE(button.isChecked(), false); // test the other buttons QMouseEvent rightPressEvent(QEvent::MouseButtonPress, QPointF(5, 5), Qt::RightButton, Qt::RightButton, Qt::NoModifier); rightPressEvent.setAccepted(false); button.event(&rightPressEvent); QCOMPARE(rightPressEvent.isAccepted(), true); QCOMPARE(button.isPressed(), true); QMouseEvent middlePressEvent(QEvent::MouseButtonPress, QPointF(5, 5), Qt::MiddleButton, Qt::MiddleButton, Qt::NoModifier); middlePressEvent.setAccepted(false); button.event(&middlePressEvent); QCOMPARE(middlePressEvent.isAccepted(), true); QCOMPARE(button.isPressed(), true); QMouseEvent middleReleaseEvent(QEvent::MouseButtonRelease, QPointF(5, 5), Qt::MiddleButton, Qt::NoButton, Qt::NoModifier); middleReleaseEvent.setAccepted(false); button.event(&middleReleaseEvent); QCOMPARE(middleReleaseEvent.isAccepted(), true); QVERIFY(maximizedHorizontallyChangedSpy.wait()); QCOMPARE(button.isPressed(), true); QCOMPARE(clickedSpy.count(), 3); QCOMPARE(button.isChecked(), false); QCOMPARE(client->isMaximizedHorizontally(), true); QCOMPARE(client->isMaximizedVertically(), false); QMouseEvent rightReleaseEvent(QEvent::MouseButtonRelease, QPointF(5, 5), Qt::RightButton, Qt::NoButton, Qt::NoModifier); rightReleaseEvent.setAccepted(false); button.event(&rightReleaseEvent); QCOMPARE(rightReleaseEvent.isAccepted(), true); QVERIFY(maximizedVerticallyChangedSpy.wait()); QCOMPARE(button.isPressed(), false); QCOMPARE(clickedSpy.count(), 4); QCOMPARE(client->isMaximizedHorizontally(), true); QCOMPARE(client->isMaximizedVertically(), true); QCOMPARE(button.isChecked(), true); } void DecorationButtonTest::testOnAllDesktops() { MockBridge bridge; auto decoSettings = QSharedPointer::create(&bridge); MockDecoration mockDecoration(&bridge); mockDecoration.setSettings(decoSettings); MockButton button(KDecoration2::DecorationButtonType::OnAllDesktops, &mockDecoration); button.setGeometry(QRect(0, 0, 10, 10)); QCOMPARE(button.isEnabled(), true); QCOMPARE(button.isCheckable(), true); QCOMPARE(button.isChecked(), false); QCOMPARE(button.isVisible(), false); QCOMPARE(button.acceptedButtons(), Qt::LeftButton); QCOMPARE(mockDecoration.client().data()->isOnAllDesktops(), false); MockSettings *settings = bridge.lastCreatedSettings(); QVERIFY(settings); QSignalSpy onAllDesktopsAvailableChangedSpy(decoSettings.data(), SIGNAL(onAllDesktopsAvailableChanged(bool))); QVERIFY(onAllDesktopsAvailableChangedSpy.isValid()); QSignalSpy visibleChangedSpy(&button, SIGNAL(visibilityChanged(bool))); QVERIFY(visibleChangedSpy.isValid()); settings->setOnAllDesktopsAvailabe(true); QCOMPARE(onAllDesktopsAvailableChangedSpy.count(), 1); QCOMPARE(onAllDesktopsAvailableChangedSpy.last().first().toBool(), true); QCOMPARE(visibleChangedSpy.count(), 1); QCOMPARE(visibleChangedSpy.last().first().toBool(), true); // clicking the button should trigger a request for on all desktops QSignalSpy clickedSpy(&button, SIGNAL(clicked(Qt::MouseButton))); QVERIFY(clickedSpy.isValid()); QSignalSpy pressedSpy(&button, SIGNAL(pressed())); QVERIFY(pressedSpy.isValid()); QSignalSpy releasedSpy(&button, SIGNAL(released())); QVERIFY(releasedSpy.isValid()); QSignalSpy onAllDesktopsChangedSpy(mockDecoration.client().data(), SIGNAL(onAllDesktopsChanged(bool))); QVERIFY(onAllDesktopsChangedSpy.isValid()); QSignalSpy pressedChangedSpy(&button, SIGNAL(pressedChanged(bool))); QVERIFY(pressedChangedSpy.isValid()); QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(5, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); pressEvent.setAccepted(false); button.event(&pressEvent); QCOMPARE(pressEvent.isAccepted(), true); QCOMPARE(button.isPressed(), true); QCOMPARE(clickedSpy.count(), 0); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(releasedSpy.count(), 0); QCOMPARE(onAllDesktopsChangedSpy.count(), 0); QCOMPARE(pressedChangedSpy.count(), 1); QCOMPARE(pressedChangedSpy.first().first().toBool(), true); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPointF(5, 5), Qt::LeftButton, Qt::NoButton, Qt::NoModifier); releaseEvent.setAccepted(false); button.event(&releaseEvent); QCOMPARE(releaseEvent.isAccepted(), true); QCOMPARE(button.isPressed(), false); QCOMPARE(clickedSpy.count(), 1); QCOMPARE(clickedSpy.first().first().value(), Qt::LeftButton); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(releasedSpy.count(), 1); QVERIFY(onAllDesktopsChangedSpy.wait()); QCOMPARE(onAllDesktopsChangedSpy.count(), 1); QCOMPARE(onAllDesktopsChangedSpy.first().first().toBool(), true); QCOMPARE(pressedChangedSpy.count(), 2); QCOMPARE(pressedChangedSpy.last().first().toBool(), false); QCOMPARE(button.isChecked(), true); } void DecorationButtonTest::testMenu() { MockBridge bridge; auto decoSettings = QSharedPointer::create(&bridge); MockDecoration mockDecoration(&bridge); mockDecoration.setSettings(decoSettings); MockClient *client = bridge.lastCreatedClient(); MockButton button(KDecoration2::DecorationButtonType::Menu, &mockDecoration); button.setGeometry(QRect(0, 0, 10, 10)); QCOMPARE(button.isEnabled(), true); QCOMPARE(button.isCheckable(), false); QCOMPARE(button.isChecked(), false); QCOMPARE(button.isVisible(), true); QCOMPARE(button.acceptedButtons(), Qt::LeftButton | Qt::RightButton); // clicking the button should trigger a request for menu button QSignalSpy clickedSpy(&button, SIGNAL(clicked(Qt::MouseButton))); QVERIFY(clickedSpy.isValid()); QSignalSpy pressedSpy(&button, SIGNAL(pressed())); QVERIFY(pressedSpy.isValid()); QSignalSpy releasedSpy(&button, SIGNAL(released())); QVERIFY(releasedSpy.isValid()); QSignalSpy pressedChangedSpy(&button, SIGNAL(pressedChanged(bool))); QVERIFY(pressedChangedSpy.isValid()); QSignalSpy menuRequestedSpy(client, SIGNAL(menuRequested())); QVERIFY(menuRequestedSpy.isValid()); QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(5, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); pressEvent.setAccepted(false); button.event(&pressEvent); QCOMPARE(pressEvent.isAccepted(), true); QCOMPARE(button.isPressed(), true); QCOMPARE(clickedSpy.count(), 0); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(releasedSpy.count(), 0); QCOMPARE(menuRequestedSpy.count(), 0); QCOMPARE(pressedChangedSpy.count(), 1); QCOMPARE(pressedChangedSpy.first().first().toBool(), true); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPointF(5, 5), Qt::LeftButton, Qt::NoButton, Qt::NoModifier); releaseEvent.setAccepted(false); button.event(&releaseEvent); QCOMPARE(releaseEvent.isAccepted(), true); QCOMPARE(button.isPressed(), false); QCOMPARE(clickedSpy.count(), 1); QCOMPARE(clickedSpy.first().first().value(), Qt::LeftButton); QCOMPARE(pressedSpy.count(), 1); QCOMPARE(releasedSpy.count(), 1); QVERIFY(menuRequestedSpy.wait()); QCOMPARE(menuRequestedSpy.count(), 1); QCOMPARE(pressedChangedSpy.count(), 2); QCOMPARE(pressedChangedSpy.last().first().toBool(), false); } void DecorationButtonTest::testMenuDoubleClick() { MockBridge bridge; auto decoSettings = QSharedPointer::create(&bridge); MockDecoration mockDecoration(&bridge); mockDecoration.setSettings(decoSettings); MockClient *client = bridge.lastCreatedClient(); MockButton button(KDecoration2::DecorationButtonType::Menu, &mockDecoration); button.setGeometry(QRect(0, 0, 10, 10)); MockSettings *settings = bridge.lastCreatedSettings(); QVERIFY(settings); QSignalSpy closeOnDoubleClickOnMenuChangedSpy(decoSettings.data(), SIGNAL(closeOnDoubleClickOnMenuChanged(bool))); QVERIFY(closeOnDoubleClickOnMenuChangedSpy.isValid()); settings->setCloseOnDoubleClickOnMenu(true); QCOMPARE(closeOnDoubleClickOnMenuChangedSpy.count(), 1); QCOMPARE(closeOnDoubleClickOnMenuChangedSpy.last().first().toBool(), true); // button used a queued connection, so we need to run event loop QCoreApplication::processEvents(); QSignalSpy clickedSpy(&button, SIGNAL(clicked(Qt::MouseButton))); QVERIFY(clickedSpy.isValid()); QSignalSpy doubleClickedSpy(&button, SIGNAL(doubleClicked())); QVERIFY(doubleClickedSpy.isValid()); QSignalSpy closeRequestedSpy(client, SIGNAL(closeRequested())); QVERIFY(closeRequestedSpy.isValid()); QSignalSpy menuRequestedSpy(client, SIGNAL(menuRequested())); QVERIFY(menuRequestedSpy.isValid()); QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(5, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); pressEvent.setAccepted(false); button.event(&pressEvent); QCOMPARE(pressEvent.isAccepted(), true); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPointF(5, 5), Qt::LeftButton, Qt::NoButton, Qt::NoModifier); releaseEvent.setAccepted(false); button.event(&releaseEvent); QCOMPARE(releaseEvent.isAccepted(), true); // should not have emitted a clicked QCOMPARE(clickedSpy.count(), 0); QCOMPARE(doubleClickedSpy.count(), 0); // another press should trigger the double click event pressEvent.setAccepted(false); button.event(&pressEvent); QCOMPARE(pressEvent.isAccepted(), true); QVERIFY(closeRequestedSpy.wait()); QCOMPARE(doubleClickedSpy.count(), 1); QCOMPARE(closeRequestedSpy.count(), 1); QCOMPARE(menuRequestedSpy.count(), 0); releaseEvent.setAccepted(false); button.event(&releaseEvent); QCOMPARE(releaseEvent.isAccepted(), true); QCOMPARE(clickedSpy.count(), 0); // run events QCoreApplication::processEvents(); QCOMPARE(closeRequestedSpy.count(), 1); QCOMPARE(menuRequestedSpy.count(), 0); // a double click of right button shouldn't trigger the double click event QMouseEvent rightPressEvent(QEvent::MouseButtonPress, QPointF(5, 5), Qt::RightButton, Qt::RightButton, Qt::NoModifier); rightPressEvent.setAccepted(false); button.event(&rightPressEvent); QCOMPARE(rightPressEvent.isAccepted(), true); QMouseEvent rightReleaseEvent(QEvent::MouseButtonRelease, QPointF(5, 5), Qt::RightButton, Qt::NoButton, Qt::NoModifier); rightReleaseEvent.setAccepted(false); button.event(&rightReleaseEvent); QCOMPARE(rightReleaseEvent.isAccepted(), true); QCOMPARE(clickedSpy.count(), 1); QVERIFY(menuRequestedSpy.wait()); QCOMPARE(menuRequestedSpy.count(), 1); // second click rightPressEvent.setAccepted(false); button.event(&rightPressEvent); QCOMPARE(rightPressEvent.isAccepted(), true); rightReleaseEvent.setAccepted(false); button.event(&rightReleaseEvent); QCOMPARE(rightReleaseEvent.isAccepted(), true); QCOMPARE(clickedSpy.count(), 2); QVERIFY(menuRequestedSpy.wait()); QCOMPARE(menuRequestedSpy.count(), 2); } void DecorationButtonTest::testMenuPressAndHold() { MockBridge bridge; auto decoSettings = QSharedPointer::create(&bridge); MockDecoration mockDecoration(&bridge); mockDecoration.setSettings(decoSettings); MockClient *client = bridge.lastCreatedClient(); MockButton button(KDecoration2::DecorationButtonType::Menu, &mockDecoration); button.setGeometry(QRect(0, 0, 10, 10)); MockSettings *settings = bridge.lastCreatedSettings(); QVERIFY(settings); QSignalSpy closeOnDoubleClickOnMenuChangedSpy(decoSettings.data(), SIGNAL(closeOnDoubleClickOnMenuChanged(bool))); QVERIFY(closeOnDoubleClickOnMenuChangedSpy.isValid()); settings->setCloseOnDoubleClickOnMenu(true); QCOMPARE(closeOnDoubleClickOnMenuChangedSpy.count(), 1); QCOMPARE(closeOnDoubleClickOnMenuChangedSpy.last().first().toBool(), true); // button used a queued connection, so we need to run event loop QCoreApplication::processEvents(); QSignalSpy menuRequestedSpy(client, SIGNAL(menuRequested())); QVERIFY(menuRequestedSpy.isValid()); QSignalSpy doubleClickedSpy(&button, SIGNAL(doubleClicked())); QVERIFY(doubleClickedSpy.isValid()); QSignalSpy closeRequestedSpy(client, SIGNAL(closeRequested())); QVERIFY(closeRequestedSpy.isValid()); QSignalSpy clickedSpy(&button, SIGNAL(clicked(Qt::MouseButton))); QVERIFY(clickedSpy.isValid()); // send a press event QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(5, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); pressEvent.setAccepted(false); button.event(&pressEvent); QCOMPARE(pressEvent.isAccepted(), true); QCOMPARE(clickedSpy.count(), 0); // and wait QVERIFY(menuRequestedSpy.wait()); QCOMPARE(menuRequestedSpy.count(), 1); QCOMPARE(clickedSpy.count(), 1); // send release event QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPointF(5, 5), Qt::LeftButton, Qt::NoButton, Qt::NoModifier); releaseEvent.setAccepted(false); button.event(&releaseEvent); QCOMPARE(releaseEvent.isAccepted(), true); QCOMPARE(clickedSpy.count(), 1); QTest::qWait(QGuiApplication::styleHints()->mouseDoubleClickInterval()); // and it shouldn't be a double click pressEvent.setAccepted(false); button.event(&pressEvent); QCOMPARE(pressEvent.isAccepted(), true); // while waiting we disable click and hold settings->setCloseOnDoubleClickOnMenu(false); QCOMPARE(closeOnDoubleClickOnMenuChangedSpy.count(), 2); QCOMPARE(closeOnDoubleClickOnMenuChangedSpy.last().first().toBool(), false); // button used a queued connection, so we need to run event loop QCoreApplication::processEvents(); // and releasing should emit the menu signal releaseEvent.setAccepted(false); button.event(&releaseEvent); QCOMPARE(releaseEvent.isAccepted(), true); QCOMPARE(clickedSpy.count(), 2); QVERIFY(menuRequestedSpy.wait()); QCOMPARE(menuRequestedSpy.count(), 2); // never got a dobule click QCOMPARE(closeRequestedSpy.count(), 0); } +void DecorationButtonTest::testApplicationMenu() +{ + MockBridge bridge; + auto decoSettings = QSharedPointer::create(&bridge); + MockDecoration mockDecoration(&bridge); + mockDecoration.setSettings(decoSettings); + MockClient *client = bridge.lastCreatedClient(); + MockButton button(KDecoration2::DecorationButtonType::ApplicationMenu, &mockDecoration); + button.setGeometry(QRect(0, 0, 10, 10)); + + QCOMPARE(button.isEnabled(), true); + QCOMPARE(button.isCheckable(), true); + QCOMPARE(button.isChecked(), false); + QCOMPARE(button.isVisible(), true); + QCOMPARE(button.acceptedButtons(), Qt::LeftButton); + + // clicking the button should trigger a request for application menu + QSignalSpy clickedSpy(&button, SIGNAL(clicked(Qt::MouseButton))); + QVERIFY(clickedSpy.isValid()); + QSignalSpy pressedSpy(&button, SIGNAL(pressed())); + QVERIFY(pressedSpy.isValid()); + QSignalSpy releasedSpy(&button, SIGNAL(released())); + QVERIFY(releasedSpy.isValid()); + QSignalSpy pressedChangedSpy(&button, SIGNAL(pressedChanged(bool))); + QVERIFY(pressedChangedSpy.isValid()); + QSignalSpy applicationMenuRequestedSpy(client, SIGNAL(applicationMenuRequested())); + QVERIFY(applicationMenuRequestedSpy.isValid()); + + QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(5, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + pressEvent.setAccepted(false); + button.event(&pressEvent); + QCOMPARE(pressEvent.isAccepted(), true); + QCOMPARE(button.isPressed(), true); + QCOMPARE(clickedSpy.count(), 0); + QCOMPARE(pressedSpy.count(), 1); + QCOMPARE(releasedSpy.count(), 0); + QCOMPARE(applicationMenuRequestedSpy.count(), 0); + QCOMPARE(pressedChangedSpy.count(), 1); + QCOMPARE(pressedChangedSpy.first().first().toBool(), true); + + QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPointF(5, 5), Qt::LeftButton, Qt::NoButton, Qt::NoModifier); + releaseEvent.setAccepted(false); + button.event(&releaseEvent); + QCOMPARE(releaseEvent.isAccepted(), true); + QCOMPARE(button.isPressed(), false); + QCOMPARE(clickedSpy.count(), 1); + QCOMPARE(clickedSpy.first().first().value(), Qt::LeftButton); + QCOMPARE(pressedSpy.count(), 1); + QCOMPARE(releasedSpy.count(), 1); + QVERIFY(applicationMenuRequestedSpy.wait()); + QCOMPARE(applicationMenuRequestedSpy.count(), 1); + QCOMPARE(pressedChangedSpy.count(), 2); + QCOMPARE(pressedChangedSpy.last().first().toBool(), false); +} + QTEST_MAIN(DecorationButtonTest) #include "decorationbuttontest.moc" diff --git a/autotests/mockclient.cpp b/autotests/mockclient.cpp index 6caf0f2..f855b3d 100644 --- a/autotests/mockclient.cpp +++ b/autotests/mockclient.cpp @@ -1,268 +1,290 @@ /* * Copyright 2014 Martin Gräßlin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "mockclient.h" #include #include MockClient::MockClient(KDecoration2::DecoratedClient *client, KDecoration2::Decoration *decoration) : QObject() - , DecoratedClientPrivate(client, decoration) + , ApplicationMenuEnabledDecoratedClientPrivate(client, decoration) { } Qt::Edges MockClient::adjacentScreenEdges() const { return Qt::Edges(); } QString MockClient::caption() const { return QString(); } WId MockClient::decorationId() const { return 0; } int MockClient::desktop() const { return 1; } int MockClient::height() const { return m_height; } QIcon MockClient::icon() const { return QIcon(); } bool MockClient::isActive() const { return false; } bool MockClient::isCloseable() const { return m_closeable; } bool MockClient::isKeepAbove() const { return m_keepAbove; } bool MockClient::isKeepBelow() const { return m_keepBelow; } bool MockClient::isMaximizeable() const { return m_maximizable; } bool MockClient::isMaximized() const { return isMaximizedHorizontally() && isMaximizedVertically(); } bool MockClient::isMaximizedHorizontally() const { return m_maximizedHorizontally; } bool MockClient::isMaximizedVertically() const { return m_maximizedVertically; } bool MockClient::isMinimizeable() const { return m_minimizable; } bool MockClient::isModal() const { return false; } bool MockClient::isMoveable() const { return false; } bool MockClient::isOnAllDesktops() const { return m_onAllDesktops; } bool MockClient::isResizeable() const { return false; } bool MockClient::isShadeable() const { return m_shadeable; } bool MockClient::isShaded() const { return m_shaded; } QPalette MockClient::palette() const { return QPalette(); } +bool MockClient::hasApplicationMenu() const +{ + return true; +} + +bool MockClient::isApplicationMenuActive() const +{ + return false; +} + bool MockClient::providesContextHelp() const { return m_contextHelp; } void MockClient::requestClose() { emit closeRequested(); } void MockClient::requestContextHelp() { emit quickHelpRequested(); } void MockClient::requestToggleMaximization(Qt::MouseButtons buttons) { bool maximizedHorizontally = m_maximizedHorizontally; bool maximizedVertically = m_maximizedVertically; if (buttons.testFlag(Qt::LeftButton)) { maximizedHorizontally = !m_maximizedHorizontally; maximizedVertically = !m_maximizedVertically; } if (buttons.testFlag(Qt::MiddleButton)) { maximizedHorizontally = !m_maximizedHorizontally; } if (buttons.testFlag(Qt::RightButton)) { maximizedVertically = !m_maximizedVertically; } const bool wasMaximized = isMaximized(); if (m_maximizedHorizontally != maximizedHorizontally) { m_maximizedHorizontally = maximizedHorizontally; emit client()->maximizedHorizontallyChanged(m_maximizedHorizontally); } if (m_maximizedVertically != maximizedVertically) { m_maximizedVertically = maximizedVertically; emit client()->maximizedVerticallyChanged(m_maximizedVertically); } if (wasMaximized != isMaximized()) { emit client()->maximizedChanged(isMaximized()); } } void MockClient::requestMinimize() { emit minimizeRequested(); } void MockClient::requestShowWindowMenu() { emit menuRequested(); } +void MockClient::requestShowApplicationMenu(const QRect &rect, int actionId) +{ + Q_UNUSED(rect); + Q_UNUSED(actionId); + emit applicationMenuRequested(); // FIXME TODO pass geometry +} + void MockClient::requestToggleKeepAbove() { m_keepAbove = !m_keepAbove; emit client()->keepAboveChanged(m_keepAbove); } void MockClient::requestToggleKeepBelow() { m_keepBelow = !m_keepBelow; emit client()->keepBelowChanged(m_keepBelow); } void MockClient::requestToggleOnAllDesktops() { m_onAllDesktops = !m_onAllDesktops; emit client()->onAllDesktopsChanged(m_onAllDesktops); } void MockClient::requestToggleShade() { m_shaded = !m_shaded; emit client()->shadedChanged(m_shaded); } int MockClient::width() const { return m_width; } WId MockClient::windowId() const { return 0; } void MockClient::setCloseable(bool set) { m_closeable = set; emit client()->closeableChanged(set); } void MockClient::setMinimizable(bool set) { m_minimizable = set; emit client()->minimizeableChanged(set); } void MockClient::setProvidesContextHelp(bool set) { m_contextHelp = set; emit client()->providesContextHelpChanged(set); } void MockClient::setShadeable(bool set) { m_shadeable = set; emit client()->shadeableChanged(set); } void MockClient::setMaximizable(bool set) { m_maximizable = set; emit client()->maximizeableChanged(set); } void MockClient::setWidth(int w) { m_width = w; emit client()->widthChanged(w); } void MockClient::setHeight(int h) { m_height = h; emit client()->heightChanged(h); } + +void MockClient::showApplicationMenu(int actionId) +{ + Q_UNUSED(actionId) +} diff --git a/autotests/mockclient.h b/autotests/mockclient.h index c55d357..8755589 100644 --- a/autotests/mockclient.h +++ b/autotests/mockclient.h @@ -1,99 +1,105 @@ /* * Copyright 2014 Martin Gräßlin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef MOCK_CLIENT_H #define MOCK_CLIENT_H #include "../src/private/decoratedclientprivate.h" #include -class MockClient : public QObject, public KDecoration2::DecoratedClientPrivate +class MockClient : public QObject, public KDecoration2::ApplicationMenuEnabledDecoratedClientPrivate { Q_OBJECT public: explicit MockClient(KDecoration2::DecoratedClient *client, KDecoration2::Decoration *decoration); Qt::Edges adjacentScreenEdges() const override; QString caption() const override; WId decorationId() const override; int desktop() const override; int height() const override; QIcon icon() const override; bool isActive() const override; bool isCloseable() const override; bool isKeepAbove() const override; bool isKeepBelow() const override; bool isMaximizeable() const override; bool isMaximized() const override; bool isMaximizedHorizontally() const override; bool isMaximizedVertically() const override; bool isMinimizeable() const override; bool isModal() const override; bool isMoveable() const override; bool isOnAllDesktops() const override; bool isResizeable() const override; bool isShadeable() const override; bool isShaded() const override; QPalette palette() const override; + bool hasApplicationMenu() const override; + bool isApplicationMenuActive() const override; bool providesContextHelp() const override; void requestClose() override; void requestContextHelp() override; void requestToggleMaximization(Qt::MouseButtons buttons) override; void requestMinimize() override; void requestShowWindowMenu() override; + void requestShowApplicationMenu(const QRect &rect, int actionId) override; void requestToggleKeepAbove() override; void requestToggleKeepBelow() override; void requestToggleOnAllDesktops() override; void requestToggleShade() override; int width() const override; WId windowId() const override; + void showApplicationMenu(int actionId) override; + void setCloseable(bool set); void setMinimizable(bool set); void setProvidesContextHelp(bool set); void setShadeable(bool set); void setMaximizable(bool set); void setWidth(int w); void setHeight(int h); Q_SIGNALS: void closeRequested(); void minimizeRequested(); void quickHelpRequested(); void menuRequested(); + void applicationMenuRequested(); private: bool m_closeable = false; bool m_minimizable = false; bool m_contextHelp = false; bool m_keepAbove = false; bool m_keepBelow = false; bool m_shadeable = false; bool m_shaded = false; bool m_maximizable = false; bool m_maximizedVertically = false; bool m_maximizedHorizontally = false; bool m_onAllDesktops = false; int m_width = 0; int m_height = 0; }; #endif diff --git a/src/decoratedclient.cpp b/src/decoratedclient.cpp index b107aa0..6e11ee7 100644 --- a/src/decoratedclient.cpp +++ b/src/decoratedclient.cpp @@ -1,87 +1,110 @@ /* * Copyright 2014 Martin Gräßlin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "decoratedclient.h" #include "private/decoratedclientprivate.h" #include "private/decorationbridge.h" #include "decoration.h" #include namespace KDecoration2 { DecoratedClient::DecoratedClient(Decoration *parent, DecorationBridge *bridge) : QObject() , d(std::move(bridge->createClient(this, parent))) { } DecoratedClient::~DecoratedClient() = default; #define DELEGATE(type, method) \ type DecoratedClient::method() const \ { \ return d->method(); \ } DELEGATE(bool, isActive) DELEGATE(QString, caption) DELEGATE(int, desktop) DELEGATE(bool, isOnAllDesktops) DELEGATE(bool, isShaded) DELEGATE(QIcon, icon) DELEGATE(bool, isMaximized) DELEGATE(bool, isMaximizedHorizontally) DELEGATE(bool, isMaximizedVertically) DELEGATE(bool, isKeepAbove) DELEGATE(bool, isKeepBelow) DELEGATE(bool, isCloseable) DELEGATE(bool, isMaximizeable) DELEGATE(bool, isMinimizeable) DELEGATE(bool, providesContextHelp) DELEGATE(bool, isModal) DELEGATE(bool, isShadeable) DELEGATE(bool, isMoveable) DELEGATE(bool, isResizeable) DELEGATE(WId, windowId) DELEGATE(WId, decorationId) DELEGATE(int, width) DELEGATE(int, height) DELEGATE(QPalette, palette) DELEGATE(Qt::Edges, adjacentScreenEdges) #undef DELEGATE +bool DecoratedClient::hasApplicationMenu() const +{ + if (const auto *appMenuEnabledPrivate = dynamic_cast(d.get())) { + return appMenuEnabledPrivate->hasApplicationMenu(); + } + return false; +} + +bool DecoratedClient::isApplicationMenuActive() const +{ + if (const auto *appMenuEnabledPrivate = dynamic_cast(d.get())) { + return appMenuEnabledPrivate->isApplicationMenuActive(); + } + return false; +} + QPointer< Decoration > DecoratedClient::decoration() const { return QPointer(d->decoration()); } QColor DecoratedClient::color(QPalette::ColorGroup group, QPalette::ColorRole role) const { return d->palette().color(group, role); } QColor DecoratedClient::color(ColorGroup group, ColorRole role) const { return d->color(group, role); } +void DecoratedClient::showApplicationMenu(int actionId) +{ + if (auto *appMenuEnabledPrivate = dynamic_cast(d.get())) { + appMenuEnabledPrivate->showApplicationMenu(actionId); + } +} + } // namespace diff --git a/src/decoratedclient.h b/src/decoratedclient.h index 501ebd6..cfd7836 100644 --- a/src/decoratedclient.h +++ b/src/decoratedclient.h @@ -1,258 +1,290 @@ /* * Copyright 2014 Martin Gräßlin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef KDECORATION2_DECORATED_CLIENT_H #define KDECORATION2_DECORATED_CLIENT_H #include #include "decorationdefines.h" #include #include #include #include #include #include #include namespace KDecoration2 { class Decoration; class DecorationBridge; class DecoratedClientPrivate; /** * @brief The Client which gets decorated. * * The DecoratedClient provides access to all the properties relevant for decorating the Client. * Each DecoratedClient is bound to one Decoration and each Decoration is bound to this one * DecoratedClient. * * The DecoratedClient only exports properties, it does not provide any means to change the state. * To change state one needs to call the methods on Decoration. This is as the backend might * disallow state changes. Therefore any changes should be bound to the change signals of the * DecoratedClient and not be bound to state changes of input elements (such as a button). */ class KDECORATIONS2_EXPORT DecoratedClient : public QObject { Q_OBJECT /** * The Decoration of this DecoratedClient **/ Q_PROPERTY(KDecoration2::Decoration *decoration READ decoration CONSTANT) /** * Whether the DecoratedClient is active (has focus) or is inactive. **/ Q_PROPERTY(bool active READ isActive NOTIFY activeChanged) /** * The caption of the DecoratedClient. **/ Q_PROPERTY(QString caption READ caption NOTIFY captionChanged) /** * The virtual desktop of the DecoratedClient. The special value @c -1 means on all * desktops. For this prefer using the property onAllDesktops. **/ Q_PROPERTY(int desktop READ desktop NOTIFY desktopChanged) /** * Whether the DecoratedClient is on all desktops or on just one. **/ Q_PROPERTY(bool onAllDesktops READ isOnAllDesktops NOTIFY onAllDesktopsChanged) /** * Whether the DecoratedClient is shaded. Shaded means that the actual content is * not visible, only the Decoration is visible. **/ Q_PROPERTY(bool shaded READ isShaded NOTIFY shadedChanged) /** * The icon of the DecoratedClient. This can be used as the icon for the window menu button. **/ Q_PROPERTY(QIcon icon READ icon NOTIFY iconChanged) /** * Whether the DecoratedClient is maximized. A DecoratedClient is maximized if it is both * maximizedHorizontally and maximizedVertically. The Decoration of a maximized DecoratedClient * should only consist of the title bar area. **/ Q_PROPERTY(bool maximized READ isMaximized NOTIFY maximizedChanged) /** * Whether the DecoratedClient is maximized horizontally. A horizontally maximized DecoratedClient * uses the maximal possible width. **/ Q_PROPERTY(bool maximizedHorizontally READ isMaximizedHorizontally NOTIFY maximizedHorizontallyChanged) /** * Whether the DecoratedClient is maximized vertically. A vertically maximized DecoratedClient * uses the maximal possible height. **/ Q_PROPERTY(bool maximizedVertically READ isMaximizedVertically NOTIFY maximizedVerticallyChanged) /** * Whether the DecoratedClient is set to be kept above other DecoratedClients. There can be multiple * DecoratedClients which are set to be kept above. **/ Q_PROPERTY(bool keepAbove READ isKeepAbove NOTIFY keepAboveChanged) /** * Whether the DecoratedClient is set to be kept below other DecoratedClients. There can be multiple * DecoratedClients which are set to be kept below. **/ Q_PROPERTY(bool keepBelow READ isKeepBelow NOTIFY keepBelowChanged) /** * Whether the DecoratedClient can be closed. If this property is @c false a DecorationButton * for closing the DecoratedClient should be disabled. **/ Q_PROPERTY(bool closeable READ isCloseable NOTIFY closeableChanged) /** * Whether the DecoratedClient can be maximized. If this property is @c false a DecorationButton * for maximizing the DecoratedClient should be disabled. **/ Q_PROPERTY(bool maximizeable READ isMaximizeable NOTIFY maximizeableChanged) /** * Whether the DecoratedClient can be minimized. If this property is @c false a DecorationButton * for minimizing the DecoratedClient should be disabled. **/ Q_PROPERTY(bool minimizeable READ isMinimizeable NOTIFY minimizeableChanged) /** * Whether the DecoratedClient provides context help. * The Decoration should only show a context help button if this property is @c true. **/ Q_PROPERTY(bool providesContextHelp READ providesContextHelp NOTIFY providesContextHelpChanged) /** * Whether the DecoratedClient is a modal dialog. **/ Q_PROPERTY(bool modal READ isModal CONSTANT) /** * Whether the DecoratedClient can be shaded. If this property is @c false a DecorationButton * for shading the DecoratedClient should be disabled. **/ Q_PROPERTY(bool shadeable READ isShadeable NOTIFY shadeableChanged) /** * Whether the DecoratedClient can be moved. **/ Q_PROPERTY(bool moveable READ isMoveable NOTIFY moveableChanged) /** * Whether the DecoratedClient can be resized. **/ Q_PROPERTY(bool resizeable READ isResizeable NOTIFY resizeableChanged) /** * The width of the DecoratedClient. **/ Q_PROPERTY(int width READ width NOTIFY widthChanged) /** * The height of the DecoratedClient. **/ Q_PROPERTY(int height READ height NOTIFY heightChanged) /** * The palette this DecoratedClient uses. The palette might be different for each * DecoratedClient and the Decoration should honor the palette. **/ Q_PROPERTY(QPalette palette READ palette NOTIFY paletteChanged) /** * The Edges which are adjacent to a screen edge. E.g. for a maximized DecoratedClient this * will include all Edges. The Decoration can use this information to hide borders. **/ Q_PROPERTY(Qt::Edges adjacentScreenEdges READ adjacentScreenEdges NOTIFY adjacentScreenEdgesChanged) + /** + * Whether the DecoratedClient has an application menu + * @since 5.9 + */ + Q_PROPERTY(bool hasApplicationMenu READ hasApplicationMenu NOTIFY hasApplicationMenuChanged) + /** + * Whether the application menu for this DecoratedClient is currently shown to the user + * The Decoration can use this information to highlight the respective button. + * @since 5.9 + */ + Q_PROPERTY(bool applicationMenuActive READ isApplicationMenuActive NOTIFY applicationMenuActiveChanged) // TODO: properties for windowId and decorationId? public: DecoratedClient() = delete; ~DecoratedClient(); bool isActive() const; QString caption() const; int desktop() const; bool isOnAllDesktops() const; bool isShaded() const; QIcon icon() const; bool isMaximized() const; bool isMaximizedHorizontally() const; bool isMaximizedVertically() const; bool isKeepAbove() const; bool isKeepBelow() const; bool isCloseable() const; bool isMaximizeable() const; bool isMinimizeable() const; bool providesContextHelp() const; bool isModal() const; bool isShadeable() const; bool isMoveable() const; bool isResizeable() const; Qt::Edges adjacentScreenEdges() const; WId windowId() const; WId decorationId() const; int width() const; int height() const; QPointer decoration() const; QPalette palette() const; /** * Used to get colors in QPalette. * @param group The color group * @param role The color role * @return palette().color(group, role) * @since 5.3 **/ QColor color(QPalette::ColorGroup group, QPalette::ColorRole role) const; /** * Used to get additional colors that are not in QPalette. * @param group The color group * @param role The color role * @return The color if provided for combination of group and role, otherwise invalid QColor. * @since 5.3 **/ QColor color(ColorGroup group, ColorRole role) const; + /** + * Whether the DecoratedClient has an application menu + * @since 5.9 + */ + bool hasApplicationMenu() const; + /** + * Whether the application menu for this DecoratedClient is currently shown to the user + * The Decoration can use this information to highlight the respective button. + * @since 5.9 + */ + bool isApplicationMenuActive() const; + + /** + * Request the application menu to be shown to the user + * @param actionId The DBus menu ID of the action that should be highlighted, 0 for none. + */ + void showApplicationMenu(int actionId); + Q_SIGNALS: void activeChanged(bool); void captionChanged(QString); void desktopChanged(int); void onAllDesktopsChanged(bool); void shadedChanged(bool); void iconChanged(QIcon); void maximizedChanged(bool); void maximizedHorizontallyChanged(bool); void maximizedVerticallyChanged(bool); void keepAboveChanged(bool); void keepBelowChanged(bool); void closeableChanged(bool); void maximizeableChanged(bool); void minimizeableChanged(bool); void providesContextHelpChanged(bool); void shadeableChanged(bool); void moveableChanged(bool); void resizeableChanged(bool); void widthChanged(int); void heightChanged(int); void paletteChanged(const QPalette &palette); void adjacentScreenEdgesChanged(Qt::Edges edges); + void hasApplicationMenuChanged(bool); + void applicationMenuActiveChanged(bool); + private: friend class Decoration; DecoratedClient(Decoration *parent, DecorationBridge *bridge); const std::unique_ptr d; }; } // namespace #endif diff --git a/src/decoration.cpp b/src/decoration.cpp index 7cb73e1..3e24f36 100644 --- a/src/decoration.cpp +++ b/src/decoration.cpp @@ -1,385 +1,403 @@ /* * Copyright 2014 Martin Gräßlin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "decoration.h" #include "decoration_p.h" #include "decoratedclient.h" #include "private/decoratedclientprivate.h" #include "private/decorationbridge.h" #include "decorationbutton.h" #include "decorationsettings.h" #include "decorationshadow.h" #include #include namespace KDecoration2 { namespace { DecorationBridge *findBridge(const QVariantList &args) { for (const auto &arg: args) { if (auto bridge = arg.toMap().value(QStringLiteral("bridge")).value()) { return bridge; } } Q_UNREACHABLE(); } } Decoration::Private::Private(Decoration *deco, const QVariantList &args) : sectionUnderMouse(Qt::NoSection) , bridge(findBridge(args)) , client(QSharedPointer(new DecoratedClient(deco, bridge))) , opaque(false) , q(deco) { Q_UNUSED(args) } void Decoration::Private::setSectionUnderMouse(Qt::WindowFrameSection section) { if (sectionUnderMouse == section) { return; } sectionUnderMouse = section; emit q->sectionUnderMouseChanged(sectionUnderMouse); } void Decoration::Private::updateSectionUnderMouse(const QPoint &mousePosition) { if (titleBar.contains(mousePosition)) { setSectionUnderMouse(Qt::TitleBarArea); return; } const QSize size = q->size(); const int corner = 2*settings->largeSpacing(); const bool left = mousePosition.x() < borders.left(); const bool top = mousePosition.y() < borders.top(); const bool bottom = size.height() - mousePosition.y() <= borders.bottom(); const bool right = size.width() - mousePosition.x() <= borders.right(); if (left) { if (top && mousePosition.y() < titleBar.top() + corner) { setSectionUnderMouse(Qt::TopLeftSection); } else if (size.height() - mousePosition.y() <= borders.bottom() + corner && mousePosition.y() > titleBar.bottom()) { setSectionUnderMouse(Qt::BottomLeftSection); } else { setSectionUnderMouse(Qt::LeftSection); } return; } if (right) { if (top && mousePosition.y() < titleBar.top() + corner) { setSectionUnderMouse(Qt::TopRightSection); } else if (size.height() - mousePosition.y() <= borders.bottom() + corner && mousePosition.y() > titleBar.bottom()) { setSectionUnderMouse(Qt::BottomRightSection); } else { setSectionUnderMouse(Qt::RightSection); } return; } if (bottom) { if (mousePosition.y() > titleBar.bottom()) { if (mousePosition.x() < borders.left() + corner) { setSectionUnderMouse(Qt::BottomLeftSection); } else if (size.width() - mousePosition.x() <= borders.right() + corner) { setSectionUnderMouse(Qt::BottomRightSection); } else { setSectionUnderMouse(Qt::BottomSection); } } else { setSectionUnderMouse(Qt::TitleBarArea); } return; } if (top) { if (mousePosition.y() < titleBar.top()) { if (mousePosition.x() < borders.left() + corner) { setSectionUnderMouse(Qt::TopLeftSection); } else if (size.width() - mousePosition.x() <= borders.right() + corner) { setSectionUnderMouse(Qt::TopRightSection); } else { setSectionUnderMouse(Qt::TopSection); } } else { setSectionUnderMouse(Qt::TitleBarArea); } return; } setSectionUnderMouse(Qt::NoSection); } void Decoration::Private::addButton(DecorationButton *button) { Q_ASSERT(!buttons.contains(button)); buttons << button; QObject::connect(button, &QObject::destroyed, q, [this](QObject *o) { auto it = buttons.begin(); while (it != buttons.end()) { if (*it == static_cast(o)) { it = buttons.erase(it); } else { it++; } } } ); } Decoration::Decoration(QObject *parent, const QVariantList &args) : QObject(parent) , d(new Private(this, args)) { connect(this, &Decoration::bordersChanged, this, [this]{ update(); }); } Decoration::~Decoration() = default; void Decoration::init() { Q_ASSERT(!d->settings.isNull()); } QWeakPointer Decoration::client() const { return d->client.toWeakRef(); } #define DELEGATE(name) \ void Decoration::name() \ { \ d->client->d->name(); \ } DELEGATE(requestClose) DELEGATE(requestContextHelp) DELEGATE(requestMinimize) DELEGATE(requestToggleOnAllDesktops) DELEGATE(requestToggleShade) DELEGATE(requestToggleKeepAbove) DELEGATE(requestToggleKeepBelow) DELEGATE(requestShowWindowMenu) #undef DELEGATE void Decoration::requestToggleMaximization(Qt::MouseButtons buttons) { d->client->d->requestToggleMaximization(buttons); } +void Decoration::showApplicationMenu(int actionId) +{ + auto it = std::find_if(d->buttons.constBegin(), d->buttons.constEnd(), [](DecorationButton *button) { + return button->type() == DecorationButtonType::ApplicationMenu; + }); + + if (it != d->buttons.constEnd()) { + requestShowApplicationMenu((*it)->geometry().toRect(), actionId); + } +} + +void Decoration::requestShowApplicationMenu(const QRect &rect, int actionId) +{ + if (auto *appMenuEnabledPrivate = dynamic_cast(d->client->d.get())) { + appMenuEnabledPrivate->requestShowApplicationMenu(rect, actionId); + } +} + #define DELEGATE(name, variableName, type, emitValue) \ void Decoration::name(type a) \ { \ if (d->variableName == a) { \ return; \ } \ d->variableName = a; \ emit variableName##Changed(emitValue); \ } DELEGATE(setBorders, borders, const QMargins&, ) DELEGATE(setResizeOnlyBorders, resizeOnlyBorders, const QMargins&, ) DELEGATE(setTitleBar, titleBar, const QRect&, ) DELEGATE(setOpaque, opaque, bool, d->opaque) DELEGATE(setShadow, shadow, const QSharedPointer &, d->shadow) #undef DELEGATE #define DELEGATE(name, type) \ type Decoration::name() const \ { \ return d->name; \ } DELEGATE(borders, QMargins) DELEGATE(resizeOnlyBorders, QMargins) DELEGATE(titleBar, QRect) DELEGATE(sectionUnderMouse, Qt::WindowFrameSection) DELEGATE(shadow, QSharedPointer) #undef DELEGATE bool Decoration::isOpaque() const { return d->opaque; } #define BORDER(name, Name) \ int Decoration::border##Name() const \ { \ return d->borders.name(); \ } \ int Decoration::resizeOnlyBorder##Name() const \ { \ return d->resizeOnlyBorders.name(); \ } BORDER(left, Left) BORDER(right, Right) BORDER(top, Top) BORDER(bottom, Bottom) #undef BORDER QSize Decoration::size() const { const QMargins &b = d->borders; return QSize(d->client->width() + b.left() + b.right(), (d->client->isShaded() ? 0 : d->client->height()) + b.top() + b.bottom()); } QRect Decoration::rect() const { return QRect(QPoint(0, 0), size()); } bool Decoration::event(QEvent *event) { switch (event->type()) { case QEvent::HoverEnter: hoverEnterEvent(static_cast(event)); return true; case QEvent::HoverLeave: hoverLeaveEvent(static_cast(event)); return true; case QEvent::HoverMove: hoverMoveEvent(static_cast(event)); return true; case QEvent::MouseButtonPress: mousePressEvent(static_cast(event)); return true; case QEvent::MouseButtonRelease: mouseReleaseEvent(static_cast(event)); return true; case QEvent::MouseMove: mouseMoveEvent(static_cast(event)); return true; case QEvent::Wheel: wheelEvent(static_cast(event)); return true; default: return QObject::event(event); } } void Decoration::hoverEnterEvent(QHoverEvent *event) { for (DecorationButton *button : d->buttons) { QCoreApplication::instance()->sendEvent(button, event); } d->updateSectionUnderMouse(event->pos()); } void Decoration::hoverLeaveEvent(QHoverEvent *event) { for (DecorationButton *button : d->buttons) { QCoreApplication::instance()->sendEvent(button, event); } d->setSectionUnderMouse(Qt::NoSection); } void Decoration::hoverMoveEvent(QHoverEvent *event) { for (DecorationButton *button : d->buttons) { if (!button->isEnabled() || !button->isVisible()) { continue; } const bool hovered = button->isHovered(); const bool contains = button->geometry().contains(event->pos()); if (!hovered && contains) { QHoverEvent e(QEvent::HoverEnter, event->posF(), event->oldPosF(), event->modifiers()); QCoreApplication::instance()->sendEvent(button, &e); } else if (hovered && !contains) { QHoverEvent e(QEvent::HoverLeave, event->posF(), event->oldPosF(), event->modifiers()); QCoreApplication::instance()->sendEvent(button, &e); } else if (hovered && contains) { QCoreApplication::instance()->sendEvent(button, event); } } d->updateSectionUnderMouse(event->pos()); } void Decoration::mouseMoveEvent(QMouseEvent *event) { for (DecorationButton *button : d->buttons) { if (button->isPressed()) { QCoreApplication::instance()->sendEvent(button, event); return; } } // not handled, take care ourselves } void Decoration::mousePressEvent(QMouseEvent *event) { for (DecorationButton *button : d->buttons) { if (button->isHovered()) { if (button->acceptedButtons().testFlag(event->button())) { QCoreApplication::instance()->sendEvent(button, event); } event->setAccepted(true); return; } } } void Decoration::mouseReleaseEvent(QMouseEvent *event) { for (DecorationButton *button : d->buttons) { if (button->isPressed() && button->acceptedButtons().testFlag(event->button())) { QCoreApplication::instance()->sendEvent(button, event); return; } } // not handled, take care ourselves d->updateSectionUnderMouse(event->pos()); } void Decoration::wheelEvent(QWheelEvent *event) { for (DecorationButton *button : d->buttons) { if (button->geometry().contains(event->posF())) { QCoreApplication::instance()->sendEvent(button, event); event->setAccepted(true); } } } void Decoration::update(const QRect &r) { d->bridge->update(this, r.isNull() ? rect() : r); } void Decoration::update() { update(QRect()); } void Decoration::setSettings(const QSharedPointer< DecorationSettings > &settings) { d->settings = settings; } QSharedPointer< DecorationSettings > Decoration::settings() const { return d->settings; } } // namespace diff --git a/src/decoration.h b/src/decoration.h index 5420238..95b0b7c 100644 --- a/src/decoration.h +++ b/src/decoration.h @@ -1,241 +1,244 @@ /* * Copyright 2014 Martin Gräßlin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef KDECORATION2_DECORATION_H #define KDECORATION2_DECORATION_H #include #include "decorationshadow.h" #include #include #include #include class QHoverEvent; class QMouseEvent; class QPainter; class QWheelEvent; /** * @brief Framework for creating window decorations. * **/ namespace KDecoration2 { class DecorationPrivate; class DecoratedClient; class DecorationButton; class DecorationSettings; /** * @brief Base class for the Decoration. * * To provide a Decoration one needs to inherit from this class. The framework will instantiate * an instance of the inherited class for each DecoratedClient. * * The main tasks of the Decoration is to provide borders around the DecoratedClient. For this * the Deocration provides border sizes: those should be adjusted depending on the state of the * DecoratedClient. E.g. commonly a maximized DecoratedClient does not have borders on the side, * only the title bar. * * Whenever the visual representation of the Decoration changes the slot @link Decoration::update @endlink * should be invoked to request a repaint. The framework will in return invoke the * @link Decoration::paint @endlink method. This method needs to be implemented by inheriting * classes. * * A Decoration commonly provides buttons for interaction. E.g. a close button to close the * DecoratedClient. For such actions the Decoration provides slots which should be connected to * the clicked signals of the buttons. For convenience the framework provides the @link DecorationButton @endlink * and the @link DecorationButtonGroup @endlink for easier layout. It is not required to use those, * if one uses different ways to represent the actions one needs to filter the events accordingly. * * @see DecoratedClient * @see DecorationButton * @see DecorationButtonGroup **/ class KDECORATIONS2_EXPORT Decoration : public QObject { Q_OBJECT Q_PROPERTY(QMargins borders READ borders NOTIFY bordersChanged) Q_PROPERTY(int borderLeft READ borderLeft NOTIFY bordersChanged) Q_PROPERTY(int borderRight READ borderRight NOTIFY bordersChanged) Q_PROPERTY(int borderTop READ borderTop NOTIFY bordersChanged) Q_PROPERTY(int borderBottom READ borderBottom NOTIFY bordersChanged) Q_PROPERTY(QMargins resizeOnlyBorders READ resizeOnlyBorders NOTIFY resizeOnlyBordersChanged) Q_PROPERTY(int resizeOnlyBorderLeft READ resizeOnlyBorderLeft NOTIFY resizeOnlyBordersChanged) Q_PROPERTY(int resizeOnlyBorderRight READ resizeOnlyBorderRight NOTIFY resizeOnlyBordersChanged) Q_PROPERTY(int resizeOnlyBorderTop READ resizeOnlyBorderTop NOTIFY resizeOnlyBordersChanged) Q_PROPERTY(int resizeOnlyBorderBottom READ resizeOnlyBorderBottom NOTIFY resizeOnlyBordersChanged) /** * This property denotes the part of the Decoration which is currently under the mouse pointer. * It gets automatically updated whenever a QMouseEvent or QHoverEvent gets processed. **/ Q_PROPERTY(Qt::WindowFrameSection sectionUnderMouse READ sectionUnderMouse NOTIFY sectionUnderMouseChanged) /** * The titleBar is the area inside the Decoration containing all controls (e.g. Buttons) * and the caption. The titleBar is the main interaction area, while all other areas of the * Decoration are normally used as resize areas. **/ Q_PROPERTY(QRect titleBar READ titleBar NOTIFY titleBarChanged) /** * Whether the Decoration is fully opaque. By default a Decoration is considered to * use the alpha channel and this property has the value @c false. But for e.g. a maximized * DecoratedClient it is possible that the Decoration is fully opaque. In this case the * Decoration should set this property to @c true. **/ Q_PROPERTY(bool opaque READ isOpaque NOTIFY opaqueChanged) public: virtual ~Decoration(); /** * The DecoratedClient for this Decoration. * As long as the Decoration is alive it is guaranteed that the object is not * deleted. Thus it is save to access using QWeakPointer::data in e.g. a sublcass * of Decoration without promoting to QSharedPointer. **/ QWeakPointer client() const; QMargins borders() const; int borderLeft() const; int borderRight() const; int borderTop() const; int borderBottom() const; QMargins resizeOnlyBorders() const; int resizeOnlyBorderLeft() const; int resizeOnlyBorderRight() const; int resizeOnlyBorderTop() const; int resizeOnlyBorderBottom() const; Qt::WindowFrameSection sectionUnderMouse() const; QRect titleBar() const; bool isOpaque() const; /** * DecorationShadow for this Decoration. It is recommended that multiple Decorations share * the same DecorationShadow. E.g one DecorationShadow for all inactive Decorations and one * for the active Decoration. **/ QSharedPointer shadow() const; /** * The decoration's geometry in local coordinates. * * Basically the size of the DecoratedClient combined with the borders. **/ QRect rect() const; QSize size() const; /** * Invoked by the framework to set the Settings for this Decoration before * init is invoked. * @internal **/ void setSettings(const QSharedPointer &settings); /** * @returns The DecorationSettings used for this Decoration. **/ QSharedPointer settings() const; /** * Implement this method in inheriting classes to provide the rendering. * * The @p painter is set up to paint on an internal QPaintDevice. The painting is * implicitly double buffered. * * @param painter The painter which needs to be used for rendering * @param repaintArea The region which needs to be repainted. **/ virtual void paint(QPainter *painter, const QRect &repaintArea) = 0; virtual bool event(QEvent *event) override; public Q_SLOTS: void requestClose(); void requestToggleMaximization(Qt::MouseButtons buttons); void requestMinimize(); void requestContextHelp(); void requestToggleOnAllDesktops(); void requestToggleShade(); void requestToggleKeepAbove(); void requestToggleKeepBelow(); void requestShowWindowMenu(); + void showApplicationMenu(int actionId); + void requestShowApplicationMenu(const QRect &rect, int actionId); + void update(const QRect &rect); void update(); /** * This method gets invoked from the framework once the Decoration is created and * completely setup. * * An inheriting class should override this method and perform all initialization in * this method instead of the constructor. **/ virtual void init(); Q_SIGNALS: void bordersChanged(); void resizeOnlyBordersChanged(); void sectionUnderMouseChanged(Qt::WindowFrameSection); void titleBarChanged(); void opaqueChanged(bool); void shadowChanged(const QSharedPointer &shadow); protected: /** * Constructor for the Decoration. * * The @p args are used by the decoration framework to pass meta information * to the Decoration. An inheriting class is supposed to pass the args to the * parent class. * * @param parent The parent of the Decoration * @param args Additional arguments passed in from the framework **/ explicit Decoration(QObject *parent, const QVariantList &args); void setBorders(const QMargins &borders); void setResizeOnlyBorders(const QMargins &borders); /** * An implementation has to invoke this method whenever the area * containing the controls and caption changes. * @param rect The new geometry of the titleBar in Decoration coordinates **/ void setTitleBar(const QRect &rect); void setOpaque(bool opaque); void setShadow(const QSharedPointer &shadow); virtual void hoverEnterEvent(QHoverEvent *event); virtual void hoverLeaveEvent(QHoverEvent *event); virtual void hoverMoveEvent(QHoverEvent *event); virtual void mouseMoveEvent(QMouseEvent *event); virtual void mousePressEvent(QMouseEvent *event); virtual void mouseReleaseEvent(QMouseEvent *event); virtual void wheelEvent(QWheelEvent *event); private: friend class DecorationButton; class Private; QScopedPointer d; }; } // namespace Q_DECLARE_METATYPE(KDecoration2::Decoration*) #endif diff --git a/src/decorationbutton.cpp b/src/decorationbutton.cpp index b8aa7ff..5d3c2f9 100644 --- a/src/decorationbutton.cpp +++ b/src/decorationbutton.cpp @@ -1,489 +1,499 @@ /* * Copyright 2014 Martin Gräßlin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "decorationbutton.h" #include "decorationbutton_p.h" #include "decoration.h" #include "decoration_p.h" #include "decoratedclient.h" #include "decorationsettings.h" #include #include #include #include #include namespace KDecoration2 { #ifndef DOXYGEN_SHOULD_SKIP_THIS uint qHash(const DecorationButtonType &type) { return static_cast(type); } #endif DecorationButton::Private::Private(DecorationButtonType type, const QPointer &decoration, DecorationButton *parent) : decoration(decoration) , type(type) , hovered(false) , enabled(true) , checkable(false) , checked(false) , visible(true) , acceptedButtons(Qt::LeftButton) , doubleClickEnabled(false) , pressAndHold(false) , q(parent) , m_pressed(Qt::NoButton) { init(); } DecorationButton::Private::~Private() = default; void DecorationButton::Private::init() { auto c = decoration->client().data(); auto settings = decoration->settings(); switch (type) { case DecorationButtonType::Menu: QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestShowWindowMenu, Qt::QueuedConnection); QObject::connect(q, &DecorationButton::doubleClicked, decoration.data(), &Decoration::requestClose, Qt::QueuedConnection); QObject::connect(settings.data(), &DecorationSettings::closeOnDoubleClickOnMenuChanged, q, [this](bool enabled) { doubleClickEnabled = enabled; setPressAndHold(enabled); }, Qt::QueuedConnection ); doubleClickEnabled = settings->isCloseOnDoubleClickOnMenu(); setPressAndHold(settings->isCloseOnDoubleClickOnMenu()); setAcceptedButtons(Qt::LeftButton | Qt::RightButton); break; + case DecorationButtonType::ApplicationMenu: + setVisible(c->hasApplicationMenu()); + setCheckable(true); // will be "checked" whilst the menu is opened + // FIXME TODO connect directly and figure out the button geometry/offset stuff + QObject::connect(q, &DecorationButton::clicked, decoration.data(), [this] { + decoration->requestShowApplicationMenu(q->geometry().toRect(), 0 /* actionId */); + }, Qt::QueuedConnection); //&Decoration::requestShowApplicationMenu, Qt::QueuedConnection); + QObject::connect(c, &DecoratedClient::hasApplicationMenuChanged, q, &DecorationButton::setVisible); + QObject::connect(c, &DecoratedClient::applicationMenuActiveChanged, q, &DecorationButton::setChecked); + break; case DecorationButtonType::OnAllDesktops: setVisible(settings->isOnAllDesktopsAvailable()); setCheckable(true); setChecked(c->isOnAllDesktops()); QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleOnAllDesktops, Qt::QueuedConnection); QObject::connect(settings.data(), &DecorationSettings::onAllDesktopsAvailableChanged, q, &DecorationButton::setVisible); QObject::connect(c, &DecoratedClient::onAllDesktopsChanged, q, &DecorationButton::setChecked); break; case DecorationButtonType::Minimize: setEnabled(c->isMinimizeable()); QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestMinimize, Qt::QueuedConnection); QObject::connect(c, &DecoratedClient::minimizeableChanged, q, &DecorationButton::setEnabled); break; case DecorationButtonType::Maximize: setEnabled(c->isMaximizeable()); setCheckable(true); setChecked(c->isMaximized()); setAcceptedButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton); QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleMaximization, Qt::QueuedConnection); QObject::connect(c, &DecoratedClient::maximizeableChanged, q, &DecorationButton::setEnabled); QObject::connect(c, &DecoratedClient::maximizedChanged, q, &DecorationButton::setChecked); break; case DecorationButtonType::Close: setEnabled(c->isCloseable()); QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestClose, Qt::QueuedConnection); QObject::connect(c, &DecoratedClient::closeableChanged, q, &DecorationButton::setEnabled); break; case DecorationButtonType::ContextHelp: setVisible(c->providesContextHelp()); QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestContextHelp, Qt::QueuedConnection); QObject::connect(c, &DecoratedClient::providesContextHelpChanged, q, &DecorationButton::setVisible); break; case DecorationButtonType::KeepAbove: setCheckable(true); setChecked(c->isKeepAbove()); QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleKeepAbove, Qt::QueuedConnection); QObject::connect(c, &DecoratedClient::keepAboveChanged, q, &DecorationButton::setChecked); break; case DecorationButtonType::KeepBelow: setCheckable(true); setChecked(c->isKeepBelow()); QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleKeepBelow, Qt::QueuedConnection); QObject::connect(c, &DecoratedClient::keepBelowChanged, q, &DecorationButton::setChecked); break; case DecorationButtonType::Shade: setEnabled(c->isShadeable()); setCheckable(true); setChecked(c->isShaded()); QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleShade, Qt::QueuedConnection); QObject::connect(c, &DecoratedClient::shadedChanged, q, &DecorationButton::setChecked); QObject::connect(c, &DecoratedClient::shadeableChanged, q, &DecorationButton::setEnabled); break; default: // nothing break; } } void DecorationButton::Private::setHovered(bool set) { if (hovered == set) { return; } hovered = set; emit q->hoveredChanged(hovered); } void DecorationButton::Private::setEnabled(bool set) { if (enabled == set) { return; } enabled = set; emit q->enabledChanged(enabled); if (!enabled) { setHovered(false); if (isPressed()) { m_pressed = Qt::NoButton; emit q->pressedChanged(false); } } } void DecorationButton::Private::setVisible(bool set) { if (visible == set) { return; } visible = set; emit q->visibilityChanged(set); if (!visible) { setHovered(false); if (isPressed()) { m_pressed = Qt::NoButton; emit q->pressedChanged(false); } } } void DecorationButton::Private::setChecked(bool set) { if (!checkable || checked == set) { return; } checked = set; emit q->checkedChanged(checked); } void DecorationButton::Private::setCheckable(bool set) { if (checkable == set) { return; } if (!set) { setChecked(false); } checkable = set; emit q->checkableChanged(checkable); } void DecorationButton::Private::setPressed(Qt::MouseButton button, bool pressed) { if (pressed) { m_pressed = m_pressed | button; } else { m_pressed = m_pressed & ~button; } emit q->pressedChanged(isPressed()); } void DecorationButton::Private::setAcceptedButtons(Qt::MouseButtons buttons) { if (acceptedButtons == buttons) { return; } acceptedButtons = buttons; emit q->acceptedButtonsChanged(acceptedButtons); } void DecorationButton::Private::startDoubleClickTimer() { if (!doubleClickEnabled) { return; } if (m_doubleClickTimer.isNull()) { m_doubleClickTimer.reset(new QElapsedTimer()); } m_doubleClickTimer->start(); } void DecorationButton::Private::invalidateDoubleClickTimer() { if (m_doubleClickTimer.isNull()) { return; } m_doubleClickTimer->invalidate(); } bool DecorationButton::Private::wasDoubleClick() const { if (m_doubleClickTimer.isNull() || !m_doubleClickTimer->isValid()) { return false; } return !m_doubleClickTimer->hasExpired(QGuiApplication::styleHints()->mouseDoubleClickInterval()); } void DecorationButton::Private::setPressAndHold(bool enable) { if (pressAndHold == enable) { return; } pressAndHold = enable; if (!pressAndHold) { m_pressAndHoldTimer.reset(); } } void DecorationButton::Private::startPressAndHold() { if (!pressAndHold) { return; } if (m_pressAndHoldTimer.isNull()) { m_pressAndHoldTimer.reset(new QTimer()); m_pressAndHoldTimer->setSingleShot(true); QObject::connect(m_pressAndHoldTimer.data(), &QTimer::timeout, q, [this]() { q->clicked(Qt::LeftButton); } ); } m_pressAndHoldTimer->start(QGuiApplication::styleHints()->mousePressAndHoldInterval()); } void DecorationButton::Private::stopPressAndHold() { if (!m_pressAndHoldTimer.isNull()) { m_pressAndHoldTimer->stop(); } } DecorationButton::DecorationButton(DecorationButtonType type, const QPointer &decoration, QObject *parent) : QObject(parent) , d(new Private(type, decoration, this)) { decoration->d->addButton(this); connect(this, &DecorationButton::geometryChanged, this, static_cast(&DecorationButton::update)); auto updateSlot = static_cast(&DecorationButton::update); connect(this, &DecorationButton::hoveredChanged, this, updateSlot); connect(this, &DecorationButton::pressedChanged, this, updateSlot); connect(this, &DecorationButton::checkedChanged, this, updateSlot); connect(this, &DecorationButton::enabledChanged, this, updateSlot); connect(this, &DecorationButton::visibilityChanged, this, updateSlot); connect(this, &DecorationButton::hoveredChanged, this, [this](bool hovered) { if (hovered) { emit pointerEntered(); } else { emit pointerLeft(); } } ); connect(this, &DecorationButton::pressedChanged, this, [this](bool p) { if (p) { emit pressed(); } else { emit released(); } } ); } DecorationButton::~DecorationButton() = default; void DecorationButton::update(const QRectF &rect) { decoration()->update(rect.isNull() ? geometry().toRect() : rect.toRect()); } void DecorationButton::update() { update(QRectF()); } QSizeF DecorationButton::size() const { return d->geometry.size(); } bool DecorationButton::isPressed() const { return d->isPressed(); } #define DELEGATE(name, variableName, type) \ type DecorationButton::name() const \ { \ return d->variableName; \ } DELEGATE(isHovered, hovered, bool) DELEGATE(isEnabled, enabled, bool) DELEGATE(isChecked, checked, bool) DELEGATE(isCheckable, checkable, bool) DELEGATE(isVisible, visible, bool) #define DELEGATE2(name, type) DELEGATE(name, name, type) DELEGATE2(geometry, QRectF) DELEGATE2(decoration, QPointer) DELEGATE2(acceptedButtons, Qt::MouseButtons) DELEGATE2(type, DecorationButtonType) #undef DELEGATE2 #undef DELEGATE #define DELEGATE(name, type) \ void DecorationButton::name(type a) \ { \ d->name(a); \ } DELEGATE(setAcceptedButtons, Qt::MouseButtons) DELEGATE(setEnabled, bool) DELEGATE(setChecked, bool) DELEGATE(setCheckable, bool) DELEGATE(setVisible, bool) #undef DELEGATE #define DELEGATE(name, variableName, type) \ void DecorationButton::name(type a) \ { \ if (d->variableName == a) { \ return; \ } \ d->variableName = a; \ emit variableName##Changed(d->variableName); \ } DELEGATE(setGeometry, geometry, const QRectF &) #undef DELEGATE bool DecorationButton::event(QEvent *event) { switch (event->type()) { case QEvent::HoverEnter: hoverEnterEvent(static_cast(event)); return true; case QEvent::HoverLeave: hoverLeaveEvent(static_cast(event)); return true; case QEvent::HoverMove: hoverMoveEvent(static_cast(event)); return true; case QEvent::MouseButtonPress: mousePressEvent(static_cast(event)); return true; case QEvent::MouseButtonRelease: mouseReleaseEvent(static_cast(event)); return true; case QEvent::MouseMove: mouseMoveEvent(static_cast(event)); return true; case QEvent::Wheel: wheelEvent(static_cast(event)); return true; default: return QObject::event(event); } } void DecorationButton::hoverEnterEvent(QHoverEvent *event) { if (!d->enabled || !d->visible || !d->geometry.contains(event->posF())) { return; } d->setHovered(true); event->setAccepted(true); } void DecorationButton::hoverLeaveEvent(QHoverEvent *event) { if (!d->enabled || !d->visible || !d->hovered || d->geometry.contains(event->posF())) { return; } d->setHovered(false); event->setAccepted(true); } void DecorationButton::hoverMoveEvent(QHoverEvent *event) { Q_UNUSED(event) } void DecorationButton::mouseMoveEvent(QMouseEvent *event) { if (!d->enabled || !d->visible || !d->hovered) { return; } if (!d->geometry.contains(event->localPos())) { d->setHovered(false); event->setAccepted(true); } } void DecorationButton::mousePressEvent(QMouseEvent *event) { if (!d->enabled || !d->visible || !d->geometry.contains(event->localPos()) || !d->acceptedButtons.testFlag(event->button())) { return; } d->setPressed(event->button(), true); event->setAccepted(true); if (d->doubleClickEnabled && event->button() == Qt::LeftButton) { // check for double click if (d->wasDoubleClick()) { event->setAccepted(true); emit doubleClicked(); } d->invalidateDoubleClickTimer(); } if (d->pressAndHold && event->button() == Qt::LeftButton) { d->startPressAndHold(); } } void DecorationButton::mouseReleaseEvent(QMouseEvent *event) { if (!d->enabled || !d->visible || !d->isPressed(event->button())) { return; } if (d->geometry.contains(event->localPos())) { if (!d->pressAndHold || event->button() != Qt::LeftButton) { emit clicked(event->button()); } else { d->stopPressAndHold(); } } d->setPressed(event->button(), false); event->setAccepted(true); if (d->doubleClickEnabled && event->button() == Qt::LeftButton) { d->startDoubleClickTimer(); } } void DecorationButton::wheelEvent(QWheelEvent *event) { Q_UNUSED(event) } } diff --git a/src/private/decoratedclientprivate.cpp b/src/private/decoratedclientprivate.cpp index bbec12e..ec1459b 100644 --- a/src/private/decoratedclientprivate.cpp +++ b/src/private/decoratedclientprivate.cpp @@ -1,71 +1,79 @@ /* * Copyright 2014 Martin Gräßlin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "decoratedclientprivate.h" #include namespace KDecoration2 { class DecoratedClientPrivate::Private { public: explicit Private(DecoratedClient *client, Decoration *decoration); DecoratedClient *client; Decoration *decoration; }; DecoratedClientPrivate::Private::Private(DecoratedClient *client, Decoration *decoration) : client(client) , decoration(decoration) { } DecoratedClientPrivate::DecoratedClientPrivate(DecoratedClient *client, Decoration *decoration) : d(new Private(client, decoration)) { } DecoratedClientPrivate::~DecoratedClientPrivate() = default; Decoration *DecoratedClientPrivate::decoration() { return d->decoration; } Decoration *DecoratedClientPrivate::decoration() const { return d->decoration; } DecoratedClient *DecoratedClientPrivate::client() { return d->client; } QColor DecoratedClientPrivate::color(ColorGroup group, ColorRole role) const { Q_UNUSED(role) Q_UNUSED(group) return QColor(); } +ApplicationMenuEnabledDecoratedClientPrivate::ApplicationMenuEnabledDecoratedClientPrivate(DecoratedClient *client, Decoration *decoration) + : DecoratedClientPrivate(client, decoration) +{ + +} + +ApplicationMenuEnabledDecoratedClientPrivate::~ApplicationMenuEnabledDecoratedClientPrivate() = default; + } diff --git a/src/private/decoratedclientprivate.h b/src/private/decoratedclientprivate.h index 77c9689..51bcead 100644 --- a/src/private/decoratedclientprivate.h +++ b/src/private/decoratedclientprivate.h @@ -1,105 +1,120 @@ /* * Copyright 2014 Martin Gräßlin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef KDECORATION2_DECORATED_CLIENT_PRIVATE_H #define KDECORATION2_DECORATED_CLIENT_PRIVATE_H #include #include "../decorationdefines.h" #include #include // // W A R N I N G // ------------- // // This file is not part of the KDecoration2 API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. // namespace KDecoration2 { class Decoration; class DecoratedClient; class KDECORATIONS_PRIVATE_EXPORT DecoratedClientPrivate { public: virtual ~DecoratedClientPrivate(); virtual bool isActive() const = 0; virtual QString caption() const = 0; virtual int desktop() const = 0; virtual bool isOnAllDesktops() const = 0; virtual bool isShaded() const = 0; virtual QIcon icon() const = 0; virtual bool isMaximized() const = 0; virtual bool isMaximizedHorizontally() const = 0; virtual bool isMaximizedVertically() const = 0; virtual bool isKeepAbove() const = 0; virtual bool isKeepBelow() const = 0; virtual bool isCloseable() const = 0; virtual bool isMaximizeable() const = 0; virtual bool isMinimizeable() const = 0; virtual bool providesContextHelp() const = 0; virtual bool isModal() const = 0; virtual bool isShadeable() const = 0; virtual bool isMoveable() const = 0; virtual bool isResizeable() const = 0; virtual WId windowId() const = 0; virtual WId decorationId() const = 0; virtual int width() const = 0; virtual int height() const = 0; virtual QPalette palette() const = 0; virtual Qt::Edges adjacentScreenEdges() const = 0; virtual void requestClose() = 0; virtual void requestToggleMaximization(Qt::MouseButtons buttons) = 0; virtual void requestMinimize() = 0; virtual void requestContextHelp() = 0; virtual void requestToggleOnAllDesktops() = 0; virtual void requestToggleShade() = 0; virtual void requestToggleKeepAbove() = 0; virtual void requestToggleKeepBelow() = 0; virtual void requestShowWindowMenu() = 0; Decoration *decoration(); Decoration *decoration() const; virtual QColor color(ColorGroup group, ColorRole role) const; protected: explicit DecoratedClientPrivate(DecoratedClient *client, Decoration *decoration); DecoratedClient *client(); private: class Private; const QScopedPointer d; }; +class KDECORATIONS_PRIVATE_EXPORT ApplicationMenuEnabledDecoratedClientPrivate : public DecoratedClientPrivate +{ +public: + ~ApplicationMenuEnabledDecoratedClientPrivate() override; + + virtual bool hasApplicationMenu() const = 0; + virtual bool isApplicationMenuActive() const = 0; + + virtual void showApplicationMenu(int actionId) = 0; + virtual void requestShowApplicationMenu(const QRect &rect, int actionId) = 0; + +protected: + explicit ApplicationMenuEnabledDecoratedClientPrivate(DecoratedClient *client, Decoration *decoration); +}; + } // namespace #endif