//
// PDecor.cc for pekwm
// Copyright (C) 2004-2006 Claes Nasten <pekdon{@}pekdon{.}net>
//
// This program is licensed under the GNU GPL.
// See the LICENSE file for more information.
//

#include "../config.h"

#include "Config.hh"
#include "PWinObj.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"
#include "Viewport.hh"

#include <functional>
#include <cstdio>
#include <cstdlib>

extern "C" {
#ifdef HAVE_SHAPE
#include <X11/Xutil.h>
#include <X11/extensions/shape.h>
#endif // HAVE_SHAPE
}

#ifdef DEBUG
#include <iostream>
using std::cerr;
using std::endl;
#endif // DEBUG

using std::find;
using std::list;
using std::string;
using std::mem_fun;

// PDecor::Button

//! @brief PDecor::Button constructor
PDecor::Button::Button(Display *dpy, PWinObj *parent, Theme::PDecorButtonData *data) : PWinObj(dpy),
_data(data), _state(BUTTON_STATE_UNFOCUSED),
_left(_data->isLeft())
{
	_parent = parent;

	_gm.width = _data->getWidth();
	_gm.height = _data->getHeight();

	XSetWindowAttributes attr;
	attr.override_redirect = True;
	_window =
		XCreateWindow(_dpy, _parent->getWindow(),
									-_gm.width, -_gm.height, _gm.width, _gm.height, 0,
									CopyFromParent, InputOutput, CopyFromParent,
									CWOverrideRedirect, &attr);

	_bg = ScreenResources::instance()->getPixmapHandler()->getPixmap(_gm.width, _gm.height, PScreen::instance()->getDepth(), true);

	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<ActionEvent>::iterator it(_data->begin());
	for (; it != _data->end(); ++it) {
		if (it->mod == ev->state && it->sym == ev->button)
			return &*it;
	}
	return NULL;
}

//! @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_HOOVER) {
		_state = state;
	}

	_data->getTexture(state)->render(_bg, 0, 0, _gm.width, _gm.height);
	clear();
}

// 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");

//! @brief PDecor constructor
//! @param dpy Display
//! @param theme Theme
//! @param decor_name String, if not DEFAULT_DECOR_NAME sets _decor_name_override
PDecor::PDecor(Display *dpy, Theme *theme, const std::string decor_name) : PWinObj(dpy),
_theme(theme), _child(NULL), _button(NULL),
_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),
_data(NULL), _decor_name(decor_name),
_border(true), _titlebar(true), _shaded(false),
_need_shape(false), _need_client_shape(false), _real_height(1),
_title_wo(dpy), _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 == NULL) {
		_data = _theme->getPDecorData(DEFAULT_DECOR_NAME);
	}

	XSetWindowAttributes attr;
	attr.override_redirect = True;
	attr.event_mask = ButtonPressMask|ButtonReleaseMask|ButtonMotionMask|
		EnterWindowMask|SubstructureRedirectMask|SubstructureNotifyMask;
	_window =
		XCreateWindow(_dpy, PScreen::instance()->getRoot(),
									_gm.x, _gm.y, _gm.width, _gm.height, 0,
									CopyFromParent, InputOutput, CopyFromParent,
									CWOverrideRedirect|CWEventMask, &attr);

	attr.override_redirect = True;
	attr.event_mask =
		ButtonPressMask|ButtonReleaseMask|ButtonMotionMask|EnterWindowMask;
	_title_wo.setWindow(XCreateWindow(_dpy, _window,
									borderLeft(), borderTop(), 1, 1, 0, // don't know our size yet
									CopyFromParent, InputOutput, CopyFromParent,
									CWOverrideRedirect|CWEventMask, &attr));

  // create border windows
  for (uint i = 0; i < BORDER_NO_POS; ++i) {
    attr.cursor =
			ScreenResources::instance()->getCursor(ScreenResources::CursorType(i));

    _border_win[i] =
      XCreateWindow(_dpy, _window,
										-1, -1, 1, 1, 0,
										CopyFromParent, InputOutput, CopyFromParent,
										CWEventMask|CWCursor, &attr);
  }

	// sets buttons etc up
	loadDecor();

	// map title and border windows
	XMapSubwindows(_dpy, _window);
}

