//
// Workspaces.cc for pekwm
// Copyright © 2002-2009 Claes Nasten <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 "Workspaces.hh"
#include "PScreen.hh"
#include "Atoms.hh"
#include "ParseUtil.hh"
#include "Config.hh"
#include "PWinObj.hh"
#include "PDecor.hh"
#include "Frame.hh"
#include "Client.hh" // For isSkip()
#include "WindowManager.hh"
#include "WorkspaceIndicator.hh"

#include <iostream>
#include <sstream>
#ifdef HAVE_LIMITS
#include <limits>
using std::numeric_limits;
#endif // HAVE_LIMITS

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

using std::list;
using std::vector;
using std::string;
using std::find;
using std::wostringstream;
using std::wstring;
using std::cerr;
using std::endl;

// Workspaces::Workspace

Workspaces::Workspace::Workspace(const std::wstring &name, uint number)
  : _name(name), _number(number), _last_focused(0)
{
}

Workspaces::Workspace::~Workspace(void)
{
}

// Workspaces

Workspaces *Workspaces::_instance = 0;


//! @brief Workspaces constructor
Workspaces::Workspaces(uint number, uint per_row)
  : _active(0), _previous(0), _per_row(per_row)
{
#ifdef DEBUG
    if (_instance) {
        cerr << __FILE__ << "@" << __LINE__ << ": "
             << "Workspaces(" << this << ")::Workspaces(" << number << ")"
             << endl << " *** _instance already set: " << _instance << endl;
    }
#endif // DEBUG
    _instance = this;

    if (number < 1) {
#ifdef DEBUG
        cerr << __FILE__ << "@" << __LINE__ << ": "
             << "Workspaces(" << this << ")::Workspaces(" << number << ")"
             << " *** number < 1" << endl;
#endif // DEBUG
        number = 1;
    }

    // create new workspaces
    for (uint i = 0; i < number; ++i) {
        _workspace_list.push_back(new Workspace(getWorkspaceName(i), i));
    }
}

//! @brief Workspaces destructor
Workspaces::~Workspaces(void)
{
    vector<Workspace*>::iterator it(_workspace_list.begin());
    for (; it != _workspace_list.end(); ++it) {
        delete *it;
    }

    _instance = 0;
}

//! @brief Sets total amount of workspaces to number
void
Workspaces::setSize(uint number)
{
    if (number < 1) {
        number = 1;
    }

    if (number == _workspace_list.size()) {
        return; // no need to change number of workspaces to the current number
    }

    uint before = _workspace_list.size();
    if (_active >= number) {
        _active = number - 1;
    }
    if (_previous >= number) {
        _previous = number - 1;
    }

    // We have more workspaces than we want, lets remove the last ones
    if (before > number) {
        list<PWinObj*>::iterator it(_wo_list.begin());
        for (; it != _wo_list.end(); ++it) {
            if ((*it)->getWorkspace() > (number - 1))
                (*it)->setWorkspace(number - 1);
        }

        for (uint i = before - 1; i >= number; --i) {
            delete _workspace_list[i];
        }

        _workspace_list.resize(number, 0);

    } else { // We need more workspaces, lets create some
        for (uint i = before; i < number; ++i) {
            _workspace_list.push_back(new Workspace(getWorkspaceName(i), i));
        }
    }

    // Tell the rest of the world how many workspaces we have.
    XChangeProperty(PScreen::instance()->getDpy(),
                    PScreen::instance()->getRoot(),
                    Atoms::getAtom(NET_NUMBER_OF_DESKTOPS),
                    XA_CARDINAL, 32, PropModeReplace,
                    (uchar *) &number, 1);

    // make sure we aren't on an non-existent workspace
    if (number <= _active) {
        setWorkspace(number - 1, true);
    }
}

/**
 * Set workspace names.
 */
void
Workspaces::setNames(void)
{
    vector<Workspace*>::iterator it(_workspace_list.begin());
    for (; it != _workspace_list.end(); ++it) {
        (*it)->setName(Config::instance()->getWorkspaceName((*it)->getNumber()));
    }
}

