//
// PDecor.cc for pekwm
// Copyright © 2004-2009 Claes Nästén <me@pekdon.net>
//
// 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 <cassert>
#include <cstdio>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <cwchar>

extern "C" {
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#ifdef HAVE_SHAPE
#include <X11/extensions/shape.h>
#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<ActionEvent>::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<uint>(_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::_pdecor_list = list<PDecor*>();

//! @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 &params,
                                  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 &params,
                                 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 &params)
{
    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, &params.attr);
}

/**
 * Create title window.
 */
void
PDecor::createTitle(CreateWindowParams &params)
{
    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, &params.attr);
    _title_wo.setWindow(title);
    addChildWindow(_title_wo.getWindow());
}

/**
 * Create border windows
 */
void
PDecor::createBorder(CreateWindowParams &params)
{
    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, &params.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<BorderPosition, Pixmap>::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<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
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<ActionEvent> *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<ActionEvent> *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<PWinObj*>::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<Theme::PDecorButtonData*>::iterator b_it(_data->buttonBegin());
    for (; b_it != _data->buttonEnd(); ++b_it) {
        uint width = std::max(static_cast<uint>(1), (*b_it)->getWidth() ? (*b_it)->getWidth() : getTitleHeight());
        uint height = std::max(static_cast<uint>(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<PWinObj*>::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<PDecor::Button*>::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<PDecor::Button*>::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<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 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<PWinObj*>::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<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(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<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) {
        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<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 || (*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<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 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<Action>::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<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)));

        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<PDecor::Button*>::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<BorderPosition, Pixmap>::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<PTextureSolid*>(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<BorderPosition, Pixmap>::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<PWinObj*>::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<PDecor*>(*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<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 = (_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<PDecor::TitleItem*>::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<PDecor::TitleItem*>::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<uint>(_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<PDecor::TitleItem*>::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<PDecor::TitleItem*>::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<uint>(_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<PDecor::TitleItem*>::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.
}
