//
// Frame.cc for pekwm
// Copyright © 2002-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 <algorithm>
#include <cstdio>
#include <cwchar>
#include <functional>
#include <iostream>

extern "C" {
#include <X11/Xatom.h>
}

#include "PWinObj.hh"
#include "PDecor.hh"
#include "Frame.hh"

#include "Compat.hh"
#include "PScreen.hh"
#include "Config.hh"
#include "ActionHandler.hh"
#include "AutoProperties.hh"
#include "Client.hh"
#include "ScreenResources.hh"
#include "StatusWindow.hh"
#include "Workspaces.hh"
#include "WindowManager.hh"
#include "KeyGrabber.hh"
#include "PMenu.hh"
#include "Theme.hh"

using std::cerr;
using std::endl;
using std::find;
using std::list;
using std::mem_fun;
using std::string;
using std::vector;
using std::wstring;
using std::swprintf;

list<Frame*> Frame::_frame_list = list<Frame*>();
vector<uint> Frame::_frameid_list = vector<uint>();

Frame* Frame::_tag_frame = 0;
bool Frame::_tag_behind = false;

//! @brief Frame constructor
Frame::Frame(Client *client, AutoProperty *ap)
    : PDecor(WindowManager::instance()->getTheme(),
             Frame::getClientDecorName(client), client->getWindow()),
      _id(0), _client(0), _class_hint(0),
      _non_fullscreen_decor_state(0), _non_fullscreen_layer(LAYER_NORMAL)
{
    // setup basic pointers
    _scr = WindowManager::instance()->getScreen();

    // PWinObj attributes
    _type = WO_FRAME;

    // PDecor attributes
    _decor_cfg_child_move_overloaded = true;
    _decor_cfg_bpr_replay_pointer = true;
    _decor_cfg_bpr_al_title = MOUSE_ACTION_LIST_TITLE_FRAME;
    _decor_cfg_bpr_al_child = MOUSE_ACTION_LIST_CHILD_FRAME;

    // grab buttons so that we can reply them
    for (uint i = 0; i < BUTTON_NO; ++i) {
        XGrabButton(_dpy, i, AnyModifier, _window,
                    True, ButtonPressMask|ButtonReleaseMask,
                    GrabModeSync, GrabModeAsync, None, None);
    }

    // get unique id of the frame, if the client didn't have an id
    if (! WindowManager::instance()->isStartup()) {
        long id;
        if (AtomUtil::getLong(client->getWindow(), Atoms::getAtom(PEKWM_FRAME_ID), id)) {
            _id = id;
        }

    } else {
        _id = findFrameID();
    }

    // get the clients class_hint
    _class_hint = new ClassHint();
    *_class_hint = *client->getClassHint();

    _scr->grabServer();

    // We don't send any ConfigurRequests during setup, we send one when we
    // are finished to minimize traffic and confusion
    client->setConfigureRequestLock(true);

    // Before setting position and size up we make sure the decor state
    // of the client match the decore state of the framewidget
    if (! client->hasBorder()) {
        setBorder(STATE_UNSET);
    }
    if (! client->hasTitlebar()) {
        setTitlebar(STATE_UNSET);
    }

    // We first get the size of the window as it will be needed when placing
    // the window with the help of WinGravity.
    resizeChild(client->getWidth(), client->getHeight());

    // setup position
    bool place = false;
    if (client->isViewable() || client->isPlaced()) {
        moveChild(client->getX(), client->getY());
    } else if (client->setPUPosition()) {
        int x, y;
        calcGravityPosition(client->getXSizeHints()->win_gravity, client->getX(), client->getY(), x, y);
        move(x, y);
    } else {
        place = Config::instance()->isPlaceNew();
    }

    // override both position and size with autoproperties
    bool ap_geometry = false;
    if (ap) {
        if (ap->isMask(AP_FRAME_GEOMETRY|AP_CLIENT_GEOMETRY)) {
            ap_geometry = true;

            setupAPGeometry(client, ap);

            if (ap->frame_gm_mask&(XValue|YValue) || ap->client_gm_mask&(XValue|YValue)) {
                place = false;
            }
        }

        if (ap->isMask(AP_PLACE_NEW)) {
            place = ap->place_new;
        }
    }

    // still need a position?
    if (place) {
        Workspaces::instance()->placeWo(this, client->getTransientClientWindow());
    }

    _old_gm = _gm;
    _non_fullscreen_decor_state = client->getDecorState();
    _non_fullscreen_layer = client->getLayer();

    _scr->ungrabServer(true); // ungrab and sync

    // now insert the client in the frame we created.
    addChild(client);

    // needs to be done before the workspace insert and after the client
    // has been inserted, in order for layer settings to be propagated
    setLayer(client->getLayer());

    // I add these to the list before I insert the client into the frame to
    // be able to skip an extra updateClientList
    _frame_list.push_back(this);
    WindowManager::instance()->addToFrameList(this);
    Workspaces::instance()->insert(this);

    activateChild(client);

    // set the window states, shaded, maximized...
    getState(client);

    if (! _client->hasStrut() && ! ap_geometry) {
        if (fixGeometry()) {
            moveResize(_gm.x, _gm.y, _gm.width, _gm.height);
        }
    }

    client->setConfigureRequestLock(false);
    client->configureRequestSend();

    // Figure out if we should be hidden or not, do not read autoprops
    PDecor::setWorkspace(_client->getWorkspace());

    woListAdd(this);
    _wo_map[_window] = this;
}

//! @brief Frame destructor
Frame::~Frame(void)
{
    // remove from lists
    _wo_map.erase(_window);
    woListRemove(this);
    _frame_list.remove(this);
    Workspaces::instance()->remove(this);
    WindowManager::instance()->removeFromFrameList(this);
    if (_tag_frame == this) {
        _tag_frame = 0;
    }

    returnFrameID(_id);

    if (_class_hint) {
        delete _class_hint;
    }

    Workspaces::instance()->updateClientList();
    Workspaces::instance()->updateClientStackingList();
}

// START - PWinObj interface.

//! @brief Iconifies the Frame.
void
Frame::iconify(void)
{
    if (_iconified) {
        return;
    }
    _iconified = true;

    unmapWindow();
}

//! @brief Toggles the Frame's sticky state
void
Frame::stick(void)
{
    _client->setSticky(_sticky); // FIXME: FRAME
    _client->stick();

    _sticky = ! _sticky;

    // make sure it's visible/hidden
    PDecor::setWorkspace(Workspaces::instance()->getActive());
}

void
Frame::raise(void)
{
    PDecor::raise();

    if (_client->transient_size()) {
        Frame *frame;
        list<Client*>::iterator it(_client->transient_begin());
        for (; it != _client->transient_end(); ++it) {
            frame = static_cast<Frame*>((*it)->getParent());
            if (frame != this && frame->getActiveClient() == *it) {
                frame->raise();
            }
        }
    }
}

//! @brief Sets workspace on frame, wrapper to allow autoproperty loading
void
Frame::setWorkspace(unsigned int workspace)
{
    // Duplicate the behavior done in PDecor::setWorkspace to have a sane
    // value on _workspace and not NET_WM_STICKY_WINDOW.
    if (workspace != NET_WM_STICKY_WINDOW) {
        // First we set the workspace, then load autoproperties for possible
        // overrun of workspace and then set the workspace.
        _workspace = workspace;
        readAutoprops(APPLY_ON_WORKSPACE);
        workspace = _workspace;
    }

    PDecor::setWorkspace(workspace);
}

void
Frame::setLayer(unsigned int layer)
{
    PDecor::setLayer(layer);

    if (_client) {
        LayerObservation *observation = new LayerObservation(Layer(layer));
        _client->notifyObservers(observation);
        delete observation;
    }
}

// event handlers

