/******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2009 Lucas Murray Copyright (C) 2012 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 "virtualdesktops.h" // KDE #include #include #include #include #include #include namespace KWin { extern int screen_number; VirtualDesktopGrid::VirtualDesktopGrid() : m_size(1, 2) // Default to tow rows , m_grid(new uint[2]) { // Initializing grid array m_grid[0] = 0; m_grid[1] = 0; } VirtualDesktopGrid::~VirtualDesktopGrid() { delete[] m_grid; } void VirtualDesktopGrid::update(const QSize &size, Qt::Orientation orientation) { // Set private variables delete[] m_grid; m_size = size; const uint width = size.width(); const uint height = size.height(); const uint length = width * height; const uint desktopCount = VirtualDesktopManager::self()->count(); m_grid = new uint[length]; // Populate grid uint desktop = 1; if (orientation == Qt::Horizontal) { for (uint y = 0; y < height; ++y) { for (uint x = 0; x < width; ++x) { m_grid[y * width + x] = (desktop <= desktopCount ? desktop++ : 0); } } } else { for (uint x = 0; x < width; ++x) { for (uint y = 0; y < height; ++y) { m_grid[y * width + x] = (desktop <= desktopCount ? desktop++ : 0); } } } } QPoint VirtualDesktopGrid::gridCoords(uint id) const { for (int y = 0; y < m_size.height(); ++y) { for (int x = 0; x < m_size.width(); ++x) { if (m_grid[y * m_size.width() + x] == id) { return QPoint(x, y); } } } return QPoint(-1, -1); } KWIN_SINGLETON_FACTORY_VARIABLE(VirtualDesktopManager, s_manager) VirtualDesktopManager::VirtualDesktopManager(QObject *parent) : QObject(parent) , m_current(0) , m_count(0) , m_navigationWrapsAround(false) , m_rootInfo(NULL) { } VirtualDesktopManager::~VirtualDesktopManager() { s_manager = NULL; } QString VirtualDesktopManager::name(uint desktop) const { if (!m_rootInfo) { return defaultName(desktop); } return QString::fromUtf8(m_rootInfo->desktopName(desktop)); } uint VirtualDesktopManager::above(uint id, bool wrap) const { if (id == 0) { id = current(); } QPoint coords = m_grid.gridCoords(id); Q_ASSERT(coords.x() >= 0); while (true) { coords.ry()--; if (coords.y() < 0) { if (wrap) { coords.setY(m_grid.height() - 1); } else { return id; // Already at the top-most desktop } } const uint desktop = m_grid.at(coords); if (desktop > 0) { return desktop; } } } uint VirtualDesktopManager::toRight(uint id, bool wrap) const { if (id == 0) { id = current(); } QPoint coords = m_grid.gridCoords(id); Q_ASSERT(coords.x() >= 0); while (true) { coords.rx()++; if (coords.x() >= m_grid.width()) { if (wrap) { coords.setX(0); } else { return id; // Already at the right-most desktop } } const uint desktop = m_grid.at(coords); if (desktop > 0) { return desktop; } } } uint VirtualDesktopManager::below(uint id, bool wrap) const { if (id == 0) { id = current(); } QPoint coords = m_grid.gridCoords(id); Q_ASSERT(coords.x() >= 0); while (true) { coords.ry()++; if (coords.y() >= m_grid.height()) { if (wrap) { coords.setY(0); } else { // Already at the bottom-most desktop return id; } } const uint desktop = m_grid.at(coords); if (desktop > 0) { return desktop; } } } uint VirtualDesktopManager::toLeft(uint id, bool wrap) const { if (id == 0) { id = current(); } QPoint coords = m_grid.gridCoords(id); Q_ASSERT(coords.x() >= 0); while (true) { coords.rx()--; if (coords.x() < 0) { if (wrap) { coords.setX(m_grid.width() - 1); } else { return id; // Already at the left-most desktop } } const uint desktop = m_grid.at(coords); if (desktop > 0) { return desktop; } } } uint VirtualDesktopManager::next(uint id, bool wrap) const { if (id == 0) { id = current(); } const uint desktop = id + 1; if (desktop > count()) { if (wrap) { return 1; } else { // are at the last desktop, without wrap return current return id; } } return desktop; } uint VirtualDesktopManager::previous(uint id, bool wrap) const { if (id == 0) { id = current(); } const uint desktop = id - 1; if (desktop == 0) { if (wrap) { return count(); } else { // are at the first desktop, without wrap return current return id; } } return desktop; } bool VirtualDesktopManager::setCurrent(uint newDesktop) { if (newDesktop < 1 || newDesktop > count() || newDesktop == m_current) { return false; } const uint oldDesktop = m_current; // change the desktop m_current = newDesktop; emit currentChanged(oldDesktop, newDesktop); return true; } void VirtualDesktopManager::setCount(uint count) { count = qBound(1, count, VirtualDesktopManager::maximum()); if (count == m_count) { // nothing to change return; } const uint oldCount = m_count; m_count = count; if (oldCount > m_count) { handleDesktopsRemoved(oldCount); } updateRootInfo(); save(); emit countChanged(oldCount, m_count); } void VirtualDesktopManager::handleDesktopsRemoved(uint previousCount) { if (current() > count()) { setCurrent(count()); } emit desktopsRemoved(previousCount); } void VirtualDesktopManager::updateRootInfo() { if (!m_rootInfo) { // Make sure the layout is still valid updateLayout(); return; } const int n = count(); m_rootInfo->setNumberOfDesktops(n); NETPoint *viewports = new NETPoint[n]; m_rootInfo->setDesktopViewport(n, *viewports); delete[] viewports; // Make sure the layout is still valid updateLayout(); } void VirtualDesktopManager::updateLayout() { int width = 0; int height = 0; Qt::Orientation orientation = Qt::Horizontal; if (m_rootInfo) { // TODO: Is there a sane way to avoid overriding the existing grid? width = m_rootInfo->desktopLayoutColumnsRows().width(); height = m_rootInfo->desktopLayoutColumnsRows().height(); orientation = m_rootInfo->desktopLayoutOrientation() == NET::OrientationHorizontal ? Qt::Horizontal : Qt::Vertical; } if (width == 0 && height == 0) { // Not given, set default layout height = 2; } setNETDesktopLayout(orientation, width, height, 0 //rootInfo->desktopLayoutCorner() // Not really worth implementing right now. ); } static bool s_loadingDesktopSettings = false; void VirtualDesktopManager::load() { s_loadingDesktopSettings = true; if (!m_config) { return; } QString groupname; if (screen_number == 0) { groupname = QStringLiteral("Desktops"); } else { groupname.sprintf("Desktops-screen-%d", screen_number); } KConfigGroup group(m_config, groupname); const int n = group.readEntry("Number", 1); setCount(n); if (m_rootInfo) { for (int i = 1; i <= n; i++) { QString s = group.readEntry(QStringLiteral("Name_%1").arg(i), i18n("Desktop %1", i)); m_rootInfo->setDesktopName(i, s.toUtf8().data()); // TODO: update desktop focus chain, why? // m_desktopFocusChain.value()[i-1] = i; } int rows = group.readEntry("Rows", 2); rows = qBound(1, rows, n); // avoid weird cases like having 3 rows for 4 desktops, where the last row is unused int columns = n / rows; if (n % rows > 0) { columns++; } m_rootInfo->setDesktopLayout(NET::OrientationHorizontal, columns, rows, NET::DesktopLayoutCornerTopLeft); m_rootInfo->activate(); } s_loadingDesktopSettings = false; } void VirtualDesktopManager::save() { if (s_loadingDesktopSettings) { return; } if (!m_config) { return; } QString groupname; if (screen_number == 0) { groupname = QStringLiteral("Desktops"); } else { groupname.sprintf("Desktops-screen-%d", screen_number); } KConfigGroup group(m_config, groupname); group.writeEntry("Number", count()); for (uint i = 1; i <= count(); ++i) { QString s = name(i); const QString defaultvalue = defaultName(i); if (s.isEmpty()) { s = defaultvalue; if (m_rootInfo) { m_rootInfo->setDesktopName(i, s.toUtf8().data()); } } if (s != defaultvalue) { group.writeEntry(QStringLiteral("Name_%1").arg(i), s); } else { QString currentvalue = group.readEntry(QStringLiteral("Name_%1").arg(i), QString()); if (currentvalue != defaultvalue) { group.deleteEntry(QStringLiteral("Name_%1").arg(i)); } } } // Save to disk group.sync(); } QString VirtualDesktopManager::defaultName(int desktop) const { return i18n("Desktop %1", desktop); } void VirtualDesktopManager::setNETDesktopLayout(Qt::Orientation orientation, uint width, uint height, int startingCorner) { Q_UNUSED(startingCorner); // Not really worth implementing right now. // Calculate valid grid size Q_ASSERT(width > 0 || height > 0); if ((width <= 0) && (height > 0)) { width = (m_count + height - 1) / height; } else if ((height <= 0) && (width > 0)) { height = (m_count + width - 1) / width; } while (width * height < m_count) { if (orientation == Qt::Horizontal) { ++width; } else { ++height; } } m_grid.update(QSize(width, height), orientation); // TODO: why is there no call to m_rootInfo->setDesktopLayout? emit layoutChanged(width, height); } void VirtualDesktopManager::initShortcuts(KActionCollection *keys) { QAction *a = keys->addAction(QStringLiteral("Group:Desktop Switching")); a->setText(i18n("Desktop Switching")); initSwitchToShortcuts(keys); addAction(keys, QStringLiteral("Switch to Next Desktop"), i18n("Switch to Next Desktop"), SLOT(slotNext())); addAction(keys, QStringLiteral("Switch to Previous Desktop"), i18n("Switch to Previous Desktop"), SLOT(slotPrevious())); addAction(keys, QStringLiteral("Switch One Desktop to the Right"), i18n("Switch One Desktop to the Right"), SLOT(slotRight())); addAction(keys, QStringLiteral("Switch One Desktop to the Left"), i18n("Switch One Desktop to the Left"), SLOT(slotLeft())); addAction(keys, QStringLiteral("Switch One Desktop Up"), i18n("Switch One Desktop Up"), SLOT(slotUp())); addAction(keys, QStringLiteral("Switch One Desktop Down"), i18n("Switch One Desktop Down"), SLOT(slotDown())); } void VirtualDesktopManager::initSwitchToShortcuts(KActionCollection *keys) { const QString toDesktop = QStringLiteral("Switch to Desktop %1"); const KLocalizedString toDesktopLabel = ki18n("Switch to Desktop %1"); addAction(keys, toDesktop, toDesktopLabel, 1, KShortcut(Qt::CTRL + Qt::Key_F1), SLOT(slotSwitchTo())); addAction(keys, toDesktop, toDesktopLabel, 2, KShortcut(Qt::CTRL + Qt::Key_F2), SLOT(slotSwitchTo())); addAction(keys, toDesktop, toDesktopLabel, 3, KShortcut(Qt::CTRL + Qt::Key_F3), SLOT(slotSwitchTo())); addAction(keys, toDesktop, toDesktopLabel, 4, KShortcut(Qt::CTRL + Qt::Key_F4), SLOT(slotSwitchTo())); for (uint i = 5; i <= maximum(); ++i) { addAction(keys, toDesktop, toDesktopLabel, i, KShortcut(), SLOT(slotSwitchTo())); } } void VirtualDesktopManager::addAction(KActionCollection *keys, const QString &name, const KLocalizedString &label, uint value, const KShortcut &key, const char *slot) { QAction *a = keys->addAction(name.arg(value), this, slot); a->setText(label.subs(value).toString()); #if KWIN_QT5_PORTING a->setGlobalShortcut(key); #endif a->setData(value); } void VirtualDesktopManager::addAction(KActionCollection *keys, const QString &name, const QString &label, const char *slot) { QAction *a = keys->addAction(name, this, slot); #if KWIN_QT5_PORTING a->setGlobalShortcut(KShortcut()); #endif a->setText(label); } void VirtualDesktopManager::slotSwitchTo() { QAction *act = qobject_cast(sender()); if (!act) { return; } bool ok = false; const uint i = act->data().toUInt(&ok); if (!ok) { return; } setCurrent(i); } void VirtualDesktopManager::setNavigationWrappingAround(bool enabled) { if (enabled == m_navigationWrapsAround) { return; } m_navigationWrapsAround = enabled; emit navigationWrappingAroundChanged(); } void VirtualDesktopManager::slotDown() { moveTo(isNavigationWrappingAround()); } void VirtualDesktopManager::slotLeft() { moveTo(isNavigationWrappingAround()); } void VirtualDesktopManager::slotPrevious() { moveTo(isNavigationWrappingAround()); } void VirtualDesktopManager::slotNext() { moveTo(isNavigationWrappingAround()); } void VirtualDesktopManager::slotRight() { moveTo(isNavigationWrappingAround()); } void VirtualDesktopManager::slotUp() { moveTo(isNavigationWrappingAround()); } } // KWin