//! @brief PDecor destructor
PDecor::~PDecor(void)
{
	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
	unmapWindow();

	// free buttons
	unloadDecor();

	// free border pixmaps
	list<Pixmap>::iterator it(_border_pix_list.begin());
	for (; it != _border_pix_list.end(); ++it) {
		ScreenResources::instance()->getPixmapHandler()->returnPixmap(*it);
	}
	ScreenResources::instance()->getPixmapHandler()->returnPixmap(_title_bg);

	XDestroyWindow(_dpy, _title_wo.getWindow());
	for (uint i = 0; i < BORDER_NO_POS; ++i) {
		XDestroyWindow(_dpy, _border_win[i]);
	}
	XDestroyWindow(_dpy, _window);
}

// START - PWinObj interface.

//! @brief Map decor and all children
void
PDecor::mapWindow(void)
{
	if (_mapped != true) {
		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 == true) {
		return;
	}
	mapWindow();

	raise(); // XMapRaised wouldn't preserver layers
}

//! @brief Unmaps decor and all children
void
PDecor::unmapWindow(void)
{
	if (_mapped == true) {
		if (_iconified == true) {
			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, bool do_virtual)
{
	// update real position
	PWinObj::move(x, y);
	if ((_child != NULL) && (_decor_cfg_child_move_overloaded)) {
		_child->move(x + borderLeft(), y + borderTop() + getTitleHeight());
	}

	// update virtual position
	if (do_virtual == true) {
		moveVirtual(x + Workspaces::instance()->getActiveViewport()->getX(),
								y + Workspaces::instance()->getActiveViewport()->getY());
	}
}

//! @brief Virtually moves the decor, and all children if any
void
PDecor::moveVirtual(int x, int y)
{
	PWinObj::moveVirtual(x, y);
	list<PWinObj*>::iterator it(_child_list.begin());
	for (; it != _child_list.end(); ++it) {
		(*it)->moveVirtual(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);

	// do before placing and shaping the rest as shaping depends on the
	// child window
	if (_child != NULL) {
      _child->resize(getChildWidth(), getChildHeight());
	}

	// place / resize title and border
	resizeTitle();
	placeBorder();

	// render title and border
	renderTitle();
	renderBorder();
}

//! @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(false, true);
}

//! @brief Lowers the window, taking _layer into account
void
PDecor::lower(void)
{
	Workspaces::instance()->lower(this);
	Workspaces::instance()->updateClientStackingList(false, true);
}

//! @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<PWinObj*>::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
bool
PDecor::giveInputFocus(void)
{
	if ((_mapped == true) && (_child != NULL) &&
			(Workspaces::instance()->getActiveViewport()->isInside(this))) {
		return _child->giveInputFocus();

	} else {
#ifdef DEBUG
		cerr << __FILE__ << "@" << __LINE__ << ": "
				 << "PDecor::giveInputFocus(" << this << ")" << endl
				 << " *** reverting to root" << endl;
#endif // DEBUG
		PWinObj::getRootPWinObj()->giveInputFocus();
		return false;
	}
}

//! @brief
ActionEvent*
PDecor::handleButtonPress(XButtonEvent *ev)
{
	ActionEvent *ae = NULL;
	MouseEventType mb = MOUSE_EVENT_PRESS;

	ev->state &= ~Button1Mask & ~Button2Mask & ~Button3Mask
		& ~Button4Mask & ~Button5Mask;
	ev->state &= ~PScreen::instance()->getNumLock() &
							 ~PScreen::instance()->getScrollLock() & ~LockMask;

	// try to do something about frame bunttons
	if (ev->subwindow != None)
		_button = findButton(ev->subwindow);

	if (_button) {
		_button->setState(BUTTON_STATE_PRESSED);

		ae = _button->findAction(ev);

		// if the button is used for resizing, we don't want to wait for release
		if ((ae != NULL) && ae->isOnlyAction(ACTION_RESIZE)) {
			_button->setState(_focused ? BUTTON_STATE_FOCUSED : BUTTON_STATE_UNFOCUSED);
			_button = NULL;
		} else {
			ae = NULL;
		}

	} else {
		// 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 == true) {
			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.

			ae = ActionHandler::findMouseAction(ev->button, ev->state, mb,
																					Config::instance()->getMouseActionList(_decor_cfg_bpr_al_child));

		// clicks on the decor title
		} else if (ev->window == getTitleWindow()) {

			ae = ActionHandler::findMouseAction(ev->button, ev->state, mb,
																					Config::instance()->getMouseActionList(_decor_cfg_bpr_al_title));

		// clicks on the decor border
		} else if ((ev->subwindow != None) && (_title_wo != ev->subwindow) &&
							 (ev->window == _window)) {
			uint pos = getBorderPosition(ev->subwindow);
			if (pos != BORDER_NO_POS) {
				list<ActionEvent> *bl = Config::instance()->getBorderListFromPosition(pos);
				ae = ActionHandler::findMouseAction(ev->button, ev->state, mb, bl);
			}
		}
	}

	return ae;
}

//! @brief
ActionEvent*
PDecor::handleButtonRelease(XButtonEvent *ev)
{
	ActionEvent *ae = NULL;
	MouseEventType mb = MOUSE_EVENT_RELEASE;

	ev->state &= ~Button1Mask & ~Button2Mask & ~Button3Mask
		& ~Button4Mask & ~Button5Mask;
	ev->state &= ~PScreen::instance()->getNumLock() &
							 ~PScreen::instance()->getScrollLock() & ~LockMask;

	// handle titlebar buttons
	if (_button) {
		// first restore the pressed buttons state
		_button->setState(_focused ? BUTTON_STATE_FOCUSED : BUTTON_STATE_UNFOCUSED);

		// 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 != NULL) && ae->isOnlyAction(ACTION_RESIZE)) {
				ae = NULL;
			}
		}

		_button = NULL;

	} else {

		// allow us to get clicks from anywhere on the window.
		if (_decor_cfg_bpr_replay_pointer == true) {
			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.

			ae = ActionHandler::findMouseAction(ev->button, ev->state, mb,
																					Config::instance()->getMouseActionList(_decor_cfg_bpr_al_child));

		// handle clicks on the decor title
		} else if (_title_wo == ev->window) {
			// first we check if it's a double click
			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);
			}

			ae = ActionHandler::findMouseAction(ev->button, ev->state, mb,
																					Config::instance()->getMouseActionList(_decor_cfg_bpr_al_title));

		// clicks on the decor border
		} else if ((ev->subwindow != None) && (_title_wo != ev->subwindow) &&
							 (ev->window == _window)) {
			uint pos = getBorderPosition(ev->subwindow);
			if (pos != BORDER_NO_POS) {
				list<ActionEvent> *bl = Config::instance()->getBorderListFromPosition(pos);
				ae = ActionHandler::findMouseAction(ev->button, ev->state, mb, bl);
			}
		}
	}

	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));
}