ActionEvent*
Frame::handleMotionEvent(XMotionEvent *ev)
{
    // This is true when we have a title button pressed and then we don't want
    // to be able to drag windows around, therefore we ignore the event
    if (_button) {
        return 0;
    }

    ActionEvent *ae = 0;
    uint button = _scr->getButtonFromState(ev->state);

    if (ev->window == getTitleWindow()) {
        ae = ActionHandler::findMouseAction(button, ev->state, MOUSE_EVENT_MOTION,
                                            Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_TITLE_FRAME));
    } else if (ev->window == _client->getWindow()) {
        ae = ActionHandler::findMouseAction(button, ev->state, MOUSE_EVENT_MOTION,
                                            Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_CHILD_FRAME));
    } else {
        uint pos = getBorderPosition(ev->subwindow);

        // If ev->subwindow wasn't one of the border windows, perhaps ev->window is.
        if (pos == BORDER_NO_POS) {
            pos = getBorderPosition(ev->window);
        }
        
        if (pos != BORDER_NO_POS) {
            list<ActionEvent> *bl = Config::instance()->getBorderListFromPosition(pos);
            ae = ActionHandler::findMouseAction(button, ev->state, MOUSE_EVENT_MOTION, bl);
        }
    }

    // check motion threshold
    if (ae && (ae->threshold > 0)) {
        if (! ActionHandler::checkAEThreshold(ev->x_root, ev->y_root,
                                             _pointer_x, _pointer_y, ae->threshold)) {
            ae = 0;
        }
    }

    return ae;
}

//! @brief
ActionEvent*
Frame::handleEnterEvent(XCrossingEvent *ev)
{
    // Run event handler to get hoovering to work but ignore action
    // returned.
    PDecor::handleEnterEvent(ev);

    ActionEvent *ae = 0;
    list<ActionEvent> *al = 0;

    if (ev->window == getTitleWindow()) {
        al = Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_TITLE_FRAME);

    } else if (ev->subwindow == _client->getWindow()) {
        al = Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_CHILD_FRAME);

    } else {
        uint pos = getBorderPosition(ev->window);
        if (pos != BORDER_NO_POS) {
            al = Config::instance()->getBorderListFromPosition(pos);
        }
    }

    if (al) {
        ae = ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_ENTER, al);
    }

    return ae;
}

//! @brief
ActionEvent*
Frame::handleLeaveEvent(XCrossingEvent *ev)
{
    // Run event handler to get hoovering to work but ignore action
    // returned.
    PDecor::handleLeaveEvent(ev);

    ActionEvent *ae;

    MouseActionListName ln = MOUSE_ACTION_LIST_TITLE_FRAME;
    if (ev->window == _client->getWindow()) {
        ln = MOUSE_ACTION_LIST_CHILD_FRAME;
    }

    ae = ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_LEAVE,
                                        Config::instance()->getMouseActionList(ln));

    return ae;
}

ActionEvent*
Frame::handleMapRequest(XMapRequestEvent *ev)
{
    if (! _client || (ev->window != _client->getWindow())) {
        return 0;
    }

    if (! _sticky && (_workspace != Workspaces::instance()->getActive())) {
#ifdef DEBUG
        cerr << __FILE__ << "@" << __LINE__ << ": "
             << "Ignoring MapRequest, not on current workspace!" << endl;
#endif // DEBUG
        return 0;
    }

    mapWindow();

    return 0;
}

ActionEvent*
Frame::handleUnmapEvent(XUnmapEvent *ev)
{
    list<PWinObj*>::iterator it(_child_list.begin());
    for (; it != _child_list.end(); ++it) {
        if (*(*it) == ev->window) {
            (*it)->handleUnmapEvent(ev);
            break;
        }
    }

    return 0;
}

// END - PWinObj interface.

void
Frame::handleShapeEvent(XAnyEvent *ev)
{
#ifdef HAVE_SHAPE
    if (! _client || (ev->window != _client->getWindow())) {
        return;
    }
    _client->setShaped(setShape());
#endif // HAVE_SHAPE
}

// START - PDecor interface.

bool
Frame::allowMove(void) const
{
    return _client ? _client->allowMove() : true;
}

/**
 * Return active client, or 0 if no clients or active child is not a Client.
 */
Client*
Frame::getActiveClient(void)
{
    if (getActiveChild() && getActiveChild()->getType() == WO_CLIENT) {
        return static_cast<Client*>(getActiveChild());
    } else {
        return 0;
    }
}

//! @brief Adds child to the frame.
void
Frame::addChild (PWinObj *child, std::list<PWinObj*>::iterator *it)
{
    PDecor::addChild(child, it);
    AtomUtil::setLong(child->getWindow(), Atoms::getAtom(PEKWM_FRAME_ID), _id);
    child->lower();
}

/**
 * Add child preserving order from the previous pekwm run.
 */
void
Frame::addChildOrdered (Client *child)
{
    Client *client;
    
    list<PWinObj*>::iterator it(_child_list.begin());
    for (; it != _child_list.end(); ++it) {
        client = static_cast<Client*>(*it);
        if (child->getInitialFrameOrder() < client->getInitialFrameOrder()) {
            break;
        }
    }

    addChild(child, &it);
}

//! @brief Removes child from the frame.
void
Frame::removeChild (PWinObj *child, bool do_delete)
{
    PDecor::removeChild(child, do_delete);
}

/**
 * Activates child in Frame, updating it's state and re-loading decor
 * rules to match updated title.
 */
void
Frame::activateChild (PWinObj *child)
{
    // FIXME: Update default decoration for this child, can change
    // decoration from DEFAULT to REMOTE or WARNING

    // Sync the frame state with the client only if we already had a client
    if (_client && _client != child) {
        applyState(static_cast<Client*>(child));
    }

    _client = static_cast<Client*>(child);
    PDecor::activateChild(child);

    // setShape uses current active child, so we need to activate the
    // child before setting shape
    if (PScreen::instance()->hasExtensionShape()) {
        _client->setShaped(setShape());
    }

#ifdef OPACITY
    setOpacity(_client);
#endif // OPACITY

    if (_focused) {
        child->giveInputFocus();
    }
    
    // Reload decor rules if needed.
    handleTitleChange(_client);

    Workspaces::instance()->updateClientList();
    Workspaces::instance()->updateClientStackingList();
}

/**
 * Called when child order is updated, re-sets the titles and updates
 * the _PEKWM_FRAME hints.
 */
void
Frame::updatedChildOrder(void)
{
    titleClear();

    Client *client;
    list<PWinObj*>::iterator it(_child_list.begin());
    for (long num = 0; it != _child_list.end(); ++num, ++it) {
        client = static_cast<Client*>(*it);
        client->setPekwmFrameOrder(num);
        titleAdd(client->getTitle());
    }

    updatedActiveChild();
}

/**
 * Set the active title and update the _PEKWM_FRAME hints.
 */
void
Frame::updatedActiveChild(void)
{
    titleSetActive(0);

    list<PWinObj*>::iterator it(_child_list.begin());
    for (uint i = 0; it != _child_list.end(); ++i, ++it) {
        static_cast<Client*>(*it)->setPekwmFrameActive(_child == *it);
        if (_child == *it) {
            titleSetActive(i);
        }
    }

    renderTitle();
}

//! @brief
void
Frame::getDecorInfo(wchar_t *buf, uint size)
{
    uint width, height;
    if (_client) {
        calcSizeInCells(width, height);
    } else {
        width = _gm.width;
        height = _gm.height;
    }
    swprintf(buf, size, L"%d+%d+%d+%d", width, height, _gm.x, _gm.y);
}

//! @brief
void
Frame::setShaded(StateAction sa)
{
    bool shaded = isShaded();
    PDecor::setShaded(sa);
    if (shaded != isShaded()) {
        _client->setShade(isShaded());
        _client->updateEwmhStates();
    }
}

//! @brief
int
Frame::resizeHorzStep(int diff) const
{
    if (! _client) {
        return diff;
    }

    int diff_ret = 0;
    uint min = _gm.width - getChildWidth();
    if (min == 0) { // borderless windows, we don't want X errors
        min = 1;
    }
    const XSizeHints *hints = _client->getXSizeHints(); // convenience

    // if we have ResizeInc hint set we use it instead of pixel diff
    if (hints->flags&PResizeInc) {
        if (diff > 0) {
            diff_ret = hints->width_inc;
        } else if ((_gm.width - hints->width_inc) >= min) {
            diff_ret = -hints->width_inc;
        }
    } else if ((_gm.width + diff) >= min) {
        diff_ret = diff;
    }

    // check max/min size hints
    if (diff > 0) {
        if ((hints->flags&PMaxSize) &&
                ((getChildWidth() + diff) > unsigned(hints->max_width))) {
            diff_ret = _gm.width - hints->max_width + min;
        }
    } else if ((hints->flags&PMinSize) &&
               ((getChildWidth() + diff) < unsigned(hints->min_width))) {
        diff_ret = _gm.width - hints->min_width + min;
    }

    return diff_ret;
}