//! @brief Activates Workspace workspace and sets the right hints
//! @param num Workspace to activate
//! @param focus whether or not to focus a window after switch
void
Workspaces::setWorkspace(uint num, bool focus)
{
    if ((num == _active) || ( num >= _workspace_list.size())) {
        return;
    }

    PScreen::instance()->grabServer();

    PWinObj *wo = PWinObj::getFocusedPWinObj();
    // Make sure that sticky windows gets unfocused on workspace change,
    // it will be set back after switch is done.
    if (wo) {
        if (wo->getType() == PWinObj::WO_CLIENT) {
            wo->getParent()->setFocused(false);
        } else {
            wo->setFocused(false);
        }
    }

    // Save the focused window object
    setLastFocused(_active, wo);
    PWinObj::setFocusedPWinObj(0);

    // switch workspace
    hideAll(_active);
    AtomUtil::setLong(PScreen::instance()->getRoot(),
                      Atoms::getAtom(NET_CURRENT_DESKTOP),
                      num);
    unhideAll(num, focus);

    PScreen::instance()->ungrabServer(true);

    // Show workspace indicator if requested
    if (Config::instance()->getShowWorkspaceIndicator() > 0) {
        WindowManager::instance()->getWorkspaceIndicator()->render();
        WindowManager::instance()->getWorkspaceIndicator()->mapWindowRaised();
        WindowManager::instance()->getWorkspaceIndicator()->updateHideTimer(Config::instance()->getShowWorkspaceIndicator());
    }
}

//! @brief
bool
Workspaces::gotoWorkspace(uint direction, bool warp)
{
    uint workspace;
    int dir = 0;
    // Using a bool flag to detect changes due to special workspaces such
    // as PREV
    bool switched = true;
    uint per_row = Config::instance()->getWorkspacesPerRow();

    uint cur_row = getRow(), row_min = getRowMin(), row_max = getRowMax();
    switch (direction) {
    case WORKSPACE_LEFT:
    case WORKSPACE_PREV:
        dir = 1;

        if (_active > row_min) {
          workspace = _active - 1;
        } else if (direction == WORKSPACE_PREV) {
          workspace = row_max;
        } else {
          switched = false;
        }
        break;
    case WORKSPACE_NEXT:
    case WORKSPACE_RIGHT:
        dir = 2;

        if (_active < row_max) {
            workspace = _active + 1;
        } else if (direction == WORKSPACE_NEXT) {
            workspace = row_min;
        } else {
            switched = false;
        }
        break;
    case WORKSPACE_PREV_V:
    case WORKSPACE_UP:
      dir = -1;

      if (_active >= per_row) {
        workspace = _active - per_row;
      } else if (direction == WORKSPACE_PREV_V) {
        // Bottom left
        workspace = _workspace_list.size() - per_row;
        // Add column
        workspace += _active - cur_row * per_row;
      } else {
        switched = false;
      }
      break;
    case WORKSPACE_NEXT_V:
    case WORKSPACE_DOWN:
      dir = -2;

      if ((_active + per_row) < _workspace_list.size()) {
        workspace = _active + per_row;
      } else if (direction == WORKSPACE_NEXT_V) {
        workspace = _active - cur_row * per_row;
      } else {
        switched = false;
      }
      break;
    case WORKSPACE_LAST:
        workspace = _previous;
        if (_active == workspace) {
            switched = false;
        }
        break;
    default:
        if (direction == _active) {
            switched = false;
        } else {
            workspace = direction;
        }
    }

    if (switched) {
        if (warp) {
            warpToWorkspace(workspace, dir);
        } else {
            setWorkspace(workspace, true);
        }
    }

    return switched;
}