//! @brief
ActionEvent*
PDecor::handleEnterEvent(XCrossingEvent *ev)
{
	return ActionHandler::findMouseAction(BUTTON_ANY, ev->state,
																				MOUSE_EVENT_ENTER,
																				Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_OTHER));
}

//! @brief
ActionEvent*
PDecor::handleLeaveEvent(XCrossingEvent *ev)
{
	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<PWinObj*>::iterator it(decor->begin());
	for (; it != decor->end(); ++it) {
		addChild(*it);
		(*it)->setWorkspace(_workspace);
	}

	decor->_child_list.clear();
	delete decor;
}

//! @brief Sets prefered 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 == NULL) {
		_data = _theme->getPDecorData(DEFAULT_DECOR_NAME);
	}

	// Load decor.
	list<Theme::PDecorButtonData*>::iterator b_it(_data->buttonBegin());
	for (; b_it != _data->buttonEnd(); ++b_it) {
		_button_list.push_back(new PDecor::Button(_dpy, &_title_wo, *b_it));
		_button_list.back()->mapWindow();
	}

	// 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<PWinObj*>::iterator c_it(_child_list.begin());
	for (; c_it != _child_list.end(); ++c_it) {
		alignChild(*c_it);
	}

	// Make sure it gets rendered correctly.
	_focused = !_focused;
	setFocused(!_focused);

	// Update the dimension of the frame.
	if (_child != NULL) {
		resizeChild(_child->getWidth(), _child->getHeight());
	}

	// Child theme change.
	loadTheme();
}

//! @brief
void
PDecor::unloadDecor(void)
{
	list<PDecor::Button*>::iterator it(_button_list.begin());
	for (; it != _button_list.end(); ++it) {
		delete *it;
	}
	_button_list.clear();
}

