kwin/virtualdesktops.cpp
Martin Gräßlin 334b4bf622 Move handling of Virtual Desktops into a VirtualDesktopManager
The ownership for virtual desktops is moved from Workspace into a new
VirtualDesktopManager. The manager is responsible for providing the count
of virtual desktops and keeping track of the currently used virtual
desktop.

All methods related to moving between desktops are also moved from
Workspace to the new manager, though all methods related to Clients on
Virtual Desktops remain in Workspace for the time being. This is to have
the new manager as independent from KWin core as possible.

An rather important change for the handling of virtual desktops is that
the count and the id of a desktop is now an unsinged integer instead of
an integer. The reason for that is that we cannot have a negative count
of desktops as well as it is not possible to be on a desktop with a
negative identifier.

In that regard it is important to remember that a Client can be on a
desktop with a negative identifier. The special value for a Client being
on all desktops is handled by using -1 as a desktop. For the time being
this is not adjusted but instead of comparing the virtual desktop ids one
should prefer to use the convenient methods like isOnDesktop and
isOnAllDesktops. This would allow in future to internally change the
representation for on all desktops.
2013-01-07 09:47:51 +01:00

526 lines
15 KiB
C++

/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2009 Lucas Murray <lmurray@undefinedfire.com>
Copyright (C) 2012 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "virtualdesktops.h"
// KWin
#include "notifications.h"
// KDE
#include <KDE/KAction>
#include <KDE/KActionCollection>
#include <KDE/KConfigGroup>
#include <KDE/KLocalizedString>
#include <KDE/NETRootInfo>
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);
}
VirtualDesktopManager *VirtualDesktopManager::s_manager = NULL;
VirtualDesktopManager::VirtualDesktopManager(QObject *parent)
: QObject(parent)
, m_current(0)
, m_count(0)
, m_navigationWrapsAround(false)
, m_rootInfo(NULL)
{
}
VirtualDesktopManager::~VirtualDesktopManager()
{
s_manager = NULL;
}
VirtualDesktopManager *VirtualDesktopManager::create(QObject *parent)
{
Q_ASSERT(!s_manager);
s_manager = new VirtualDesktopManager(parent);
return s_manager;
}
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;
Notify::raise((Notify::Event)(Notify::DesktopChange + newDesktop));
// change the desktop
m_current = newDesktop;
emit currentChanged(oldDesktop, newDesktop);
return true;
}
void VirtualDesktopManager::setCount(uint count)
{
count = qBound<uint>(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.isNull()) {
return;
}
QString groupname;
if (screen_number == 0) {
groupname = "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(QString("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<int>("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.isNull()) {
return;
}
QString groupname;
if (screen_number == 0) {
groupname = "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(QString("Name_%1").arg(i), s);
} else {
QString currentvalue = group.readEntry(QString("Name_%1").arg(i), QString());
if (currentvalue != defaultvalue) {
group.deleteEntry(QString("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)
{
KAction *a = keys->addAction("Group:Desktop Switching");
a->setText(i18n("Desktop Switching"));
initSwitchToShortcuts(keys);
addAction(keys, "Switch to Next Desktop", i18n("Switch to Next Desktop"), SLOT(slotNext()));
addAction(keys, "Switch to Previous Desktop", i18n("Switch to Previous Desktop"), SLOT(slotPrevious()));
addAction(keys, "Switch One Desktop to the Right", i18n("Switch One Desktop to the Right"), SLOT(slotRight()));
addAction(keys, "Switch One Desktop to the Left", i18n("Switch One Desktop to the Left"), SLOT(slotLeft()));
addAction(keys, "Switch One Desktop Up", i18n("Switch One Desktop Up"), SLOT(slotUp()));
addAction(keys, "Switch One Desktop Down", i18n("Switch One Desktop Down"), SLOT(slotDown()));
}
void VirtualDesktopManager::initSwitchToShortcuts(KActionCollection *keys)
{
const QString toDesktop = "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)
{
KAction *a = keys->addAction(name.arg(value), this, slot);
a->setText(label.subs(value).toString());
a->setGlobalShortcut(key);
a->setData(value);
}
void VirtualDesktopManager::addAction(KActionCollection *keys, const QString &name, const QString &label, const char *slot)
{
KAction *a = keys->addAction(name, this, slot);
a->setText(label);
}
void VirtualDesktopManager::slotSwitchTo()
{
QAction *act = qobject_cast<QAction*>(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<DesktopBelow>(isNavigationWrappingAround());
}
void VirtualDesktopManager::slotLeft()
{
moveTo<DesktopLeft>(isNavigationWrappingAround());
}
void VirtualDesktopManager::slotPrevious()
{
moveTo<DesktopPrevious>(isNavigationWrappingAround());
}
void VirtualDesktopManager::slotNext()
{
moveTo<DesktopNext>(isNavigationWrappingAround());
}
void VirtualDesktopManager::slotRight()
{
moveTo<DesktopRight>(isNavigationWrappingAround());
}
void VirtualDesktopManager::slotUp()
{
moveTo<DesktopAbove>(isNavigationWrappingAround());
}
} // KWin