//! @brief
bool
Workspaces::warpToWorkspace(uint num, int dir)
{
    if ((num == _active) || ( num >= _workspace_list.size())) {
        return false;
    }

    int x, y;
    PScreen::instance()->getMousePosition(x, y);

    if (dir != 0) {
      switch(dir) {
      case 1:
        x = PScreen::instance()->getWidth() - std::max(Config::instance()->getScreenEdgeSize(SCREEN_EDGE_LEFT) + 2, 2);
        break;
      case 2:
        x = std::max(Config::instance()->getScreenEdgeSize(SCREEN_EDGE_RIGHT) * 2, 2);
        break;
      case -1:
        y = PScreen::instance()->getHeight() - std::max(Config::instance()->getScreenEdgeSize(SCREEN_EDGE_BOTTOM) + 2, 2);
        break;
      case -2:
        y = std::max(Config::instance()->getScreenEdgeSize(SCREEN_EDGE_TOP) + 2, 2);
        break;
      }

        // warp pointer
        XWarpPointer(PScreen::instance()->getDpy(), None,
                     PScreen::instance()->getRoot(), 0, 0, 0, 0, x, y);
    }

    // set workpsace
    setWorkspace(num, true);

    return true;
}

/**
 * Adds a PWinObj to the stacking list.
 * @param wo PWinObj to insert
 * @param raise Whether to insert at the bottom or top of the layer (defaults to true).
 */
void
Workspaces::insert(PWinObj *wo, bool raise)
{
    list<PWinObj*>::iterator it(_wo_list.begin()), position(_wo_list.end());
    for (; it != _wo_list.end() && position == _wo_list.end(); ++it) {
        if (raise) {
            // If raising, make sure the inserted wo gets below the first
            // window in the next layer.
            if ((*it)->getLayer() > wo->getLayer()) {
                position = it;
            }
        } else {
            // If lowering, put the window below the first window with the same level.
            if (wo->getLayer() <= (*it)->getLayer()) {
                position = it;
            }
        }
    }

    _wo_list.insert(position, wo);

    if (position == _wo_list.end()) {
         XRaiseWindow(PScreen::instance()->getDpy(), wo->getWindow());
    } else {
         stackWinUnderWin((*position)->getWindow(), wo->getWindow());
    }
}

//! @brief Removes a PWinObj from the stacking list.
void
Workspaces::remove(PWinObj* wo)
{
    _wo_list.remove(wo);

    // remove from last focused
    vector<Workspace*>::iterator it(_workspace_list.begin());
    for (; it != _workspace_list.end(); ++it) {
        if (wo == (*it)->getLastFocused()) {
            (*it)->setLastFocused(0);
        }
    }
}

//! @brief Hides all non-sticky Frames on the workspace.
void
Workspaces::hideAll(uint workspace)
{
    list<PWinObj*>::iterator it(_wo_list.begin());
    for (; it != _wo_list.end(); ++it) {
        if (! ((*it)->isSticky()) && ! ((*it)->isHidden()) &&
                ((*it)->getWorkspace() == workspace)) {
            (*it)->unmapWindow();
        }
    }
}

//! @brief Unhides all hidden PWinObjs on the workspace.
void
Workspaces::unhideAll(uint workspace, bool focus)
{
    if (workspace >= _workspace_list.size())
        return;
    _previous = _active;
    _active = workspace;


    list<PWinObj*>::iterator it(_wo_list.begin());
    for (; it != _wo_list.end(); ++it) {
        if (! (*it)->isMapped() && ! (*it)->isIconified() && ! (*it)->isHidden()
                && ((*it)->getWorkspace() == workspace)) {
            (*it)->mapWindow(); // don't restack ontop windows
        }
    }

    // Try to focus last focused window and if that fails we get the top-most
    // Frame if any and give it focus.
    if (focus) {
        PWinObj *wo = _workspace_list[workspace]->getLastFocused();
        if (! wo || ! PWinObj::windowObjectExists(wo)) {
            wo = getTopWO(PWinObj::WO_FRAME);
        }

        if (wo && wo->isMapped() && wo->isFocusable()) {
            // Render as focused
            if (wo->getType() == PWinObj::WO_CLIENT) {
                wo->getParent()->setFocused(true);
            } else {
                wo->setFocused(true);
            }

            // Get the active child if a frame, to get correct focus behavior
            if (wo->getType() == PWinObj::WO_FRAME) {
                wo = static_cast<Frame*>(wo)->getActiveChild();
            }

            // Focus
            wo->giveInputFocus();
            PWinObj::setFocusedPWinObj(wo);
        }

        // If focusing fails, focus the root window.
        if (! PWinObj::getFocusedPWinObj()
                || ! PWinObj::getFocusedPWinObj()->isMapped()) {
            PWinObj::getRootPWinObj()->giveInputFocus();
        }
    }
}

