1 | /******************************************************************** |
2 | KWin - the KDE window manager |
3 | This file is part of the KDE project. |
4 | |
5 | Copyright (C) 2009 Lucas Murray <lmurray@undefinedfire.com> |
6 | Copyright (C) 2012 Martin Gräßlin <mgraesslin@kde.org> |
7 | |
8 | This program is free software; you can redistribute it and/or modify |
9 | it under the terms of the GNU General Public License as published by |
10 | the Free Software Foundation; either version 2 of the License, or |
11 | (at your option) any later version. |
12 | |
13 | This program is distributed in the hope that it will be useful, |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | GNU General Public License for more details. |
17 | |
18 | You should have received a copy of the GNU General Public License |
19 | along 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 | |
29 | namespace KWin { |
30 | |
31 | extern int screen_number; |
32 | |
33 | VirtualDesktopGrid::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 | |
42 | VirtualDesktopGrid::~VirtualDesktopGrid() |
43 | { |
44 | delete[] m_grid; |
45 | } |
46 | |
47 | void 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 | |
75 | QPoint 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 | |
87 | KWIN_SINGLETON_FACTORY_VARIABLE(VirtualDesktopManager, s_manager) |
88 | |
89 | VirtualDesktopManager::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 | |
98 | VirtualDesktopManager::~VirtualDesktopManager() |
99 | { |
100 | s_manager = NULL; |
101 | } |
102 | |
103 | QString 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 | |
111 | uint 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 | |
134 | uint 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 | |
157 | uint 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 | |
181 | uint 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 | |
204 | uint 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 | |
221 | uint 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 | |
238 | bool 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 | |
250 | void 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 | |
269 | void VirtualDesktopManager::handleDesktopsRemoved(uint previousCount) |
270 | { |
271 | if (current() > count()) { |
272 | setCurrent(count()); |
273 | } |
274 | emit desktopsRemoved(previousCount); |
275 | } |
276 | |
277 | void 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 | |
293 | void 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 | |
313 | static bool s_loadingDesktopSettings = false; |
314 | |
315 | void 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 | |
351 | void 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 | |
392 | QString VirtualDesktopManager::defaultName(int desktop) const |
393 | { |
394 | return i18n("Desktop %1" , desktop); |
395 | } |
396 | |
397 | void 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 | |
421 | void 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 | |
435 | void 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 | |
449 | void 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 | |
457 | void 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 | |
464 | void 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 | |
478 | void VirtualDesktopManager::setNavigationWrappingAround(bool enabled) |
479 | { |
480 | if (enabled == m_navigationWrapsAround) { |
481 | return; |
482 | } |
483 | m_navigationWrapsAround = enabled; |
484 | emit navigationWrappingAroundChanged(); |
485 | } |
486 | |
487 | void VirtualDesktopManager::slotDown() |
488 | { |
489 | moveTo<DesktopBelow>(isNavigationWrappingAround()); |
490 | } |
491 | |
492 | void VirtualDesktopManager::slotLeft() |
493 | { |
494 | moveTo<DesktopLeft>(isNavigationWrappingAround()); |
495 | } |
496 | |
497 | void VirtualDesktopManager::slotPrevious() |
498 | { |
499 | moveTo<DesktopPrevious>(isNavigationWrappingAround()); |
500 | } |
501 | |
502 | void VirtualDesktopManager::slotNext() |
503 | { |
504 | moveTo<DesktopNext>(isNavigationWrappingAround()); |
505 | } |
506 | |
507 | void VirtualDesktopManager::slotRight() |
508 | { |
509 | moveTo<DesktopRight>(isNavigationWrappingAround()); |
510 | } |
511 | |
512 | void VirtualDesktopManager::slotUp() |
513 | { |
514 | moveTo<DesktopAbove>(isNavigationWrappingAround()); |
515 | } |
516 | |
517 | } // KWin |
518 | |