1 | /******************************************************************** |
2 | KWin - the KDE window manager |
3 | This file is part of the KDE project. |
4 | |
5 | Copyright (C) 2012 Martin Gräßlin <mgraesslin@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 | #include "focuschain.h" |
21 | #include "client.h" |
22 | #include "screens.h" |
23 | |
24 | namespace KWin |
25 | { |
26 | |
27 | KWIN_SINGLETON_FACTORY_VARIABLE(FocusChain, s_manager) |
28 | |
29 | FocusChain::FocusChain(QObject *parent) |
30 | : QObject(parent) |
31 | , m_separateScreenFocus(false) |
32 | , m_activeClient(NULL) |
33 | , m_currentDesktop(0) |
34 | { |
35 | } |
36 | |
37 | FocusChain::~FocusChain() |
38 | { |
39 | s_manager = NULL; |
40 | } |
41 | |
42 | void FocusChain::remove(Client *client) |
43 | { |
44 | for (DesktopChains::iterator it = m_desktopFocusChains.begin(); |
45 | it != m_desktopFocusChains.end(); |
46 | ++it) { |
47 | it.value().removeAll(client); |
48 | } |
49 | m_mostRecentlyUsed.removeAll(client); |
50 | } |
51 | |
52 | void FocusChain::resize(uint previousSize, uint newSize) |
53 | { |
54 | for (uint i = previousSize + 1; i <= newSize; ++i) { |
55 | m_desktopFocusChains.insert(i, QList<Client*>()); |
56 | } |
57 | for (uint i = previousSize; i > newSize; --i) { |
58 | m_desktopFocusChains.remove(i); |
59 | } |
60 | } |
61 | |
62 | Client *FocusChain::getForActivation(uint desktop) const |
63 | { |
64 | return getForActivation(desktop, screens()->current()); |
65 | } |
66 | |
67 | Client *FocusChain::getForActivation(uint desktop, int screen) const |
68 | { |
69 | DesktopChains::const_iterator it = m_desktopFocusChains.find(desktop); |
70 | if (it == m_desktopFocusChains.constEnd()) { |
71 | return NULL; |
72 | } |
73 | const QList<Client*> &chain = it.value(); |
74 | for (int i = chain.size() - 1; i >= 0; --i) { |
75 | Client *tmp = chain.at(i); |
76 | // TODO: move the check into Client |
77 | if (tmp->isShown(false) && tmp->isOnCurrentActivity() |
78 | && ( !m_separateScreenFocus || tmp->screen() == screen)) { |
79 | return tmp; |
80 | } |
81 | } |
82 | return NULL; |
83 | } |
84 | |
85 | void FocusChain::update(Client *client, FocusChain::Change change) |
86 | { |
87 | if (!client->wantsTabFocus()) { |
88 | // Doesn't want tab focus, remove |
89 | remove(client); |
90 | return; |
91 | } |
92 | |
93 | if (client->isOnAllDesktops()) { |
94 | // Now on all desktops, add it to focus chains it is not already in |
95 | for (DesktopChains::iterator it = m_desktopFocusChains.begin(); |
96 | it != m_desktopFocusChains.end(); |
97 | ++it) { |
98 | QList<Client*> &chain = it.value(); |
99 | // Making first/last works only on current desktop, don't affect all desktops |
100 | if (it.key() == m_currentDesktop |
101 | && (change == MakeFirst || change == MakeLast)) { |
102 | if (change == MakeFirst) { |
103 | makeFirstInChain(client, chain); |
104 | } else { |
105 | makeLastInChain(client, chain); |
106 | } |
107 | } else { |
108 | insertClientIntoChain(client, chain); |
109 | } |
110 | } |
111 | } else { |
112 | // Now only on desktop, remove it anywhere else |
113 | for (DesktopChains::iterator it = m_desktopFocusChains.begin(); |
114 | it != m_desktopFocusChains.end(); |
115 | ++it) { |
116 | QList<Client*> &chain = it.value(); |
117 | if (client->isOnDesktop(it.key())) { |
118 | updateClientInChain(client, change, chain); |
119 | } else { |
120 | chain.removeAll(client); |
121 | } |
122 | } |
123 | } |
124 | |
125 | // add for most recently used chain |
126 | updateClientInChain(client, change, m_mostRecentlyUsed); |
127 | } |
128 | |
129 | void FocusChain::updateClientInChain(Client *client, FocusChain::Change change, QList< Client * >& chain) |
130 | { |
131 | if (change == MakeFirst) { |
132 | makeFirstInChain(client, chain); |
133 | } else if (change == MakeLast) { |
134 | makeLastInChain(client, chain); |
135 | } else { |
136 | insertClientIntoChain(client, chain); |
137 | } |
138 | } |
139 | |
140 | void FocusChain::insertClientIntoChain(Client *client, QList< Client * >& chain) |
141 | { |
142 | if (chain.contains(client)) { |
143 | return; |
144 | } |
145 | if (m_activeClient && m_activeClient != client && |
146 | !chain.empty() && chain.last() == m_activeClient) { |
147 | // Add it after the active client |
148 | chain.insert(chain.size() - 1, client); |
149 | } else { |
150 | // Otherwise add as the first one |
151 | chain.append(client); |
152 | } |
153 | } |
154 | |
155 | void FocusChain::moveAfterClient(Client *client, Client *reference) |
156 | { |
157 | if (!client->wantsTabFocus()) { |
158 | return; |
159 | } |
160 | |
161 | for (DesktopChains::iterator it = m_desktopFocusChains.begin(); |
162 | it != m_desktopFocusChains.end(); |
163 | ++it) { |
164 | if (!client->isOnDesktop(it.key())) { |
165 | continue; |
166 | } |
167 | moveAfterClientInChain(client, reference, it.value()); |
168 | } |
169 | moveAfterClientInChain(client, reference, m_mostRecentlyUsed); |
170 | } |
171 | |
172 | void FocusChain::moveAfterClientInChain(Client *client, Client *reference, QList<Client *> &chain) |
173 | { |
174 | if (!chain.contains(reference)) { |
175 | return; |
176 | } |
177 | if (Client::belongToSameApplication(reference, client)) { |
178 | chain.removeAll(client); |
179 | chain.insert(chain.indexOf(reference), client); |
180 | } else { |
181 | chain.removeAll(client); |
182 | for (int i = chain.size() - 1; i >= 0; --i) { |
183 | if (Client::belongToSameApplication(reference, chain.at(i))) { |
184 | chain.insert(i, client); |
185 | break; |
186 | } |
187 | } |
188 | } |
189 | } |
190 | |
191 | Client *FocusChain::firstMostRecentlyUsed() const |
192 | { |
193 | if (m_mostRecentlyUsed.isEmpty()) { |
194 | return NULL; |
195 | } |
196 | return m_mostRecentlyUsed.first(); |
197 | } |
198 | |
199 | Client *FocusChain::nextMostRecentlyUsed(Client *reference) const |
200 | { |
201 | if (m_mostRecentlyUsed.isEmpty()) { |
202 | return NULL; |
203 | } |
204 | const int index = m_mostRecentlyUsed.indexOf(reference); |
205 | if (index == -1 || index == 0) { |
206 | return m_mostRecentlyUsed.last(); |
207 | } |
208 | return m_mostRecentlyUsed.at(index - 1); |
209 | } |
210 | |
211 | // copied from activation.cpp |
212 | bool FocusChain::isUsableFocusCandidate(Client *c, Client *prev) const |
213 | { |
214 | return c != prev && |
215 | c->isShown(false) && c->isOnCurrentDesktop() && c->isOnCurrentActivity() && |
216 | (!m_separateScreenFocus || c->isOnScreen(prev ? prev->screen() : screens()->current())); |
217 | } |
218 | |
219 | Client *FocusChain::nextForDesktop(Client *reference, uint desktop) const |
220 | { |
221 | DesktopChains::const_iterator it = m_desktopFocusChains.find(desktop); |
222 | if (it == m_desktopFocusChains.end()) { |
223 | return NULL; |
224 | } |
225 | const QList<Client*> &chain = it.value(); |
226 | for (int i = chain.size() - 1; i >= 0; --i) { |
227 | Client* client = chain.at(i); |
228 | if (isUsableFocusCandidate(client, reference)) { |
229 | return client; |
230 | } |
231 | } |
232 | return NULL; |
233 | } |
234 | |
235 | void FocusChain::makeFirstInChain(Client *client, QList< Client * >& chain) |
236 | { |
237 | chain.removeAll(client); |
238 | if (client->isMinimized()) { // add it before the first minimized ... |
239 | for (int i = chain.count()-1; i >= 0; --i) { |
240 | if (chain.at(i)->isMinimized()) { |
241 | chain.insert(i+1, client); |
242 | return; |
243 | } |
244 | } |
245 | chain.prepend(client); // ... or at end of chain |
246 | } else { |
247 | chain.append(client); |
248 | } |
249 | } |
250 | |
251 | void FocusChain::makeLastInChain(Client *client, QList< Client * >& chain) |
252 | { |
253 | chain.removeAll(client); |
254 | chain.prepend(client); |
255 | } |
256 | |
257 | bool FocusChain::contains(Client *client, uint desktop) const |
258 | { |
259 | DesktopChains::const_iterator it = m_desktopFocusChains.find(desktop); |
260 | if (it == m_desktopFocusChains.end()) { |
261 | return false; |
262 | } |
263 | return it.value().contains(client); |
264 | } |
265 | |
266 | } // namespace |
267 | |