//! @brief
PDecor::Button*
PDecor::findButton(Window win)
{
	list<PDecor::Button*>::iterator it(_button_list.begin());
	for (; it != _button_list.end(); ++it) {
		if (**it == win) {
			return *it;
		}
	}

	return NULL;
}

//! @brief
PWinObj*
PDecor::getChildFromPos(int x)
{
	if (!_child_list.size() || (_child_list.size() != _title_list.size()))
		return NULL;
	if (_child_list.size() == 1)
		return _child_list.front();

	if (x < static_cast<int>(_titles_left)) {
		return _child_list.front();
	} else if (x > static_cast<int>(_title_wo.getWidth() - _titles_right)) {
		return _child_list.back();
	}

	list<PWinObj*>::iterator c_it(_child_list.begin());
	list<PDecor::TitleItem*>::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 NULL;
}

//! @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 allready loaded decor stuff for us.
	if (!updateDecorName() && _child) {
		alignChild(_child);
		resizeChild(_child->getWidth(), _child->getHeight());
	}
}

//! @brief Adds a child to the decor, reparenting the window
void
PDecor::addChild(PWinObj *child)
{
	child->reparent(this, borderLeft(), borderRight() + getTitleHeight());
	_child_list.push_back(child);

	updatedChildOrder();

	// if it's the first child, we'll want to initialize decor with
	// shaping etc
	if (_child_list.size() == 1) {
		_focused = !_focused;
		setFocused(!_focused);
	}
}

//! @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<PWinObj*>::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(char *buf, uint size)
{
	snprintf(buf, size, "%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<PWinObj*>::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 == NULL) {
		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 == NULL) || (child == _child)) {
		return;
	}

	bool at_begin = (_child == _child_list.front());
	bool at_end = (_child == _child_list.back());

	// switch place
	_child_list.remove(_child);
	list<PWinObj*>::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 == true) || (*it != _child_list.front())) {
			++it;
		}

		_child_list.insert(it, _child);
	} else {
		if ((at_end != true) && (*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 NULL;
	}

	list<PWinObj*>::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 Pointer enabled moving
void
PDecor::doMove(XMotionEvent *ev)
{
	PScreen *scr = PScreen::instance(); // convenience
	StatusWindow *sw = StatusWindow::instance(); // convenience

	if (scr->grabPointer(scr->getRoot(), ButtonMotionMask|ButtonReleaseMask,
											 ScreenResources::instance()->getCursor(ScreenResources::CURSOR_MOVE)) == false) {
		return;
	}

	uint x = 0, y = 0;
	if (ev != NULL) {
		x = ev->x + borderLeft();
		y = ev->y + borderTop();
		if ((_child != NULL) && (*_child == ev->window)) {
			y += getTitleHeight();
		}
	}

	bool outline = (Config::instance()->getOpaqueMove() == false);
	EdgeType edge;

	// grab server, we don't want invert traces
	if (outline) {
		scr->grabServer();
	}

	char buf[128];
	getDecorInfo(buf, sizeof(buf)/sizeof(char));

	if (Config::instance()->isShowStatusWindow()) {
		sw->draw(buf, true);
		sw->mapWindowRaised();
		sw->draw(buf);
	}

	XEvent e;
	bool exit = false;
	while (exit != true) {
		if (outline) {
			drawOutline(_gm);
		}
		XMaskEvent(_dpy, ButtonPressMask|ButtonReleaseMask|PointerMotionMask, &e);
		if (outline) {
			drawOutline(_gm); // clear
		}

		switch (e.type) {
		case MotionNotify:
			_gm.x = e.xmotion.x_root - x;
			_gm.y = e.xmotion.y_root - y;
			checkSnap();

			if (outline == false) {
				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, sizeof(buf)/sizeof(char));
			if (Config::instance()->isShowStatusWindow()) {
				sw->draw(buf, true);
			}

			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)
{
	if (Config::instance()->getScreenEdgeSize() == 0) {
		return SCREEN_EDGE_NO;
	}

	EdgeType edge = SCREEN_EDGE_NO;
	if (x <= signed(Config::instance()->getScreenEdgeSize())) {
		edge = SCREEN_EDGE_LEFT;
	} else if (x >= signed(PScreen::instance()->getWidth() -
												 Config::instance()->getScreenEdgeSize())) {
		edge = SCREEN_EDGE_RIGHT;
	} else if (y <= signed(Config::instance()->getScreenEdgeSize())) {
		edge = SCREEN_EDGE_TOP;
	} else if (y >= signed(PScreen::instance()->getHeight() -
												 Config::instance()->getScreenEdgeSize())) {
		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 != NULL) {
		ActionPerformed ap(this, *ae);
		ap.type = ev->type;
		ap.event.motion = ev;

		ActionHandler::instance()->handleAction(ap);
	}
}

//! @brief
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)) == false) {
		return;
	}
	if (scr->grabKeyboard(scr->getRoot()) == false) {
		scr->ungrabPointer();
		return;
	}

	Geometry old_gm = _gm; // backup geometry if we cancel
	bool outline = ((Config::instance()->getOpaqueMove() == false) ||
								  (Config::instance()->getOpaqueResize() == false));
	ActionEvent *ae;
	list<Action>::iterator it;

	char buf[128];
	getDecorInfo(buf, sizeof(buf)/sizeof(char));

	if (Config::instance()->isShowStatusWindow()) {
		sw->draw(buf, true);
		sw->mapWindowRaised();
		sw->draw(buf);
	}

	if (outline) {
		PScreen::instance()->grabServer();
	}

	XEvent e;
	bool exit = false;
	while (exit != true) {
		if (outline) {
			drawOutline(_gm);
		}
		XMaskEvent(_dpy, KeyPressMask, &e);
		if (outline) {
			drawOutline(_gm); // clear
		}

		if ((ae = KeyGrabber::instance()->findMoveResizeAction(&e.xkey)) != NULL) {
			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 == false) {
						move(_gm.x, _gm.y);
					}
					break;
				case MOVE_VERTICAL:
					_gm.y += it->getParamI(0);
					if (outline == false) {
						move(_gm.x, _gm.y);
					}
					break;
				case RESIZE_HORIZONTAL:
					_gm.width += resizeHorzStep(it->getParamI(0));
					if (outline == false) {
						resize(_gm.width, _gm.height);
					}
					break;
				case RESIZE_VERTICAL:
					_gm.height += resizeVertStep(it->getParamI(0));
					if (outline == false) {
						resize(_gm.width, _gm.height);
					}
					break;
				case MOVE_SNAP:
					checkSnap();
					if (outline == false) {
						move(_gm.x, _gm.y);
					}
					break;
				case MOVE_CANCEL:
					_gm = old_gm; // restore position

					if (outline == false) {
						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, sizeof(buf)/sizeof(char));
				if (Config::instance()->isShowStatusWindow()) {
					sw->draw(buf, true);
				}
			}
		}
	}

	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);
	placeBorder();
	restackBorder();
}