//! @brief Raises a PWinObj and restacks windows.
void
Workspaces::raise(PWinObj* wo)
{
    list<PWinObj*>::iterator it(find(_wo_list.begin(), _wo_list.end(), wo));

    if (it == _wo_list.end()) { // no Frame to raise.
        return;
    }
    _wo_list.erase(it);

    insert(wo, true); // reposition and restack
}

//! @brief Lower a PWinObj and restacks windows.
void
Workspaces::lower(PWinObj* wo)
{
    list<PWinObj*>::iterator it(find(_wo_list.begin(), _wo_list.end(), wo));

    if (it == _wo_list.end()) // no Frame to raise.
        return;
    _wo_list.erase(it);

    insert(wo, false); // reposition and restack
}

//! @brief Places the PWinObj above the window win
//! @param wo PWinObj to place.
//! @param win Window to place Frame above.
//! @param restack Restack the X windows, defaults to true.
void
Workspaces::stackAbove(PWinObj *wo, Window win, bool restack)
{
    list<PWinObj*>::iterator old_pos(find(_wo_list.begin(), _wo_list.end(), wo));

    if (old_pos != _wo_list.end()) {
        list<PWinObj*>::iterator it(_wo_list.begin());
        for (; it != _wo_list.end(); ++it) {
            if (win == (*it)->getWindow()) {
                _wo_list.erase(old_pos);
                _wo_list.insert(++it, wo);

                // Before restacking make sure we are the active frame
                // also that there are two different frames
                if (restack) {
                    stackWinUnderWin(win, wo->getWindow());
                }
                break;
            }
        }
    }
}

//! @brief Places the PWinObj below the window win
//! @param wo PWinObj to place.
//! @param win Window to place Frame under
//! @param restack Restack the X windows, defaults to true
void
Workspaces::stackBelow(PWinObj* wo, Window win, bool restack)
{
    list<PWinObj*>::iterator old_pos(find(_wo_list.begin(), _wo_list.end(), wo));

    if (old_pos != _wo_list.end()) {
        list<PWinObj*>::iterator it(_wo_list.begin());
        for (; it != _wo_list.end(); ++it) {
            if (win == (*it)->getWindow()) {
                _wo_list.erase(old_pos);
                _wo_list.insert(it, wo);

                if (restack) {
                    stackWinUnderWin(wo->getWindow(), win);
                }
                break;
            }
        }
    }
}


//! @brief
PWinObj*
Workspaces::getLastFocused(uint workspace)
{
    if (workspace >= _workspace_list.size()) {
        return 0;
    }
    return _workspace_list[workspace]->getLastFocused();
}

//! @brief
void
Workspaces::setLastFocused(uint workspace, PWinObj* wo)
{
    if (workspace >= _workspace_list.size()) {
        return;
    }
    
    _workspace_list[workspace]->setLastFocused(wo);
}

//! @brief Helper function to stack a window below another
//! @param win_over Window to place win_under under
//! @param win_under Window to place under win_over
void
Workspaces::stackWinUnderWin(Window over, Window under)
{
    if (over == under) {
        return;
    }

    Window windows[2] = { over, under };
    XRestackWindows(PScreen::instance()->getDpy(), windows, 2);
}