//! @brief
int
Frame::resizeVertStep(int diff) const
{
    if (! _client) {
        return diff;
    }

    int diff_ret = 0;
    uint min = _gm.height - getChildHeight();
    if (min == 0) { // borderless windows, we don't want X errors
        min = 1;
    }
    const XSizeHints *hints = _client->getXSizeHints(); // convenience

    // if we have ResizeInc hint set we use it instead of pixel diff
    if (hints->flags&PResizeInc) {
        if (diff > 0) {
            diff_ret = hints->height_inc;
        } else if ((_gm.height - hints->height_inc) >= min) {
            diff_ret = -hints->height_inc;
        }
    } else {
        diff_ret = diff;
    }

    // check max/min size hints
    if (diff > 0) {
        if ((hints->flags&PMaxSize) &&
                ((getChildHeight() + diff) > unsigned(hints->max_height))) {
            diff_ret = _gm.height - hints->max_height + min;
        }
    } else if ((hints->flags&PMinSize) &&
               ((getChildHeight() + diff) < unsigned(hints->min_height))) {
        diff_ret = _gm.height - hints->min_width + min;
    }

    return diff_ret;
}

// END - PDecor interface.

//! @brief Sets _PEKWM_FRAME_ID on all children in the frame
void
Frame::setId(uint id)
{
    _id = id;

    list<PWinObj*>::iterator it(_child_list.begin());
    long atom = Atoms::getAtom(PEKWM_FRAME_ID);
    for (; it != _child_list.end(); ++it) {
        AtomUtil::setLong((*it)->getWindow(), atom, id);
    }
}

//! @brief Gets the state from the Client
void
Frame::getState(Client *cl)
{
    if (! cl)
        return;

    bool b_client_iconified = cl->isIconified ();

    if (_sticky != cl->isSticky())
        _sticky = ! _sticky;
    if (_maximized_horz != cl->isMaximizedHorz())
        setStateMaximized(STATE_TOGGLE, true, false, false);
    if (_maximized_vert != cl->isMaximizedVert())
        setStateMaximized(STATE_TOGGLE, false, true, false);
    if (isShaded() != cl->isShaded())
        setShaded(STATE_TOGGLE);
    if (getLayer() != cl->getLayer()) {
        setLayer(cl->getLayer());
    }
    if (_workspace != cl->getWorkspace())
        PDecor::setWorkspace(cl->getWorkspace());

    // We need to set border and titlebar before setting fullscreen, as
    // fullscreen will unset border and titlebar if needed.
    if (hasBorder() != _client->hasBorder())
        setBorder(STATE_TOGGLE);
    if (hasTitlebar() != _client->hasTitlebar())
        setTitlebar(STATE_TOGGLE);
    if (_fullscreen != cl->isFullscreen())
        setStateFullscreen(STATE_TOGGLE);

    if (_iconified != b_client_iconified) {
        if (_iconified) {
            mapWindow();
        } else {
            iconify();
        }
    }

    if (_skip != cl->getSkip()) {
        setSkip(cl->getSkip());
    }
}

//! @brief Applies the frame's state on the Client
void
Frame::applyState(Client *cl)
{
    if (! cl) {
        return;
    }
    
    cl->setSticky(_sticky);
    cl->setMaximizedHorz(_maximized_horz);
    cl->setMaximizedVert(_maximized_vert);
    cl->setShade(isShaded());
    cl->setWorkspace(_workspace);
    cl->setLayer(getLayer());

    // fix border / titlebar state
    cl->setBorder(hasBorder());
    cl->setTitlebar(hasTitlebar());
    // make sure the window has the correct mapped state
    if (_mapped != cl->isMapped()) {
        if (! _mapped) {
            cl->unmapWindow();
        } else {
            cl->mapWindow();
        }
    }

    cl->updateEwmhStates();
}

//! @brief Sets skip state.
void
Frame::setSkip(uint skip)
{
    PDecor::setSkip(skip);

    // Propagate changes on client as well
    if (_client) {
        _client->setSkip(skip);
    }
}

//! @brief Find Frame with Window
//! @param win Window to search for.
//! @return Frame if found, else 0.
Frame*
Frame::findFrameFromWindow(Window win)
{
    // Validate input window.
    if ((win == None) || (win == PScreen::instance()->getRoot())) {
        return 0;
    }

    list<Frame*>::iterator it(_frame_list.begin());
    for(; it !=_frame_list.end(); ++it) {
        if (win == (*it)->getWindow()) { // operator == does more than that
            return (*it);
        }
    }

    return 0;
}

//! @brief Find Frame with id.
//! @param id ID to search for.
//! @return Frame if found, else 0.
Frame*
Frame::findFrameFromID(uint id)
{
    list<Frame*>::iterator it(Frame::frame_begin());
    for (; it != Frame::frame_end(); ++it) {
        if ((*it)->getId() == id) {
            return (*it);
        }
    }

    return 0;
}

//! @brief
void
Frame::setupAPGeometry(Client *client, AutoProperty *ap)
{
    // frame geomtry overides client geometry

    // get client geometry
    if (ap->isMask(AP_CLIENT_GEOMETRY)) {
        Geometry gm(client->_gm);
        applyAPGeometry(gm, ap->client_gm, ap->client_gm_mask);

        if (ap->client_gm_mask&(XValue|YValue)) {
            moveChild(gm.x, gm.y);
        }
        if(ap->client_gm_mask&(WidthValue|HeightValue)) {
            resizeChild(gm.width, gm.height);
        }
    }

    // get frame geometry
    if (ap->isMask(AP_FRAME_GEOMETRY)) {
        applyAPGeometry(_gm, ap->frame_gm, ap->frame_gm_mask);
        if (ap->frame_gm_mask&(XValue|YValue)) {
            move(_gm.x, _gm.y);
        }
        if (ap->frame_gm_mask&(WidthValue|HeightValue)) {
            resize(_gm.width, _gm.height);
        }
    }
}

//! @brief Apply autoproperties geometry.
//! @param gm Geometry to modify.
//! @param ap_gm Geometry to get values from.
//! @param mask Geometry mask.
void
Frame::applyAPGeometry(Geometry &gm, const Geometry &ap_gm, int mask)
{
    // Read size before position so negative position works, if size is
    // < 1 consider it to be full screen size.
    if (mask&WidthValue) {
        if (ap_gm.width < 1) {
            gm.width = _scr->getWidth();
        } else {
            gm.width = ap_gm.width;
        }
    }
    
    if (mask&HeightValue) {
        if (ap_gm.height < 1) {
        } else {
            gm.height = ap_gm.height;
        }
    }

    // Read position
    if (mask&XValue) {
        gm.x = ap_gm.x;
        if (mask&XNegative) {
            gm.x += _scr->getWidth() - gm.width;
        }
    }
    if (mask&YValue) {
        gm.y = ap_gm.y;
        if (mask&YNegative) {
            gm.y += _scr->getHeight() - gm.height;
        }
    }
}

//! @brief Finds free Frame ID.
//! @return First free Frame ID.
uint
Frame::findFrameID(void)
{
    uint id = 0;

    if (_frameid_list.size()) {
        // Check for used Frame IDs
        id = _frameid_list.back();
        _frameid_list.pop_back();
    } else {
        // No free, get next number (Frame is not in list when this is called.)
        id = _frame_list.size() + 1;
    }

    return id;
}

//! @brief Returns Frame ID to used frame id list.
//! @param id ID to return.
void
Frame::returnFrameID(uint id)
{
    vector<uint>::iterator it(_frameid_list.begin());
    for (; it != _frameid_list.end() && id < *it; ++it)
        ;
    _frameid_list.insert(it, id);
}

/**
 * Return decor name matching clients property, defaults to
 * PDecor::DEFAULT_DECOR_NAME
 */
std::string
Frame::getClientDecorName(Client *client)
{
  DecorProperty *prop = WindowManager::instance()->getAutoProperties()->findDecorProperty(client->getClassHint());
  return prop ? prop->getName() : PDecor::DEFAULT_DECOR_NAME;
}

