diff --git a/autotests/integration/CMakeLists.txt b/autotests/integration/CMakeLists.txt index 73cd8b6d4..5f1d37992 100644 --- a/autotests/integration/CMakeLists.txt +++ b/autotests/integration/CMakeLists.txt @@ -1,66 +1,66 @@ add_definitions(-DKWINBACKENDPATH="${CMAKE_BINARY_DIR}/plugins/platforms/virtual/KWinWaylandVirtualBackend.so") add_definitions(-DKWINQPAPATH="${CMAKE_BINARY_DIR}/plugins/qpa/") add_subdirectory(helper) add_library(KWinIntegrationTestFramework STATIC kwin_wayland_test.cpp test_helpers.cpp) target_link_libraries(KWinIntegrationTestFramework kwin Qt5::Test) function(integrationTest) set(oneValueArgs NAME) set(multiValueArgs SRCS LIBS) cmake_parse_arguments(ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) add_executable(${ARGS_NAME} ${ARGS_SRCS}) target_link_libraries(${ARGS_NAME} KWinIntegrationTestFramework kwin Qt5::Test ${ARGS_LIBS}) add_test(NAME kwin-${ARGS_NAME} COMMAND dbus-run-session ${CMAKE_CURRENT_BINARY_DIR}/${ARGS_NAME}) endfunction() integrationTest(NAME testStart SRCS start_test.cpp) integrationTest(NAME testTransientNoInput SRCS transient_no_input_test.cpp) -integrationTest(NAME testQuickTiling SRCS quick_tiling_test.cpp) integrationTest(NAME testDontCrashGlxgears SRCS dont_crash_glxgears.cpp) integrationTest(NAME testLockScreen SRCS lockscreen.cpp) integrationTest(NAME testDecorationInput SRCS decoration_input_test.cpp) integrationTest(NAME testInternalWindow SRCS internal_window.cpp) integrationTest(NAME testTouchInput SRCS touch_input_test.cpp) integrationTest(NAME testInputStackingOrder SRCS input_stacking_order.cpp) integrationTest(NAME testPointerInput SRCS pointer_input.cpp) integrationTest(NAME testPlatformCursor SRCS platformcursor.cpp) integrationTest(NAME testDontCrashCancelAnimation SRCS dont_crash_cancel_animation.cpp) integrationTest(NAME testTransientPlacmenet SRCS transient_placement.cpp) integrationTest(NAME testDebugConsole SRCS debug_console_test.cpp) integrationTest(NAME testDontCrashEmptyDeco SRCS dont_crash_empty_deco.cpp) integrationTest(NAME testPlasmaSurface SRCS plasma_surface_test.cpp) integrationTest(NAME testMaximized SRCS maximize_test.cpp) integrationTest(NAME testShellClient SRCS shell_client_test.cpp) integrationTest(NAME testDontCrashNoBorder SRCS dont_crash_no_border.cpp) integrationTest(NAME testXClipboardSync SRCS xclipboardsync_test.cpp) integrationTest(NAME testSceneOpenGL SRCS scene_opengl_test.cpp) integrationTest(NAME testSceneQPainter SRCS scene_qpainter_test.cpp) integrationTest(NAME testNoXdgRuntimeDir SRCS no_xdg_runtime_dir_test.cpp) integrationTest(NAME testScreenChanges SRCS screen_changes_test.cpp) integrationTest(NAME testModiferOnlyShortcut SRCS modifier_only_shortcut_test.cpp) integrationTest(NAME testTabBox SRCS tabbox_test.cpp) integrationTest(NAME testGlobalShortcuts SRCS globalshortcuts_test.cpp) integrationTest(NAME testWindowSelection SRCS window_selection_test.cpp) integrationTest(NAME testPointerConstraints SRCS pointer_constraints_test.cpp) integrationTest(NAME testKeyboardLayout SRCS keyboard_layout_test.cpp) if (XCB_ICCCM_FOUND) integrationTest(NAME testMoveResize SRCS move_resize_window_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testStruts SRCS struts_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testShade SRCS shade_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testDontCrashAuroraeDestroyDeco SRCS dont_crash_aurorae_destroy_deco.cpp LIBS XCB::ICCCM) integrationTest(NAME testPlasmaWindow SRCS plasmawindow_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testScreenEdgeClientShow SRCS screenedge_client_show_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testX11DesktopWindow SRCS desktop_window_x11_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testXwaylandInput SRCS xwayland_input_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testWindowRules SRCS window_rules_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testX11Client SRCS x11_client_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testQuickTiling SRCS quick_tiling_test.cpp LIBS XCB::ICCCM) if (KWIN_BUILD_ACTIVITIES) integrationTest(NAME testActivities SRCS activities_test.cpp LIBS XCB::ICCCM) endif() endif() add_subdirectory(scripting) add_subdirectory(effects) diff --git a/autotests/integration/quick_tiling_test.cpp b/autotests/integration/quick_tiling_test.cpp index 9456ee655..f5068108b 100644 --- a/autotests/integration/quick_tiling_test.cpp +++ b/autotests/integration/quick_tiling_test.cpp @@ -1,412 +1,595 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "abstract_client.h" +#include "client.h" #include "cursor.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include +#include +#include + #include Q_DECLARE_METATYPE(KWin::AbstractClient::QuickTileMode) Q_DECLARE_METATYPE(KWin::MaximizeMode) namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_quick_tiling-0"); class QuickTilingTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testQuickTiling_data(); void testQuickTiling(); void testQuickMaximizing_data(); void testQuickMaximizing(); void testQuickTilingKeyboardMove_data(); void testQuickTilingKeyboardMove(); void testQuickTilingPointerMove_data(); void testQuickTilingPointerMove(); + void testX11QuickTiling_data(); + void testX11QuickTiling(); + void testX11QuickTilingAfterVertMaximize_data(); + void testX11QuickTilingAfterVertMaximize(); private: KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::Shell *m_shell = nullptr; }; void QuickTilingTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType("MaximizeMode"); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); // set custom config which disables the Outline KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup group = config->group("Outline"); group.writeEntry(QStringLiteral("QmlPath"), QString("/does/not/exist.qml")); group.sync(); kwinApp()->setConfig(config); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); } void QuickTilingTest::init() { QVERIFY(Test::setupWaylandConnection()); m_connection = Test::waylandConnection(); m_compositor = Test::waylandCompositor(); m_shell = Test::waylandShell(); screens()->setCurrent(0); } void QuickTilingTest::cleanup() { Test::destroyWaylandConnection(); } void QuickTilingTest::testQuickTiling_data() { QTest::addColumn("mode"); QTest::addColumn("expectedGeometry"); QTest::addColumn("secondScreen"); #define FLAG(name) AbstractClient::QuickTileMode(AbstractClient::QuickTile##name) QTest::newRow("left") << FLAG(Left) << QRect(0, 0, 640, 1024) << QRect(1280, 0, 640, 1024); QTest::newRow("top") << FLAG(Top) << QRect(0, 0, 1280, 512) << QRect(1280, 0, 1280, 512); QTest::newRow("right") << FLAG(Right) << QRect(640, 0, 640, 1024) << QRect(1920, 0, 640, 1024); QTest::newRow("bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512) << QRect(1280, 512, 1280, 512); QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRect(0, 0, 640, 512) << QRect(1280, 0, 640, 512); QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRect(640, 0, 640, 512) << QRect(1920, 0, 640, 512); QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRect(0, 512, 640, 512) << QRect(1280, 512, 640, 512); QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRect(640, 512, 640, 512) << QRect(1920, 512, 640, 512); QTest::newRow("maximize") << FLAG(Maximize) << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024); #undef FLAG } void QuickTilingTest::testQuickTiling() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), AbstractClient::QuickTileNone); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QFETCH(AbstractClient::QuickTileMode, mode); QFETCH(QRect, expectedGeometry); c->setQuickTileMode(mode, true); QCOMPARE(quickTileChangedSpy.count(), 1); // at this point the geometry did not yet change QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); // but quick tile mode already changed QCOMPARE(c->quickTileMode(), mode); // but we got requested a new geometry QVERIFY(sizeChangeSpy.wait()); QCOMPARE(sizeChangeSpy.count(), 1); QCOMPARE(sizeChangeSpy.first().first().toSize(), expectedGeometry.size()); // attach a new image Test::render(surface.data(), expectedGeometry.size(), Qt::red); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QEXPECT_FAIL("maximize", "Geometry changed called twice for maximize", Continue); QCOMPARE(geometryChangedSpy.count(), 1); QCOMPARE(c->geometry(), expectedGeometry); // send window to other screen QCOMPARE(c->screen(), 0); c->sendToScreen(1); QCOMPARE(c->screen(), 1); // quick tile should not be changed QCOMPARE(c->quickTileMode(), mode); QTEST(c->geometry(), "secondScreen"); } void QuickTilingTest::testQuickMaximizing_data() { QTest::addColumn("mode"); #define FLAG(name) AbstractClient::QuickTileMode(AbstractClient::QuickTile##name) QTest::newRow("maximize") << FLAG(Maximize); QTest::newRow("none") << FLAG(None); #undef FLAG } void QuickTilingTest::testQuickMaximizing() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), AbstractClient::QuickTileNone); QCOMPARE(c->maximizeMode(), MaximizeRestore); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy maximizeChangedSpy1(c, SIGNAL(clientMaximizedStateChanged(KWin::AbstractClient*,MaximizeMode))); QVERIFY(maximizeChangedSpy1.isValid()); QSignalSpy maximizeChangedSpy2(c, SIGNAL(clientMaximizedStateChanged(KWin::AbstractClient*,bool,bool))); QVERIFY(maximizeChangedSpy2.isValid()); c->setQuickTileMode(AbstractClient::QuickTileMaximize, true); QCOMPARE(quickTileChangedSpy.count(), 1); QCOMPARE(maximizeChangedSpy1.count(), 1); QCOMPARE(maximizeChangedSpy1.first().first().value(), c); QCOMPARE(maximizeChangedSpy1.first().last().value(), MaximizeFull); QCOMPARE(maximizeChangedSpy2.count(), 1); QCOMPARE(maximizeChangedSpy2.first().first().value(), c); QCOMPARE(maximizeChangedSpy2.first().at(1).toBool(), true); QCOMPARE(maximizeChangedSpy2.first().at(2).toBool(), true); // at this point the geometry did not yet change QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); // but quick tile mode already changed QCOMPARE(c->quickTileMode(), AbstractClient::QuickTileMaximize); QCOMPARE(c->maximizeMode(), MaximizeFull); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); // but we got requested a new geometry QVERIFY(sizeChangeSpy.wait()); QCOMPARE(sizeChangeSpy.count(), 1); QCOMPARE(sizeChangeSpy.first().first().toSize(), QSize(1280, 1024)); // attach a new image Test::render(surface.data(), QSize(1280, 1024), Qt::red); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 2); QCOMPARE(c->geometry(), QRect(0, 0, 1280, 1024)); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); // go back to quick tile none QFETCH(AbstractClient::QuickTileMode, mode); c->setQuickTileMode(mode, true); QCOMPARE(quickTileChangedSpy.count(), 2); QCOMPARE(maximizeChangedSpy1.count(), 2); QCOMPARE(maximizeChangedSpy1.last().first().value(), c); QCOMPARE(maximizeChangedSpy1.last().last().value(), MaximizeRestore); QCOMPARE(maximizeChangedSpy2.count(), 2); QCOMPARE(maximizeChangedSpy2.last().first().value(), c); QCOMPARE(maximizeChangedSpy2.last().at(1).toBool(), false); QCOMPARE(maximizeChangedSpy2.last().at(2).toBool(), false); QCOMPARE(c->quickTileMode(), AbstractClient::QuickTileNone); QCOMPARE(c->maximizeMode(), MaximizeRestore); // geometry not yet changed QCOMPARE(c->geometry(), QRect(0, 0, 1280, 1024)); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); // we got requested a new geometry QVERIFY(sizeChangeSpy.wait()); QCOMPARE(sizeChangeSpy.count(), 2); QCOMPARE(sizeChangeSpy.last().first().toSize(), QSize(100, 50)); // render again Test::render(surface.data(), QSize(100, 50), Qt::yellow); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 4); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); } void QuickTilingTest::testQuickTilingKeyboardMove_data() { QTest::addColumn("targetPos"); QTest::addColumn("expectedMode"); QTest::newRow("topRight") << QPoint(2559, 24) << AbstractClient::QuickTileMode(AbstractClient::QuickTileTop | AbstractClient::QuickTileRight); QTest::newRow("right") << QPoint(2559, 512) << AbstractClient::QuickTileMode(AbstractClient::QuickTileRight); QTest::newRow("bottomRight") << QPoint(2559, 1023) << AbstractClient::QuickTileMode(AbstractClient::QuickTileBottom | AbstractClient::QuickTileRight); QTest::newRow("bottomLeft") << QPoint(0, 1023) << AbstractClient::QuickTileMode(AbstractClient::QuickTileBottom | AbstractClient::QuickTileLeft); QTest::newRow("Left") << QPoint(0, 512) << AbstractClient::QuickTileMode(AbstractClient::QuickTileLeft); QTest::newRow("topLeft") << QPoint(0, 24) << AbstractClient::QuickTileMode(AbstractClient::QuickTileTop | AbstractClient::QuickTileLeft); } void QuickTilingTest::testQuickTilingKeyboardMove() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), AbstractClient::QuickTileNone); QCOMPARE(c->maximizeMode(), MaximizeRestore); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); workspace()->performWindowOperation(c, Options::UnrestrictedMoveOp); QCOMPARE(c, workspace()->getMovingClient()); QCOMPARE(Cursor::pos(), QPoint(49, 24)); QFETCH(QPoint, targetPos); quint32 timestamp = 1; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); while (Cursor::pos().x() > targetPos.x()) { kwinApp()->platform()->keyboardKeyPressed(KEY_LEFT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFT, timestamp++); } while (Cursor::pos().x() < targetPos.x()) { kwinApp()->platform()->keyboardKeyPressed(KEY_RIGHT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_RIGHT, timestamp++); } while (Cursor::pos().y() < targetPos.y()) { kwinApp()->platform()->keyboardKeyPressed(KEY_DOWN, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_DOWN, timestamp++); } while (Cursor::pos().y() > targetPos.y()) { kwinApp()->platform()->keyboardKeyPressed(KEY_UP, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_UP, timestamp++); } kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_ENTER, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_ENTER, timestamp++); QCOMPARE(Cursor::pos(), targetPos); QVERIFY(!workspace()->getMovingClient()); QCOMPARE(quickTileChangedSpy.count(), 1); QTEST(c->quickTileMode(), "expectedMode"); } void QuickTilingTest::testQuickTilingPointerMove_data() { QTest::addColumn("targetPos"); QTest::addColumn("expectedMode"); QTest::newRow("topRight") << QPoint(2559, 24) << AbstractClient::QuickTileMode(AbstractClient::QuickTileTop | AbstractClient::QuickTileRight); QTest::newRow("right") << QPoint(2559, 512) << AbstractClient::QuickTileMode(AbstractClient::QuickTileRight); QTest::newRow("bottomRight") << QPoint(2559, 1023) << AbstractClient::QuickTileMode(AbstractClient::QuickTileBottom | AbstractClient::QuickTileRight); QTest::newRow("bottomLeft") << QPoint(0, 1023) << AbstractClient::QuickTileMode(AbstractClient::QuickTileBottom | AbstractClient::QuickTileLeft); QTest::newRow("Left") << QPoint(0, 512) << AbstractClient::QuickTileMode(AbstractClient::QuickTileLeft); QTest::newRow("topLeft") << QPoint(0, 24) << AbstractClient::QuickTileMode(AbstractClient::QuickTileTop | AbstractClient::QuickTileLeft); } void QuickTilingTest::testQuickTilingPointerMove() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), AbstractClient::QuickTileNone); QCOMPARE(c->maximizeMode(), MaximizeRestore); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); workspace()->performWindowOperation(c, Options::UnrestrictedMoveOp); QCOMPARE(c, workspace()->getMovingClient()); QCOMPARE(Cursor::pos(), QPoint(49, 24)); QFETCH(QPoint, targetPos); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(targetPos, timestamp++); kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QCOMPARE(Cursor::pos(), targetPos); QVERIFY(!workspace()->getMovingClient()); QCOMPARE(quickTileChangedSpy.count(), 1); QTEST(c->quickTileMode(), "expectedMode"); } +struct XcbConnectionDeleter +{ + static inline void cleanup(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +void QuickTilingTest::testX11QuickTiling_data() +{ + QTest::addColumn("mode"); + QTest::addColumn("expectedGeometry"); + +#define FLAG(name) AbstractClient::QuickTileMode(AbstractClient::QuickTile##name) + + QTest::newRow("left") << FLAG(Left) << QRect(0, 0, 640, 1024); + QTest::newRow("top") << FLAG(Top) << QRect(0, 0, 1280, 512); + QTest::newRow("right") << FLAG(Right) << QRect(640, 0, 640, 1024); + QTest::newRow("bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512); + + QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRect(0, 0, 640, 512); + QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRect(640, 0, 640, 512); + QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRect(0, 512, 640, 512); + QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRect(640, 512, 640, 512); + + QTest::newRow("maximize") << FLAG(Maximize) << QRect(0, 0, 1280, 1024); + +#undef FLAG +} +void QuickTilingTest::testX11QuickTiling() +{ + QScopedPointer c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.data())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t w = xcb_generate_id(c.data()); + xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); + xcb_map_window(c.data(), w); + xcb_flush(c.data()); + + // we should get a client for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); + QVERIFY(windowCreatedSpy.isValid()); + QVERIFY(windowCreatedSpy.wait()); + Client *client = windowCreatedSpy.first().first().value(); + QVERIFY(client); + QCOMPARE(client->window(), w); + + // now quick tile + QSignalSpy quickTileChangedSpy(client, &AbstractClient::quickTileModeChanged); + QVERIFY(quickTileChangedSpy.isValid()); + const QRect origGeo = client->geometry(); + QFETCH(AbstractClient::QuickTileMode, mode); + client->setQuickTileMode(mode, true); + QCOMPARE(client->quickTileMode(), mode); + QTEST(client->geometry(), "expectedGeometry"); + QCOMPARE(client->geometryRestore(), origGeo); + QEXPECT_FAIL("maximize", "For maximize we get two changed signals", Continue); + QCOMPARE(quickTileChangedSpy.count(), 1); + + // and destroy the window again + xcb_unmap_window(c.data(), w); + xcb_destroy_window(c.data(), w); + xcb_flush(c.data()); + c.reset(); + + QSignalSpy windowClosedSpy(client, &Client::windowClosed); + QVERIFY(windowClosedSpy.isValid()); + QVERIFY(windowClosedSpy.wait()); +} + +void QuickTilingTest::testX11QuickTilingAfterVertMaximize_data() +{ + QTest::addColumn("mode"); + QTest::addColumn("expectedGeometry"); + +#define FLAG(name) AbstractClient::QuickTileMode(AbstractClient::QuickTile##name) + + QTest::newRow("left") << FLAG(Left) << QRect(0, 0, 640, 1024); + QTest::newRow("top") << FLAG(Top) << QRect(0, 0, 1280, 512); + QTest::newRow("right") << FLAG(Right) << QRect(640, 0, 640, 1024); + QTest::newRow("bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512); + + QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRect(0, 0, 640, 512); + QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRect(640, 0, 640, 512); + QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRect(0, 512, 640, 512); + QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRect(640, 512, 640, 512); + + QTest::newRow("maximize") << FLAG(Maximize) << QRect(0, 0, 1280, 1024); + +#undef FLAG +} + +void QuickTilingTest::testX11QuickTilingAfterVertMaximize() +{ + QScopedPointer c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.data())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t w = xcb_generate_id(c.data()); + xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); + xcb_map_window(c.data(), w); + xcb_flush(c.data()); + + // we should get a client for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); + QVERIFY(windowCreatedSpy.isValid()); + QVERIFY(windowCreatedSpy.wait()); + Client *client = windowCreatedSpy.first().first().value(); + QVERIFY(client); + QCOMPARE(client->window(), w); + + const QRect origGeo = client->geometry(); + QCOMPARE(client->maximizeMode(), MaximizeRestore); + // vertically maximize the window + client->maximize(client->maximizeMode() ^ MaximizeVertical); + QCOMPARE(client->geometry().width(), origGeo.width()); + QCOMPARE(client->height(), screens()->size(client->screen()).height()); + QCOMPARE(client->geometryRestore(), origGeo); + + // now quick tile + QSignalSpy quickTileChangedSpy(client, &AbstractClient::quickTileModeChanged); + QVERIFY(quickTileChangedSpy.isValid()); + QFETCH(AbstractClient::QuickTileMode, mode); + client->setQuickTileMode(mode, true); + QEXPECT_FAIL("left", "Quick tiling not working", Continue); + QEXPECT_FAIL("right", "Quick tiling not working", Continue); + QEXPECT_FAIL("top", "Quick tiling not working", Continue); + QEXPECT_FAIL("bottom", "Quick tiling not working", Continue); + QEXPECT_FAIL("top left", "Quick tiling not working", Continue); + QEXPECT_FAIL("top right", "Quick tiling not working", Continue); + QEXPECT_FAIL("bottom left", "Quick tiling not working", Continue); + QEXPECT_FAIL("bottom right", "Quick tiling not working", Continue); + QCOMPARE(client->quickTileMode(), mode); + QEXPECT_FAIL("left", "Quick tiling not working", Continue); + QEXPECT_FAIL("right", "Quick tiling not working", Continue); + QEXPECT_FAIL("top", "Quick tiling not working", Continue); + QEXPECT_FAIL("bottom", "Quick tiling not working", Continue); + QEXPECT_FAIL("top left", "Quick tiling not working", Continue); + QEXPECT_FAIL("top right", "Quick tiling not working", Continue); + QEXPECT_FAIL("bottom left", "Quick tiling not working", Continue); + QEXPECT_FAIL("bottom right", "Quick tiling not working", Continue); + QTEST(client->geometry(), "expectedGeometry"); + QEXPECT_FAIL("", "We get two changed events", Continue); + QCOMPARE(quickTileChangedSpy.count(), 1); + + // and destroy the window again + xcb_unmap_window(c.data(), w); + xcb_destroy_window(c.data(), w); + xcb_flush(c.data()); + c.reset(); + + QSignalSpy windowClosedSpy(client, &Client::windowClosed); + QVERIFY(windowClosedSpy.isValid()); + QVERIFY(windowClosedSpy.wait()); +} + } WAYLANDTEST_MAIN(KWin::QuickTilingTest) #include "quick_tiling_test.moc" diff --git a/kcmkwin/kwindecoration/kcm.cpp b/kcmkwin/kwindecoration/kcm.cpp index 035bc8b2a..231b3e6a8 100644 --- a/kcmkwin/kwindecoration/kcm.cpp +++ b/kcmkwin/kwindecoration/kcm.cpp @@ -1,445 +1,433 @@ /* * Copyright 2014 Martin Gräßlin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 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 14 of version 3 of the license. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kcm.h" #include "decorationmodel.h" #include "declarative-plugin/buttonsmodel.h" #include // KDE #include #include #include #include #include #include // Qt #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY(KDecorationFactory, registerPlugin(); ) Q_DECLARE_METATYPE(KDecoration2::BorderSize) namespace KDecoration2 { namespace Configuration { static const QString s_pluginName = QStringLiteral("org.kde.kdecoration2"); #if HAVE_BREEZE_DECO static const QString s_defaultPlugin = QStringLiteral(BREEZE_KDECORATION_PLUGIN_ID); static const QString s_defaultTheme; #else static const QString s_defaultPlugin = QStringLiteral("org.kde.kwin.aurorae"); static const QString s_defaultTheme = QStringLiteral("kwin4_decoration_qml_plastik"); #endif static const QString s_borderSizeNormal = QStringLiteral("Normal"); static const QString s_ghnsIcon = QStringLiteral("get-hot-new-stuff"); ConfigurationForm::ConfigurationForm(QWidget *parent) : QWidget(parent) { setupUi(this); } static bool s_loading = false; ConfigurationModule::ConfigurationModule(QWidget *parent, const QVariantList &args) : KCModule(parent, args) , m_model(new DecorationsModel(this)) , m_proxyModel(new QSortFilterProxyModel(this)) , m_ui(new ConfigurationForm(this)) , m_leftButtons(new Preview::ButtonsModel(QVector(), this)) , m_rightButtons(new Preview::ButtonsModel(QVector(), this)) , m_availableButtons(new Preview::ButtonsModel(this)) { m_proxyModel->setSourceModel(m_model); m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); connect(m_ui->filter, &QLineEdit::textChanged, m_proxyModel, &QSortFilterProxyModel::setFilterFixedString); m_quickView = new QQuickView(0); KDeclarative::KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(m_quickView->engine()); kdeclarative.setTranslationDomain(QStringLiteral(TRANSLATION_DOMAIN)); kdeclarative.setupBindings(); qmlRegisterType(); QWidget *widget = QWidget::createWindowContainer(m_quickView, this); QVBoxLayout* layout = new QVBoxLayout(m_ui->view); layout->setContentsMargins(0,0,0,0); layout->addWidget(widget); m_quickView->rootContext()->setContextProperty(QStringLiteral("decorationsModel"), m_proxyModel); updateColors(); m_quickView->rootContext()->setContextProperty("_borderSizesIndex", 3); // 3 is normal - m_quickView->rootContext()->setContextProperty("configurationModule", this); + m_quickView->rootContext()->setContextProperty("leftButtons", m_leftButtons); + m_quickView->rootContext()->setContextProperty("rightButtons", m_rightButtons); + m_quickView->rootContext()->setContextProperty("availableButtons", m_availableButtons); + m_quickView->rootContext()->setContextProperty("titleFont", QFontDatabase::systemFont(QFontDatabase::TitleFont)); m_quickView->setResizeMode(QQuickView::SizeRootObjectToView); m_quickView->setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/kcm_kwindecoration/main.qml")))); if (m_quickView->status() == QQuickView::Ready) { auto listView = m_quickView->rootObject()->findChild("listView"); if (listView) { connect(listView, SIGNAL(currentIndexChanged()), this, SLOT(changed())); } } m_ui->tabWidget->tabBar()->disconnect(); auto setCurrentTab = [this](int index) { if (index == 0) m_ui->doubleClickMessage->hide(); m_ui->filter->setVisible(index == 0); m_ui->knsButton->setVisible(index == 0); if (auto themeList = m_quickView->rootObject()->findChild("themeList")) { themeList->setVisible(index == 0); } m_ui->borderSizesLabel->setVisible(index == 0); m_ui->borderSizesCombo->setVisible(index == 0); m_ui->closeWindowsDoubleClick->setVisible(index == 1); if (auto buttonLayout = m_quickView->rootObject()->findChild("buttonLayout")) { buttonLayout->setVisible(index == 1); } }; connect(m_ui->tabWidget->tabBar(), &QTabBar::currentChanged, this, setCurrentTab); setCurrentTab(0); m_ui->doubleClickMessage->setVisible(false); m_ui->doubleClickMessage->setText(i18n("Close by double clicking:\n To open the menu, keep the button pressed until it appears.")); m_ui->doubleClickMessage->setCloseButtonVisible(true); m_ui->borderSizesCombo->setItemData(0, QVariant::fromValue(BorderSize::None)); m_ui->borderSizesCombo->setItemData(1, QVariant::fromValue(BorderSize::NoSides)); m_ui->borderSizesCombo->setItemData(2, QVariant::fromValue(BorderSize::Tiny)); m_ui->borderSizesCombo->setItemData(3, QVariant::fromValue(BorderSize::Normal)); m_ui->borderSizesCombo->setItemData(4, QVariant::fromValue(BorderSize::Large)); m_ui->borderSizesCombo->setItemData(5, QVariant::fromValue(BorderSize::VeryLarge)); m_ui->borderSizesCombo->setItemData(6, QVariant::fromValue(BorderSize::Huge)); m_ui->borderSizesCombo->setItemData(7, QVariant::fromValue(BorderSize::VeryHuge)); m_ui->borderSizesCombo->setItemData(8, QVariant::fromValue(BorderSize::Oversized)); m_ui->knsButton->setIcon(QIcon::fromTheme(s_ghnsIcon)); auto changedSlot = static_cast(&ConfigurationModule::changed); connect(m_ui->closeWindowsDoubleClick, &QCheckBox::stateChanged, this, changedSlot); connect(m_ui->closeWindowsDoubleClick, &QCheckBox::toggled, this, [this] (bool toggled) { if (s_loading) { return; } if (toggled) m_ui->doubleClickMessage->animatedShow(); else m_ui->doubleClickMessage->animatedHide(); } ); connect(m_ui->borderSizesCombo, static_cast(&QComboBox::currentIndexChanged), this, [this] (int index) { auto listView = m_quickView->rootObject()->findChild("listView"); if (listView) { listView->setProperty("borderSizesIndex", index); } changed(); } ); connect(m_model, &QAbstractItemModel::modelReset, this, [this] { const auto &kns = m_model->knsProviders(); m_ui->knsButton->setEnabled(!kns.isEmpty()); if (kns.isEmpty()) { return; } if (kns.count() > 1) { QMenu *menu = new QMenu(m_ui->knsButton); for (auto it = kns.begin(); it != kns.end(); ++it) { QAction *action = menu->addAction(QIcon::fromTheme(s_ghnsIcon), it.value()); action->setData(it.key()); connect(action, &QAction::triggered, this, [this, action] { showKNS(action->data().toString());}); } m_ui->knsButton->setMenu(menu); } } ); connect(m_ui->knsButton, &QPushButton::clicked, this, [this] { const auto &kns = m_model->knsProviders(); if (kns.isEmpty()) { return; } showKNS(kns.firstKey()); } ); connect(m_leftButtons, &QAbstractItemModel::rowsInserted, this, changedSlot); connect(m_leftButtons, &QAbstractItemModel::rowsMoved, this, changedSlot); connect(m_leftButtons, &QAbstractItemModel::rowsRemoved, this, changedSlot); connect(m_rightButtons, &QAbstractItemModel::rowsInserted, this, changedSlot); connect(m_rightButtons, &QAbstractItemModel::rowsMoved, this, changedSlot); connect(m_rightButtons, &QAbstractItemModel::rowsRemoved, this, changedSlot); QVBoxLayout *l = new QVBoxLayout(this); l->addWidget(m_ui); QMetaObject::invokeMethod(m_model, "init", Qt::QueuedConnection); m_ui->installEventFilter(this); } ConfigurationModule::~ConfigurationModule() = default; void ConfigurationModule::showEvent(QShowEvent *ev) { KCModule::showEvent(ev); } static const QMap s_sizes = QMap({ {QStringLiteral("None"), BorderSize::None}, {QStringLiteral("NoSides"), BorderSize::NoSides}, {QStringLiteral("Tiny"), BorderSize::Tiny}, {s_borderSizeNormal, BorderSize::Normal}, {QStringLiteral("Large"), BorderSize::Large}, {QStringLiteral("VeryLarge"), BorderSize::VeryLarge}, {QStringLiteral("Huge"), BorderSize::Huge}, {QStringLiteral("VeryHuge"), BorderSize::VeryHuge}, {QStringLiteral("Oversized"), BorderSize::Oversized} }); static BorderSize stringToSize(const QString &name) { auto it = s_sizes.constFind(name); if (it == s_sizes.constEnd()) { // non sense values are interpreted just like normal return BorderSize::Normal; } return it.value(); } static QString sizeToString(BorderSize size) { return s_sizes.key(size, s_borderSizeNormal); } static QHash s_buttonNames; static void initButtons() { if (!s_buttonNames.isEmpty()) { return; } s_buttonNames[KDecoration2::DecorationButtonType::Menu] = QChar('M'); s_buttonNames[KDecoration2::DecorationButtonType::ApplicationMenu] = QChar('N'); s_buttonNames[KDecoration2::DecorationButtonType::OnAllDesktops] = QChar('S'); s_buttonNames[KDecoration2::DecorationButtonType::ContextHelp] = QChar('H'); s_buttonNames[KDecoration2::DecorationButtonType::Minimize] = QChar('I'); s_buttonNames[KDecoration2::DecorationButtonType::Maximize] = QChar('A'); s_buttonNames[KDecoration2::DecorationButtonType::Close] = QChar('X'); s_buttonNames[KDecoration2::DecorationButtonType::KeepAbove] = QChar('F'); s_buttonNames[KDecoration2::DecorationButtonType::KeepBelow] = QChar('B'); s_buttonNames[KDecoration2::DecorationButtonType::Shade] = QChar('L'); } static QString buttonsToString(const QVector &buttons) { auto buttonToString = [](KDecoration2::DecorationButtonType button) -> QChar { const auto it = s_buttonNames.constFind(button); if (it != s_buttonNames.constEnd()) { return it.value(); } return QChar(); }; QString ret; for (auto button : buttons) { ret.append(buttonToString(button)); } return ret; } static QVector< KDecoration2::DecorationButtonType > readDecorationButtons(const KConfigGroup &config, const char *key, const QVector< KDecoration2::DecorationButtonType > &defaultValue) { initButtons(); auto buttonsFromString = [](const QString &buttons) -> QVector { QVector ret; for (auto it = buttons.begin(); it != buttons.end(); ++it) { for (auto it2 = s_buttonNames.constBegin(); it2 != s_buttonNames.constEnd(); ++it2) { if (it2.value() == (*it)) { ret << it2.key(); } } } return ret; }; return buttonsFromString(config.readEntry(key, buttonsToString(defaultValue))); } void ConfigurationModule::load() { s_loading = true; const KConfigGroup config = KSharedConfig::openConfig("kwinrc")->group(s_pluginName); const QString plugin = config.readEntry("library", s_defaultPlugin); const QString theme = config.readEntry("theme", s_defaultTheme); const QModelIndex index = m_proxyModel->mapFromSource(m_model->findDecoration(plugin, theme)); if (auto listView = m_quickView->rootObject()->findChild("listView")) { listView->setProperty("currentIndex", index.isValid() ? index.row() : -1); } m_ui->closeWindowsDoubleClick->setChecked(config.readEntry("CloseOnDoubleClickOnMenu", false)); const QVariant border = QVariant::fromValue(stringToSize(config.readEntry("BorderSize", s_borderSizeNormal))); m_ui->borderSizesCombo->setCurrentIndex(m_ui->borderSizesCombo->findData(border)); // buttons const auto &left = readDecorationButtons(config, "ButtonsOnLeft", QVector{ KDecoration2::DecorationButtonType::Menu, KDecoration2::DecorationButtonType::OnAllDesktops }); while (m_leftButtons->rowCount() > 0) { m_leftButtons->remove(0); } for (auto it = left.begin(); it != left.end(); ++it) { m_leftButtons->add(*it); } const auto &right = readDecorationButtons(config, "ButtonsOnRight", QVector{ KDecoration2::DecorationButtonType::ContextHelp, KDecoration2::DecorationButtonType::Minimize, KDecoration2::DecorationButtonType::Maximize, KDecoration2::DecorationButtonType::Close }); while (m_rightButtons->rowCount() > 0) { m_rightButtons->remove(0); } for (auto it = right.begin(); it != right.end(); ++it) { m_rightButtons->add(*it); } KCModule::load(); s_loading = false; } void ConfigurationModule::save() { KConfigGroup config = KSharedConfig::openConfig("kwinrc")->group(s_pluginName); config.writeEntry("CloseOnDoubleClickOnMenu", m_ui->closeWindowsDoubleClick->isChecked()); config.writeEntry("BorderSize", sizeToString(m_ui->borderSizesCombo->currentData().value())); if (auto listView = m_quickView->rootObject()->findChild("listView")) { const int currentIndex = listView->property("currentIndex").toInt(); if (currentIndex != -1) { const QModelIndex index = m_proxyModel->index(currentIndex, 0); if (index.isValid()) { config.writeEntry("library", index.data(Qt::UserRole + 4).toString()); const QString theme = index.data(Qt::UserRole +5).toString(); if (theme.isEmpty()) { config.deleteEntry("theme"); } else { config.writeEntry("theme", theme); } } } } config.writeEntry("ButtonsOnLeft", buttonsToString(m_leftButtons->buttons())); config.writeEntry("ButtonsOnRight", buttonsToString(m_rightButtons->buttons())); config.sync(); KCModule::save(); // Send signal to all kwin instances QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KWin"), QStringLiteral("org.kde.KWin"), QStringLiteral("reloadConfig")); QDBusConnection::sessionBus().send(message); } void ConfigurationModule::defaults() { if (auto listView = m_quickView->rootObject()->findChild("listView")) { const QModelIndex index = m_proxyModel->mapFromSource(m_model->findDecoration(s_defaultPlugin)); listView->setProperty("currentIndex", index.isValid() ? index.row() : -1); } m_ui->borderSizesCombo->setCurrentIndex(m_ui->borderSizesCombo->findData(QVariant::fromValue(stringToSize(s_borderSizeNormal)))); m_ui->closeWindowsDoubleClick->setChecked(false); KCModule::defaults(); } void ConfigurationModule::showKNS(const QString &config) { QPointer downloadDialog = new KNS3::DownloadDialog(config, this); if (downloadDialog->exec() == QDialog::Accepted && !downloadDialog->changedEntries().isEmpty()) { auto listView = m_quickView->rootObject()->findChild("listView"); QString selectedPluginName; QString selectedThemeName; if (listView) { const QModelIndex index = m_proxyModel->index(listView->property("currentIndex").toInt(), 0); if (index.isValid()) { selectedPluginName = index.data(Qt::UserRole + 4).toString(); selectedThemeName = index.data(Qt::UserRole + 5).toString(); } } m_model->init(); if (!selectedPluginName.isEmpty()) { const QModelIndex index = m_model->findDecoration(selectedPluginName, selectedThemeName); const QModelIndex proxyIndex = m_proxyModel->mapFromSource(index); if (listView) { listView->setProperty("currentIndex", proxyIndex.isValid() ? proxyIndex.row() : -1); } } } delete downloadDialog; } -QAbstractItemModel *ConfigurationModule::leftButtons() const -{ - return m_leftButtons; -} - -QAbstractItemModel *ConfigurationModule::rightButtons() const -{ - return m_rightButtons; -} - -QAbstractItemModel *ConfigurationModule::availableButtons() const -{ - return m_availableButtons; -} - bool ConfigurationModule::eventFilter(QObject *watched, QEvent *e) { if (watched != m_ui) { return false; } if (e->type() == QEvent::PaletteChange) { updateColors(); } return false; } void ConfigurationModule::updateColors() { m_quickView->rootContext()->setContextProperty("backgroundColor", m_ui->palette().color(QPalette::Active, QPalette::Window)); m_quickView->rootContext()->setContextProperty("highlightColor", m_ui->palette().color(QPalette::Active, QPalette::Highlight)); m_quickView->rootContext()->setContextProperty("baseColor", m_ui->palette().color(QPalette::Active, QPalette::Base)); } } } #include "kcm.moc" diff --git a/kcmkwin/kwindecoration/kcm.h b/kcmkwin/kwindecoration/kcm.h index 1d7ee7378..99fa43114 100644 --- a/kcmkwin/kwindecoration/kcm.h +++ b/kcmkwin/kwindecoration/kcm.h @@ -1,87 +1,80 @@ /* * Copyright 2014 Martin Gräßlin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 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 14 of version 3 of the license. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KDECORATIONS_KCM_H #define KDECORATIONS_KCM_H #include #include #include class QSortFilterProxyModel; class QQuickView; namespace KDecoration2 { namespace Preview { class PreviewBridge; class ButtonsModel; } namespace Configuration { class DecorationsModel; class ConfigurationForm : public QWidget, public Ui::KCMForm { public: explicit ConfigurationForm(QWidget* parent); }; class ConfigurationModule : public KCModule { Q_OBJECT - Q_PROPERTY(QAbstractItemModel* leftButtons READ leftButtons CONSTANT) - Q_PROPERTY(QAbstractItemModel* rightButtons READ rightButtons CONSTANT) - Q_PROPERTY(QAbstractItemModel* availableButtons READ availableButtons CONSTANT) public: explicit ConfigurationModule(QWidget *parent = nullptr, const QVariantList &args = QVariantList()); virtual ~ConfigurationModule(); - QAbstractItemModel *leftButtons() const; - QAbstractItemModel *rightButtons() const; - QAbstractItemModel *availableButtons() const; - bool eventFilter(QObject *watched, QEvent *e) override; public Q_SLOTS: void defaults() override; void load() override; void save() override; protected: void showEvent(QShowEvent *ev) override; private: void showKNS(const QString &config); void updateColors(); DecorationsModel *m_model; QSortFilterProxyModel *m_proxyModel; ConfigurationForm *m_ui; QQuickView *m_quickView; Preview::ButtonsModel *m_leftButtons; Preview::ButtonsModel *m_rightButtons; Preview::ButtonsModel *m_availableButtons; }; } } #endif diff --git a/kcmkwin/kwindecoration/qml/Buttons.qml b/kcmkwin/kwindecoration/qml/Buttons.qml index 2e6487306..64ab0f9be 100644 --- a/kcmkwin/kwindecoration/qml/Buttons.qml +++ b/kcmkwin/kwindecoration/qml/Buttons.qml @@ -1,233 +1,233 @@ /* * Copyright 2014 Martin Gräßlin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 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 14 of version 3 of the license. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.3 import QtQuick.Controls 1.2 import QtQuick.Layouts 1.1 import org.kde.kwin.private.kdecoration 1.0 as KDecoration import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons; import org.kde.plasma.core 2.0 as PlasmaCore; Item { objectName: "buttonLayout" Layout.preferredHeight: layout.height KDecoration.Bridge { id: bridgeItem plugin: "org.kde.breeze" } KDecoration.Settings { id: settingsItem bridge: bridgeItem.bridge } Rectangle { anchors.fill: parent anchors.topMargin: units.gridUnit / 2 border.width: Math.ceil(units.gridUnit / 16.0) color: SystemPalette.base; border.color: highlightColor; ColumnLayout { id: layout width: parent.width height: titlebarRect.height + availableGrid.height + dragHint.height + 5*layout.spacing Rectangle { id: titlebarRect height: buttonPreviewRow.height + units.smallSpacing Layout.fillWidth: true border.width: Math.ceil(units.gridUnit / 16.0) border.color: highlightColor color: SystemPalette.base; RowLayout { id: buttonPreviewRow anchors.top: parent.top; anchors.left: parent.left; anchors.right: parent.right; anchors.margins: units.smallSpacing / 2 height: Math.max(units.iconSizes.small, titlebar.implicitHeight) + units.smallSpacing/2 ButtonGroup { id: leftButtonsView anchors.left: parent.left; height: buttonPreviewRow.height - model: configurationModule.leftButtons + model: leftButtons key: "decoButtonLeft" } Item { Layout.fillWidth: true anchors.centerIn: parent height: titlebar.implicitHeight Label { id: titlebar anchors.centerIn: parent font: titleFont text: i18n("Titlebar") } } ButtonGroup { id: rightButtonsView anchors.right: parent.right; height: buttonPreviewRow.height - model: configurationModule.rightButtons + model: rightButtons key: "decoButtonRight" } DropArea { anchors.fill: parent keys: [ "decoButtonAdd", "decoButtonRight", "decoButtonLeft" ] onEntered: { drag.accept(); } onDropped: { var view = undefined; var left = drag.x - (leftButtonsView.x + leftButtonsView.width); var right = drag.x - rightButtonsView.x; if (Math.abs(left) <= Math.abs(right)) { view = leftButtonsView; } else { view = rightButtonsView; } if (!view) { return; } var point = mapToItem(view, drag.x, drag.y); var index = view.indexAt(point.x, point.y); if (index == -1 && (view.x + view.width <= drag.x)) { index = view.count - 1; } if (drop.keys.indexOf("decoButtonAdd") != -1) { view.model.add(index, drag.source.type); } else if (drop.keys.indexOf("decoButtonLeft") != -1) { if (view == leftButtonsView) { // move in same view if (index != drag.source.itemIndex) { drag.source.buttonsModel.move(drag.source.itemIndex, index); } } else { // move to right view view.model.add(index, drag.source.type); drag.source.buttonsModel.remove(drag.source.itemIndex); } } else if (drop.keys.indexOf("decoButtonRight") != -1) { if (view == rightButtonsView) { // move in same view if (index != drag.source.itemIndex) { drag.source.buttonsModel.move(drag.source.itemIndex, index); } } else { // move to right view view.model.add(index, drag.source.type); drag.source.buttonsModel.remove(drag.source.itemIndex); } } } } } } Text { id: iCannotBelieveIDoThis text: "gnarf" visible: false } GridView { id: availableGrid Layout.fillWidth: true - model: configurationModule.availableButtons + model: availableButtons interactive: false cellWidth: iconLabel.implicitWidth cellHeight: units.iconSizes.small + iCannotBelieveIDoThis.implicitHeight + 4*units.smallSpacing height: Math.ceil(cellHeight * 2.5) opacity: (leftButtonsView.dragging || rightButtonsView.dragging) ? 0.25 : 1.0 delegate: Item { id: availableDelegate width: availableGrid.cellWidth height: availableGrid.cellHeight KDecoration.Button { id: availableButton anchors.centerIn: Drag.active ? undefined : parent bridge: bridgeItem.bridge settings: settingsItem type: model["button"] width: units.iconSizes.small height: units.iconSizes.small Drag.keys: [ "decoButtonAdd" ] Drag.active: dragArea.drag.active } Label { id: iconLabel text: model["display"] horizontalAlignment: Text.AlignHCenter anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right elide: Text.ElideRight wrapMode: Text.NoWrap } MouseArea { id: dragArea anchors.fill: parent drag.target: availableButton cursorShape: Qt.PointingHandCursor onReleased: { if (availableButton.Drag.target) { availableButton.Drag.drop(); } else { availableButton.Drag.cancel(); } } } } } Text { id: dragHint visible: !(leftButtonsView.dragging || rightButtonsView.dragging || availableGrid.dragging) Layout.fillWidth: true color: SystemPalette.text; opacity: 0.66 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignTop text: i18n("Drag buttons between here and the titlebar") } DropArea { anchors.fill: availableGrid keys: [ "decoButtonRemove" ] onEntered: { drag.accept(); } onDropped: { drag.source.buttonsModel.remove(drag.source.itemIndex); } ColumnLayout { anchors.centerIn: parent visible: leftButtonsView.dragging || rightButtonsView.dragging Label { text: i18n("Drop here to remove button") font.bold: true } KQuickControlsAddons.QIconItem { id: icon width: 64 height: 64 icon: "list-remove" Layout.alignment: Qt.AlignHCenter } Item { Layout.fillHeight: true } } } } } }