//! @brief Renders and sets title background
void
PDecor::renderTitle(void)
{
	if (getTitleHeight() == 0) {
		return;
	}

	if (_data->getTitleWidthMin()) {
		resizeTitle();
		applyBorderShape(); // update title shape
	} else {
		calcTabsWidth();
	}

	PFont *font = getFont(getFocusedState(false));
	PTexture *t_sep = _data->getTextureSeparator(getFocusedState(false));

	// get new title pixmap
	PixmapHandler *pm = ScreenResources::instance()->getPixmapHandler();
	pm->returnPixmap(_title_bg);
	_title_bg = pm->getPixmap(_title_wo.getWidth(), _title_wo.getHeight(),
														PScreen::instance()->getDepth(), false);

	// 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 = // Amount of horizontal padding
    _data->getPad(DIRECTION_LEFT) + _data->getPad(DIRECTION_RIGHT);

	list<PDecor::TitleItem*>::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)));

		// this is a icky one, draws the text in the tab depending on justify
		// and wheter or not the title has a TitleRule applied
		if ((*it) != NULL) {
			font->draw(_title_bg,
								 x + _data->getPad(DIRECTION_LEFT), // X position
								 _data->getPad(DIRECTION_UP), // Y position
								 (*it)->getVisible().c_str(), 0, // Text and max chars
                 (*it)->getWidth() - pad_horiz, // Available width
								 (*it)->isRuleApplied() // Type of trimming
                   ? PFont::FONT_TRIM_END : PFont::FONT_TRIM_MIDDLE);
		}

		// 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.setBackgroundPixmap(_title_bg);
	_title_wo.clear();
}