//! @brief Resets Frame IDs.
void
Frame::resetFrameIDs(void)
{
    list<Frame*>::iterator it(_frame_list.begin());
    for (uint id = 1; it != _frame_list.end(); ++id, ++it) {
        (*it)->setId(id);
    }
}


//! @brief Removes the client from the Frame and creates a new Frame for it
void
Frame::detachClient(Client *client)
{
    if (client->getParent() != this) {
        return;
    }

    if (_child_list.size() > 1) {
        removeChild(client);

        client->move(_gm.x, _gm.y + borderTop());
        Frame *frame = new Frame(client, 0);

        client->setParent(frame);
        client->setWorkspace(Workspaces::instance()->getActive());

        setFocused(false);
    }
}

//! @brief Makes sure the frame doesn't cover any struts / the harbour.
bool
Frame::fixGeometry(void)
{
    Geometry head, before;
    PScreen::instance()->getHeadInfoWithEdge(getNearestHead(), head);

    before = _gm;

    // fix size
    if (_gm.width > head.width) {
        _gm.width = head.width;
    } if (_gm.height > head.height) {
        _gm.height = head.height;
    }

    // fix position
    if (_gm.x < head.x) {
        _gm.x = head.x;
    } else if ((_gm.x + _gm.width) > (head.x + head.width)) {
        _gm.x = head.x + head.width - _gm.width;
    }
    if (_gm.y < head.y) {
        _gm.y = head.y;
    } else if ((_gm.y + _gm.height) > (head.y + head.height)) {
        _gm.y = head.y + head.height - _gm.height;
    }

    return (_gm != before);
}

//! @brief Initiates grouping move, based on a XMotionEvent.
void
Frame::doGroupingDrag(XMotionEvent *ev, Client *client, bool behind) // FIXME: rewrite
{
    if (! client) {
        return;
    }

    int o_x, o_y;
    o_x = ev ? ev->x_root : 0;
    o_y = ev ? ev->y_root : 0;

    wstring name(L"Grouping ");
    if (client->getTitle()->getVisible().size() > 0) {
        name += client->getTitle()->getVisible();
    } else {
        name += L"No Name";
    }

    bool status = _scr->grabPointer(_scr->getRoot(),
                                    ButtonReleaseMask|PointerMotionMask, None);
    if (status != true) {
        return;
    }

    StatusWindow *sw = StatusWindow::instance();

    sw->draw(name); // resize window and render bg
    sw->move(o_x, o_y);
    sw->mapWindowRaised();
    sw->draw(name); // redraw after map

    XEvent e;
    while (true) { // this breaks when we get an button release
        XMaskEvent(_dpy, PointerMotionMask|ButtonReleaseMask, &e);

        switch (e.type)  {
        case MotionNotify:
            // update the position
            o_x = e.xmotion.x_root;
            o_y = e.xmotion.y_root;

            sw->move(o_x, o_y);
            sw->draw(name);
            break;

        case ButtonRelease:
            sw->unmapWindow();
            _scr->ungrabPointer();

            Client *search = 0;

            // only group if we have grouping turned on
            if (WindowManager::instance()->isAllowGrouping()) {
                int x, y;
                Window win;

                // find the frame we dropped the client on
                XTranslateCoordinates(_dpy, _scr->getRoot(), _scr->getRoot(),
                                      e.xmotion.x_root, e.xmotion.y_root,
                                      &x, &y, &win);

                search = Client::findClient(win);
            }

            // if we found a client, and it's not in the current frame and
            // it has a "normal" ( make configurable? ) layer we group
            if (search && search->getParent() &&
                    (search->getParent() != this) &&
                    (search->getLayer() > LAYER_BELOW) &&
                    (search->getLayer() < LAYER_ONTOP)) {

                // if we currently have focus and the frame exists after we remove
                // this client we need to redraw it as unfocused
                bool focus = behind ? false : (_child_list.size() > 1);

                removeChild(client);

                Frame *frame = static_cast<Frame*>(search->getParent());
                frame->addChild(client);
                if (! behind) {
                    frame->activateChild(client);
                    frame->giveInputFocus();
                }

                if (focus) {
                    setFocused(false);
                }

            }  else if (_child_list.size() > 1) {
                // if we have more than one client in the frame detach this one
                removeChild(client);

                client->move(e.xmotion.x_root, e.xmotion.y_root);

                Frame *frame = new Frame(client, 0);
                client->setParent(frame);

                // make sure the client ends up on the current workspace
                client->setWorkspace(Workspaces::instance()->getActive());

                // make sure it get's focus
                setFocused(false);
                frame->giveInputFocus();
            }

            return;
        }
    }
}

//! @brief Initiates resizing of a window based on motion event
void
Frame::doResize(XMotionEvent *ev)
{
    if (! ev) {
        return;
    }
    
    // figure out which part of the window we are in
    bool left = false, top = false;
    if (ev->x < signed(_gm.width / 2)) {
        left = true;
    }
    if (ev->y < signed(_gm.height / 2)) {
        top = true;
    }
    
    doResize(left, true, top, true);
}

//! @brief Initiates resizing of a window based border position
void
Frame::doResize(BorderPosition pos)
{
    bool x = false, y = false;
    bool left = false, top = false, resize = true;

    switch (pos) {
    case BORDER_TOP_LEFT:
        x = y = left = top = true;
        break;
    case BORDER_TOP:
        y = top = true;
        break;
    case BORDER_TOP_RIGHT:
        x = y = top = true;
        break;
    case BORDER_LEFT:
        x = left = true;
        break;
    case BORDER_RIGHT:
        x = true;
        break;
    case BORDER_BOTTOM_LEFT:
        x = y = left = true;
        break;
    case BORDER_BOTTOM:
        y = true;
        break;
    case BORDER_BOTTOM_RIGHT:
        x = y = true;
        break;
    default:
        resize = false;
        break;
    }

    if (resize) {
        doResize(left, x, top, y);
    }
}

//! @brief Resizes the frame by handling MotionNotify events.
void
Frame::doResize(bool left, bool x, bool top, bool y)
{
    if (! _client->allowResize() || isShaded()) {
        return;
    }

    if (! _scr->grabPointer(_scr->getRoot(), ButtonMotionMask|ButtonReleaseMask,
                           ScreenResources::instance()->getCursor(ScreenResources::CURSOR_RESIZE))) {
        return;
    }

    setShaded(STATE_UNSET); // make sure the frame isn't shaded
    setStateFullscreen(STATE_UNSET);

    // Initialize variables
    int start_x, new_x;
    int start_y, new_y;
    uint last_width, old_width;
    uint last_height, old_height;

    start_x = new_x = left ? _gm.x : (_gm.x + _gm.width);
    start_y = new_y = top ? _gm.y : (_gm.y + _gm.height);
    last_width = old_width = _gm.width;
    last_height = old_height = _gm.height;

    // the basepoint of our window
    _click_x = left ? (_gm.x + _gm.width) : _gm.x;
    _click_y = top ? (_gm.y + _gm.height) : _gm.y;

    int pointer_x = _gm.x, pointer_y = _gm.y;
    _scr->getMousePosition(pointer_x, pointer_y);

    wchar_t buf[128];
    getDecorInfo(buf, 128);

    bool center_on_root = Config::instance()->isShowStatusWindowOnRoot();
    StatusWindow *sw = StatusWindow::instance();
    if (Config::instance()->isShowStatusWindow()) {
        sw->draw(buf, true, center_on_root ? 0 : &_gm);
        sw->mapWindowRaised();
        sw->draw(buf, true, center_on_root ? 0 : &_gm);
    }

    bool outline = ! Config::instance()->getOpaqueResize();

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

    const long resize_mask = ButtonPressMask|ButtonReleaseMask|ButtonMotionMask;
    XEvent ev;
    bool exit = false;
    while (exit != true) {
        if (outline) {
            drawOutline(_gm);
        }
        XMaskEvent(_dpy, resize_mask, &ev);
        if (outline) {
            drawOutline(_gm); // clear
        }

        switch (ev.type) {
        case MotionNotify:
            // Flush all pointer motion, no need to redraw and redraw.
            PScreen::removeMotionEvents();

            if (x) {
                new_x = start_x - pointer_x + ev.xmotion.x;
            }
            if (y) {
                new_y = start_y - pointer_y + ev.xmotion.y;
            }

            recalcResizeDrag(new_x, new_y, left, top);

            getDecorInfo(buf, 128);
            if (Config::instance()->isShowStatusWindow()) {
                sw->draw(buf, true, center_on_root ? 0 : &_gm);
            }

            // only updated when needed when in opaque mode
            if (! outline) {
                if ((old_width != _gm.width) || (old_height != _gm.height)) {
                    moveResize(_gm.x, _gm.y, _gm.width, _gm.height);
                }
                old_width = _gm.width;
                old_height = _gm.height;
            }
            break;
        case ButtonRelease:
            exit = true;
            XPutBackEvent(_dpy, &ev);
            break;
        }
    }

    if (Config::instance()->isShowStatusWindow()) {
        sw->unmapWindow();
    }

    _scr->ungrabPointer();

    // Make sure the state isn't set to maximized after we've resized.
    if (_maximized_horz || _maximized_vert) {
        _maximized_horz = false;
        _maximized_vert = false;
        _client->setMaximizedHorz(false);
        _client->setMaximizedVert(false);
        _client->updateEwmhStates();
    }

    if (outline) {
        moveResize(_gm.x, _gm.y, _gm.width, _gm.height);
        _scr->ungrabServer(true);
    }
}

