1/*******************************************************************************
2KWin - the KDE window manager
3This file is part of the KDE project.
4
5Copyright (C) 2011/2012 The KWin team <kwin@kde.org>
6
7This program is free software: you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation, either version 2 of the License, or
10(at your option) any later version.
11
12This program is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with this program. If not, see <http://www.gnu.org/licenses/>.
19*******************************************************************************/
20
21#include "tabgroup.h"
22
23#include "client.h"
24#include "decorations.h"
25#include "effects.h"
26#include "workspace.h"
27
28namespace KWin
29{
30
31TabGroup::TabGroup(Client *c)
32 : m_clients()
33 , m_current(c)
34 , m_minSize(c->minSize())
35 , m_maxSize(c->maxSize())
36 , m_stateUpdatesBlocked(0)
37 , m_pendingUpdates(TabGroup::None)
38{
39 QIcon icon(c->icon());
40 icon.addPixmap(c->miniIcon());
41 m_clients << c;
42 c->setTabGroup(this);
43 c->setClientShown(true);
44}
45
46TabGroup::~TabGroup()
47{
48}
49
50
51void TabGroup::activateNext()
52{
53 int index = m_clients.indexOf(m_current);
54 setCurrent(m_clients.at((index < m_clients.count() - 1) ? index + 1 : 0));
55}
56
57void TabGroup::activatePrev()
58{
59 int index = m_clients.indexOf(m_current);
60 setCurrent(m_clients.at((index > 0) ? index - 1 : m_clients.count() - 1));
61}
62
63bool TabGroup::add(Client* c, Client *other, bool after, bool becomeVisible)
64{
65 Q_ASSERT(!c->tabGroup());
66
67 if (!decorationPlugin()->supportsTabbing() || contains(c) || !contains(other))
68 return false;
69
70 // Tabbed windows MUST have a decoration
71 c->setNoBorder(false);
72 if (c->noBorder())
73 return false;
74
75 // If it's not possible to have the same states then ungroup them, TODO: Check all states
76 // We do this here as the ungroup code in updateStates() cannot be called until add() completes
77
78 bool cannotTab = false;
79 ShadeMode oldShadeMode = c->shadeMode();
80 QRect oldGeom = c->geometry();
81 int oldDesktop = c->desktop();
82
83 c->setShade(m_current->shadeMode());
84 cannotTab = c->shadeMode() != m_current->shadeMode();
85 if (!cannotTab) {
86 c->setDesktop(m_current->desktop());
87 cannotTab = c->desktop() != m_current->desktop();
88 }
89 if (!cannotTab) {
90 c->setGeometry(m_current->geometry());
91 cannotTab = c->geometry() != m_current->geometry();
92 }
93
94 if (cannotTab) {
95 c->setShade(oldShadeMode);
96 c->setDesktop(oldDesktop);
97 c->setGeometry(oldGeom);
98 // trigger decoration repaint on the group to make sure that hover animations are properly reset.
99 m_current->triggerDecorationRepaint();
100 return false; // cannot tab
101 }
102
103 // Actually add to new group ----------------------------------------
104
105 // Notify effects of merge
106 if (effects)
107 static_cast<EffectsHandlerImpl*>(effects)->slotTabAdded(c->effectWindow(), other->effectWindow());
108
109 // next: aling the client states BEFORE adding it to the group
110 // otherwise the caused indirect state changes would be taken as the dominating ones and break
111 // the main client
112 // example: QuickTiling is aligned to None, this restores the former QuickTiled size and alignes
113 // all other windows in the group - including the actual main client! - to this size and thus
114 // breaks the actually required alignment to the main windows geometry (because that now has the
115 // restored geometry of the formerly Q'tiled window) - bug #303937
116 updateStates(m_current, All, c);
117
118 int index = other ? m_clients.indexOf(other) : m_clients.size();
119 index += after;
120 if (index > m_clients.size())
121 index = m_clients.size();
122
123 m_clients.insert(index, c);
124
125 c->setTabGroup(this); // Let the client know which group it belongs to
126
127 updateMinMaxSize();
128
129 if (!becomeVisible)
130 c->setClientShown(false);
131 else {
132 c->setClientShown(true);
133 if (!effects || c->readyForPainting()) {
134 setCurrent(c);
135 if (options->focusPolicyIsReasonable())
136 workspace()->requestFocus( c );
137 }
138 else {
139 if (options->focusPolicyIsReasonable())
140 workspace()->requestFocus( m_current );
141 m_current = c; // setCurrent will be called by Toplevel::setReadyForPainting()
142 }
143 }
144
145 m_current->triggerDecorationRepaint();
146 return true;
147}
148
149bool TabGroup::remove(Client* c)
150{
151 if (!c)
152 return false;
153
154 int index = m_clients.indexOf(c);
155 if (index < 0)
156 return false;
157
158 c->setTabGroup(NULL);
159
160 m_clients.removeAt(index);
161 updateMinMaxSize();
162
163 if (m_clients.count() == 1) { // split
164 remove(m_clients.at(0));
165 }
166 if (m_clients.isEmpty()) { // remaining singleton "tab"
167 c->setClientShown(true);
168 return true; // group is gonna be deleted after this anyway
169 }
170
171 if (c == m_current) {
172 m_current = index < m_clients.count() ? m_clients.at(index) : m_clients.last();
173 m_current->setClientShown(true);
174
175 if (effects) // "c -> m_current" because c was m_current
176 static_cast<EffectsHandlerImpl*>(effects)->slotCurrentTabAboutToChange(c->effectWindow(), m_current->effectWindow());
177 }
178
179 // Notify effects of removal
180 if (effects)
181 static_cast<EffectsHandlerImpl*>(effects)->slotTabRemoved(c->effectWindow(), m_current->effectWindow());
182
183 m_current->triggerDecorationRepaint();
184 return true;
185}
186
187void TabGroup::closeAll()
188{
189 // NOTICE - in theory it's OK to use the list because closing sends an event to the client and
190 // due to the async X11 nature, the client would be released and thus removed from m_clients
191 // after this function exits.
192 // However later Wayland support or similar might not share this bahaviour - and we really had
193 // enough trouble with a polluted client list around the tabbing code ....
194 ClientList list(m_clients);
195 for (ClientList::const_iterator i = list.constBegin(), end = list.constEnd(); i != end; ++i)
196 if (*i != m_current)
197 (*i)->closeWindow();
198
199 m_current->closeWindow();
200}
201
202void TabGroup::move(Client *c, Client *other, bool after)
203{
204 if (c == other)
205 return;
206
207 int from = m_clients.indexOf(c);
208 if (from < 0)
209 return;
210
211 int to = other ? m_clients.indexOf(other) : m_clients.size() - 1;
212 if (to < 0)
213 return;
214 to += after;
215 if (to >= m_clients.size())
216 to = m_clients.size() - 1;
217
218 if (from == to)
219 return;
220
221 m_clients.move(from, to);
222 m_current->triggerDecorationRepaint();
223}
224
225bool TabGroup::isActive() const
226{
227 return contains(Workspace::self()->activeClient());
228}
229
230void TabGroup::setCurrent(Client* c, bool force)
231{
232 if ((c == m_current && !force) || !contains(c))
233 return;
234
235 // Notify effects of switch
236 if (effects)
237 static_cast<EffectsHandlerImpl*>(effects)->slotCurrentTabAboutToChange(m_current->effectWindow(), c->effectWindow());
238
239 m_current = c;
240 c->setClientShown(true); // reduce flicker?
241 for (ClientList::const_iterator i = m_clients.constBegin(), end = m_clients.constEnd(); i != end; ++i)
242 (*i)->setClientShown((*i) == m_current);
243}
244
245void TabGroup::sync(const char *property, Client *c)
246{
247 if (c->metaObject()->indexOfProperty(property) > -1) {
248 qWarning("caught attempt to sync non dynamic property: %s", property);
249 return;
250 }
251 QVariant v = c->property(property);
252 for (ClientList::iterator i = m_clients.begin(), end = m_clients.end(); i != end; ++i) {
253 if (*i != m_current)
254 (*i)->setProperty(property, v);
255 }
256}
257
258void TabGroup::updateMinMaxSize()
259{
260 // Determine entire group's minimum and maximum sizes
261 // TODO this used to be signalled out but i didn't find a receiver - or got an idea what this would be good for
262 // find purpose & solution or kick it
263// QSize oldMin = m_minSize;
264// QSize oldMax = m_maxSize;
265 m_minSize = QSize(0, 0);
266 m_maxSize = QSize(INT_MAX, INT_MAX);
267
268 for (ClientList::const_iterator i = m_clients.constBegin(); i != m_clients.constEnd(); ++i) {
269 m_minSize = m_minSize.expandedTo((*i)->minSize());
270 m_maxSize = m_maxSize.boundedTo((*i)->maxSize());
271 }
272
273 // TODO: this actually resolves a conflict that should be caught when adding?
274 m_maxSize = m_maxSize.expandedTo(m_minSize);
275
276 // calculate this _once_ to get a common size.
277 // TODO this leaves another unresolved conflict about the base increment (luckily not used too often)
278 const QSize size = m_current->clientSize().expandedTo(m_minSize).boundedTo(m_maxSize);
279 if (size != m_current->clientSize()) {
280 const QRect r(m_current->pos(), m_current->sizeForClientSize(size));
281 for (ClientList::const_iterator i = m_clients.constBegin(), end = m_clients.constEnd(); i != end; ++i)
282 (*i)->setGeometry(r);
283 }
284}
285
286
287void TabGroup::blockStateUpdates(bool more) {
288 more ? ++m_stateUpdatesBlocked : --m_stateUpdatesBlocked;
289 if (m_stateUpdatesBlocked < 0) {
290 m_stateUpdatesBlocked = 0;
291 qWarning("TabGroup: Something is messed up with TabGroup::blockStateUpdates() invocation\nReleased more than blocked!");
292 }
293}
294
295void TabGroup::updateStates(Client* main, States states, Client* only)
296{
297 if (main == only)
298 return; // there's no need to only align "us" to "us"
299 if (m_stateUpdatesBlocked > 0) {
300 m_pendingUpdates |= states;
301 return;
302 }
303
304 states |= m_pendingUpdates;
305 m_pendingUpdates = TabGroup::None;
306
307 ClientList toBeRemoved, onlyDummy;
308 ClientList *list = &m_clients;
309 if (only) {
310 onlyDummy << only;
311 list = &onlyDummy;
312 }
313
314 for (ClientList::const_iterator i = list->constBegin(), end = list->constEnd(); i != end; ++i) {
315 Client *c = (*i);
316 if (c != main) {
317 if ((states & Minimized) && c->isMinimized() != main->isMinimized()) {
318 if (main->isMinimized())
319 c->minimize(true);
320 else
321 c->unminimize(true);
322 }
323
324 // the order QuickTile -> Maximized -> Geometry is somewhat important because one will change the other
325 // don't change w/o good reason and care
326 if ((states & QuickTile) && c->quickTileMode() != main->quickTileMode())
327 c->setQuickTileMode(main->quickTileMode());
328 if ((states & Maximized) && c->maximizeMode() != main->maximizeMode())
329 c->maximize(main->maximizeMode());
330 // the order Shaded -> Geometry is somewhat important because one will change the other
331 if ((states & Shaded))
332 c->setShade(main->shadeMode());
333 if ((states & Geometry) && c->geometry() != main->geometry())
334 c->setGeometry(main->geometry());
335 if (states & Desktop) {
336 if (c->isOnAllDesktops() != main->isOnAllDesktops())
337 c->setOnAllDesktops(main->isOnAllDesktops());
338 if (c->desktop() != main->desktop())
339 c->setDesktop(main->desktop());
340 }
341 if ((states & Activity) && c->activities() != main->activities()) {
342 c->setOnActivities(main->activities());
343 }
344 if (states & Layer) {
345 if (c->keepAbove() != main->keepAbove())
346 c->setKeepAbove(main->keepAbove());
347 if (c->keepBelow() != main->keepBelow())
348 c->setKeepBelow(main->keepBelow());
349 }
350
351 // If it's not possible to have the same states then ungroup them, TODO: Check all states
352 if (((states & Geometry) && c->geometry() != main->geometry()) ||
353 ((states & Desktop) && c->desktop() != main->desktop()))
354 toBeRemoved << c;
355 }
356 }
357
358 for (ClientList::const_iterator i = toBeRemoved.constBegin(), end = toBeRemoved.constEnd(); i != end; ++i)
359 remove(*i);
360}
361
362}
363