1/********************************************************************
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5Copyright (C) 2009 Lucas Murray <lmurray@undefinedfire.com>
6Copyright (C) 2012 Martin Gräßlin <mgraesslin@kde.org>
7
8This program is free software; you can redistribute it and/or modify
9it under the terms of the GNU General Public License as published by
10the Free Software Foundation; either version 2 of the License, or
11(at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with this program. If not, see <http://www.gnu.org/licenses/>.
20*********************************************************************/
21#include "virtualdesktops.h"
22// KDE
23#include <KDE/KAction>
24#include <KDE/KActionCollection>
25#include <KDE/KConfigGroup>
26#include <KDE/KLocalizedString>
27#include <KDE/NETRootInfo>
28
29namespace KWin {
30
31extern int screen_number;
32
33VirtualDesktopGrid::VirtualDesktopGrid()
34 : m_size(1, 2) // Default to tow rows
35 , m_grid(new uint[2])
36{
37 // Initializing grid array
38 m_grid[0] = 0;
39 m_grid[1] = 0;
40}
41
42VirtualDesktopGrid::~VirtualDesktopGrid()
43{
44 delete[] m_grid;
45}
46
47void VirtualDesktopGrid::update(const QSize &size, Qt::Orientation orientation)
48{
49 // Set private variables
50 delete[] m_grid;
51 m_size = size;
52 const uint width = size.width();
53 const uint height = size.height();
54 const uint length = width * height;
55 const uint desktopCount = VirtualDesktopManager::self()->count();
56 m_grid = new uint[length];
57
58 // Populate grid
59 uint desktop = 1;
60 if (orientation == Qt::Horizontal) {
61 for (uint y = 0; y < height; ++y) {
62 for (uint x = 0; x < width; ++x) {
63 m_grid[y * width + x] = (desktop <= desktopCount ? desktop++ : 0);
64 }
65 }
66 } else {
67 for (uint x = 0; x < width; ++x) {
68 for (uint y = 0; y < height; ++y) {
69 m_grid[y * width + x] = (desktop <= desktopCount ? desktop++ : 0);
70 }
71 }
72 }
73}
74
75QPoint VirtualDesktopGrid::gridCoords(uint id) const
76{
77 for (int y = 0; y < m_size.height(); ++y) {
78 for (int x = 0; x < m_size.width(); ++x) {
79 if (m_grid[y * m_size.width() + x] == id) {
80 return QPoint(x, y);
81 }
82 }
83 }
84 return QPoint(-1, -1);
85}
86
87KWIN_SINGLETON_FACTORY_VARIABLE(VirtualDesktopManager, s_manager)
88
89VirtualDesktopManager::VirtualDesktopManager(QObject *parent)
90 : QObject(parent)
91 , m_current(0)
92 , m_count(0)
93 , m_navigationWrapsAround(false)
94 , m_rootInfo(NULL)
95{
96}
97
98VirtualDesktopManager::~VirtualDesktopManager()
99{
100 s_manager = NULL;
101}
102
103QString VirtualDesktopManager::name(uint desktop) const
104{
105 if (!m_rootInfo) {
106 return defaultName(desktop);
107 }
108 return QString::fromUtf8(m_rootInfo->desktopName(desktop));
109}
110
111uint VirtualDesktopManager::above(uint id, bool wrap) const
112{
113 if (id == 0) {
114 id = current();
115 }
116 QPoint coords = m_grid.gridCoords(id);
117 Q_ASSERT(coords.x() >= 0);
118 while (true) {
119 coords.ry()--;
120 if (coords.y() < 0) {
121 if (wrap) {
122 coords.setY(m_grid.height() - 1);
123 } else {
124 return id; // Already at the top-most desktop
125 }
126 }
127 const uint desktop = m_grid.at(coords);
128 if (desktop > 0) {
129 return desktop;
130 }
131 }
132}
133
134uint VirtualDesktopManager::toRight(uint id, bool wrap) const
135{
136 if (id == 0) {
137 id = current();
138 }
139 QPoint coords = m_grid.gridCoords(id);
140 Q_ASSERT(coords.x() >= 0);
141 while (true) {
142 coords.rx()++;
143 if (coords.x() >= m_grid.width()) {
144 if (wrap) {
145 coords.setX(0);
146 } else {
147 return id; // Already at the right-most desktop
148 }
149 }
150 const uint desktop = m_grid.at(coords);
151 if (desktop > 0) {
152 return desktop;
153 }
154 }
155}
156
157uint VirtualDesktopManager::below(uint id, bool wrap) const
158{
159 if (id == 0) {
160 id = current();
161 }
162 QPoint coords = m_grid.gridCoords(id);
163 Q_ASSERT(coords.x() >= 0);
164 while (true) {
165 coords.ry()++;
166 if (coords.y() >= m_grid.height()) {
167 if (wrap) {
168 coords.setY(0);
169 } else {
170 // Already at the bottom-most desktop
171 return id;
172 }
173 }
174 const uint desktop = m_grid.at(coords);
175 if (desktop > 0) {
176 return desktop;
177 }
178 }
179}
180
181uint VirtualDesktopManager::toLeft(uint id, bool wrap) const
182{
183 if (id == 0) {
184 id = current();
185 }
186 QPoint coords = m_grid.gridCoords(id);
187 Q_ASSERT(coords.x() >= 0);
188 while (true) {
189 coords.rx()--;
190 if (coords.x() < 0) {
191 if (wrap) {
192 coords.setX(m_grid.width() - 1);
193 } else {
194 return id; // Already at the left-most desktop
195 }
196 }
197 const uint desktop = m_grid.at(coords);
198 if (desktop > 0) {
199 return desktop;
200 }
201 }
202}
203
204uint VirtualDesktopManager::next(uint id, bool wrap) const
205{
206 if (id == 0) {
207 id = current();
208 }
209 const uint desktop = id + 1;
210 if (desktop > count()) {
211 if (wrap) {
212 return 1;
213 } else {
214 // are at the last desktop, without wrap return current
215 return id;
216 }
217 }
218 return desktop;
219}
220
221uint VirtualDesktopManager::previous(uint id, bool wrap) const
222{
223 if (id == 0) {
224 id = current();
225 }
226 const uint desktop = id - 1;
227 if (desktop == 0) {
228 if (wrap) {
229 return count();
230 } else {
231 // are at the first desktop, without wrap return current
232 return id;
233 }
234 }
235 return desktop;
236}
237
238bool VirtualDesktopManager::setCurrent(uint newDesktop)
239{
240 if (newDesktop < 1 || newDesktop > count() || newDesktop == m_current) {
241 return false;
242 }
243 const uint oldDesktop = m_current;
244 // change the desktop
245 m_current = newDesktop;
246 emit currentChanged(oldDesktop, newDesktop);
247 return true;
248}
249
250void VirtualDesktopManager::setCount(uint count)
251{
252 count = qBound<uint>(1, count, VirtualDesktopManager::maximum());
253 if (count == m_count) {
254 // nothing to change
255 return;
256 }
257 const uint oldCount = m_count;
258 m_count = count;
259
260 if (oldCount > m_count) {
261 handleDesktopsRemoved(oldCount);
262 }
263 updateRootInfo();
264
265 save();
266 emit countChanged(oldCount, m_count);
267}
268
269void VirtualDesktopManager::handleDesktopsRemoved(uint previousCount)
270{
271 if (current() > count()) {
272 setCurrent(count());
273 }
274 emit desktopsRemoved(previousCount);
275}
276
277void VirtualDesktopManager::updateRootInfo()
278{
279 if (!m_rootInfo) {
280 // Make sure the layout is still valid
281 updateLayout();
282 return;
283 }
284 const int n = count();
285 m_rootInfo->setNumberOfDesktops(n);
286 NETPoint *viewports = new NETPoint[n];
287 m_rootInfo->setDesktopViewport(n, *viewports);
288 delete[] viewports;
289 // Make sure the layout is still valid
290 updateLayout();
291}
292
293void VirtualDesktopManager::updateLayout()
294{
295 int width = 0;
296 int height = 0;
297 Qt::Orientation orientation = Qt::Horizontal;
298 if (m_rootInfo) {
299 // TODO: Is there a sane way to avoid overriding the existing grid?
300 width = m_rootInfo->desktopLayoutColumnsRows().width();
301 height = m_rootInfo->desktopLayoutColumnsRows().height();
302 orientation = m_rootInfo->desktopLayoutOrientation() == NET::OrientationHorizontal ? Qt::Horizontal : Qt::Vertical;
303 }
304 if (width == 0 && height == 0) {
305 // Not given, set default layout
306 height = 2;
307 }
308 setNETDesktopLayout(orientation,
309 width, height, 0 //rootInfo->desktopLayoutCorner() // Not really worth implementing right now.
310 );
311}
312
313static bool s_loadingDesktopSettings = false;
314
315void VirtualDesktopManager::load()
316{
317 s_loadingDesktopSettings = true;
318 if (m_config.isNull()) {
319 return;
320 }
321 QString groupname;
322 if (screen_number == 0) {
323 groupname = "Desktops";
324 } else {
325 groupname.sprintf("Desktops-screen-%d", screen_number);
326 }
327 KConfigGroup group(m_config, groupname);
328 const int n = group.readEntry("Number", 1);
329 setCount(n);
330 if (m_rootInfo) {
331 for (int i = 1; i <= n; i++) {
332 QString s = group.readEntry(QString("Name_%1").arg(i), i18n("Desktop %1", i));
333 m_rootInfo->setDesktopName(i, s.toUtf8().data());
334 // TODO: update desktop focus chain, why?
335// m_desktopFocusChain.value()[i-1] = i;
336 }
337
338 int rows = group.readEntry<int>("Rows", 2);
339 rows = qBound(1, rows, n);
340 // avoid weird cases like having 3 rows for 4 desktops, where the last row is unused
341 int columns = n / rows;
342 if (n % rows > 0) {
343 columns++;
344 }
345 m_rootInfo->setDesktopLayout(NET::OrientationHorizontal, columns, rows, NET::DesktopLayoutCornerTopLeft);
346 m_rootInfo->activate();
347 }
348 s_loadingDesktopSettings = false;
349}
350
351void VirtualDesktopManager::save()
352{
353 if (s_loadingDesktopSettings) {
354 return;
355 }
356 if (m_config.isNull()) {
357 return;
358 }
359 QString groupname;
360 if (screen_number == 0) {
361 groupname = "Desktops";
362 } else {
363 groupname.sprintf("Desktops-screen-%d", screen_number);
364 }
365 KConfigGroup group(m_config, groupname);
366
367 group.writeEntry("Number", count());
368 for (uint i = 1; i <= count(); ++i) {
369 QString s = name(i);
370 const QString defaultvalue = defaultName(i);
371 if (s.isEmpty()) {
372 s = defaultvalue;
373 if (m_rootInfo) {
374 m_rootInfo->setDesktopName(i, s.toUtf8().data());
375 }
376 }
377
378 if (s != defaultvalue) {
379 group.writeEntry(QString("Name_%1").arg(i), s);
380 } else {
381 QString currentvalue = group.readEntry(QString("Name_%1").arg(i), QString());
382 if (currentvalue != defaultvalue) {
383 group.deleteEntry(QString("Name_%1").arg(i));
384 }
385 }
386 }
387
388 // Save to disk
389 group.sync();
390}
391
392QString VirtualDesktopManager::defaultName(int desktop) const
393{
394 return i18n("Desktop %1", desktop);
395}
396
397void VirtualDesktopManager::setNETDesktopLayout(Qt::Orientation orientation, uint width, uint height, int startingCorner)
398{
399 Q_UNUSED(startingCorner); // Not really worth implementing right now.
400
401 // Calculate valid grid size
402 Q_ASSERT(width > 0 || height > 0);
403 if ((width <= 0) && (height > 0)) {
404 width = (m_count + height - 1) / height;
405 } else if ((height <= 0) && (width > 0)) {
406 height = (m_count + width - 1) / width;
407 }
408 while (width * height < m_count) {
409 if (orientation == Qt::Horizontal) {
410 ++width;
411 } else {
412 ++height;
413 }
414 }
415
416 m_grid.update(QSize(width, height), orientation);
417 // TODO: why is there no call to m_rootInfo->setDesktopLayout?
418 emit layoutChanged(width, height);
419}
420
421void VirtualDesktopManager::initShortcuts(KActionCollection *keys)
422{
423 KAction *a = keys->addAction("Group:Desktop Switching");
424 a->setText(i18n("Desktop Switching"));
425 initSwitchToShortcuts(keys);
426
427 addAction(keys, "Switch to Next Desktop", i18n("Switch to Next Desktop"), SLOT(slotNext()));
428 addAction(keys, "Switch to Previous Desktop", i18n("Switch to Previous Desktop"), SLOT(slotPrevious()));
429 addAction(keys, "Switch One Desktop to the Right", i18n("Switch One Desktop to the Right"), SLOT(slotRight()));
430 addAction(keys, "Switch One Desktop to the Left", i18n("Switch One Desktop to the Left"), SLOT(slotLeft()));
431 addAction(keys, "Switch One Desktop Up", i18n("Switch One Desktop Up"), SLOT(slotUp()));
432 addAction(keys, "Switch One Desktop Down", i18n("Switch One Desktop Down"), SLOT(slotDown()));
433}
434
435void VirtualDesktopManager::initSwitchToShortcuts(KActionCollection *keys)
436{
437 const QString toDesktop = "Switch to Desktop %1";
438 const KLocalizedString toDesktopLabel = ki18n("Switch to Desktop %1");
439 addAction(keys, toDesktop, toDesktopLabel, 1, KShortcut(Qt::CTRL + Qt::Key_F1), SLOT(slotSwitchTo()));
440 addAction(keys, toDesktop, toDesktopLabel, 2, KShortcut(Qt::CTRL + Qt::Key_F2), SLOT(slotSwitchTo()));
441 addAction(keys, toDesktop, toDesktopLabel, 3, KShortcut(Qt::CTRL + Qt::Key_F3), SLOT(slotSwitchTo()));
442 addAction(keys, toDesktop, toDesktopLabel, 4, KShortcut(Qt::CTRL + Qt::Key_F4), SLOT(slotSwitchTo()));
443
444 for (uint i = 5; i <= maximum(); ++i) {
445 addAction(keys, toDesktop, toDesktopLabel, i, KShortcut(), SLOT(slotSwitchTo()));
446 }
447}
448
449void VirtualDesktopManager::addAction(KActionCollection *keys, const QString &name, const KLocalizedString &label, uint value, const KShortcut &key, const char *slot)
450{
451 KAction *a = keys->addAction(name.arg(value), this, slot);
452 a->setText(label.subs(value).toString());
453 a->setGlobalShortcut(key);
454 a->setData(value);
455}
456
457void VirtualDesktopManager::addAction(KActionCollection *keys, const QString &name, const QString &label, const char *slot)
458{
459 KAction *a = keys->addAction(name, this, slot);
460 a->setGlobalShortcut(KShortcut());
461 a->setText(label);
462}
463
464void VirtualDesktopManager::slotSwitchTo()
465{
466 QAction *act = qobject_cast<QAction*>(sender());
467 if (!act) {
468 return;
469 }
470 bool ok = false;
471 const uint i = act->data().toUInt(&ok);
472 if (!ok) {
473 return;
474 }
475 setCurrent(i);
476}
477
478void VirtualDesktopManager::setNavigationWrappingAround(bool enabled)
479{
480 if (enabled == m_navigationWrapsAround) {
481 return;
482 }
483 m_navigationWrapsAround = enabled;
484 emit navigationWrappingAroundChanged();
485}
486
487void VirtualDesktopManager::slotDown()
488{
489 moveTo<DesktopBelow>(isNavigationWrappingAround());
490}
491
492void VirtualDesktopManager::slotLeft()
493{
494 moveTo<DesktopLeft>(isNavigationWrappingAround());
495}
496
497void VirtualDesktopManager::slotPrevious()
498{
499 moveTo<DesktopPrevious>(isNavigationWrappingAround());
500}
501
502void VirtualDesktopManager::slotNext()
503{
504 moveTo<DesktopNext>(isNavigationWrappingAround());
505}
506
507void VirtualDesktopManager::slotRight()
508{
509 moveTo<DesktopRight>(isNavigationWrappingAround());
510}
511
512void VirtualDesktopManager::slotUp()
513{
514 moveTo<DesktopAbove>(isNavigationWrappingAround());
515}
516
517} // KWin
518