//! @brief Updates the width, height of the frame when resizing it.
void
Frame::recalcResizeDrag(int nx, int ny, bool left, bool top)
{
    uint brdr_lr = borderLeft() + borderRight();
    uint brdr_tb = borderTop() + borderBottom();

    if (left) {
        if (nx >= signed(_click_x - brdr_lr))
            nx = _click_x - brdr_lr - 1;
    } else {
        if (nx <= signed(_click_x + brdr_lr))
            nx = _click_x + brdr_lr + 1;
    }

    if (top) {
        if (ny >= signed(_click_y - getTitleHeight() - brdr_tb))
            ny = _click_y - getTitleHeight() - brdr_tb - 1;
    } else {
        if (ny <= signed(_click_y + getTitleHeight() + brdr_tb))
            ny = _click_y + getTitleHeight() + brdr_tb + 1;
    }

    uint width = left ? (_click_x - nx) : (nx - _click_x);
    uint height = top ? (_click_y - ny) : (ny - _click_y);

    width -= brdr_lr;
    height -= brdr_tb + getTitleHeight();

    _client->getAspectSize(&width, &height, width, height);

    const XSizeHints *hints = _client->getXSizeHints();
    // check so we aren't overriding min or max size
    if (hints->flags & PMinSize) {
        if (signed(width) < hints->min_width)
            width = hints->min_width;
        if (signed(height) < hints->min_height)
            height = hints->min_height;
    }

    if (hints->flags & PMaxSize) {
        if (signed(width) > hints->max_width)
            width = hints->max_width;
        if (signed(height) > hints->max_height)
            height = hints->max_height;
    }

    _gm.width = width + brdr_lr;
    _gm.height = height + getTitleHeight() + brdr_tb;

    _gm.x = left ? (_click_x - _gm.width) : _click_x;
    _gm.y = top ? (_click_y - _gm.height) : _click_y;
}

//! @brief Moves the Frame to the screen edge ori ( considering struts )
void
Frame::moveToEdge(OrientationType ori)
{
    uint head_nr;
    Geometry head, real_head;

    head_nr = getNearestHead();
    _scr->getHeadInfo(head_nr, real_head);
    _scr->getHeadInfoWithEdge(head_nr, head);

    switch (ori) {
    case TOP_LEFT:
        _gm.x = head.x;
        _gm.y = head.y;
        break;
    case TOP_EDGE:
        _gm.y = head.y;
        break;
    case TOP_CENTER_EDGE:
        _gm.x = real_head.x + ((real_head.width - _gm.width) / 2);
        _gm.y = head.y;
        break;
    case TOP_RIGHT:
        _gm.x = head.x + head.width - _gm.width;
        _gm.y = head.y;
        break;
    case BOTTOM_RIGHT:
        _gm.x = head.x + head.width - _gm.width;
        _gm.y = head.y + head.height - _gm.height;
        break;
    case BOTTOM_EDGE:
        _gm.y = head.y + head.height - _gm.height;
        break;
    case BOTTOM_CENTER_EDGE:
        _gm.x = real_head.x + ((real_head.width - _gm.width) / 2);
        _gm.y = head.y + head.height - _gm.height;
        break;
    case BOTTOM_LEFT:
        _gm.x = head.x;
        _gm.y = head.y + head.height - _gm.height;
        break;
    case LEFT_EDGE:
        _gm.x = head.x;
        break;
    case LEFT_CENTER_EDGE:
        _gm.x = head.x;
        _gm.y = real_head.y + ((real_head.height - _gm.height) / 2);
        break;
    case RIGHT_EDGE:
        _gm.x = head.x + head.width - _gm.width;
        break;
    case RIGHT_CENTER_EDGE:
        _gm.x = head.x + head.width - _gm.width;
        _gm.y = real_head.y + ((real_head.height - _gm.height) / 2);
        break;
    case CENTER:
        _gm.x = real_head.x + ((real_head.width - _gm.width) / 2);
        _gm.y = real_head.y + ((real_head.height - _gm.height) / 2);
    default:
        // DO NOTHING
        break;
    }

    move(_gm.x, _gm.y);
}

//! @brief Updates all inactive childrens geometry and state
void
Frame::updateInactiveChildInfo(void)
{
    if (! _client) {
        return;
    }
    
    list<PWinObj*>::iterator it(_child_list.begin());
    for (; it != _child_list.end(); ++it) {
        if (*it != _client) {
            applyState(static_cast<Client*>(*it));
            (*it)->resize(getChildWidth(), getChildHeight());
        }
    }
}

// STATE actions begin

//! @brief Toggles current clients max size
//! @param sa State to set
//! @param horz Include horizontal in (de)maximize
//! @param vert Include vertcical in (de)maximize
//! @param fill Limit size by other frame boundaries ( defaults to false )
void
Frame::setStateMaximized(StateAction sa, bool horz, bool vert, bool fill)
{
    // we don't want to maximize transients
    if (_client->isTransient()) {
        return;
    }

    setShaded(STATE_UNSET);
    setStateFullscreen(STATE_UNSET);

    // make sure the two states are in sync if toggling
    if ((horz == vert) && (sa == STATE_TOGGLE)) {
        if (_maximized_horz != _maximized_vert) {
            horz = ! _maximized_horz;
            vert = ! _maximized_vert;
        }
    }

    XSizeHints *size_hint = _client->getXSizeHints(); // convenience

    Geometry head;
    _scr->getHeadInfoWithEdge(getNearestHead(), head);

    int max_x, max_r, max_y, max_b;
    max_x = head.x;
    max_r = head.width + head.x;
    max_y = head.y;
    max_b = head.height + head.y;

    if (fill) {
        getMaxBounds(max_x, max_r, max_y, max_b);

        // make sure vert and horz gets set if fill is on
        sa = STATE_SET;
    }

    if (horz && (fill || _client->allowMaximizeHorz())) {
        // maximize
        if ((sa == STATE_SET) ||
                ((sa == STATE_TOGGLE) && ! _maximized_horz)) {
            uint h_decor = _gm.width - getChildWidth();

            if (! fill) {
                _old_gm.x = _gm.x;
                _old_gm.width = _gm.width;
            }

            _gm.x = max_x;
            _gm.width = max_r - max_x;

            if ((size_hint->flags&PMaxSize) &&
                    (_gm.width > (size_hint->max_width + h_decor))) {
                _gm.width = size_hint->max_width + h_decor;
            }
            // demaximize
        } else if ((sa == STATE_UNSET) ||
                   ((sa == STATE_TOGGLE) && _maximized_horz)) {
            _gm.x = _old_gm.x;
            _gm.width = _old_gm.width;
        }

        // we unset the maximized state if we use maxfill
        _maximized_horz = fill ? false : ! _maximized_horz;
        _client->setMaximizedHorz(_maximized_horz);
    }

    if (vert && (fill || _client->allowMaximizeVert())) {
        // maximize
        if ((sa == STATE_SET) ||
                ((sa == STATE_TOGGLE) && ! _maximized_vert)) {
            uint v_decor = _gm.height - getChildHeight();

            if (! fill) {
                _old_gm.y = _gm.y;
                _old_gm.height = _gm.height;
            }

            _gm.y = max_y;
            _gm.height = max_b - max_y;

            if ((size_hint->flags&PMaxSize) &&
                    (_gm.height > (size_hint->max_height + v_decor))) {
                _gm.height = size_hint->max_height + v_decor;
            }
            // demaximize
        } else if ((sa == STATE_UNSET) ||
                   ((sa == STATE_TOGGLE) && _maximized_vert)) {
            _gm.y = _old_gm.y;
            _gm.height = _old_gm.height;
        }

        // we unset the maximized state if we use maxfill
        _maximized_vert = fill ? false : ! _maximized_vert;
        _client->setMaximizedVert(_maximized_vert);
    }

    fixGeometry(); // harbour already considered
    downSize(_gm, true, true); // keep x and keep y ( make conform to inc size )

    moveResize(_gm.x, _gm.y, _gm.width, _gm.height);

    _client->updateEwmhStates();
}

