1/********************************************************************
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5Copyright (C) 2012 Martin Gräßlin <mgraesslin@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#include "focuschain.h"
21#include "client.h"
22#include "screens.h"
23
24namespace KWin
25{
26
27KWIN_SINGLETON_FACTORY_VARIABLE(FocusChain, s_manager)
28
29FocusChain::FocusChain(QObject *parent)
30 : QObject(parent)
31 , m_separateScreenFocus(false)
32 , m_activeClient(NULL)
33 , m_currentDesktop(0)
34{
35}
36
37FocusChain::~FocusChain()
38{
39 s_manager = NULL;
40}
41
42void 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
52void 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
62Client *FocusChain::getForActivation(uint desktop) const
63{
64 return getForActivation(desktop, screens()->current());
65}
66
67Client *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
85void 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
129void 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
140void 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
155void 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
172void 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
191Client *FocusChain::firstMostRecentlyUsed() const
192{
193 if (m_mostRecentlyUsed.isEmpty()) {
194 return NULL;
195 }
196 return m_mostRecentlyUsed.first();
197}
198
199Client *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
212bool 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
219Client *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
235void 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
251void FocusChain::makeLastInChain(Client *client, QList< Client * >& chain)
252{
253 chain.removeAll(client);
254 chain.prepend(client);
255}
256
257bool 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