File indexing completed on 2024-02-25 17:23:36

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2012 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "focuschain.h"
0010 #include "window.h"
0011 #include "workspace.h"
0012 
0013 namespace KWin
0014 {
0015 
0016 void FocusChain::remove(Window *window)
0017 {
0018     for (auto it = m_desktopFocusChains.begin();
0019          it != m_desktopFocusChains.end();
0020          ++it) {
0021         it.value().removeAll(window);
0022     }
0023     m_mostRecentlyUsed.removeAll(window);
0024 }
0025 
0026 void FocusChain::addDesktop(VirtualDesktop *desktop)
0027 {
0028     m_desktopFocusChains.insert(desktop, Chain());
0029 }
0030 
0031 void FocusChain::removeDesktop(VirtualDesktop *desktop)
0032 {
0033     if (m_currentDesktop == desktop) {
0034         m_currentDesktop = nullptr;
0035     }
0036     m_desktopFocusChains.remove(desktop);
0037 }
0038 
0039 Window *FocusChain::getForActivation(VirtualDesktop *desktop) const
0040 {
0041     return getForActivation(desktop, workspace()->activeOutput());
0042 }
0043 
0044 Window *FocusChain::getForActivation(VirtualDesktop *desktop, Output *output) const
0045 {
0046     auto it = m_desktopFocusChains.constFind(desktop);
0047     if (it == m_desktopFocusChains.constEnd()) {
0048         return nullptr;
0049     }
0050     const auto &chain = it.value();
0051     for (int i = chain.size() - 1; i >= 0; --i) {
0052         auto tmp = chain.at(i);
0053         // TODO: move the check into Window
0054         if (!tmp->isShade() && tmp->isShown() && tmp->isOnCurrentActivity()
0055             && (!m_separateScreenFocus || tmp->output() == output)) {
0056             return tmp;
0057         }
0058     }
0059     return nullptr;
0060 }
0061 
0062 void FocusChain::update(Window *window, FocusChain::Change change)
0063 {
0064     if (!window->wantsTabFocus()) {
0065         // Doesn't want tab focus, remove
0066         remove(window);
0067         return;
0068     }
0069 
0070     if (window->isOnAllDesktops()) {
0071         // Now on all desktops, add it to focus chains it is not already in
0072         for (auto it = m_desktopFocusChains.begin();
0073              it != m_desktopFocusChains.end();
0074              ++it) {
0075             auto &chain = it.value();
0076             // Making first/last works only on current desktop, don't affect all desktops
0077             if (it.key() == m_currentDesktop
0078                 && (change == MakeFirst || change == MakeLast)) {
0079                 if (change == MakeFirst) {
0080                     makeFirstInChain(window, chain);
0081                 } else {
0082                     makeLastInChain(window, chain);
0083                 }
0084             } else {
0085                 insertWindowIntoChain(window, chain);
0086             }
0087         }
0088     } else {
0089         // Now only on desktop, remove it anywhere else
0090         for (auto it = m_desktopFocusChains.begin();
0091              it != m_desktopFocusChains.end();
0092              ++it) {
0093             auto &chain = it.value();
0094             if (window->isOnDesktop(it.key())) {
0095                 updateWindowInChain(window, change, chain);
0096             } else {
0097                 chain.removeAll(window);
0098             }
0099         }
0100     }
0101 
0102     // add for most recently used chain
0103     updateWindowInChain(window, change, m_mostRecentlyUsed);
0104 }
0105 
0106 void FocusChain::updateWindowInChain(Window *window, FocusChain::Change change, Chain &chain)
0107 {
0108     if (change == MakeFirst) {
0109         makeFirstInChain(window, chain);
0110     } else if (change == MakeLast) {
0111         makeLastInChain(window, chain);
0112     } else {
0113         insertWindowIntoChain(window, chain);
0114     }
0115 }
0116 
0117 void FocusChain::insertWindowIntoChain(Window *window, Chain &chain)
0118 {
0119     Q_ASSERT(!window->isDeleted());
0120     if (chain.contains(window)) {
0121         return;
0122     }
0123     if (m_activeWindow && m_activeWindow != window && !chain.empty() && chain.last() == m_activeWindow) {
0124         // Add it after the active window
0125         chain.insert(chain.size() - 1, window);
0126     } else {
0127         // Otherwise add as the first one
0128         chain.append(window);
0129     }
0130 }
0131 
0132 void FocusChain::moveAfterWindow(Window *window, Window *reference)
0133 {
0134     Q_ASSERT(!window->isDeleted());
0135     if (!window->wantsTabFocus()) {
0136         return;
0137     }
0138 
0139     for (auto it = m_desktopFocusChains.begin();
0140          it != m_desktopFocusChains.end();
0141          ++it) {
0142         if (!window->isOnDesktop(it.key())) {
0143             continue;
0144         }
0145         moveAfterWindowInChain(window, reference, it.value());
0146     }
0147     moveAfterWindowInChain(window, reference, m_mostRecentlyUsed);
0148 }
0149 
0150 void FocusChain::moveAfterWindowInChain(Window *window, Window *reference, Chain &chain)
0151 {
0152     Q_ASSERT(!window->isDeleted());
0153     if (!chain.contains(reference)) {
0154         return;
0155     }
0156     if (Window::belongToSameApplication(reference, window)) {
0157         chain.removeAll(window);
0158         chain.insert(chain.indexOf(reference), window);
0159     } else {
0160         chain.removeAll(window);
0161         for (int i = chain.size() - 1; i >= 0; --i) {
0162             if (Window::belongToSameApplication(reference, chain.at(i))) {
0163                 chain.insert(i, window);
0164                 break;
0165             }
0166         }
0167     }
0168 }
0169 
0170 Window *FocusChain::firstMostRecentlyUsed() const
0171 {
0172     if (m_mostRecentlyUsed.isEmpty()) {
0173         return nullptr;
0174     }
0175     return m_mostRecentlyUsed.first();
0176 }
0177 
0178 Window *FocusChain::nextMostRecentlyUsed(Window *reference) const
0179 {
0180     if (m_mostRecentlyUsed.isEmpty()) {
0181         return nullptr;
0182     }
0183     const int index = m_mostRecentlyUsed.indexOf(reference);
0184     if (index == -1) {
0185         return m_mostRecentlyUsed.first();
0186     }
0187     if (index == 0) {
0188         return m_mostRecentlyUsed.last();
0189     }
0190     return m_mostRecentlyUsed.at(index - 1);
0191 }
0192 
0193 // copied from activation.cpp
0194 bool FocusChain::isUsableFocusCandidate(Window *c, Window *prev) const
0195 {
0196     return c != prev && !c->isShade() && c->isShown() && c->isOnCurrentDesktop() && c->isOnCurrentActivity() && (!m_separateScreenFocus || c->isOnOutput(prev ? prev->output() : workspace()->activeOutput()));
0197 }
0198 
0199 Window *FocusChain::nextForDesktop(Window *reference, VirtualDesktop *desktop) const
0200 {
0201     auto it = m_desktopFocusChains.constFind(desktop);
0202     if (it == m_desktopFocusChains.constEnd()) {
0203         return nullptr;
0204     }
0205     const auto &chain = it.value();
0206     for (int i = chain.size() - 1; i >= 0; --i) {
0207         auto window = chain.at(i);
0208         if (isUsableFocusCandidate(window, reference)) {
0209             return window;
0210         }
0211     }
0212     return nullptr;
0213 }
0214 
0215 void FocusChain::makeFirstInChain(Window *window, Chain &chain)
0216 {
0217     Q_ASSERT(!window->isDeleted());
0218     chain.removeAll(window);
0219     if (options->moveMinimizedWindowsToEndOfTabBoxFocusChain()) {
0220         if (window->isMinimized()) { // add it before the first minimized ...
0221             for (int i = chain.count() - 1; i >= 0; --i) {
0222                 if (chain.at(i)->isMinimized()) {
0223                     chain.insert(i + 1, window);
0224                     return;
0225                 }
0226             }
0227             chain.prepend(window); // ... or at end of chain
0228         } else {
0229             chain.append(window);
0230         }
0231     } else {
0232         chain.append(window);
0233     }
0234 }
0235 
0236 void FocusChain::makeLastInChain(Window *window, Chain &chain)
0237 {
0238     Q_ASSERT(!window->isDeleted());
0239     chain.removeAll(window);
0240     chain.prepend(window);
0241 }
0242 
0243 bool FocusChain::contains(Window *window, VirtualDesktop *desktop) const
0244 {
0245     auto it = m_desktopFocusChains.constFind(desktop);
0246     if (it == m_desktopFocusChains.constEnd()) {
0247         return false;
0248     }
0249     return it.value().contains(window);
0250 }
0251 
0252 } // namespace