//! @brief Set fullscreen state
void
Frame::setStateFullscreen(StateAction sa)
{
    if (! ActionUtil::needToggle(sa, _fullscreen)) {
        return;
    }

    bool lock = _client->setConfigureRequestLock(true);

    if (_fullscreen) {
        if ((_non_fullscreen_decor_state&DECOR_BORDER) != hasBorder()) {
            setBorder(STATE_TOGGLE);
        }
        if ((_non_fullscreen_decor_state&DECOR_TITLEBAR) != hasTitlebar()) {
            setTitlebar(STATE_TOGGLE);
        }
        _gm = _old_gm;

    } else {
        _old_gm = _gm;
        _non_fullscreen_decor_state = _client->getDecorState();
        _non_fullscreen_layer = _client->getLayer();

        setBorder(STATE_UNSET);
        setTitlebar(STATE_UNSET);

        Geometry head;
        uint nr = getNearestHead();
        _scr->getHeadInfo(nr, head);

        _gm = head;
    }

    _fullscreen = !_fullscreen;
    _client->setFullscreen(_fullscreen);

    moveResize(_gm.x, _gm.y, _gm.width, _gm.height);

    // Re-stack window if fullscreen is above other windows.
    if (Config::instance()->isFullscreenAbove()) {
        setLayer(_fullscreen ? LAYER_ABOVE_DOCK : _non_fullscreen_layer);
        raise();
    }
    
    _client->setConfigureRequestLock(lock);
    _client->configureRequestSend();

    _client->updateEwmhStates();
}

//! @brief
void
Frame::setStateSticky(StateAction sa)
{
    if (ActionUtil::needToggle(sa, _sticky)) {
        stick();
    }
}

void
Frame::setStateAlwaysOnTop(StateAction sa)
{
    if (! ActionUtil::needToggle(sa, getLayer() == LAYER_ONTOP)) {
        return;
    }

    _client->alwaysOnTop(getLayer() < LAYER_ONTOP);
    setLayer(_client->getLayer());

    raise();
}

void
Frame::setStateAlwaysBelow(StateAction sa)
{
    if (! ActionUtil::needToggle(sa, getLayer() == LAYER_BELOW)) {
        return;
    }

    _client->alwaysBelow(getLayer() > LAYER_BELOW);
    setLayer(_client->getLayer());

    lower();
}

//! @brief Hides/Shows the border depending on _client
//! @param sa State to set
void
Frame::setStateDecorBorder(StateAction sa)
{
    bool border = hasBorder();

    setBorder(sa);

    // state changed, update client and atom state
    if (border != hasBorder()) {
        _client->setBorder(hasBorder());

        // update the _PEKWM_FRAME_DECOR hint
        AtomUtil::setLong(_client->getWindow(),
                          Atoms::getAtom(PEKWM_FRAME_DECOR),
                          _client->getDecorState());
    }
}

//! @brief Hides/Shows the titlebar depending on _client
//! @param sa State to set
void
Frame::setStateDecorTitlebar(StateAction sa)
{
    bool titlebar = hasTitlebar();

    setTitlebar(sa);

    // state changed, update client and atom state
    if (titlebar != hasTitlebar()) {
        _client->setTitlebar(hasTitlebar());

        AtomUtil::setLong(_client->getWindow(),
                          Atoms::getAtom(PEKWM_FRAME_DECOR),
                          _client->getDecorState());
    }
}

//! @brief
void
Frame::setStateIconified(StateAction sa)
{
    if (! ActionUtil::needToggle(sa, _iconified)) {
        return;
    }

    if (_iconified) {
        mapWindow();
    } else {
        iconify();
    }
}

//! (Un)Sets the tagged Frame.
//!
//! @param sa Set/Unset or Toggle the state.
//! @param behind Should tagged actions be behind (non-focused).
void
Frame::setStateTagged(StateAction sa, bool behind)
{
    if (ActionUtil::needToggle(sa, (_tag_frame != 0))) {
        _tag_frame = (this == _tag_frame) ? 0 : this;
        _tag_behind = behind;
    }
}

//! @brief
void
Frame::setStateSkip(StateAction sa, uint skip)
{
    if (! ActionUtil::needToggle(sa, _skip&skip)) {
        return;
    }

    if (_skip&skip) {
        _skip &= ~skip;
    } else {
        _skip |= skip;
    }

    setSkip(_skip);
}

//! @brief Sets client title
void
Frame::setStateTitle(StateAction sa, Client *client, const std::wstring &title)
{
    if (sa == STATE_SET) {
        client->getTitle()->setUser(title);

    } else if (sa == STATE_UNSET) {
        client->getTitle()->setUser(L"");
        client->readName();
    } else {
        if (client->getTitle()->isUserSet()) {
            client->getTitle()->setUser(L"");
        } else {
            client->getTitle()->setUser(title);
        }
    }

    // Set PEKWM_TITLE atom to preserve title on client between sessions.
    AtomUtil::setString(client->getWindow(),
                        Atoms::getAtom(PEKWM_TITLE),
                        Util::to_mb_str(client->getTitle()->getUser()));


    renderTitle();
}

//! @brief Sets clients marked state.
//! @param sa
//! @param client
void
Frame::setStateMarked(StateAction sa, Client *client)
{
    if (! client || ! ActionUtil::needToggle(sa, client->isMarked())) {
        return;
    }

    // Set marked state and re-render title to update visual marker.
    client->setStateMarked(sa);
    renderTitle();
}

#ifdef OPACITY
void
Frame::setStateOpaque(StateAction sa)
{
    if (! ActionUtil::needToggle(sa, _opaque)) {
        return;
    }
    _client->setOpaque(!_opaque);
    setOpaque(!_opaque);
}
#endif // OPACITY
// STATE actions end

//! @brief
void
Frame::getMaxBounds(int &max_x,int &max_r, int &max_y, int &max_b)
{
    int f_r, f_b;
    int x, y, h, w, r, b;

    f_r = getRX();
    f_b = getBY();

    list<Frame*>::iterator it = _frame_list.begin();
    for (; it != _frame_list.end(); ++it) {
        if (! (*it)->isMapped()) {
            continue;
        }

        x = (*it)->getX();
        y = (*it)->getY();
        h = (*it)->getHeight();
        w = (*it)->getWidth();
        r = (*it)->getRX();
        b = (*it)->getBY();

        // update max borders when other frame border lies between
        // this border and prior max border (originally screen/head edge)
        if ((r >= max_x) && (r <= _gm.x) && ! ((y >= f_b) || (b <= _gm.y))) {
            max_x = r;
        }
        if ((x <= max_r) && (x >= f_r) && ! ((y >= f_b) || (b <= _gm.y))) {
            max_r = x;
        }
        if ((b >= max_y) && (b <= _gm.y) && ! ((x >= f_r) || (r <= _gm.x))) {
            max_y = b;
        }
        if ((y <= max_b) && (y >= f_b) && ! ((x >= f_r) || (r <= _gm.x))) {
            max_b = y;
        }
    }
}