//! @brief Create name for workspace num
wstring
Workspaces::getWorkspaceName(uint num)
{
  wostringstream buf;
  buf << num + 1;
  buf << L": ";
  buf << Config::instance()->getWorkspaceName(num);

  return buf.str();
}

// MISC METHODS

//! @brief Returns the first focusable PWinObj with the highest stacking
PWinObj*
Workspaces::getTopWO(uint type_mask)
{
    list<PWinObj*>::reverse_iterator r_it = _wo_list.rbegin();
    for (; r_it != _wo_list.rend(); ++r_it) {
        if ((*r_it)->isMapped()
                && (*r_it)->isFocusable()
                && ((*r_it)->getType()&type_mask)) {
            return (*r_it);
        }
    }
    return 0;
}

/**
 * Builds a list of all clients in stacking order, clients in the same
 * frame come after each other.
 */
Window*
Workspaces::buildClientList(unsigned int &num_windows)
{
    Frame *frame;
    Client *client, *client_active;

    list<Window> windows_list;
    list<PWinObj*>::iterator it_f, it_c;
    for (it_f = _wo_list.begin(); it_f != _wo_list.end(); ++it_f) {
        if ((*it_f)->getType() != PWinObj::WO_FRAME) {
	  continue;
	}

	frame = static_cast<Frame*>(*it_f);
	client_active = frame->getActiveClient();

	if (Config::instance()->isReportAllClients()) {
            for (it_c = frame->begin(); it_c != frame->end(); ++it_c) {
                client = dynamic_cast<Client*>(*it_c);
                if (client
		    && ! client->isSkip(SKIP_TASKBAR)
		    && client != client_active) {
                    windows_list.push_back(client->getWindow());
	        }
	    }
	}

	if (client_active && ! client_active->isSkip(SKIP_TASKBAR)) {
	    windows_list.push_back(client_active->getWindow());
	}
    }

    num_windows = windows_list.size();
    Window *windows = new Window[num_windows ? num_windows : 1];
    if (num_windows > 0) {
      copy(windows_list.begin(), windows_list.end(), windows);
    }

    return windows;
}

/**
 * Updates the Ewmh Client list hint.
 */
void
Workspaces::updateClientList(void)
{
    unsigned int num_windows;
    Window *windows = buildClientList(num_windows);

    AtomUtil::setWindows(PScreen::instance()->getRoot(),
			 Atoms::getAtom(NET_CLIENT_LIST),
			 windows, num_windows);
    
    delete [] windows;
}

/**
 * Updates the Ewmh Stacking list hint.
 */
void
Workspaces::updateClientStackingList(void)
{
    unsigned int num_windows;
    Window *windows = buildClientList(num_windows);

    AtomUtil::setWindows(PScreen::instance()->getRoot(),
			 Atoms::getAtom(NET_CLIENT_LIST_STACKING),
			 windows, num_windows);

    delete [] windows;
}

// PLACEMENT ROUTINES

//! @brief Tries to place the Wo.
void
Workspaces::placeWo(PWinObj *wo, Window parent)
{
    bool placed = false;

    list<uint>::iterator it(Config::instance()->getPlacementModelBegin());
    for (; (placed != true) &&
            (it != Config::instance()->getPlacementModelEnd()); ++it) {
        switch (*it) {
        case PLACE_SMART:
            placed = placeSmart(wo);
            break;
        case PLACE_MOUSE_NOT_UNDER:
            placed = placeMouseNotUnder(wo);
            break;
        case PLACE_MOUSE_CENTERED:
            placed = placeMouseCentered(wo);
            break;
        case PLACE_MOUSE_TOP_LEFT:
            placed = placeMouseTopLeft(wo);
            break;
        case PLACE_CENTERED_ON_PARENT:
            placed = placeCenteredOnParent(wo, parent);
            break;
        default:
            // do nothing
            break;
        }
    }
}

/**
 * Make sure window is inside screen boundaries.
 */