//! @brief
void
PDecor::renderButtons(void)
{
	list<PDecor::Button*>::iterator it(_button_list.begin());
	for (; it != _button_list.end(); ++it) {
		(*it)->setState(_focused ? BUTTON_STATE_FOCUSED : BUTTON_STATE_UNFOCUSED);
	}
}

//! @brief
void
PDecor::renderBorder(void)
{
	if (!_border)
		return;

	PixmapHandler *pm = ScreenResources::instance()->getPixmapHandler();

	// return pixmaps used for the border
	list<Pixmap>::iterator it(_border_pix_list.begin());
	for (; it != _border_pix_list.end(); ++it) {
		pm->returnPixmap(*it);
	}
	_border_pix_list.clear();

	Pixmap pix;
	PTexture *tex;
	FocusedState state = getFocusedState(false);
	uint width, height;

	for (uint i = 0; i < BORDER_NO_POS; ++i) {
		tex = _data->getBorderTexture(state, BorderPosition(i));

		// solid texture, get the color and set as bg, no need to render pixmap
		if (tex->getType() == PTexture::TYPE_SOLID) {
			XSetWindowBackground(_dpy, _border_win[i],
													 static_cast<PTextureSolid*>(tex)->getColor()->pixel);

		} else {
		// not a solid texture, get pixmap, render, set as bg
			getBorderSize(BorderPosition(i), width, height);

			if ((width > 0) && (height > 0)) {
				pix = pm->getPixmap(width, height, PScreen::instance()->getDepth(), false);
				tex->render(pix, 0, 0, width, height);
				_border_pix_list.push_back(pix);

				XSetWindowBackgroundPixmap(_dpy, _border_win[i], pix);
			}
		}
	}

	// refresh painted data
	for (uint i = 0; i < BORDER_NO_POS; ++i) {
		XClearWindow(_dpy, _border_win[i]);
	}
}

