1 | /******************************************************************************* |
2 | KWin - the KDE window manager |
3 | This file is part of the KDE project. |
4 | |
5 | Copyright (C) 2011/2012 The KWin team <kwin@kde.org> |
6 | |
7 | This program is free software: you can redistribute it and/or modify |
8 | it under the terms of the GNU General Public License as published by |
9 | the Free Software Foundation, either version 2 of the License, or |
10 | (at your option) any later version. |
11 | |
12 | This program is distributed in the hope that it will be useful, |
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | GNU General Public License for more details. |
16 | |
17 | You should have received a copy of the GNU General Public License |
18 | along 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 | |
28 | namespace KWin |
29 | { |
30 | |
31 | TabGroup::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 | |
46 | TabGroup::~TabGroup() |
47 | { |
48 | } |
49 | |
50 | |
51 | void 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 | |
57 | void 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 | |
63 | bool 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 | |
149 | bool 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 | |
187 | void 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 | |
202 | void 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 | |
225 | bool TabGroup::isActive() const |
226 | { |
227 | return contains(Workspace::self()->activeClient()); |
228 | } |
229 | |
230 | void 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 | |
245 | void 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 | |
258 | void 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 | |
287 | void 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 | |
295 | void 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 | |