//! @brief
void
Frame::growDirection(uint direction)
{
    Geometry head;
    _scr->getHeadInfoWithEdge(getNearestHead(), head);

    switch (direction) {
    case DIRECTION_UP:
        _gm.height = getBY() - head.y;
        _gm.y = head.y;
        break;
    case DIRECTION_DOWN:
        _gm.height = head.y + head.height - _gm.y;
        break;
    case DIRECTION_LEFT:
        _gm.width = getRX() - head.x;
        _gm.x = head.x;
        break;
    case DIRECTION_RIGHT:
        _gm.width = head.x + head.width - _gm.x;
        break;
    default:
        break;
    }

    downSize(_gm, (direction != DIRECTION_LEFT), (direction != DIRECTION_UP));

    moveResize(_gm.x, _gm.y, _gm.width, _gm.height);
}

//! @brief Closes the frame and all clients in it
void
Frame::close(void)
{
    list<PWinObj*>::iterator it(_child_list.begin());
    for (; it != _child_list.end(); ++it) {
        static_cast<Client*>(*it)->close();
    }
}

//! @brief Reads autoprops for the active client.
//! @param type Defaults to APPLY_ON_RELOAD
void
Frame::readAutoprops(uint type)
{
    if ((type != APPLY_ON_RELOAD) && (type != APPLY_ON_WORKSPACE))
        return;

    _class_hint->title = _client->getTitle()->getReal();
    AutoProperty *data =
        AutoProperties::instance()->findAutoProperty(_class_hint, _workspace, type);
    _class_hint->title = L"";

    if (! data) {
        return;
    }

    // Set the correct group of the window
    _class_hint->group = data->group_name;

    if (_class_hint == _client->getClassHint()
        && (_client->isTransient()
            && ! data->isApplyOn(APPLY_ON_TRANSIENT))) {
        return;
    }

    if (data->isMask(AP_STICKY) && _sticky != data->sticky) {
        stick();
    }
    if (data->isMask(AP_SHADED) && (isShaded() != data->shaded)) {
        setShaded(STATE_UNSET);
    }
    if (data->isMask(AP_MAXIMIZED_HORIZONTAL)
        && (_maximized_horz != data->maximized_horizontal)) {
        setStateMaximized(STATE_TOGGLE, true, false, false);
    }
    if (data->isMask(AP_MAXIMIZED_VERTICAL)
        && (_maximized_vert != data->maximized_vertical)) {
        setStateMaximized(STATE_TOGGLE, false, true, false);
    }
    if (data->isMask(AP_FULLSCREEN) && (_fullscreen != data->fullscreen)) {
        setStateFullscreen(STATE_TOGGLE);
    }

    if (data->isMask(AP_ICONIFIED) && (_iconified != data->iconified)) {
        if (_iconified) {
            mapWindow();
        } else {
            iconify();
        }
    }
    if (data->isMask(AP_WORKSPACE)) {
        // In order to avoid eternal recursion, the workspace is only set here
        // and then actually called PDecor::setWorkspace from Frame::setWorkspace
        if (type == APPLY_ON_WORKSPACE) {
            _workspace = data->workspace;
        }	else if (_workspace != data->workspace) {
            // Call PDecor directly to avoid recursion.
            PDecor::setWorkspace(data->workspace);
        }
    }

    if (data->isMask(AP_SHADED) && (isShaded() != data->shaded)) {
        setShaded(STATE_TOGGLE);
    }
    if (data->isMask(AP_LAYER) && (data->layer <= LAYER_MENU)) {
        _client->setLayer(data->layer);
        raise(); // restack the frame
    }

    if (data->isMask(AP_FRAME_GEOMETRY|AP_CLIENT_GEOMETRY)) {
        setupAPGeometry(_client, data);

        // Apply changes
        moveResize(_gm.x, _gm.y, _gm.width, _gm.height);
    }

    if (data->isMask(AP_BORDER) && (hasBorder() != data->border)) {
        setStateDecorBorder(STATE_TOGGLE);
    }
    if (data->isMask(AP_TITLEBAR) && (hasTitlebar() != data->titlebar)) {
        setStateDecorTitlebar(STATE_TOGGLE);
    }

    if (data->isMask(AP_SKIP)) {
        _client->setSkip(data->skip);
        setSkip(_client->getSkip());
    }

    if (data->isMask(AP_FOCUSABLE)) {
        _client->setFocusable(data->focusable);
    }
}

//! @brief Figure out how large the frame is in cells.
void
Frame::calcSizeInCells(uint &width, uint &height)
{
    const XSizeHints *hints = _client->getXSizeHints();

    if (hints->flags&PResizeInc) {
        width = (getChildWidth() - hints->base_width) / hints->width_inc;
        height = (getChildHeight() - hints->base_height) / hints->height_inc;
    } else {
        width = _gm.width;
        height = _gm.height;
    }
}

//! @brief Calculates position based on current gravity
void
Frame::calcGravityPosition(int gravity, int x, int y, int &g_x, int &g_y)
{
    switch (gravity) {
    case NorthEastGravity: // outside border corner
        g_x = x - _gm.x;
        g_y = y;
        break;
    case SouthWestGravity: // outside border corner
        g_x = x;
        g_y = y - _gm.y;
        break;
    case SouthEastGravity: // outside border corner
        g_x = x - _gm.x;
        g_y = y - _gm.y;
        break;

    case NorthGravity: // outside border center
        g_x = x - (_gm.x / 2);
        g_y = y;
        break;
    case SouthGravity: // outside border center
        g_x = x - (_gm.x / 2);
        g_y = y - _gm.y;
        break;
    case WestGravity: // outside border center
        g_x = x;
        g_y = y - (_gm.y / 2);
        break;
    case EastGravity: // outside border center
        g_x = x - _gm.x;
        g_y = y - (_gm.y / 2);
        break;

    case CenterGravity: // center of window
        g_x = x - (_gm.x / 2);
        g_y = y - (_gm.y / 2);
        break;
    case StaticGravity: // client top left
        g_x = x - (_gm.width - getChildWidth());
        g_y = y - (_gm.height - getChildHeight());
        break;
    case NorthWestGravity: // outside border corner
    default:
        g_x = x;
        g_y = y;
        break;
    }
}

/**
 * Makes gm conform to _clients width and height inc.
 */
void
Frame::downSize(Geometry &gm, bool keep_x, bool keep_y)
{
    XSizeHints *size_hint = _client->getXSizeHints(); // convenience

    // conform to width_inc
    if (size_hint->flags&PResizeInc) {
        int o_r = getRX();
        int b_x = (size_hint->flags&PBaseSize)
                  ? size_hint->base_width
                  : (size_hint->flags&PMinSize) ? size_hint->min_width : 0;

        gm.width -= (getChildWidth() - b_x) % size_hint->width_inc;
        if (! keep_x) {
            gm.x = o_r - gm.width;
        }
    }

    // conform to height_inc
    if (size_hint->flags&PResizeInc) {
        int o_b = getBY();
        int b_y = (size_hint->flags&PBaseSize)
                  ? size_hint->base_height
                  : (size_hint->flags&PMinSize) ? size_hint->min_height : 0;

        gm.height -= (getChildHeight() - b_y) % size_hint->height_inc;
        if (! keep_y) {
            gm.y = o_b - gm.height;
        }
    }
}

// Below this Client message handling is done

//! @brief Handle XConfgiureRequestEvents
//! @todo Should we send a ConfigureRequest back to the Client if ignoring?
void
Frame::handleConfigureRequest(XConfigureRequestEvent *ev, Client *client)
{
    if (client != _client) {
        return; // only handle the active client's events
    }

    // Handled geometry, this is handled seperatley due to fullscreen
    // detection
    handleConfigureRequestGeometry(ev, client);

    // update the stacking
    if (! client->isCfgDeny(CFG_DENY_STACKING)) {
        if (ev->value_mask&CWStackMode) {
            if (ev->value_mask&CWSibling) {
                switch(ev->detail) {
                case Above:
                    Workspaces::instance()->stackAbove(this, ev->above);
                    break;
                case Below:
                    Workspaces::instance()->stackBelow(this, ev->above);
                    break;
                case TopIf:
                case BottomIf:
                    // FIXME: What does occlude mean?
                    break;
                }
            } else {
                switch(ev->detail) { // FIXME: Is this broken?
                case Above:
                    raise();
                    break;
                case Below:
                    lower();
                    break;
                case TopIf:
                case BottomIf:
                    // FIXME: Why does the manual say that it should care about siblings
                    // even if we don't have any specified?
                    break;
                }
            }
        }
    }
}