void
Workspaces::placeWoInsideScreen(PWinObj *wo)
{
    Geometry gm_before(wo->getX(), wo->getY(), wo->getWidth(), wo->getHeight());
    Geometry gm_after(gm_before);

    Strut *strut = 0;
    if (wo->getType() == PWinObj::WO_FRAME) {
        Client *client = static_cast<Frame*>(wo)->getActiveClient();
        if (client) {
            strut = client->getStrut();
        }
    }

    placeInsideScreen(gm_after, strut);
    if (gm_before != gm_after) {
        wo->move(gm_after.x, gm_after.y);
    }
}

//! @brief Tries to find empty space to place the client in
//! @return true if client got placed, else false
//! @todo What should we do about Xinerama as when we don't have it enabled we care about the struts.
bool
Workspaces::placeSmart(PWinObj* wo)
{
    PWinObj *wo_e;
    bool placed = false;

    int step_x = (Config::instance()->getPlacementLtR()) ? 1 : -1;
    int step_y = (Config::instance()->getPlacementTtB()) ? 1 : -1;
    int offset_x = (Config::instance()->getPlacementLtR())
                   ? Config::instance()->getPlacementOffsetX()
                   : -Config::instance()->getPlacementOffsetX();
    int offset_y = (Config::instance()->getPlacementTtB())
                   ? Config::instance()->getPlacementOffsetY()
                   : -Config::instance()->getPlacementOffsetY();
    int start_x, start_y, test_x = 0, test_y = 0;

    // Wrap these up, to get proper checking of space.
    uint wo_width = wo->getWidth() + Config::instance()->getPlacementOffsetX();
    uint wo_height = wo->getHeight() + Config::instance()->getPlacementOffsetY();

    Geometry head;
    PScreen::instance()->getHeadInfoWithEdge(PScreen::instance()->getCurrHead(),
            head);

    start_x = (Config::instance()->getPlacementLtR())
              ? (head.x)
              : (head.x + head.width - wo_width);
    start_y = (Config::instance()->getPlacementTtB())
              ? (head.y)
              : (head.y + head.height - wo_height);

    if (Config::instance()->getPlacementRow()) { // row placement
        test_y = start_y;
        while (! placed && (Config::instance()->getPlacementTtB()
                           ? ((test_y + wo_height) <= (head.y + head.height))
                           : (test_y >= head.y))) {
            test_x = start_x;
            while (! placed && (Config::instance()->getPlacementLtR()
                               ? ((test_x + wo_width) <= (head.x + head.width))
                               : (test_x >= head.x))) {
                // see if we can place the window here
                if ((wo_e = isEmptySpace(test_x, test_y, wo))) {
                    placed = false;
                    test_x = Config::instance()->getPlacementLtR()
                             ? (wo_e->getX() + wo_e->getWidth()) : (wo_e->getX() - wo_width);
                } else {
                    placed = true;
                    wo->move(test_x + offset_x, test_y + offset_y);
                }
            }
            test_y += step_y;
        }
    } else { // column placement
        test_x = start_x;
        while (! placed && (Config::instance()->getPlacementLtR()
                           ? ((test_x + wo_width) <= (head.x + head.width))
                           : (test_x >= head.x))) {
            test_y = start_y;
            while (! placed && (Config::instance()->getPlacementTtB()
                               ? ((test_y + wo_height) <= (head.y + head.height))
                               : (test_y >= head.y))) {
                // see if we can place the window here
                if ((wo_e = isEmptySpace(test_x, test_y, wo))) {
                    placed = false;
                    test_y = Config::instance()->getPlacementTtB()
                             ? (wo_e->getY() + wo_e->getHeight()) : (wo_e->getY() - wo_height);
                } else {
                    placed = true;
                    wo->move(test_x + offset_x, test_y + offset_y);
                }
            }
            test_x += step_x;
        }
    }

    return placed;

}

