From 43f690ff7bd11637e0a7a91bcd99fe6b451ac6e5 Mon Sep 17 00:00:00 2001 From: fluxgen Date: Sat, 18 Feb 2006 09:20:50 +0000 Subject: moved all focus handling to class FocusControl --- src/FocusControl.cc | 671 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/FocusControl.hh | 120 ++++++++++ 2 files changed, 791 insertions(+) create mode 100644 src/FocusControl.cc create mode 100644 src/FocusControl.hh diff --git a/src/FocusControl.cc b/src/FocusControl.cc new file mode 100644 index 0000000..d18af44 --- /dev/null +++ b/src/FocusControl.cc @@ -0,0 +1,671 @@ +// FocusControl.hh +// Copyright (c) 2006 Fluxbox Team (fluxgen at fluxbox dot org) +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +// $Id$ + +#include "FocusControl.hh" + +#include "Screen.hh" +#include "Window.hh" +#include "WinClient.hh" +#include "Workspace.hh" +#include "fluxbox.hh" +#include "FbWinFrameTheme.hh" + +#include +#include +#include +using std::cerr; +using std::endl; +using std::string; + +WinClient *FocusControl::s_focused_window = 0; + +FocusControl::FocusControl(BScreen &screen): + m_screen(screen), + m_focus_model(screen.resourceManager(), + CLICKFOCUS, + screen.name()+".focusModel", + screen.altName()+".FocusModel"), + m_tab_focus_model(screen.resourceManager(), + CLICKTABFOCUS, + screen.name()+".tabFocusModel", + screen.altName()+".TabFocusModel"), + m_focus_last(screen.resourceManager(), true, + screen.name()+".focusLastWindow", + screen.altName()+".FocusLastWindow"), + m_focus_new(screen.resourceManager(), true, + screen.name()+".focusNewWindows", + screen.altName()+".FocusNewWindows"), + m_cycling_focus(false), + m_cycling_last(0) { + + m_cycling_window = m_focused_list.end(); + +} + +// true if the windows should be skiped else false +bool doSkipWindow(const WinClient &winclient, int opts) { + const FluxboxWindow *win = winclient.fbwindow(); + return (!win || + // skip if stuck + (opts & FocusControl::CYCLESKIPSTUCK) != 0 && win->isStuck() || + // skip if not active client (i.e. only visit each fbwin once) + (opts & FocusControl::CYCLEGROUPS) != 0 && win->winClient().window() != winclient.window() || + // skip if shaded + (opts & FocusControl::CYCLESKIPSHADED) != 0 && win->isShaded() || + // skip if hidden + win->isFocusHidden() + ); +} + +void FocusControl::prevFocus(int opts) { + int num_windows = m_screen.currentWorkspace()->numberOfWindows(); + + if (num_windows < 1) + return; + + if (!(opts & CYCLELINEAR)) { + if (!m_cycling_focus) { + m_cycling_focus = true; + m_cycling_window = m_focused_list.end(); + m_cycling_last = 0; + } else { + // already cycling, so restack to put windows back in their proper order + m_screen.layerManager().restack(); + } + // if it is stacked, we want the highest window in the focused list + // that is on the same workspace + FocusedWindows::iterator it = m_cycling_window; + FocusedWindows::iterator it_end = m_focused_list.end(); + + while (true) { + --it; + if (it == it_end) { + it = m_focused_list.end(); + --it; + } + // give up [do nothing] if we reach the current focused again + if ((*it) == (*m_cycling_window)) { + break; + } + + FluxboxWindow *fbwin = (*it)->fbwindow(); + if (fbwin && !fbwin->isIconic() && + (fbwin->isStuck() + || fbwin->workspaceNumber() == m_screen.currentWorkspaceID())) { + // either on this workspace, or stuck + + // keep track of the originally selected window in a set + WinClient &last_client = fbwin->winClient(); + + + if (! (doSkipWindow(**it, opts) || !fbwin->setCurrentClient(**it)) ) { + // moved onto a new fbwin + if (!m_cycling_last || m_cycling_last->fbwindow() != fbwin) { + if (m_cycling_last) { + // set back to orig current Client in that fbwin + m_cycling_last->fbwindow()->setCurrentClient(*m_cycling_last, false); + } + m_cycling_last = &last_client; + } + fbwin->tempRaise(); + break; + } + } + } + m_cycling_window = it; + } else { // not stacked cycling + + Workspace &wksp = *m_screen.currentWorkspace(); + Workspace::Windows &wins = wksp.windowList(); + Workspace::Windows::iterator it = wins.begin(); + + FluxboxWindow *focused_group = 0; + // start from the focused window + bool have_focused = false; + WinClient *focused = Fluxbox::instance()->getFocusedWindow(); + if (focused != 0) { + if (focused->screen().screenNumber() == m_screen.screenNumber()) { + have_focused = true; + focused_group = focused->fbwindow(); + } + } + + if (!have_focused) { + focused_group = (*it); + } else { + //get focused window iterator + for (; it != wins.end() && (*it) != focused_group; ++it) + continue; + } + + do { + if (it == wins.begin()) + it = wins.end(); + --it; + // see if the window should be skipped + if (! (doSkipWindow((*it)->winClient(), opts) || !(*it)->setInputFocus()) ) + break; + } while ((*it) != focused_group); + + if ((*it) != focused_group && it != wins.end()) + (*it)->raise(); + } + +} + +void FocusControl::addFocusFront(WinClient &client) { + m_focused_list.push_front(&client); +} + +void FocusControl::addFocusBack(WinClient &client) { + m_focused_list.push_back(&client); +} + +void FocusControl::stopCyclingFocus() { + // nothing to do + if (!m_cycling_focus) + return; + + m_cycling_focus = false; + m_cycling_last = 0; + // put currently focused window to top + // the iterator may be invalid if the window died + // in which case we'll do a proper revert focus + if (m_cycling_window != m_focused_list.end()) { + WinClient *client = *m_cycling_window; + m_focused_list.erase(m_cycling_window); + m_focused_list.push_front(client); + client->fbwindow()->raise(); + } else { + Fluxbox::instance()->revertFocus(m_screen); + } + +} + +/** + * Used to find out which window was last focused on the given workspace + * If workspace is outside the ID range, then the absolute last focused window + * is given. + */ +WinClient *FocusControl::lastFocusedWindow(int workspace) { + if (m_focused_list.empty()) return 0; + if (workspace < 0 || workspace >= (int) m_screen.numberOfWorkspaces()) + return m_focused_list.front(); + + FocusedWindows::iterator it = m_focused_list.begin(); + FocusedWindows::iterator it_end = m_focused_list.end(); + for (; it != it_end; ++it) { + if ((*it)->fbwindow() && + (((int)(*it)->fbwindow()->workspaceNumber()) == workspace + && !(*it)->fbwindow()->isIconic() + && (!(*it)->fbwindow()->isStuck() || (*it)->fbwindow()->isFocused()))) + // only give focus to a stuck window if it is currently focused + // otherwise they tend to override normal workspace focus + return *it; + } + return 0; +} + +/** + * Used to find out which window was last active in the given group + * If ignore_client is given, it excludes that client. + * Stuck, iconic etc don't matter within a group + */ +WinClient *FocusControl::lastFocusedWindow(FluxboxWindow &group, WinClient *ignore_client) { + if (m_focused_list.empty()) return 0; + + FocusedWindows::iterator it = m_focused_list.begin(); + FocusedWindows::iterator it_end = m_focused_list.end(); + for (; it != it_end; ++it) { + if (((*it)->fbwindow() == &group) && + (*it) != ignore_client) + return *it; + } + return 0; +} + +void FocusControl::nextFocus(int opts) { + const int num_windows = m_screen.currentWorkspace()->numberOfWindows(); + + if (num_windows < 1) + return; + + if (!(opts & CYCLELINEAR)) { + if (!m_cycling_focus) { + m_cycling_focus = True; + m_cycling_window = m_focused_list.begin(); + m_cycling_last = 0; + } else { + // already cycling, so restack to put windows back in their proper order + m_screen.layerManager().restack(); + } + // if it is stacked, we want the highest window in the focused list + // that is on the same workspace + FocusedWindows::iterator it = m_cycling_window; + const FocusedWindows::iterator it_end = m_focused_list.end(); + + while (true) { + ++it; + if (it == it_end) { + it = m_focused_list.begin(); + } + // give up [do nothing] if we reach the current focused again + if ((*it) == (*m_cycling_window)) { + break; + } + + FluxboxWindow *fbwin = (*it)->fbwindow(); + if (fbwin && !fbwin->isIconic() && + (fbwin->isStuck() + || fbwin->workspaceNumber() == m_screen.currentWorkspaceID())) { + // either on this workspace, or stuck + + // keep track of the originally selected window in a set + WinClient &last_client = fbwin->winClient(); + + if (! (doSkipWindow(**it, opts) || !fbwin->setCurrentClient(**it)) ) { + // moved onto a new fbwin + if (!m_cycling_last || m_cycling_last->fbwindow() != fbwin) { + if (m_cycling_last) { + // set back to orig current Client in that fbwin + m_cycling_last->fbwindow()->setCurrentClient(*m_cycling_last, false); + } + m_cycling_last = &last_client; + } + fbwin->tempRaise(); + break; + } + } + } + m_cycling_window = it; + } else { // not stacked cycling + // I really don't like this, but evidently some people use it(!) + Workspace &wksp = *m_screen.currentWorkspace(); + Workspace::Windows &wins = wksp.windowList(); + Workspace::Windows::iterator it = wins.begin(); + + FluxboxWindow *focused_group = 0; + // start from the focused window + bool have_focused = false; + WinClient *focused = Fluxbox::instance()->getFocusedWindow(); + if (focused != 0) { + if (focused->screen().screenNumber() == m_screen.screenNumber()) { + have_focused = true; + focused_group = focused->fbwindow(); + } + } + + if (!have_focused) { + focused_group = (*it); + } else { + // get focused window iterator + for (; it != wins.end() && (*it) != focused_group; ++it) + continue; + } + + do { + ++it; + if (it == wins.end()) + it = wins.begin(); + // see if the window should be skipped + if (! (doSkipWindow((*it)->winClient(), opts) || !(*it)->setInputFocus()) ) + break; + } while ((*it) != focused_group); + + if ((*it) != focused_group && it != wins.end()) + (*it)->raise(); + } + +} + +void FocusControl::raiseFocus() { + bool have_focused = false; + Fluxbox &fb = *Fluxbox::instance(); + // set have_focused if the currently focused window + // is on this screen + if (fb.getFocusedWindow()) { + if (fb.getFocusedWindow()->screen().screenNumber() == m_screen.screenNumber()) { + have_focused = true; + } + } + + // if we have a focused window on this screen and + // number of windows is greater than one raise the focused window + if (m_screen.currentWorkspace()->numberOfWindows() > 1 && have_focused) + fb.getFocusedWindow()->raise(); + +} + +void FocusControl::setScreenFocusedWindow(WinClient &win_client) { + cerr<<__FUNCTION__<windowList(); + Workspace::Windows::iterator it = wins.begin(); + for (; it != wins.end(); ++it) { + if ((*it) == &win + || (*it)->isIconic() + || (*it)->isFocusHidden() + || !(*it)->winClient().acceptsFocus()) + continue; // skip self + + // we check things against an edge, and within the bounds (draw a picture) + int edge=0, upper=0, lower=0, oedge=0, oupper=0, olower=0; + + int otop = (*it)->y(), + obottom = (*it)->y() + (*it)->height() + 2*borderW, + oleft = (*it)->x(), + oright = (*it)->x() + (*it)->width() + 2*borderW; + // check if they intersect + switch (dir) { + case FOCUSUP: + edge = obottom; + oedge = bottom; + upper = left; + oupper = oleft; + lower = right; + olower = oright; + break; + case FOCUSDOWN: + edge = top; + oedge = otop; + upper = left; + oupper = oleft; + lower = right; + olower = oright; + break; + case FOCUSLEFT: + edge = oright; + oedge = right; + upper = top; + oupper = otop; + lower = bottom; + olower = obottom; + break; + case FOCUSRIGHT: + edge = left; + oedge = oleft; + upper = top; + oupper = otop; + lower = bottom; + olower = obottom; + break; + } + + if (oedge < edge) continue; // not in the right direction + if (olower <= upper || oupper >= lower) { + // outside our horz bounds, get a heavy weight penalty + int myweight = 100000 + oedge - edge + abs(upper-oupper)+abs(lower-olower); + if (myweight < weight) { + foundwin = *it; + exposure = 0; + weight = myweight; + } + } else if ((oedge - edge) < weight) { + foundwin = *it; + weight = oedge - edge; + exposure = ((lower < olower)?lower:olower) - ((upper > oupper)?upper:oupper); + } else if (foundwin && oedge - edge == weight) { + int myexp = ((lower < olower)?lower:olower) - ((upper > oupper)?upper:oupper); + if (myexp > exposure) { + foundwin = *it; + // weight is same + exposure = myexp; + } + } // else not improvement + } + + if (foundwin) + foundwin->setInputFocus(); + +} + +void FocusControl::removeClient(WinClient &client) { + WinClient *cyc = 0; + if (m_cycling_window != m_focused_list.end()) + cyc = *m_cycling_window; + + m_focused_list.remove(&client); + if (cyc == &client) { + m_cycling_window = m_focused_list.end(); + } + + if (m_cycling_last == &client) + m_cycling_last = 0; + +} + +/** + * This function is called whenever we aren't quite sure what + * focus is meant to be, it'll make things right ;-) + * last_focused is set to something if we want to make use of the + * previously focused window (it must NOT be set focused now, it + * is probably dying). + * + * ignore_event means that it ignores the given event until + * it gets a focusIn + */ +void FocusControl::revertFocus(BScreen &screen) { + cerr<<__FUNCTION__<fbwindow() && + next_focus->fbwindow()->setCurrentClient(*next_focus, true))) { + setFocusedWindow(0); // so we don't get dangling m_focused_window pointer + switch (screen.focusControl().focusModel()) { + case FocusControl::MOUSEFOCUS: + XSetInputFocus(screen.rootWindow().display(), + PointerRoot, None, CurrentTime); + break; + case FocusControl::CLICKFOCUS: + screen.rootWindow().setInputFocus(RevertToPointerRoot, CurrentTime); + break; + } + } +} + +/* + * Like revertFocus, but specifically related to this window (transients etc) + * if full_revert, we fallback to a full revertFocus if we can't find anything + * local to the client. + * If unfocus_frame is true, we won't focus anything in the same frame + * as the client. + * + * So, we first prefer to choose a transient parent, then the last + * client in this window, and if no luck (or unfocus_frame), then + * we just use the normal revertFocus on the screen. + * + * assumption: client has focus + */ +void FocusControl::unfocusWindow(WinClient &client, + bool full_revert, + bool unfocus_frame) { + cerr<<__FUNCTION__<fbwindow() && // can't focus if no fbwin + (!unfocus_frame || trans_parent->fbwindow() != fbwin) && // can't be this window + trans_parent->fbwindow()->isVisible() && + trans_parent->fbwindow()-> + setCurrentClient(*trans_parent, + Fluxbox::instance()->getFocusedWindow() == &client)) { + return; + } + trans_parent = trans_parent->transientFor(); + } + + if (fbwin == 0) + return; // nothing more we can do + + BScreen &screen = fbwin->screen(); + + if (!unfocus_frame) { + WinClient *last_focus = screen.focusControl().lastFocusedWindow(*fbwin, &client); + if (last_focus != 0 && + fbwin->setCurrentClient(*last_focus, + Fluxbox::instance()->getFocusedWindow() == &client)) { + return; + } + } + + if (full_revert && Fluxbox::instance()->getFocusedWindow() == &client) + revertFocus(screen); + +} + + +void FocusControl::setFocusedWindow(WinClient *client) { + assert(false); +#ifdef DEBUG + cerr<<__FILE__<fbwindow()->title()<searchWindow(s_focused_window) != 0) { + s_focused_window = 0; + } else { + old_client = s_focused_window; + if (old_client->fbwindow()) { + FluxboxWindow *old_win = old_client->fbwindow(); + + if (!client || client->fbwindow() != old_win) + old_win->setFocusFlag(false); + } + } + } + + if (client && client->fbwindow() && !client->fbwindow()->isIconic()) { + // screen should be ok + FluxboxWindow *win = client->fbwindow(); + s_focused_window = client; // update focused window + win->setCurrentClient(*client, false); // don't setinputfocus + win->setFocusFlag(true); // set focus flag + + } else + s_focused_window = 0; + + Fluxbox::instance()->updateFocusedWindow(s_focused_window, old_client); + */ +} + +////////////////////// FocusControl RESOURCES +template<> +std::string FbTk::Resource::getString() const { + switch (m_value) { + case FocusControl::MOUSEFOCUS: + return string("MouseFocus"); + case FocusControl::CLICKFOCUS: + return string("ClickFocus"); + } + // default string + return string("ClickFocus"); +} + +template<> +void FbTk::Resource:: +setFromString(char const *strval) { + if (strcasecmp(strval, "MouseFocus") == 0) + m_value = FocusControl::MOUSEFOCUS; + else if (strcasecmp(strval, "ClickToFocus") == 0) + m_value = FocusControl::CLICKFOCUS; + else + setDefaultValue(); +} + +template<> +std::string FbTk::Resource::getString() const { + switch (m_value) { + case FocusControl::MOUSETABFOCUS: + return string("SloppyTabFocus"); + case FocusControl::CLICKTABFOCUS: + return string("ClickToTabFocus"); + } + // default string + return string("ClickToTabFocus"); +} + +template<> +void FbTk::Resource:: +setFromString(char const *strval) { + + if (strcasecmp(strval, "SloppyTabFocus") == 0 ) + m_value = FocusControl::MOUSETABFOCUS; + else if (strcasecmp(strval, "ClickToTabFocus") == 0) + m_value = FocusControl::CLICKTABFOCUS; + else + setDefaultValue(); +} + diff --git a/src/FocusControl.hh b/src/FocusControl.hh new file mode 100644 index 0000000..4870c1f --- /dev/null +++ b/src/FocusControl.hh @@ -0,0 +1,120 @@ +// FocusControl.hh +// Copyright (c) 2006 Fluxbox Team (fluxgen at fluxbox dot org) +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +// $Id$ + +#ifndef FOCUSCONTROL_HH +#define FOCUSCONTROL_HH + +#include + +#include "FbTk/Resource.hh" + +class WinClient; +class FluxboxWindow; +class BScreen; + +/** + * Handles window focus for a specific screen. + * It also holds the static "global" focused window + */ +class FocusControl { +public: + typedef std::list FocusedWindows; + + enum FocusModel { + MOUSEFOCUS = 0, ///< focus follows + CLICKFOCUS ///< focus on click + }; + enum TabFocusModel { + MOUSETABFOCUS = 0, ///< tab focus follows mouse + CLICKTABFOCUS ///< tab focus on click + }; + + enum FocusDir { + FOCUSUP, + FOCUSDOWN, + FOCUSLEFT, + FOCUSRIGHT + }; + + // prevFocus/nextFocus option bits + enum { + CYCLEGROUPS = 0x01, + CYCLESKIPSTUCK = 0x02, + CYCLESKIPSHADED = 0x04, + CYCLELINEAR = 0x08, + CYCLEDEFAULT = 0x00 + }; + + explicit FocusControl(BScreen &screen); + + void prevFocus() { prevFocus(0); } + void nextFocus() { nextFocus(0); } + void prevFocus(int options); + void nextFocus(int options); + void raiseFocus(); + + void setScreenFocusedWindow(WinClient &win_client); + void setFocusModel(FocusModel model); + void setTabFocusModel(TabFocusModel model); + + void stopCyclingFocus(); + + void dirFocus(FluxboxWindow &win, FocusDir dir); + bool isMouseFocus() const { return focusModel() == MOUSEFOCUS; } + bool isMouseTabFocus() const { return tabFocusModel() == MOUSETABFOCUS; } + void addFocusFront(WinClient &client); + void addFocusBack(WinClient &client); + + FocusModel focusModel() const { return *m_focus_model; } + TabFocusModel tabFocusModel() const { return *m_tab_focus_model; } + bool focusLast() const { return *m_focus_last; } + bool focusNew() const { return *m_focus_new; } + + WinClient *lastFocusedWindow(int workspace); + WinClient *lastFocusedWindow(FluxboxWindow &group, WinClient *ignore_client); + + void removeClient(WinClient &client); + + static void revertFocus(BScreen &screen); + static void unfocusWindow(WinClient &client, bool full_revert, bool unfocus_frame); + static void setFocusedWindow(WinClient *focus_to); + static WinClient *focusedWindow() { return s_focused_window; } +private: + + BScreen &m_screen; + + FbTk::Resource m_focus_model; + FbTk::Resource m_tab_focus_model; + FbTk::Resource m_focus_last, m_focus_new; + + // This list keeps the order of window focusing for this screen + // Screen global so it works for sticky windows too. + FocusedWindows m_focused_list; + FocusedWindows::iterator m_cycling_window; + bool m_cycling_focus; + WinClient *m_cycling_last; + + static WinClient *s_focused_window; +}; + +#endif // FOCUSCONTROL_HH -- cgit v0.11.2