/**
 * Handle size and position part of configure request, detects
 * fullscreen mode if detection is enabled.
 */
void
Frame::handleConfigureRequestGeometry(XConfigureRequestEvent *ev, Client *client)
{
    if (isRequestGeometryFullscreen(ev, client)) {
        client->configureRequestSend();
        return;
    }

    bool change_geometry = false;
    if (! client->isCfgDeny(CFG_DENY_SIZE)
        && (ev->value_mask & (CWWidth|CWHeight))) {
        resizeChild(ev->width, ev->height);
        _client->setShaped(setShape());
        change_geometry = true;
    }

    if (! client->isCfgDeny(CFG_DENY_POSITION)
        && (ev->value_mask & (CWX|CWY)) ) {
        calcGravityPosition(_client->getXSizeHints()->win_gravity,
                            ev->x, ev->y, _gm.x, _gm.y);
        move(_gm.x, _gm.y);
        change_geometry = true;
    }

    // Remove fullscreen state if client changes it size
    if (change_geometry && Config::instance()->isFullscreenDetect()) {
        setStateFullscreen(STATE_UNSET);
    }
}

/**
 * Check if requested size if "fullscreen"
 */
bool
Frame::isRequestGeometryFullscreen(XConfigureRequestEvent *ev, Client *client)
{
    bool is_fullscreen = false;
    if (! client->isCfgDeny(CFG_DENY_SIZE)
        && ! client->isCfgDeny(CFG_DENY_POSITION)) {
        int nearest_head = _scr->getNearestHead(ev->x, ev->y);
        Geometry gm_request(ev->x, ev->y, ev->width, ev->height);
        Geometry gm_screen(_scr->getScreenGeometry());
        Geometry gm_head(_scr->getHeadGeometry(nearest_head));

        if (gm_request == gm_screen || gm_request == gm_head) {
            is_fullscreen = true;
        } else {
            downSize(gm_screen, true, true);
            downSize(gm_head, true, true);
            if (gm_request == gm_screen || gm_request == gm_head) {
                is_fullscreen = true;
            }
        }

        if (is_fullscreen && Config::instance()->isFullscreenDetect()) {
            setStateFullscreen(STATE_SET);
        }
    }
    return is_fullscreen;
}

/**
 * Handle client message.
 */
void
Frame::handleClientMessage(XClientMessageEvent *ev, Client *client)
{
    if (ev->message_type == Atoms::getAtom(STATE)) {
        handleClientStateMessage(ev, client);
    } else if (ev->message_type == Atoms::getAtom(NET_ACTIVE_WINDOW)) {
        if (! client->isCfgDeny(CFG_DENY_ACTIVE_WINDOW)) {
            // Active child if it's not the active child
            if (client != _client) {
                activateChild(client);
	        }

            // If we aren't mapped we check if we make sure we're on the right
            // workspace and then map the window.
            if (! _mapped) {
                if (_workspace != Workspaces::instance()->getActive() &&
                        !isSticky()) {
                    Workspaces::instance()->setWorkspace(_workspace, false);
                }
                mapWindow();
            }
            // Seems as if raising the window is implied in activating it
            raise();
            giveInputFocus();
        }
    } else if (ev->message_type == Atoms::getAtom(NET_CLOSE_WINDOW)) {
        client->close();
    } else if (ev->message_type == Atoms::getAtom(NET_WM_DESKTOP)) {
        if (client == _client) {
            setWorkspace(ev->data.l[0]);
        }
    } else if (ev->message_type == Atoms::getAtom(WM_CHANGE_STATE) &&
               (ev->format == 32) && (ev->data.l[0] == IconicState)) {
        if (client == _client) {
            iconify();
        }
    }
}

/**
 * Handle _NET_WM_STATE atom.
 */
void
Frame::handleClientStateMessage(XClientMessageEvent *ev, Client *client)
{
    StateAction sa = getStateActionFromMessage(ev);
    handleStateAtom(sa, ev->data.l[1], client);
    if (ev->data.l[2] != 0) {
        handleStateAtom(sa, ev->data.l[2], client);
    }
    client->updateEwmhStates();
}

/**
 * Get StateAction from NET_WM atom.
 */
StateAction
Frame::getStateActionFromMessage(XClientMessageEvent *ev)
{
    StateAction sa = STATE_SET;
    if (ev->data.l[0]== NET_WM_STATE_REMOVE) {
        sa = STATE_UNSET;
    } else if (ev->data.l[0]== NET_WM_STATE_ADD) {
        sa = STATE_SET;
    } else if (ev->data.l[0]== NET_WM_STATE_TOGGLE) {
        sa = STATE_TOGGLE;
    }
    return sa;
}

/**
 * Handle state atom for client.
 */
void
Frame::handleStateAtom(StateAction sa, Atom atom, Client *client)
{
    if (client == _client) {
        handleCurrentClientStateAtom(sa, atom, client);
    }

    switch (atom) {
    case STATE_SKIP_TASKBAR:
        client->setStateSkip(sa, SKIP_TASKBAR);
        break;
    case STATE_SKIP_PAGER:
        client->setStateSkip(sa, SKIP_PAGER);
        break;
    case STATE_DEMANDS_ATTENTION:
        client->setStateDemandsAttention(sa, true);
        break;
    }
}

/**
 * Handle state atom for actions that apply only on active client.
 */
void
Frame::handleCurrentClientStateAtom(StateAction sa, Atom atom, Client *client)
{
    if (atom == Atoms::getAtom(STATE_STICKY)) {
        setStateSticky(sa);
    }
    if (atom == Atoms::getAtom(STATE_MAXIMIZED_HORZ)
            && ! client->isCfgDeny(CFG_DENY_STATE_MAXIMIZED_HORZ)) {
        setStateMaximized(sa, true, false, false);
    }
    if (atom == Atoms::getAtom(STATE_MAXIMIZED_VERT)
            && ! client->isCfgDeny(CFG_DENY_STATE_MAXIMIZED_VERT)) {
        setStateMaximized(sa, false, true, false);
    }
    if (atom == Atoms::getAtom(STATE_SHADED)) {
        setShaded(sa);
    }
    if (atom == Atoms::getAtom(STATE_HIDDEN)
            && ! client->isCfgDeny(CFG_DENY_STATE_HIDDEN)) {
        setStateIconified(sa);
    }
    if (atom == Atoms::getAtom(STATE_FULLSCREEN)
            && ! client->isCfgDeny(CFG_DENY_STATE_FULLSCREEN)) {
        setStateFullscreen(sa);
    }
    if (atom == Atoms::getAtom(STATE_ABOVE)
            && ! client->isCfgDeny(CFG_DENY_STATE_ABOVE)) {
        setStateAlwaysOnTop(sa);
    }
    if (atom == Atoms::getAtom(STATE_BELOW)
            && ! client->isCfgDeny(CFG_DENY_STATE_BELOW)) {
        setStateAlwaysBelow(sa);
    }
}

//! @brief
void
Frame::handlePropertyChange(XPropertyEvent *ev, Client *client)
{
    if (ev->atom == Atoms::getAtom(NET_WM_DESKTOP)) {
        if (client == _client) {
            long workspace;

            if (AtomUtil::getLong(client->getWindow(),
                                  Atoms::getAtom(NET_WM_DESKTOP), workspace)) {
                if (workspace != signed(_workspace))
                    setWorkspace(workspace);
            }
        }
    } else if (ev->atom == Atoms::getAtom(NET_WM_STRUT)) {
        client->getStrutHint();
    } else if (ev->atom == Atoms::getAtom(NET_WM_NAME) || ev->atom == XA_WM_NAME) {
        handleTitleChange(client);
    } else if (ev->atom == XA_WM_NORMAL_HINTS) {
        client->getWMNormalHints();
    } else if (ev->atom == XA_WM_TRANSIENT_FOR) {
        client->getTransientForHint();
    }
}

/**
 * Handle title change, find decoration rules based on changed title
 * and update if changed.
 */
void
Frame::handleTitleChange(Client *client)
{
    // Update title
    client->readName();

    bool require_render = true;
    if (client == _client) {
        string new_decor_name(getClientDecorName(client));
        if (new_decor_name != _decor_name) {
            require_render = ! setDecor(new_decor_name);
        }
    }

    // Render title if decoration was not updated
    if (require_render) {
        renderTitle();
    }
}