//! @brief Places the wo in a corner of the screen not under the pointer
bool
Workspaces::placeMouseNotUnder(PWinObj *wo)
{
    Geometry head;
    PScreen::instance()->getHeadInfoWithEdge(PScreen::instance()->getCurrHead(),
            head);

    int mouse_x, mouse_y;
    PScreen::instance()->getMousePosition(mouse_x, mouse_y);

    // compensate for head offset
    mouse_x -= head.x;
    mouse_y -= head.y;

    // divide the screen into four rectangles using the pointer as divider
    if ((wo->getWidth() < unsigned(mouse_x)) && (wo->getHeight() < head.height)) {
        wo->move(head.x, head.y);
        return true;
    }

    if ((wo->getWidth() < head.width) && (wo->getHeight() < unsigned(mouse_y))) {
        wo->move(head.x + head.width - wo->getWidth(), head.y);
        return true;
    }

    if ((wo->getWidth() < (head.width - mouse_x)) && (wo->getHeight() < head.height)) {
        wo->move(head.x + head.width - wo->getWidth(), head.y + head.height - wo->getHeight());
        return true;
    }

    if ((wo->getWidth() < head.width) && (wo->getHeight() < (head.height - mouse_y))) {
        wo->move(head.x, head.y + head.height - wo->getHeight());
        return true;
    }

    return false;
}

//! @brief Places the client centered under the mouse
bool
Workspaces::placeMouseCentered(PWinObj *wo)
{
    int mouse_x, mouse_y;
    PScreen::instance()->getMousePosition(mouse_x, mouse_y);

    Geometry gm(mouse_x - (wo->getWidth() / 2), mouse_y - (wo->getHeight() / 2),
                wo->getWidth(), wo->getHeight());

    // make sure it's within the screens border
    placeInsideScreen(gm);

    wo->move(gm.x, gm.y);

    return true;
}

//! @brief Places the client like the menu gets placed
bool
Workspaces::placeMouseTopLeft(PWinObj *wo)
{
    int mouse_x, mouse_y;
    PScreen::instance()->getMousePosition(mouse_x, mouse_y);

    Geometry gm(mouse_x, mouse_y, wo->getWidth(), wo->getHeight());
    placeInsideScreen(gm); // make sure it's within the screens border

    wo->move(gm.x, gm.y);

    return true;
}

//! @brief Places centerd on the window parent
bool
Workspaces::placeCenteredOnParent(PWinObj *wo, Window parent)
{
    if (parent == None) {
        return false;
    }

    PWinObj *wo_s = PWinObj::findPWinObj(parent);
    if (wo_s) {
        wo->move(wo_s->getX() + wo_s->getWidth() / 2 - wo->getWidth() / 2,
                 wo_s->getY() + wo_s->getHeight() / 2 - wo->getHeight() / 2);
        return true;
    }

    return false;
}

