// // PDecor.cc for pekwm // Copyright © 2004-2009 Claes Nästén // // This program is licensed under the GNU GPL. // See the LICENSE file for more information. // #ifdef HAVE_CONFIG_H #include "config.h" #endif // HAVE_CONFIG_H #include #include #include #include #include #include extern "C" { #include #include #ifdef HAVE_SHAPE #include #endif // HAVE_SHAPE } #include "Compat.hh" #include "Config.hh" #include "PWinObj.hh" #include "PFont.hh" #include "PDecor.hh" #include "PScreen.hh" #include "PTexture.hh" #include "PTexturePlain.hh" // PTextureSolid #include "ActionHandler.hh" #include "ScreenResources.hh" #include "StatusWindow.hh" #include "KeyGrabber.hh" #include "Theme.hh" #include "PixmapHandler.hh" #include "Workspaces.hh" using std::cerr; using std::endl; using std::find; using std::list; using std::map; using std::mem_fun; using std::string; using std::vector; using std::swprintf; // PDecor::Button //! @brief PDecor::Button constructor PDecor::Button::Button(PWinObj *parent, Theme::PDecorButtonData *data, uint width, uint height) : PWinObj(), _data(data), _state(BUTTON_STATE_UNFOCUSED), _left(_data->isLeft()) { _parent = parent; _gm.width = width; _gm.height = height; XSetWindowAttributes attr; attr.event_mask = EnterWindowMask|LeaveWindowMask; attr.override_redirect = True; _window = XCreateWindow(_dpy, _parent->getWindow(), -_gm.width, -_gm.height, _gm.width, _gm.height, 0, CopyFromParent, InputOutput, CopyFromParent, CWEventMask|CWOverrideRedirect, &attr); _bg = ScreenResources::instance()->getPixmapHandler()->getPixmap(_gm.width, _gm.height, PScreen::instance()->getDepth()); setBackgroundPixmap(_bg); setState(_state); } //! @brief PDecor::Button destructor PDecor::Button::~Button(void) { XDestroyWindow(_dpy, _window); ScreenResources::instance()->getPixmapHandler()->returnPixmap(_bg); } //! @brief Searches the PDecorButtonData for an action matching ev ActionEvent* PDecor::Button::findAction(XButtonEvent *ev) { list::iterator it(_data->begin()); for (; it != _data->end(); ++it) { if (it->mod == ev->state && it->sym == ev->button) return &*it; } return 0; } //! @brief Sets the state of the button void PDecor::Button::setState(ButtonState state) { if (state == BUTTON_STATE_NO) { return; } // Only update if we don't hoover, we want to be able to turn back if (state != BUTTON_STATE_HOVER) { _state = state; } PTexture *texture = _data->getTexture(state); if (texture) { texture->render(_bg, 0, 0, _gm.width, _gm.height); #ifdef HAVE_SHAPE // Get shape mask bool need_free; Pixmap shape = _data->getTexture(state)->getMask(0, 0, need_free); if (shape != None) { XShapeCombineMask(_dpy, _window, ShapeBounding, 0, 0, shape, ShapeSet); if (need_free) { ScreenResources::instance()->getPixmapHandler()->returnPixmap(shape); } } else { XRectangle rect; rect.x = 0; rect.y = 0; rect.width = _gm.width; rect.height = _gm.height; XShapeCombineRectangles(_dpy, _window, ShapeBounding, 0, 0, &rect, 1, ShapeSet, YXBanded); } #endif // HAVE_SHAPE clear(); } } //! @brief Update visible version of title void PDecor::TitleItem::updateVisible(void) { // Start with empty string _visible = L""; // Add client info to title if ((_info != 0) && ((_info != INFO_ID) || Config::instance()->isShowClientID())) { _visible.append(L"["); if (infoIs(INFO_ID) && Config::instance()->isShowClientID()) { _visible.append(Util::to_wide_str(Util::to_string(_id))); } if (infoIs(INFO_MARKED)) { _visible.append(L"M"); } _visible.append(L"] "); } // Add title if (_user.size() > 0) { _visible.append(_user); } else if (_custom.size() > 0) { _visible.append(_custom); } else { _visible.append(_real); } // Add client number to title if (_count > 0) { _visible.append(Util::to_wide_str(Config::instance()->getClientUniqueNamePre())); _visible.append(Util::to_wide_str(Util::to_string(_count))); _visible.append(Util::to_wide_str(Config::instance()->getClientUniqueNamePost())); } } // PDecor const string PDecor::DEFAULT_DECOR_NAME = string("DEFAULT"); const string PDecor::DEFAULT_DECOR_NAME_BORDERLESS = string("BORDERLESS"); const string PDecor::DEFAULT_DECOR_NAME_TITLEBARLESS = string("TITLEBARLESS"); list PDecor::_pdecor_list = list(); //! @brief PDecor constructor //! @param dpy Display //! @param theme Theme //! @param decor_name String, if not DEFAULT_DECOR_NAME sets _decor_name_override PDecor::PDecor(Theme *theme, const std::string decor_name, const Window child_window) : PWinObj(), _theme(theme),_decor_name(decor_name), _child(0), _button(0), _button_press_win(None), _pointer_x(0), _pointer_y(0), _click_x(0), _click_y(0), _decor_cfg_keep_empty(false), _decor_cfg_child_move_overloaded(false), _decor_cfg_bpr_replay_pointer(false), _decor_cfg_bpr_al_child(MOUSE_ACTION_LIST_CHILD_OTHER), _decor_cfg_bpr_al_title(MOUSE_ACTION_LIST_TITLE_OTHER), _maximized_vert(false), _maximized_horz(false), _fullscreen(false), _skip(0), _data(0), _border(true), _titlebar(true), _shaded(false), _need_shape(false), _need_client_shape(false), _dirty_resized(true), _real_height(1), _title_wo(), _title_bg(None), _title_active(0), _titles_left(0), _titles_right(1) { if (_decor_name != PDecor::DEFAULT_DECOR_NAME) { _decor_name_override = _decor_name; } // we be reset in loadDecor later on, inlines using the _data used before // loadDecor needs this though _data = _theme->getPDecorData(_decor_name); if (! _data) { _data = _theme->getPDecorData(DEFAULT_DECOR_NAME); } CreateWindowParams window_params; getParentWindowAttributes(window_params, child_window); createParentWindow(window_params); if (window_params.mask & CWColormap) { window_params.depth = PScreen::instance()->getDepth(); window_params.visual = PScreen::instance()->getVisual()->getXVisual(); window_params.attr.colormap = PScreen::instance()->getColormap(); } createTitle(window_params); createBorder(window_params); // sets buttons etc up loadDecor(); // map title and border windows XMapSubwindows(_dpy, _window); _pdecor_list.push_back(this); } /** * Create window attributes */ void PDecor::getParentWindowAttributes(CreateWindowParams ¶ms, Window child_window) { params.mask = CWOverrideRedirect|CWEventMask|CWBorderPixel|CWBackPixel; params.depth = CopyFromParent; params.visual = CopyFromParent; params.attr.override_redirect = True; params.attr.border_pixel = 0; params.attr.background_pixel = 0; if (child_window != None) { getChildWindowAttributes(params, child_window); } } /** * Get window attributes from child window */ void PDecor::getChildWindowAttributes(CreateWindowParams ¶ms, Window child_window) { XWindowAttributes attr; Status status = XGetWindowAttributes(_dpy, child_window, &attr); if (status != BadDrawable && status != BadWindow && attr.depth == 32) { params.mask |= CWColormap; params.depth = attr.depth; params.visual = attr.visual; params.attr.colormap = XCreateColormap(_dpy, PScreen::instance()->getRoot(), params.visual, AllocNone); } } /** * Create container window. */ void PDecor::createParentWindow(CreateWindowParams ¶ms) { params.attr.event_mask = ButtonPressMask|ButtonReleaseMask| ButtonMotionMask|EnterWindowMask|SubstructureRedirectMask| SubstructureNotifyMask; _window = XCreateWindow(_dpy, PScreen::instance()->getRoot(), _gm.x, _gm.y, _gm.width, _gm.height, 0, params.depth, InputOutput, params.visual, params.mask, ¶ms.attr); } /** * Create title window. */ void PDecor::createTitle(CreateWindowParams ¶ms) { params.attr.event_mask = ButtonPressMask|ButtonReleaseMask| ButtonMotionMask|EnterWindowMask; Window title = XCreateWindow(_dpy, _window, borderLeft(), borderTop(), 1, 1, 0, params.depth, InputOutput, params.visual, params.mask, ¶ms.attr); _title_wo.setWindow(title); addChildWindow(_title_wo.getWindow()); } /** * Create border windows */ void PDecor::createBorder(CreateWindowParams ¶ms) { params.attr.event_mask = ButtonPressMask|ButtonReleaseMask| ButtonMotionMask|EnterWindowMask; ScreenResources *sr = ScreenResources::instance(); for (uint i = 0; i < BORDER_NO_POS; ++i) { params.attr.cursor = sr->getCursor(ScreenResources::CursorType(i)); _border_win[i] = XCreateWindow(_dpy, _window, -1, -1, 1, 1, 0, params.depth, InputOutput, params.visual, params.mask|CWCursor, ¶ms.attr); _border_pos_map[BorderPosition(i)] = None; addChildWindow(_border_win[i]); } } //! @brief PDecor destructor PDecor::~PDecor(void) { _pdecor_list.remove(this); if (_child_list.size() > 0) { while (_child_list.size() != 0) { removeChild(_child_list.back(), false); // Don't call delete this. } } // Make things look smoother, buttons will be noticed as deleted // otherwise. Using X call directly to avoid re-drawing and other // special features not required when removing the window. XUnmapWindow(_dpy, _window); // free buttons unloadDecor(); // free border pixmaps map::iterator it(_border_pos_map.begin()); for (; it != _border_pos_map.end(); ++it) { if (it->second != None) { ScreenResources::instance()->getPixmapHandler()->returnPixmap(it->second); } } ScreenResources::instance()->getPixmapHandler()->returnPixmap(_title_bg); removeChildWindow(_title_wo.getWindow()); XDestroyWindow(_dpy, _title_wo.getWindow()); for (uint i = 0; i < BORDER_NO_POS; ++i) { removeChildWindow(_border_win[i]); XDestroyWindow(_dpy, _border_win[i]); } XDestroyWindow(_dpy, _window); } // START - PWinObj interface. //! @brief Map decor and all children void PDecor::mapWindow(void) { if (! _mapped) { PWinObj::mapWindow(); for_each(_child_list.begin(), _child_list.end(), mem_fun(&PWinObj::mapWindow)); } } //! @brief Maps the window raised void PDecor::mapWindowRaised(void) { if (_mapped) { return; } mapWindow(); raise(); // XMapRaised wouldn't preserver layers } //! @brief Unmaps decor and all children void PDecor::unmapWindow(void) { if (_mapped) { if (_iconified) { for_each(_child_list.begin(), _child_list.end(), mem_fun(&PWinObj::iconify)); } else { for_each(_child_list.begin(), _child_list.end(), mem_fun(&PWinObj::unmapWindow)); } PWinObj::unmapWindow(); } } //! @brief Moves the decor void PDecor::move(int x, int y) { // update real position PWinObj::move(x, y); if (_child && (_decor_cfg_child_move_overloaded)) { _child->move(x + borderLeft(), y + borderTop() + getTitleHeight()); } } //! @brief Resizes the decor, and active child if any void PDecor::resize(uint width, uint height) { // If shaded, don't resize to the size specified just update width // and set _real_height to height if (_shaded) { _real_height = height; height = getTitleHeight(); // Shading in non full width title mode will make border go away if (! _data->getTitleWidthMin()) { height += borderTop() + borderBottom(); } } PWinObj::resize(width, height); // Update size before moving and shaping the rest as shaping // depends on the child window if (_child) { _child->resize(getChildWidth(), getChildHeight()); } // place / resize title and border resizeTitle(); placeBorder(); // render title and border _dirty_resized = true; // Set and apply shape on window, all parts of the border can now // be shaped. setBorderShape(); applyBorderShape(); renderTitle(); renderBorder(); _dirty_resized = false; } //! @brief Move and resize window. void PDecor::moveResize(int x, int y, uint width, uint height) { // If shaded, don't resize to the size specified just update width // and set _real_height to height if (_shaded) { _real_height = height; height = getTitleHeight(); // Shading in non full width title mode will make border go away if (! _data->getTitleWidthMin()) { height += borderTop() + borderBottom(); } } PWinObj::moveResize(x, y, width, height); // Update size before moving and shaping the rest as shaping // depends on the child window if (_child) { _child->moveResize(x + borderLeft(), y + borderTop() + getTitleHeight(), getChildWidth(), getChildHeight()); // The client window may have its window gravity set to something different // than NorthWestGravity (see Xlib manual chapter 3.2.3). Therefore the // X server may not move its top left corner along with the decoration. // We correct these cases by calling alignChild(). It is called only for // _child and not all members of _child_list, because activateChild.*() // does it itself. alignChild(_child); } // Place and resize title and border resizeTitle(); placeBorder(); // render title and border _dirty_resized = true; // Apply shape on window, all parts of the border can now be shaped. setBorderShape(); applyBorderShape(); renderTitle(); renderBorder(); _dirty_resized = false; } //! @brief void PDecor::resizeTitle(void) { if (getTitleHeight()) { _title_wo.resize(calcTitleWidth(), getTitleHeight()); calcTabsWidth(); } // place buttons, also updates title information placeButtons(); } //! @brief Raises the window, taking _layer into account void PDecor::raise(void) { Workspaces::instance()->raise(this); Workspaces::instance()->updateClientStackingList(); } //! @brief Lowers the window, taking _layer into account void PDecor::lower(void) { Workspaces::instance()->lower(this); Workspaces::instance()->updateClientStackingList(); } //! @brief void PDecor::setFocused(bool focused) { if (_focused != focused) { // save repaints PWinObj::setFocused(focused); renderTitle(); renderButtons(); renderBorder(); setBorderShape(); applyBorderShape(); } } //! @brief void PDecor::setWorkspace(uint workspace) { if (workspace != NET_WM_STICKY_WINDOW) { if (workspace >= Workspaces::instance ()->size ()) { #ifdef DEBUG cerr << __FILE__ << "@" << __LINE__ << ": " << "PDecor(" << this << ")::setWorkspace(" << workspace << ")" << endl << " *** workspace > number of workspaces:" << Workspaces::instance ()->size () << endl; #endif // DEBUG workspace = Workspaces::instance()->size() - 1; } _workspace = workspace; } list::iterator it (_child_list.begin ()); for (; it != _child_list.end (); ++it) { (*it)->setWorkspace (workspace); } if (! _mapped && ! _iconified) { if (_sticky || (_workspace == Workspaces::instance()->getActive())) { mapWindow(); } } else if (! _sticky && (_workspace != Workspaces::instance()->getActive())) { unmapWindow(); } } //! @brief Gives decor input focus, fails if not mapped or not visible void PDecor::giveInputFocus(void) { if (_mapped && _child) { _child->giveInputFocus(); } else { #ifdef DEBUG cerr << __FILE__ << "@" << __LINE__ << ": " << "PDecor::giveInputFocus(" << this << ")" << endl << " *** reverting to root" << endl; #endif // DEBUG PWinObj::getRootPWinObj()->giveInputFocus(); } } /** * Handle button press events. */ ActionEvent* PDecor::handleButtonPress(XButtonEvent *ev) { ActionEvent *ae = 0; list *actions = 0; // Remove state modifiers from event PScreen::stripStateModifiers(&ev->state); PScreen::stripButtonModifiers(&ev->state); // Try to do something about frame buttons if (ev->subwindow != None && (_button = findButton(ev->subwindow)) != 0) { ae = handleButtonPressButton(ev, _button); } else { // Record position, used in the motion handler when doing a window move. _click_x = _gm.x; _click_y = _gm.y; _pointer_x = ev->x_root; _pointer_y = ev->y_root; // Allow us to get clicks from anywhere on the window. if (_decor_cfg_bpr_replay_pointer) { XAllowEvents(_dpy, ReplayPointer, CurrentTime); } if (ev->window == _child->getWindow() || (ev->state == 0 && ev->subwindow == _child->getWindow())) { // Clicks on the child window // NOTE: If the we're matching against the subwindow we need to make // sure that the state is 0, meaning we didn't have any modifier // because if we don't care about the modifier we'll get two actions // performed when using modifiers. _button_press_win = _child->getWindow(); actions = Config::instance()->getMouseActionList(_decor_cfg_bpr_al_child); } else if (_title_wo == ev->window) { // Clicks on the decor title _button_press_win = ev->window; actions = Config::instance()->getMouseActionList(_decor_cfg_bpr_al_title); } else { // Clicks on the decor border, default case. Try both window and sub-window. uint pos = getBorderPosition(ev->window); if (pos != BORDER_NO_POS) { _button_press_win = ev->window; actions = Config::instance()->getBorderListFromPosition(pos); } } } if (! ae && actions) { ae = ActionHandler::findMouseAction(ev->button, ev->state, MOUSE_EVENT_PRESS, actions); } return ae; } /** * Handle button press events pressing decor buttons. */ ActionEvent* PDecor::handleButtonPressButton(XButtonEvent *ev, PDecor::Button *button) { // Keep track of pressed button. _button->setState(BUTTON_STATE_PRESSED); ActionEvent *ae = _button->findAction(ev); // if the button is used for resizing, we don't want to wait for release if (ae && ae->isOnlyAction(ACTION_RESIZE)) { _button->setState(_focused ? BUTTON_STATE_FOCUSED : BUTTON_STATE_UNFOCUSED); _button = 0; } else { ae = 0; } return ae; } /** * Handle button release events. */ ActionEvent* PDecor::handleButtonRelease(XButtonEvent *ev) { ActionEvent *ae = 0; list *actions = 0; MouseEventType mb = MOUSE_EVENT_RELEASE; // Remove state modifiers from event PScreen::stripStateModifiers(&ev->state); PScreen::stripButtonModifiers(&ev->state); // handle titlebar buttons if (_button) { ae = handleButtonReleaseButton(ev, _button); } else { // Allow us to get clicks from anywhere on the window. if (_decor_cfg_bpr_replay_pointer) { XAllowEvents(_dpy, ReplayPointer, CurrentTime); } // clicks on the child window if (ev->window == _child->getWindow() || (ev->state == 0 && ev->subwindow == _child->getWindow())) { // NOTE: If the we're matching against the subwindow we need to make // sure that the state is 0, meaning we didn't have any modifier // because if we don't care about the modifier we'll get two actions // performed when using modifiers. if (_button_press_win == _child->getWindow()) { actions = Config::instance()->getMouseActionList(_decor_cfg_bpr_al_child); } } else if (_title_wo == ev->window) { if (_button_press_win == ev->window) { // Handle clicks on the decor title, checking double clicks first. if (PScreen::instance()->isDoubleClick(ev->window, ev->button - 1, ev->time, Config::instance()->getDoubleClickTime())) { PScreen::instance()->setLastClickID(ev->window); PScreen::instance()->setLastClickTime(ev->button - 1, 0); mb = MOUSE_EVENT_DOUBLE; } else { PScreen::instance()->setLastClickID(ev->window); PScreen::instance()->setLastClickTime(ev->button - 1, ev->time); } actions = Config::instance()->getMouseActionList(_decor_cfg_bpr_al_title); } } else { // Clicks on the decor border, check subwindow then window. uint pos = getBorderPosition(ev->window); if (pos != BORDER_NO_POS && (_button_press_win == ev->window)) { actions = Config::instance()->getBorderListFromPosition(pos); } } } if (! ae && actions) { ae = ActionHandler::findMouseAction(ev->button, ev->state, mb, actions); } return ae; } /** * Handle button release events when button is in pressed state. */ ActionEvent* PDecor::handleButtonReleaseButton(XButtonEvent *ev, PDecor::Button *button) { // First restore the pressed buttons state _button->setState(_focused ? BUTTON_STATE_FOCUSED : BUTTON_STATE_UNFOCUSED); ActionEvent *ae = 0; // Then see if the button was released over ( to execute an action ) if (*_button == ev->subwindow) { ae = _button->findAction(ev); // This is a little hack, resizing isn't wanted on both press and release if (ae && ae->isOnlyAction(ACTION_RESIZE)) { ae = 0; } } _button = 0; return ae; } //! @brief ActionEvent* PDecor::handleMotionEvent(XMotionEvent *ev) { uint button = PScreen::instance()->getButtonFromState(ev->state); return ActionHandler::findMouseAction(button, ev->state, MOUSE_EVENT_MOTION, Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_OTHER)); } /** * Handle enter event, find action and toggle hoover state if enter * was on a button. */ ActionEvent* PDecor::handleEnterEvent(XCrossingEvent *ev) { PDecor::Button *button = findButton(ev->window); if (button) { button->setState(BUTTON_STATE_HOVER); } return ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_ENTER, Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_OTHER)); } /** * Handle leave event, find action and toggle hoover state if leave * was from a button. */ ActionEvent* PDecor::handleLeaveEvent(XCrossingEvent *ev) { PDecor::Button *button = findButton(ev->window); if (button) { button->setState(button->getState()); } return ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_LEAVE, Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_OTHER)); } // END - PWinObj interface. //! @brief Adds a children in another decor to this decor void PDecor::addDecor(PDecor *decor) { if (this == decor) { #ifdef DEBUG cerr << __FILE__ << "@" << __LINE__ << ": " << "PDecor(" << this << ")::addDecor(" << decor << ")" << " *** this == decor" << endl; #endif // DEBUG return; } list::iterator it(decor->begin()); for (; it != decor->end(); ++it) { addChild(*it); (*it)->setWorkspace(_workspace); } decor->_child_list.clear(); delete decor; } //! @brief Sets preferred decor_name //! @return True on change, False if unchanged bool PDecor::setDecor(const std::string &name) { string new_name(_decor_name_override); if (new_name.size() == 0) { new_name = name; } if (_decor_name == new_name) { return false; } _decor_name = new_name; loadDecor(); return true; } //! @brief Sets override decor name void PDecor::setDecorOverride(StateAction sa, const std::string &name) { if ((sa == STATE_SET) || ((sa == STATE_TOGGLE) && (_decor_name_override.size() == 0))) { _decor_name_override = name; setDecor(""); } else if ((sa == STATE_UNSET) || ((sa == STATE_TOGGLE) && (_decor_name_override.size() > 0))) { _decor_name_override = ""; // .clear() doesn't work with old g++ updateDecorName(); } } //! @brief Load and update decor. void PDecor::loadDecor(void) { unloadDecor(); // Get decordata with name. _data = _theme->getPDecorData(_decor_name); if (! _data) { _data = _theme->getPDecorData(DEFAULT_DECOR_NAME); } assert(_data); // Load decor. list::iterator b_it(_data->buttonBegin()); for (; b_it != _data->buttonEnd(); ++b_it) { uint width = std::max(static_cast(1), (*b_it)->getWidth() ? (*b_it)->getWidth() : getTitleHeight()); uint height = std::max(static_cast(1), (*b_it)->getHeight() ? (*b_it)->getHeight() : getTitleHeight()); _button_list.push_back(new PDecor::Button(&_title_wo, *b_it, width, height)); _button_list.back()->mapWindow(); addChildWindow(_button_list.back()->getWindow()); } // Update title position. if (_data->getTitleWidthMin() == 0) { _title_wo.move(borderTopLeft(), borderTop()); _need_shape = false; } else { _title_wo.move(0, 0); _need_shape = true; } // Update child positions. list::iterator c_it(_child_list.begin()); for (; c_it != _child_list.end(); ++c_it) { alignChild(*c_it); } // Make sure it gets rendered correctly. _dirty_resized = true; _focused = ! _focused; setFocused(! _focused); _dirty_resized = false; // Update the dimension of the frame. if (_child) { resizeChild(_child->getWidth(), _child->getHeight()); } // Child theme change. loadTheme(); } //! @brief Frees resources used by PDecor. void PDecor::unloadDecor(void) { // Set active button to 0 as it can not be valid after deleting // the current buttons. _button = 0; list::iterator it(_button_list.begin()); for (; it != _button_list.end(); ++it) { removeChildWindow((*it)->getWindow()); delete *it; } _button_list.clear(); } //! @brief PDecor::Button* PDecor::findButton(Window win) { list::iterator it(_button_list.begin()); for (; it != _button_list.end(); ++it) { if (**it == win) { return *it; } } return 0; } //! @brief PWinObj* PDecor::getChildFromPos(int x) { if (! _child_list.size() || (_child_list.size() != _title_list.size())) return 0; if (_child_list.size() == 1) return _child_list.front(); if (x < static_cast(_titles_left)) { return _child_list.front(); } else if (x > static_cast(_title_wo.getWidth() - _titles_right)) { return _child_list.back(); } list::iterator c_it(_child_list.begin()); list::iterator t_it(_title_list.begin()); // FIXME: make getChildFromPos separator aware! uint pos = _titles_left, xx = x; for (uint i = 0; i < _title_list.size(); ++i, ++t_it, ++c_it) { if ((xx >= pos) && (xx <= (pos + (*t_it)->getWidth()))) { return *c_it; } pos += (*t_it)->getWidth(); } return 0; } //! @brief Moves making the child be positioned at x y void PDecor::moveChild(int x, int y) { move(x - borderLeft(), y - borderTop() - getTitleHeight()); } //! @brief Resizes the decor, giving width x height space for the child void PDecor::resizeChild(uint width, uint height) { resize(width + borderLeft() + borderRight(), height + borderTop() + borderBottom() + getTitleHeight()); } //! @brief Sets border state of the decor void PDecor::setBorder(StateAction sa) { if (! ActionUtil::needToggle(sa, _border)) { return; } // If we are going to remove the border, we need to check carefully // that we don't try to make the window disappear. if (! _border) { _border = true; } else if (! _shaded || _titlebar) { _border = false; } restackBorder(); if (! updateDecorName() && _child) resizeChild(_child->getWidth(), _child->getHeight()); } //! @brief Sets titlebar state of the decor void PDecor::setTitlebar(StateAction sa) { if (! ActionUtil::needToggle(sa, _titlebar)) return; // If we are going to remove the titlebar, we need to check carefully // that we don't try to make the window disappear. if (! _titlebar) { _title_wo.mapWindow(); _titlebar = true; } else if (! _shaded || _border) { _title_wo.unmapWindow(); _titlebar = false; } // If updateDecorName returns true, it already loaded decor stuff for us. if (! updateDecorName() && _child) { alignChild(_child); resizeChild(_child->getWidth(), _child->getHeight()); } } //! @brief Calculate title height, 0 if titlebar is disabled. uint PDecor::getTitleHeight(void) const { if (! _titlebar) { return 0; } if (_data->isTitleHeightAdapt()) { return getFont(getFocusedState(false))->getHeight() + _data->getPad(PAD_UP) + _data->getPad(PAD_DOWN); } else { return _data->getTitleHeight(); } } //! @brief Adds a child to the decor, reparenting the window void PDecor::addChild(PWinObj *child, std::list::iterator *it) { child->reparent(this, borderLeft(), borderTop() + getTitleHeight()); if (it == 0) { _child_list.push_back(child); } else { _child_list.insert(*it, child); } updatedChildOrder(); // Sync focused state if it is the first child, the child will be // activated later on. If there are children here already fit the // child into the decor. if (_child_list.size() == 1) { _focused = ! _focused; setFocused(! _focused); } else { alignChild(child); child->resize(getChildWidth(), getChildHeight()); } } //! @brief Removes PWinObj from this PDecor. //! @param child PWinObj to remove from the this PDecor. //! @param do_delete Wheter to call delete this when empty. (Defaults to true) void PDecor::removeChild(PWinObj *child, bool do_delete) { child->reparent(PWinObj::getRootPWinObj(), _gm.x + borderLeft(), _gm.y + borderTop() + getTitleHeight()); list::iterator it(find(_child_list.begin(), _child_list.end(), child)); if (it == _child_list.end()) { #ifdef DEBUG cerr << __FILE__ << "@" << __LINE__ << ": " << "PDecor(" << this << ")::removeChild(" << child << ")" << endl << " *** child not in _child_list, bailing out." << endl; #endif // DEBUG return; } it = _child_list.erase(it); if (_child_list.size() > 0) { if (_child == child) { if (it == _child_list.end()) { --it; } activateChild(*it); } updatedChildOrder(); } else if (_decor_cfg_keep_empty) { updatedChildOrder(); } else if (do_delete) { delete this; // no children, and we don't want empty PDecors } } //! @brief Activates the child, updating size and position void PDecor::activateChild(PWinObj *child) { // sync state _focusable = child->isFocusable(); alignChild(child); // place correct acording to border and title child->resize(getChildWidth(), getChildHeight()); child->raise(); _child = child; restackBorder(); updatedChildOrder(); } //! @brief void PDecor::getDecorInfo(wchar_t *buf, uint size) { swprintf(buf, size, L"%dx%d+%d+%d", _gm.width, _gm.height, _gm.x, _gm.y); } //! @brief void PDecor::activateChildNum(uint num) { if (num >= _child_list.size()) { #ifdef DEBUG cerr << __FILE__ << "@" << __LINE__ << ": " << "PDecor(" << this << ")::activeChildNum(" << num << ")" << endl << " *** num > _child_list.size()" << endl; #endif // DEBUG return; } list::iterator it(_child_list.begin()); for (uint i = 0; i < num; ++i, ++it); activateChild(*it); } //! @brief void PDecor::activateChildRel(int off) { PWinObj *child = getChildRel(off); if (! child) { child = _child_list.front(); } activateChild(child); } //! @brief Moves the current child off steps //! @param off - for left and + for right void PDecor::moveChildRel(int off) { PWinObj *child = getChildRel(off); if (! child || (child == _child)) { return; } bool at_begin = (_child == _child_list.front()); bool at_end = (_child == _child_list.back()); // switch place _child_list.remove(_child); list::iterator it(find(_child_list.begin(), _child_list.end(), child)); if (off > 0) { // when wrapping, we want to be able to insert at first place if (at_begin || (*it != _child_list.front())) { ++it; } _child_list.insert(it, _child); } else { if (! at_end && (*it == _child_list.back())) { ++it; } _child_list.insert(it, _child); } updatedChildOrder(); } //! @brief PWinObj* PDecor::getChildRel(int off) { if ((off == 0) || (_child_list.size() < 2)) { #ifdef DEBUG cerr << __FILE__ << "@" << __LINE__ << ": " << "PDecor(" << this << ")::getChildRel(" << off << ")" << endl << " *** off == 0 or not enough children" << endl; #endif // DEBUG return 0; } list::iterator it(find(_child_list.begin(), _child_list.end(), _child)); // if no active child, use first if (it == _child_list.end()) { it = _child_list.begin(); } int dir = (off > 0) ? 1 : -1; off = abs(off); for (int i = 0; i < off; ++i) { if (dir == 1) { // forward if (++it == _child_list.end()) { it = _child_list.begin(); } } else { // backward if (it == _child_list.begin()) { it = _child_list.end(); } --it; } } return *it; } //! @brief Do move of Decor with the mouse. //! @param x_root X position of the pointer when event was triggered. //! @param y_root Y position of the pointer when event was triggered. void PDecor::doMove(int x_root, int y_root) { if (! allowMove()) { return; } PScreen *scr = PScreen::instance(); // convenience StatusWindow *sw = StatusWindow::instance(); // convenience if (! scr->grabPointer(scr->getRoot(), ButtonMotionMask|ButtonReleaseMask, ScreenResources::instance()->getCursor(ScreenResources::CURSOR_MOVE))) { return; } // Get relative position to root int x = x_root - _gm.x; int y = y_root - _gm.y; bool outline = ! Config::instance()->getOpaqueMove(); bool center_on_root = Config::instance()->isShowStatusWindowOnRoot(); EdgeType edge; // grab server, we don't want invert traces if (outline) { scr->grabServer(); } wchar_t buf[128]; getDecorInfo(buf, 128); if (Config::instance()->isShowStatusWindow()) { sw->draw(buf, true, center_on_root ? 0 : &_gm); sw->mapWindowRaised(); sw->draw(buf, true, center_on_root ? 0 : &_gm); } Geometry last_gm(_gm); XEvent e; const long move_mask = ButtonPressMask|ButtonReleaseMask|PointerMotionMask; bool exit = false; while (! exit) { if (outline) { drawOutline(_gm); } XMaskEvent(_dpy, move_mask, &e); if (outline) { drawOutline(_gm); // clear } switch (e.type) { case MotionNotify: // Flush all pointer motion, no need to redraw and redraw. PScreen::removeMotionEvents(); _gm.x = e.xmotion.x_root - x; _gm.y = e.xmotion.y_root - y; checkSnap(); if (! outline && _gm != last_gm) { last_gm = _gm; move(_gm.x, _gm.y); } edge = doMoveEdgeFind(e.xmotion.x_root, e.xmotion.y_root); if (edge != SCREEN_EDGE_NO) { doMoveEdgeAction(&e.xmotion, edge); } getDecorInfo(buf, 128); if (Config::instance()->isShowStatusWindow()) { sw->draw(buf, true, center_on_root ? 0 : &_gm); } break; case ButtonRelease: exit = true; break; } } if (Config::instance()->isShowStatusWindow()) { sw->unmapWindow(); } // ungrab the server if (outline) { move(_gm.x, _gm.y); scr->ungrabServer(true); } scr->ungrabPointer(); } //! @brief Matches cordinates against screen edge EdgeType PDecor::doMoveEdgeFind(int x, int y) { EdgeType edge = SCREEN_EDGE_NO; if (x <= signed(Config::instance()->getScreenEdgeSize(SCREEN_EDGE_LEFT))) { edge = SCREEN_EDGE_LEFT; } else if (x >= signed(PScreen::instance()->getWidth() - Config::instance()->getScreenEdgeSize(SCREEN_EDGE_RIGHT))) { edge = SCREEN_EDGE_RIGHT; } else if (y <= signed(Config::instance()->getScreenEdgeSize(SCREEN_EDGE_TOP))) { edge = SCREEN_EDGE_TOP; } else if (y >= signed(PScreen::instance()->getHeight() - Config::instance()->getScreenEdgeSize(SCREEN_EDGE_BOTTOM))) { edge = SCREEN_EDGE_BOTTOM; } return edge; } //! @brief Finds and executes action if any void PDecor::doMoveEdgeAction(XMotionEvent *ev, EdgeType edge) { ActionEvent *ae; uint button = PScreen::instance()->getButtonFromState(ev->state); ae = ActionHandler::findMouseAction(button, ev->state, MOUSE_EVENT_ENTER_MOVING, Config::instance()->getEdgeListFromPosition(edge)); if (ae) { ActionPerformed ap(this, *ae); ap.type = ev->type; ap.event.motion = ev; ActionHandler::instance()->handleAction(ap); } } /** * Move and resize window with keybindings. */ void PDecor::doKeyboardMoveResize(void) { PScreen *scr = PScreen::instance(); // convenience StatusWindow *sw = StatusWindow::instance(); // convenience if (! scr->grabPointer(scr->getRoot(), NoEventMask, ScreenResources::instance()->getCursor(ScreenResources::CURSOR_MOVE))) { return; } if (! scr->grabKeyboard(scr->getRoot())) { scr->ungrabPointer(); return; } Geometry old_gm = _gm; // backup geometry if we cancel bool outline = (! Config::instance()->getOpaqueMove() || ! Config::instance()->getOpaqueResize()); ActionEvent *ae; list::iterator it; wchar_t buf[128]; getDecorInfo(buf, 128); bool center_on_root = Config::instance()->isShowStatusWindowOnRoot(); if (Config::instance()->isShowStatusWindow()) { sw->draw(buf, true, center_on_root ? 0 : &_gm); sw->mapWindowRaised(); sw->draw(buf, true, center_on_root ? 0 : &_gm); } if (outline) { PScreen::instance()->grabServer(); } XEvent e; bool exit = false; while (! exit) { if (outline) { drawOutline(_gm); } XMaskEvent(_dpy, KeyPressMask, &e); if (outline) { drawOutline(_gm); // clear } if ((ae = KeyGrabber::instance()->findMoveResizeAction(&e.xkey)) != 0) { for (it = ae->action_list.begin(); it != ae->action_list.end(); ++it) { switch (it->getAction()) { case MOVE_HORIZONTAL: _gm.x += it->getParamI(0); if (! outline) { move(_gm.x, _gm.y); } break; case MOVE_VERTICAL: _gm.y += it->getParamI(0); if (! outline) { move(_gm.x, _gm.y); } break; case RESIZE_HORIZONTAL: _gm.width += resizeHorzStep(it->getParamI(0)); if (! outline) { resize(_gm.width, _gm.height); } break; case RESIZE_VERTICAL: _gm.height += resizeVertStep(it->getParamI(0)); if (! outline) { resize(_gm.width, _gm.height); } break; case MOVE_SNAP: checkSnap(); if (! outline) { move(_gm.x, _gm.y); } break; case MOVE_CANCEL: _gm = old_gm; // restore position if (! outline) { move(_gm.x, _gm.y); resize(_gm.width, _gm.height); } exit = true; break; case MOVE_END: if (outline) { if ((_gm.x != old_gm.x) || (_gm.y != old_gm.y)) move(_gm.x, _gm.y); if ((_gm.width != old_gm.width) || (_gm.height != old_gm.height)) resize(_gm.width, _gm.height); } exit = true; break; default: // do nothing break; } getDecorInfo(buf, 128); if (Config::instance()->isShowStatusWindow()) { sw->draw(buf, true, center_on_root ? 0 : &_gm); } } } } if (Config::instance()->isShowStatusWindow()) { sw->unmapWindow(); } if (outline) { scr->ungrabServer(true); } scr->ungrabKeyboard(); scr->ungrabPointer(); } //! @brief Sets shaded state void PDecor::setShaded(StateAction sa) { if (! ActionUtil::needToggle(sa, _shaded)) return; // If we are going to shade the window, we need to check carefully // that we don't try to make the window disappear. if (_shaded) { _gm.height = _real_height; _shaded = false; // If we have a titlebar OR border (border will only be visible in // full-width mode only) } else if (_titlebar || (_border && ! _data->getTitleWidthMin())) { _real_height = _gm.height; _gm.height = getTitleHeight(); // shading in non full width title mode will make border go away if (! _data->getTitleWidthMin()) { _gm.height += borderTop() + borderBottom(); } _shaded = true; } PWinObj::resize(_gm.width, _gm.height); setBorderShape(); placeBorder(); restackBorder(); } //! @brief Sets skip state. void PDecor::setSkip(uint skip) { _skip = skip; } //! @brief Remove iconified state. void PDecor::deiconify(void) { if (_iconified) { if (_workspace == Workspaces::instance()->getActive()) { mapWindow(); } _iconified = false; } } //! @brief Renders and sets title background void PDecor::renderTitle(void) { if (! getTitleHeight()) { return; } bool force_update = false; if (_data->getTitleWidthMin()) { uint width_before = _title_wo.getWidth(); resizeTitle(); applyBorderShape(); // update title shape if (width_before != _title_wo.getWidth()) { force_update = true; } } else { calcTabsWidth(); } PFont *font = getFont(getFocusedState(false)); PTexture *t_sep = _data->getTextureSeparator(getFocusedState(false)); PixmapHandler *pm = ScreenResources::instance()->getPixmapHandler(); // Get new title pixmap if (_dirty_resized || force_update) { pm->returnPixmap(_title_bg); _title_bg = pm->getPixmap(_title_wo.getWidth(), _title_wo.getHeight(), PScreen::instance()->getDepth()); _title_wo.setBackgroundPixmap(_title_bg); } // Render main background on pixmap _data->getTextureMain(getFocusedState(false))->render(_title_bg, 0, 0, _title_wo.getWidth(), _title_wo.getHeight()); bool sel; // Current tab selected flag uint x = _titles_left; // Position uint pad_horiz = _data->getPad(PAD_LEFT) + _data->getPad(PAD_RIGHT); // Amount of horizontal padding list::iterator it(_title_list.begin()); for (uint i = 0; it != _title_list.end(); ++i, ++it) { sel = (_title_active == i); // render tab _data->getTextureTab(getFocusedState(sel))->render(_title_bg, x, 0, (*it)->getWidth(), _title_wo.getHeight()); font = getFont(getFocusedState(sel)); font->setColor(_data->getFontColor(getFocusedState(sel))); if ((*it)) { PFont::TrimType trim = PFont::FONT_TRIM_MIDDLE; if ((*it)->isCustom() || (*it)->isUserSet()) { trim = PFont::FONT_TRIM_END; } font->draw(_title_bg, x + _data->getPad(PAD_LEFT), // X position _data->getPad(PAD_UP), // Y position (*it)->getVisible(), 0, // Text and max chars (*it)->getWidth() - pad_horiz, // Available width trim); // Type of trim } // move to next tab (or separator if any) x += (*it)->getWidth(); // draw separator if ((_title_list.size() > 1) && (i < (_title_list.size() - 1))) { t_sep->render(_title_bg, x, 0, 0, 0); x += t_sep->getWidth(); } } _title_wo.clear(); } //! @brief void PDecor::renderButtons(void) { list::iterator it(_button_list.begin()); for (; it != _button_list.end(); ++it) { (*it)->setState(_focused ? BUTTON_STATE_FOCUSED : BUTTON_STATE_UNFOCUSED); } } //! @brief Renders the border of the decor. void PDecor::renderBorder(void) { if (! _border) { return; } map::iterator it; PixmapHandler *pm = ScreenResources::instance()->getPixmapHandler(); // Return pixmaps used for the border if (_dirty_resized) { for (it = _border_pos_map.begin(); it != _border_pos_map.end(); ++it) { pm->returnPixmap(it->second); it->second = None; } } PTexture *tex; FocusedState state = getFocusedState(false); uint width, height; for (it = _border_pos_map.begin(); it != _border_pos_map.end(); ++it) { tex = _data->getBorderTexture(state, it->first); // Solid texture, get the color and set as bg, no need to render pixmap if (tex->getType() == PTexture::TYPE_SOLID) { XSetWindowBackground(_dpy, _border_win[it->first], static_cast(tex)->getColor()->pixel); } else { // not a solid texture, get pixmap, render, set as bg getBorderSize(it->first, width, height); if ((width > 0) && (height > 0)) { if (_dirty_resized) { it->second = pm->getPixmap(width, height, PScreen::instance()->getDepth()); XSetWindowBackgroundPixmap(_dpy, _border_win[it->first], it->second); } tex->render(it->second, 0, 0, width, height); } } XClearWindow(_dpy, _border_win[it->first]); } } //! @brief Sets shape on the border windows. void PDecor::setBorderShape(void) { #ifdef HAVE_SHAPE Pixmap pix; bool do_free; unsigned int width, height; FocusedState state = getFocusedState(false); XRectangle rect = {0, 0, 0, 0}; map::iterator it(_border_pos_map.begin()); for (; it != _border_pos_map.end(); ++it) { // Get the size of the border at position getBorderSize(it->first, width, height); // Get shape pixmap pix = _data->getBorderTexture(state, it->first)->getMask(width, height, do_free); if (pix != None) { _need_shape = true; XShapeCombineMask(_dpy, _border_win[it->first], ShapeBounding, 0, 0, pix, ShapeSet); if (do_free) { ScreenResources::instance()->getPixmapHandler()->returnPixmap(pix); } } else { rect.width = width; rect.height = height; XShapeCombineRectangles(_dpy, _border_win[it->first], ShapeBounding, 0, 0, &rect, 1, ShapeSet, YXBanded); } } #endif // HAVE_SHAPE } //! @brief Finds the Head closest to x y from the center of the decor uint PDecor::getNearestHead(void) { return PScreen::instance()->getNearestHead(_gm.x + (_gm.width / 2), _gm.y + (_gm.height / 2)); } //! @brief void PDecor::checkSnap(void) { if ((Config::instance()->getWOAttract() > 0) || (Config::instance()->getWOResist() > 0)) { checkWOSnap(); } if ((Config::instance()->getEdgeAttract() > 0) || (Config::instance()->getEdgeResist() > 0)) { checkEdgeSnap(); } } inline bool isBetween(int x1, int x2, int t1, int t2) { if (x1 > t1) { if (x1 < t2) { return true; } } else if (x2 > t1) { return true; } return false; } //! @brief //! @todo PDecor/PWinObj doesn't have _skip property void PDecor::checkWOSnap(void) { PDecor *decor; Geometry gm = _gm; int x = getRX(); int y = getBY(); int attract = Config::instance()->getWOAttract(); int resist = Config::instance()->getWOResist(); bool snapped; vector::reverse_iterator it = _wo_list.rbegin(); for (; it != _wo_list.rend(); ++it) { if (((*it) == this) || ! (*it)->isMapped() || ((*it)->getType() != PWinObj::WO_FRAME)) { continue; } // Skip snapping, only valid on PDecor and up. decor = dynamic_cast(*it); if (decor && decor->isSkip(SKIP_SNAP)) { continue; } snapped = false; // check snap if ((x >= ((*it)->getX() - attract)) && (x <= ((*it)->getX() + resist))) { if (isBetween(gm.y, y, (*it)->getY(), (*it)->getBY())) { _gm.x = (*it)->getX() - gm.width; snapped = true; } } else if ((gm.x >= signed((*it)->getRX() - resist)) && (gm.x <= signed((*it)->getRX() + attract))) { if (isBetween(gm.y, y, (*it)->getY(), (*it)->getBY())) { _gm.x = (*it)->getRX(); snapped = true; } } if (y >= ((*it)->getY() - attract) && (y <= (*it)->getY() + resist)) { if (isBetween(gm.x, x, (*it)->getX(), (*it)->getRX())) { _gm.y = (*it)->getY() - gm.height; if (snapped) break; } } else if ((gm.y >= signed((*it)->getBY() - resist)) && (gm.y <= signed((*it)->getBY() + attract))) { if (isBetween(gm.x, x, (*it)->getX(), (*it)->getRX())) { _gm.y = (*it)->getBY(); if (snapped) break; } } } } //! @brief Snaps decor agains head edges. Only updates _gm, no real move. //! @todo Add support for checking for harbour and struts void PDecor::checkEdgeSnap(void) { int attract = Config::instance()->getEdgeAttract(); int resist = Config::instance()->getEdgeResist(); Geometry head; PScreen::instance()->getHeadInfoWithEdge(PScreen::instance()->getNearestHead(_gm.x, _gm.y), head); if ((_gm.x >= (head.x - resist)) && (_gm.x <= (head.x + attract))) { _gm.x = head.x; } else if ((_gm.x + _gm.width) >= (head.x + head.width - attract) && ((_gm.x + _gm.width) <= (head.x + head.width + resist))) { _gm.x = head.x + head.width - _gm.width; } if ((_gm.y >= (head.y - resist)) && (_gm.y <= (head.y + attract))) { _gm.y = head.y; } else if (((_gm.y + _gm.height) >= (head.y + head.height - attract)) && ((_gm.y + _gm.height) <= (head.y + head.height + resist))) { _gm.y = head.y + head.height - _gm.height; } } //! @brief Set shapes of decor //! @return true if shape is on, else false. bool PDecor::setShape(void) { #ifdef HAVE_SHAPE if (! _child) { return false; } int num, t; XRectangle *rect; rect = XShapeGetRectangles(_dpy, _child->getWindow(), ShapeBounding, &num, &t); // If there is more than one Rectangle it has an irregular shape. _need_client_shape = (num > 1); // Will set or unset shape depending on _need_shape and _need_client_shape. applyBorderShape(); XFree(rect); return (num > 1); #else // !HAVE_SHAPE return false; #endif // HAVE_SHAPE } /** * Move child into position with regards to title and border. */ void PDecor::alignChild(PWinObj *child) { if (child) { XMoveWindow(_dpy, child->getWindow(), borderLeft(), borderTop() + getTitleHeight()); } } //! @brief Draws outline of the decor with geometry gm void PDecor::drawOutline(const Geometry &gm) { XDrawRectangle(_dpy, PScreen::instance()->getRoot(), _theme->getInvertGC(), gm.x, gm.y, gm.width, _shaded ? _gm.height : gm.height); } //! @brief Places decor buttons void PDecor::placeButtons(void) { _titles_left = 0; _titles_right = 0; list::iterator it(_button_list.begin()); for (; it != _button_list.end(); ++it) { if ((*it)->isLeft()) { (*it)->move(_titles_left, 0); _titles_left += (*it)->getWidth(); } else { _titles_right += (*it)->getWidth(); (*it)->move(_title_wo.getWidth() - _titles_right, 0); } } } //! @brief Places border windows void PDecor::placeBorder(void) { // if we have tab min == 0 then we have full width title and place the // border ontop, else we put the border under the title uint bt_off = (_data->getTitleWidthMin() > 0) ? getTitleHeight() : 0; if (borderTop() > 0) { XMoveResizeWindow(_dpy, _border_win[BORDER_TOP], borderTopLeft(), bt_off, _gm.width - borderTopLeft() - borderTopRight(), borderTop()); XMoveResizeWindow(_dpy, _border_win[BORDER_TOP_LEFT], 0, bt_off, borderTopLeft(), borderTopLeftHeight()); XMoveResizeWindow(_dpy, _border_win[BORDER_TOP_RIGHT], _gm.width - borderTopRight(), bt_off, borderTopRight(), borderTopRightHeight()); if (borderLeft()) { XMoveResizeWindow(_dpy, _border_win[BORDER_LEFT], 0, borderTopLeftHeight() + bt_off, borderLeft(), _gm.height - borderTopLeftHeight() - borderBottomLeftHeight()); } if (borderRight()) { XMoveResizeWindow(_dpy, _border_win[BORDER_RIGHT], _gm.width - borderRight(), borderTopRightHeight() + bt_off, borderRight(), _gm.height - borderTopRightHeight() - borderBottomRightHeight()); } } else { if (borderLeft()) { XMoveResizeWindow(_dpy, _border_win[BORDER_LEFT], 0, getTitleHeight(), borderLeft(), _gm.height - getTitleHeight() - borderBottom()); } if (borderRight()) { XMoveResizeWindow(_dpy, _border_win[BORDER_RIGHT], _gm.width - borderRight(), getTitleHeight(), borderRight(), _gm.height - getTitleHeight() - borderBottom()); } } if (borderBottom()) { XMoveResizeWindow(_dpy, _border_win[BORDER_BOTTOM], borderBottomLeft(), _gm.height - borderBottom(), _gm.width - borderBottomLeft() - borderBottomRight(), borderBottom()); XMoveResizeWindow(_dpy, _border_win[BORDER_BOTTOM_LEFT], 0, _gm.height - borderBottomLeftHeight(), borderBottomLeft(), borderBottomLeftHeight()); XMoveResizeWindow(_dpy, _border_win[BORDER_BOTTOM_RIGHT], _gm.width - borderBottomRight(), _gm.height - borderBottomRightHeight(), borderBottomRight(), borderBottomRightHeight()); } applyBorderShape(); } /** * Apply shaping on the window based on the shape of the client and * the borders. */ void PDecor::applyBorderShape(void) { #ifdef HAVE_SHAPE if (_need_shape || _need_client_shape) { // if we have tab min == 0 then we have full width title and place the // border ontop, else we put the border under the title uint bt_off = (_data->getTitleWidthMin() > 0) ? getTitleHeight() : 0; Window shape; shape = XCreateSimpleWindow(_dpy, PScreen::instance()->getRoot(), 0, 0, _gm.width, _gm.height, 0, 0, 0); if (_child) { XShapeCombineShape(_dpy, shape, ShapeBounding, borderLeft(), borderTop() + getTitleHeight(), _child->getWindow(), ShapeBounding, ShapeSet); } // Apply border shape. Need to be carefull wheter or not to include it. if (_border && ! (_shaded && bt_off) // Shaded in non-full-width mode removes border. && ! (_need_client_shape)) { // Shaped clients should appear bordeless. // top if (borderTop() > 0) { XShapeCombineShape(_dpy, shape, ShapeBounding, 0, bt_off, _border_win[BORDER_TOP_LEFT], ShapeBounding, ShapeUnion); XShapeCombineShape(_dpy, shape, ShapeBounding, borderTopLeft(), bt_off, _border_win[BORDER_TOP], ShapeBounding, ShapeUnion); XShapeCombineShape(_dpy, shape, ShapeBounding, _gm.width - borderTopRight(), bt_off, _border_win[BORDER_TOP_RIGHT], ShapeBounding, ShapeUnion); } bool use_bt_off = bt_off || borderTop(); // Left border if (borderLeft() > 0) { XShapeCombineShape(_dpy, shape, ShapeBounding, 0, use_bt_off ? bt_off + borderTopLeftHeight() : getTitleHeight(), _border_win[BORDER_LEFT], ShapeBounding, ShapeUnion); } // Right border if (borderRight() > 0) { XShapeCombineShape(_dpy, shape, ShapeBounding, _gm.width - borderRight(), use_bt_off ? bt_off + borderTopRightHeight() : getTitleHeight(), _border_win[BORDER_RIGHT], ShapeBounding, ShapeUnion); } // bottom if (borderBottom() > 0) { XShapeCombineShape(_dpy, shape, ShapeBounding, 0, _gm.height - borderBottomLeftHeight(), _border_win[BORDER_BOTTOM_LEFT], ShapeBounding, ShapeUnion); XShapeCombineShape(_dpy, shape, ShapeBounding, borderBottomLeft(), _gm.height - borderBottom(), _border_win[BORDER_BOTTOM], ShapeBounding, ShapeUnion); XShapeCombineShape(_dpy, shape, ShapeBounding, _gm.width - borderBottomRight(), _gm.height - borderBottomRightHeight(), _border_win[BORDER_BOTTOM_RIGHT], ShapeBounding, ShapeUnion); } } if (_titlebar) { // apply title shape XShapeCombineShape(_dpy, shape, ShapeBounding, _title_wo.getX(), _title_wo.getY(), _title_wo.getWindow(), ShapeBounding, ShapeUnion); } // Apply the shape mask to the window XShapeCombineShape(_dpy, _window, ShapeBounding, 0, 0, shape, ShapeBounding, ShapeSet); XDestroyWindow(_dpy, shape); } else { // No shaping is required, apply a square shape to remove // possible stale shaping. XRectangle rect_square; rect_square.x = 0; rect_square.y = 0; rect_square.width = _gm.width; rect_square.height = _gm.height; XShapeCombineRectangles(_dpy, _window, ShapeBounding, 0, 0, &rect_square, 1, ShapeSet, YXBanded); } #endif // HAVE_SHAPE } //! @brief Restacks child, title and border windows. void PDecor::restackBorder(void) { // List of windows, adding two possible for title and child window int extra = 0; Window windows[BORDER_NO_POS + 2]; // Only put the Child over if not shaded. if (_child && ! _shaded) { windows[extra++] = _child->getWindow(); } // Add title if any if (_titlebar) { windows[extra++] = _title_wo.getWindow(); } // Add border windows for (int i = 0; i < BORDER_NO_POS; ++i) { windows[i + extra] = _border_win[i]; } // Raise the top window so actual restacking is done. XRaiseWindow(_dpy, windows[0]); XRestackWindows(_dpy, windows, BORDER_NO_POS + extra); } //! @brief Updates _decor_name to represent decor state //! @return true if decor was changed bool PDecor::updateDecorName(void) { string name; if (_titlebar && _border) { name = PDecor::DEFAULT_DECOR_NAME; } else if (_titlebar) { name = PDecor::DEFAULT_DECOR_NAME_BORDERLESS; } else if (_border) { name = PDecor::DEFAULT_DECOR_NAME_TITLEBARLESS; } return setDecor(name); } /** * Get size of border at position. * * @param pos Position to get size for. * @param width Width of border at position. * @param height Height of border at position. */ void PDecor::getBorderSize(BorderPosition pos, uint &width, uint &height) { FocusedState state = getFocusedState(false); // convenience switch (pos) { case BORDER_TOP_LEFT: case BORDER_TOP_RIGHT: case BORDER_BOTTOM_LEFT: case BORDER_BOTTOM_RIGHT: width = _data->getBorderTexture(state, pos)->getWidth(); height = _data->getBorderTexture(state, pos)->getHeight(); break; case BORDER_TOP: if ((borderTopLeft() + borderTopRight()) < _gm.width) { width = _gm.width - borderTopLeft() - borderTopRight(); } else { width = 1; } height = _data->getBorderTexture(state, pos)->getHeight(); break; case BORDER_BOTTOM: if ((borderBottomLeft() + borderBottomRight()) < _gm.width) { width = _gm.width - borderBottomLeft() - borderBottomRight(); } else { width = 1; } height = _data->getBorderTexture(state, pos)->getHeight(); break; case BORDER_LEFT: width = _data->getBorderTexture(state, pos)->getWidth(); if ((borderTopLeftHeight() + borderBottomLeftHeight()) < _gm.height) { height = _gm.height - borderTopLeftHeight() - borderBottomLeftHeight(); } else { height = 1; } break; case BORDER_RIGHT: width = _data->getBorderTexture(state, pos)->getWidth(); if ((borderTopRightHeight() + borderBottomRightHeight()) < _gm.height) { height = _gm.height - borderTopRightHeight() - borderBottomRightHeight(); } else { height = 1; } break; default: #ifdef DEBUG cerr << __FILE__ << "@" << __LINE__ << ": " << "PDecor(" << this << ")::getBorderSize()" << endl << " *** invalid border position" << endl; #endif // DEBUG width = 1; height = 1; break; }; } /** * Calculate width of title, if width min is 0 use the full available * size minus borders. Else get width from content. */ uint PDecor::calcTitleWidth(void) { uint width = 0; if (_data->getTitleWidthMin() == 0) { width = _gm.width; if (width > (borderTopLeft() + borderTopRight())) { width -= borderTopLeft() + borderTopRight(); } } else { uint width_max = 0; // FIXME: what about selected tabs? PFont *font = getFont(getFocusedState(false)); if (_data->isTitleWidthSymetric()) { // Symetric mode, get max tab width, multiply with number of tabs list::iterator it(_title_list.begin()); for (; it != _title_list.end(); ++it) { width = font->getWidth((*it)->getVisible()); if (width > width_max) { width_max = width; } } width = width_max + _data->getPad(PAD_LEFT) + _data->getPad(PAD_RIGHT); width *= _title_list.size(); } else { // Asymetric mode, get individual widths list::iterator it(_title_list.begin()); for (; it != _title_list.end(); ++it) { width += font->getWidth((*it)->getVisible()) + _data->getPad(PAD_LEFT) + _data->getPad(PAD_RIGHT); } } // Add width of separators and buttons width += (_title_list.size() - 1) * _data->getTextureSeparator(getFocusedState(false))->getWidth(); width += _titles_left + _titles_right; // Validate sizes, make sure it is not less than width min // pixels and more than width max percent. if (width < static_cast(_data->getTitleWidthMin())) { width = _data->getTitleWidthMin(); } if (width > (_gm.width * _data->getTitleWidthMax() / 100)) { width = _gm.width * _data->getTitleWidthMax() / 100; } } return width; } /** * Calculate tab width, wrapper to choose correct algorithm.. */ void PDecor::calcTabsWidth(void) { if (! _title_list.size()) { return; } if (_data->isTitleWidthSymetric()) { calcTabsWidthSymetric(); } else { calcTabsWidthAsymetric(); } } /** * Get available tabs width and average width of a single tab. * * @param width_avail Number of pixels available in the titlebar. * @param tab_width Width in pixels to use for one tab. * @param off Number of pixels left with tab_width pixels wide tabs. */ void PDecor::calcTabsGetAvailAndTabWidth(uint &width_avail, uint &tab_width, int &off) { // Calculate width width_avail = _title_wo.getWidth(); // Remove spacing if enough space is available if (width_avail > (_titles_left + _titles_right)) { width_avail -= _titles_left + _titles_right; } // Remove separators if enough space is available uint sep_width = (_title_list.size() - 1) * _data->getTextureSeparator(getFocusedState(false))->getWidth(); if (width_avail > sep_width) { width_avail -= sep_width; } tab_width = width_avail / _title_list.size(); off = width_avail % _title_list.size(); } //! @brief Calculate tab width symetric, sets up _title_list widths. void PDecor::calcTabsWidthSymetric(void) { int off; uint width_avail, tab_width; calcTabsGetAvailAndTabWidth(width_avail, tab_width, off); // Assign width to elements list::iterator it(_title_list.begin()); for (; it != _title_list.end(); ++it) { (*it)->setWidth(tab_width + ((off-- > 0) ? 1 : 0)); } } /** * Calculate tab width asymmetrically. This is done with the following * priority: * * 1. Give tabs their required width, if it fits this is complete. * 2. Tabs did not fit, calculate average width. * 3. Add width of tabs requiring less space to average. * 4. Re-assign width equally to tabs using more than average. */ void PDecor::calcTabsWidthAsymetric(void) { int off; uint width, width_avail, tab_width; calcTabsGetAvailAndTabWidth(width_avail, tab_width, off); // Convenience PFont *font = getFont(getFocusedState(false)); // 1. give tabs their required width. uint width_total = 0; list::iterator it(_title_list.begin()); for (; it != _title_list.end(); ++it) { // This should set the tab width to be only the size needed width = font->getWidth((*it)->getVisible().c_str()) + _data->getPad(PAD_LEFT) + _data->getPad(PAD_RIGHT) + ((off-- > 0) ? 1 : 0); width_total += width; (*it)->setWidth(width); } if (width_total > width_avail) { calcTabsWidthAsymetricShrink(width_avail, tab_width); } else if (width_total < static_cast(_data->getTitleWidthMin())) { calcTabsWidthAsymetricGrow(width_avail, tab_width); } } /** * This is called to shrink the tabs that are over the tab_width in * order to make room for all */ void PDecor::calcTabsWidthAsymetricShrink(uint width_avail, uint tab_width) { // 2. Tabs did not fit uint tabs_left = _title_list.size(); list::iterator it(_title_list.begin()); for (; it != _title_list.end(); ++it) { if ((*it)->getWidth() < tab_width) { // 3. Add width of tabs requiring less space to the average. tabs_left--; width_avail -= (*it)->getWidth(); } } // 4. Re-assign width equally to tabs using more than average tab_width = width_avail / tabs_left; uint off = width_avail % tabs_left; for (it = _title_list.begin(); it != _title_list.end(); ++it) { if ((*it)->getWidth() >= tab_width) { (*it)->setWidth(tab_width + ((off-- > 0) ? 1 : 0)); } } } /** * This is called when tabs in the decor are using less space than * width min, grow tabs that are smaller than the average. */ void PDecor::calcTabsWidthAsymetricGrow(uint width_avail, uint tab_width) { // FIXME: Implement growing of asymetric tabs. }