//! @brief
void
PDecor::setBorderShape(void)
{
#ifdef HAVE_SHAPE
  uint pos[] = { BORDER_TOP_LEFT, BORDER_TOP_RIGHT,
                 BORDER_BOTTOM_LEFT, BORDER_BOTTOM_RIGHT };
  bool f;
  Pixmap pix;
  FocusedState state = getFocusedState(false);

  XRectangle rect;
  rect.x = 0;
  rect.y = 0;

  for (uint i = pos[0]; i < (sizeof(pos)/sizeof(pos[0])); ++i) {
    pix =
      _data->getBorderTexture(state, BorderPosition(pos[i]))->getMask(0, 0, f);
    if (pix != None) {
      _need_shape = true;
      XShapeCombineMask(_dpy, _border_win[pos[i]],
                        ShapeBounding, 0, 0, pix, ShapeSet);
      if (f) {
        ScreenResources::instance()->getPixmapHandler()->returnPixmap(pix);
      }
    } else {
      rect.width =
        _data->getBorderTexture(state, BorderPosition(pos[i]))->getWidth();
      rect.height =
        _data->getBorderTexture(state, BorderPosition(pos[i]))->getHeight();
      XShapeCombineRectangles(_dpy, _border_win[pos[i]], 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 // FIXME: hmm
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)
{
	Geometry gm = _gm;

	int x = getRX();
	int y = getBY();

	int attract = Config::instance()->getWOAttract();
	int resist = Config::instance()->getWOResist();

	bool snapped;

	list<PWinObj*>::reverse_iterator it = _wo_list.rbegin();
	for (; it != _wo_list.rend(); ++it) {
		if (((*it) == this) || ((*it)->isMapped() == false) ||
				((*it)->getType() != PWinObj::WO_FRAME)) {
			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()->getHeadInfo(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;
	}
}

#ifdef HAVE_SHAPE
//! @brief
bool
PDecor::setShape(bool remove)
{
	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);
}
#endif // HAVE_SHAPE

//! @brief
void
PDecor::alignChild(PWinObj *child)
{
	if (child == NULL) {
		return;
	}

	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<PDecor::Button*>::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 = 0;
	if (_data->getTitleWidthMin() > 0)
		bt_off = getTitleHeight();

  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();
}

//! @brief
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 = 0;
    if (_data->getTitleWidthMin() > 0)
      bt_off = getTitleHeight();

    Window shape;
    shape = XCreateSimpleWindow(_dpy, PScreen::instance()->getRoot(),
                                0, 0, _gm.width, _gm.height, 0, 0, 0);

    if (_child != NULL) {
      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);
      }

      // y position for left and right border
      int side_y;
      if (!bt_off && !borderTop())
        side_y = getTitleHeight();
      else
        side_y = bt_off + borderTopLeftHeight();

      // left
      if (borderLeft() > 0) {
        XShapeCombineShape(_dpy, shape, ShapeBounding,
                           0, side_y,
                           _border_win[BORDER_LEFT],
                           ShapeBounding, ShapeUnion);
      }

      // right
      if (borderRight() > 0) {
        XShapeCombineShape(_dpy, shape, ShapeBounding,
                           _gm.width - borderRight(), side_y,
                           _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 {
    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<Window> window_list(_border_win, _border_win + BORDER_NO_POS);

	// Add title if any
	if (_titlebar) {
		window_list.push_front(_title_wo.getWindow());
	}

	// Only put the Child over if not shaded.
	if (_child && !_shaded) {
		window_list.push_front(_child->getWindow());
	}

	Window *windows = new Window[window_list.size()];
	copy(window_list.begin(), window_list.end(), windows);

	// Raise the top window so actual restacking is done.
	XRaiseWindow(_dpy, windows[0]);
	XRestackWindows(_dpy, windows, window_list.size());

	delete [] windows;
}

//! @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);
}

//! @brief
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;
	};
}

//! @brief
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));

		// Symetric mode, get max tab width, multiply with num
		if (_data->isTitleWidthSymetric()) {
			list<PDecor::TitleItem*>::iterator it(_title_list.begin());
			for (; it != _title_list.end(); ++it) {
				width = font->getWidth((*it)->getVisible().c_str());
				if (width > width_max)
					width_max = width;
			}

			width = width_max
				+ _data->getPad(DIRECTION_LEFT)	+ _data->getPad(DIRECTION_RIGHT);
			width *= _title_list.size();

		// Asymetric mode, get individual widths
		} else {
			list<PDecor::TitleItem*>::iterator it(_title_list.begin());
			for (; it != _title_list.end(); ++it) {
				width += font->getWidth((*it)->getVisible().c_str())
					+ _data->getPad(DIRECTION_LEFT)	+ _data->getPad(DIRECTION_RIGHT);
			}
		}

		// add width of all separatorns
		width += (_title_list.size() - 1)
			* _data->getTextureSeparator(getFocusedState(false))->getWidth();

		// add width of buttons
		width += _titles_left + _titles_right;

		// validate size
		if (width < static_cast<uint>(_data->getTitleWidthMin()))
			width = _data->getTitleWidthMin();
		if (width > (_gm.width * _data->getTitleWidthMax() / 100))
			width = _gm.width * _data->getTitleWidthMax() / 100;
	}

	return width;
}

//! @brief Calculate tab width, wrapper to choose correct algorithm.
void
PDecor::calcTabsWidth(void)
{
	if (!_title_list.size())
		return;

	if (_data->isTitleWidthSymetric()) {
		calcTabsWidthSymetric();
 	} else {
 		calcTabsWidthAsymetric();
 	}
}

//! @brief Calculate tab width symetric, sets up _title_list widths.
void
PDecor::calcTabsWidthSymetric(void)
{
	// include separators in calculation if needed
	uint sep_width = (_title_list.size() - 1)
		* _data->getTextureSeparator(getFocusedState(false))->getWidth();

	// Calculate width
  uint width_avail = _title_wo.getWidth();

  // Remove spacing if enough space available
  if (width_avail > (_titles_left + _titles_right)) {
    width_avail -= _titles_left + _titles_right;
  }
  if (width_avail > sep_width) {
    width_avail -= sep_width;
  }

	uint width = width_avail / _title_list.size();
	uint off = width_avail % _title_list.size();

	// Assign width to elements
	list<PDecor::TitleItem*>::iterator it(_title_list.begin());
	for (; it != _title_list.end(); ++it) {
    if (off > 0) {
      (*it)->setWidth(width + 1);
      off--;
    } else {
      (*it)->setWidth(width);
    }
	}
}

//! @brief
void
PDecor::calcTabsWidthAsymetric(void)
{
	calcTabsWidthSymetric();
}


syntax highlighted by Code2HTML, v. 0.9.1