//! @brief Makes sure the window is inside the screen.
void
Workspaces::placeInsideScreen(Geometry &gm, Strut *strut)
{
    // Do not include screen edges when calculating the position if the window
    // has a strut as it then is likely to be a panel or the like placed
    // along the edge of the screen.
    Geometry head;
    if (strut) {
        PScreen::instance()->getHeadInfo(PScreen::instance()->getCurrHead(), head);
    } else {
        PScreen::instance()->getHeadInfoWithEdge(PScreen::instance()->getCurrHead(), head);
    }

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

//! @brief
PWinObj*
Workspaces::isEmptySpace(int x, int y, const PWinObj* wo)
{
    if (! wo) {
        return 0;
    }

    // say that it's placed, now check if we are wrong!
    list<PWinObj*>::iterator it(_wo_list.begin());
    for (; it != _wo_list.end(); ++it) {
        // Skip ourselves, non-mapped and desktop objects. Iconified means
        // skip placement.
        if (wo == (*it) || ! (*it)->isMapped() || (*it)->isIconified()
            || ((*it)->getLayer() == LAYER_DESKTOP)) {
            continue;
        }

        // Also skip windows tagged as Maximized as they cause us to
        // automatically fail.
        if ((*it)->getType() == PWinObj::WO_FRAME) {
            Client *client = static_cast<Frame*>((*it))->getActiveClient();
            if (client &&
                (client->isFullscreen()
                 || (client->isMaximizedVert() && client->isMaximizedHorz()))) {
                continue;
            }
        }

        // Check if we are "intruding" on some other window's place
        if (((*it)->getX() < signed(x + wo->getWidth())) &&
            (signed((*it)->getX() + (*it)->getWidth()) > x) &&
            ((*it)->getY() < signed(y + wo->getHeight())) &&
            (signed((*it)->getY() + (*it)->getHeight()) > y)) {
            return (*it);
        }
    }

    return 0; // we passed the test, no frames in the way
}

//! @brief
//! @param wo PWinObj to originate from when searching
//! @param dir Direction to search
//! @param skip Bitmask for skipping window objects, defaults to 0
PWinObj*
Workspaces::findDirectional(PWinObj *wo, DirectionType dir, uint skip)
{
    // search from the frame not client, if client
    if (wo->getType() == PWinObj::WO_CLIENT) {
        wo = static_cast<Client*>(wo)->getParent();
    }

    PWinObj *found_wo = 0;

    uint score = 0, score_min;
    int wo_main, wo_sec;
    int diff_main = 0;

#ifdef HAVE_LIMITS
    score_min = numeric_limits<uint>::max();
#else // !HAVE_LIMITS
    score_min = ~0;
#endif // HAVE_LIMITS

    // init wo variables
    if ((dir == DIRECTION_UP) || (dir == DIRECTION_DOWN)) {
        wo_main = wo->getY() + wo->getHeight() / 2;
        wo_sec = wo->getX() + wo->getWidth() / 2;
    } else {
        wo_main = wo->getX() + wo->getWidth() / 2;
        wo_sec = wo->getY() + wo->getHeight() / 2;
    }

    list<PWinObj*>::iterator it(_wo_list.begin());
    for (; it != _wo_list.end(); ++it) {
        if ((wo == (*it)) || ! ((*it)->isMapped())) {
            continue; // skip ourselves and unmapped wo's
        }
        if (((*it)->getType() != PWinObj::WO_FRAME) ||
                static_cast<Frame*>(*it)->isSkip(skip)) {
            continue; // only include frames and not having skip set
        }

        // check main direction, making sure it's at the right side
        // we check against the middle of the window as it gives a saner feeling
        // than the edges IMHO
        switch (dir) {
        case DIRECTION_UP:
            diff_main = wo_main - ((*it)->getY() + (*it)->getHeight() / 2);
            break;
        case DIRECTION_DOWN:
            diff_main = ((*it)->getY() + (*it)->getHeight() / 2) - wo_main;
            break;
        case DIRECTION_LEFT:
            diff_main = wo_main - ((*it)->getX() + (*it)->getWidth() / 2);
            break;
        case DIRECTION_RIGHT:
            diff_main = ((*it)->getX() + (*it)->getWidth() / 2) - wo_main;
            break;
        default:
            return 0; // no direction to search
        }

        if (diff_main < 0) {
            continue; // wrong direction
        }

        score = diff_main;

        if ((dir == DIRECTION_UP) || (dir == DIRECTION_DOWN)) {
            if ((wo_sec < (*it)->getX()) || (wo_sec > (*it)->getRX())) {
                score += PScreen::instance()->getHeight() / 2;
            }
            score += abs (static_cast<long> (wo_sec - ((*it)->getX ()
                                             + (*it)->getWidth () / 2)));

        } else {
            if ((wo_sec < (*it)->getY()) || (wo_sec > (*it)->getBY())) {
                score += PScreen::instance()->getWidth() / 2;
            }

            score += abs (static_cast<long> (wo_sec - ((*it)->getY ()
                                             + (*it)->getHeight () / 2)));
        }

        if (score < score_min) {
            found_wo = *it;
            score_min = score;
        }
    }

    return found_wo;
}

