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

#include "../config.h"
#ifdef HARBOUR

#include "Harbour.hh"

#include "PScreen.hh"
#include "Config.hh"
#include "PWinObj.hh"
#include "DockApp.hh"
#include "Workspaces.hh"
#include "AutoProperties.hh"
#ifdef MENUS
#include "PDecor.hh"
#include "PMenu.hh"
#include "HarbourMenu.hh"
#endif // MENUS

#include <algorithm>
#include <functional>

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

//! @brief Harbour constructor
Harbour::Harbour(PScreen *s, Theme *t, Workspaces *w) :
_scr(s), _theme(t), _workspaces(w),
#ifdef MENUS
_harbour_menu(NULL),
#endif // MENUS
_size(0), _strut(NULL),
_last_button_x(0), _last_button_y(0)
{
	_strut = new Strut();
	_scr->addStrut(_strut);
#ifdef HAVE_XINERAMA
	_strut->head = Config::instance()->getHarbourHead();
#endif // HAVE_XINERAMA

#ifdef MENUS
	_harbour_menu = new HarbourMenu(_scr, _theme, this);
#endif // MENUS
}

//! @brief Harbour destructor
Harbour::~Harbour(void)
{
	removeAllDockApps();

	_scr->removeStrut(_strut);
	delete _strut;

#ifdef MENUS
	if (_harbour_menu != NULL) {
		delete _harbour_menu;
	}
#endif // MENUS
}

//! @brief Adds a DockApp to the Harbour
//! @todo Add sort option
void
Harbour::addDockApp(DockApp *da)
{
	if (da == NULL) {
		return;
	}

	// add to the list
	if (AutoProperties::instance()->isHarbourSort()) {
		insertDockAppSorted(da);
		placeDockAppsSorted(); // place in sorted way
	} else {
		_da_list.push_back(da);
		placeDockApp(da); // place it in a empty space
	}

	da->setLayer(Config::instance()->isHarbourOntop() ? LAYER_DOCK : LAYER_DESKTOP);
	_workspaces->insert(da); // add the dockapp to the stacking list

	if (da->isMapped() == false) { // make sure it's visible
		da->mapWindow();
	}

	updateHarbourSize();
}

//! @brief Removes a DockApp from the Harbour
void
Harbour::removeDockApp(DockApp *da)
{
	if (da == NULL)
		return;

	list<DockApp*>::iterator it(find(_da_list.begin(), _da_list.end(), da));

	if (it != _da_list.end()) {
		_da_list.remove(da);
		_workspaces->remove(da); // remove the dockapp to the stacking list
		delete da;

		if (AutoProperties::instance()->isHarbourSort()) {
			placeDockAppsSorted();
		}
	}

	updateHarbourSize();
}

//! @brief Removes all DockApps from the Harbour
void
Harbour::removeAllDockApps(void)
{
	list<DockApp*>::iterator it(_da_list.begin());
	for (; it != _da_list.end(); ++it) {
		_workspaces->remove(*it); // remove the dockapp to the stacking list
		delete (*it);
	}
	_da_list.clear();
}

//! @brief Tries to find a dockapp which uses the window win
DockApp*
Harbour::findDockApp(Window win)
{
	DockApp *dockapp = NULL;

	list<DockApp*>::iterator it(_da_list.begin());
	for (; it != _da_list.end(); ++it) {
		if ((*it)->findDockApp(win)) {
			dockapp = (*it);
			break;
		}
	}

	return dockapp;
}

//! @brief Tries to find a dockapp which has the window win as frame.
DockApp*
Harbour::findDockAppFromFrame(Window win)
{
	DockApp *dockapp = NULL;

	list<DockApp*>::iterator it(_da_list.begin());
	for (; it != _da_list.end(); ++it) {
		if ((*it)->findDockAppFromFrame(win)) {
			dockapp = (*it);
			break;
		}
	}

	return dockapp;
}

#ifdef HAVE_XRANDR
//! @brief Refetches the root-window size and takes appropriate actions.
void
Harbour::updateGeometry(void)
{
	list<DockApp*>::iterator it(_da_list.begin());
	for (; it != _da_list.end(); ++it) {
		placeDockAppInsideScreen(*it);
	}
}
#endif // HAVE_XRANDR

//! @brief Lowers or Raises all the DockApps in the harbour.
void
Harbour::restack(void)
{
#ifdef HAVE_XINERAMA
	PScreen::instance()->removeStrut(_strut);
	if (Config::instance()->isHarbourOntop()) {
		PScreen::instance()->addStrut(_strut);
	}
#endif // HAVE_XINERAMA
	uint l = Config::instance()->isHarbourOntop() ? LAYER_DOCK : LAYER_DESKTOP;

	list<DockApp*>::iterator it(_da_list.begin());
	for (; it != _da_list.end(); ++it) {
		(*it)->setLayer(l);

		if (Config::instance()->isHarbourOntop()) {
			_workspaces->raise(*it);
		} else {
			_workspaces->lower(*it);
		}
	}
}

//! @brief Goes through the DockApp list and places the dockapp.
void
Harbour::rearrange(void)
{
#ifdef HAVE_XINERAMA
  _strut->head = Config::instance()->getHarbourHead();
#endif // HAVE_XINERAMA

  if (AutoProperties::instance()->isHarbourSort ())
    placeDockAppsSorted();
  else
    {
      list<DockApp*>::iterator it (_da_list.begin ());
      for (; it != _da_list.end (); ++it)
        placeDockApp (*it);
    }
}

//! @brief Repaints all dockapps with the new theme
void
Harbour::loadTheme(void)
{
  for_each(_da_list.begin(), _da_list.end(), mem_fun(&DockApp::loadTheme));
}

//! @brief Updates the harbour max size variable.
void
Harbour::updateHarbourSize(void)
{
  _size = 0;

  list<DockApp*>::iterator it(_da_list.begin());
  for (; it != _da_list.end(); ++it)
    {
      switch (Config::instance()->getHarbourPlacement())
        {
        case TOP:
        case BOTTOM:
          if ((*it)->getHeight() > _size)
            _size = (*it)->getHeight();
          break;
        case LEFT:
        case RIGHT:
          if ((*it)->getWidth() > _size)
            _size = (*it)->getWidth();
          break;
        default:
          // Do nothing.
          break;
        }
  }

  updateStrutSize();
}

//! @brief Updates Harbour strut size.
void
Harbour::updateStrutSize(void)
{
	_strut->left = _strut->right = _strut->top = _strut->bottom = 0;

	if (!Config::instance()->isHarbourMaximizeOver()) {
		switch (Config::instance()->getHarbourPlacement()) {
		case TOP:
			_strut->top = _size;
			break;
		case BOTTOM:
			_strut->bottom = _size;
			break;
		case LEFT:
			_strut->left = _size;
			break;
		case RIGHT:
			_strut->right = _size;
			break;
		}
	}

	_scr->updateStrut();
}

//! @brief Handles XButtonEvents made on the DockApp's frames.
void
Harbour::handleButtonEvent(XButtonEvent* ev, DockApp* da)
{
	if (da == 0)
		return;

	_last_button_x = ev->x;
	_last_button_y = ev->y;

#ifdef MENUS
	// FIXME: Make configurable
	if (ev->type == ButtonPress) {
		if (ev->button == BUTTON3) {
			if (_harbour_menu->isMapped()) {
				_harbour_menu->unmapWindow();
			} else {
				_harbour_menu->setDockApp(da);
				_harbour_menu->mapUnderMouse();
			}
		} else if (_harbour_menu->isMapped()) {
			_harbour_menu->unmapWindow();
		}
	}
#endif // MENUS
}

//! @brief Initiates moving of a DockApp based on info from a XMotionEvent.
void
Harbour::handleMotionNotifyEvent(XMotionEvent* ev, DockApp* da)
{
	if (da == NULL)
		return;

	Geometry head;
	int x = 0, y = 0;

#ifdef HAVE_XINERAMA
	_scr->getHeadInfo(Config::instance()->getHarbourHead(), head);
#else // !HAVE_XINERAMA
	_scr->getHeadInfo(0, head);
#endif // HAVE_XINERAMA

	switch(Config::instance()->getHarbourPlacement()) {
	case TOP:
	case BOTTOM:
		x = ev->x_root - _last_button_x;
		y = da->getY();
		if (x < head.x) {
			x = head.x;
		} else if ((x + da->getWidth()) > (head.x + head.width)) {
			x = head.x + head.width - da->getWidth();
		}
		break;
	case LEFT:
	case RIGHT:
		x = da->getX();
		y = ev->y_root - _last_button_y;
		if (y < head.y) {
			y = head.y;
		} else if ((y + da->getHeight()) > (head.y + head.height)) {
			y = head.y + head.height - da->getHeight();
		}
		break;
	default:
		// do nothing
		break;
	}

	da->move(x, y);
}

//! @brief Handles XConfigureRequestEvents.
void
Harbour::handleConfigureRequestEvent(XConfigureRequestEvent* ev, DockApp* da)
{
	if (da == NULL)
		return;

	list<DockApp*>::iterator it(find(_da_list.begin(), _da_list.end(), da));

	if (it != _da_list.end()) {
		// Thing is that we doesn't listen to border width, position or
		// stackign so the only thing that we'll alter is size if that's
		// what we want to configure

		uint width = (ev->value_mask&CWWidth) ? ev->width : da->getClientWidth();
		uint height = (ev->value_mask&CWHeight) ? ev->height : da->getClientHeight();

		da->resize(width, height);

		placeDockAppInsideScreen(da);
	}
}

//! @brief Tries to find a empty spot for the DockApp
void
Harbour::placeDockApp(DockApp *da)
{
  if (!da || !_da_list.size ())
    return;

  bool right = (Config::instance()->getHarbourOrientation() == BOTTOM_TO_TOP);

  int test, x = 0, y = 0;
  bool placed = false, increase = false, x_place = false;

  Geometry head;
#ifdef HAVE_XINERAMA
  PScreen::instance()->getHeadInfo(Config::instance()->getHarbourHead(), head);
#else // !HAVE_XINERAMA
  PScreen::instance()->getHeadInfo(0, head);
#endif // HAVE_XINERAMA

  getPlaceStartPosition (da, x, y, x_place);
  if (right)
    {
      if (x_place)
        x -= da->getWidth ();
      else
        y -= da->getHeight ();
    }

  list<DockApp*>::iterator it;
  if (x_place)
    {
      x = test = right ? head.x + head.width - da->getWidth() : head.x;

      while (!placed
             && (right
                   ? (test >= 0)
                   : ((test + da->getWidth()) < (head.x + head.width))))
        {
          placed = increase = true;

          it = _da_list.begin();
          for (; placed && (it != _da_list.end()); ++it)
            {
              if ((*it) == da)
                continue; // exclude ourselves

              if (((*it)->getX() < signed(test + da->getWidth())) &&
                  ((*it)->getRX() > test))
                {
                  placed = increase = false;
                  test = right ? (*it)->getX() - da->getWidth() : (*it)->getRX();
                }
            }

          if (placed)
            x = test;
          else if (increase)
            test += right ? -1 : 1;
        }
    }
  else // !x_place
    {
      y = test = right ? head.y + head.height - da->getHeight() : head.y;

      while (!placed
             && (right
                   ? (test >= 0)
                   : ((test + da->getHeight()) < (head.y + head.height))))
        {
          placed = increase = true;

          it = _da_list.begin();
          for (; placed && (it != _da_list.end()); ++it)
            {
              if ((*it) == da)
                continue; // exclude ourselves

              if (((*it)->getY() < signed(test + da->getHeight())) &&
                  ((*it)->getBY() > test))
                {
                  placed = increase = false;
                  test = right ? (*it)->getY() - da->getHeight() : (*it)->getBY();
                }
            }

          if (placed)
            y = test;
          else if (increase)
            test += right ? -1 : 1;
        }
    }

  da->move (x, y);
}


//! @brief Inserts DockApp and places all dockapps in sorted order
//! @todo Screen boundary checking
void
Harbour::placeDockAppsSorted(void)
{
  if (!_da_list.size ())
    return;

  // place the dockapps
  int x, y, x_real, y_real;
  bool inc_x = false;
  bool right = (Config::instance ()->getHarbourOrientation () == BOTTOM_TO_TOP);

  getPlaceStartPosition (_da_list.front (), x_real, y_real, inc_x);

  list<DockApp*>::iterator it (_da_list.begin ());
  for (; it != _da_list.end (); ++it)
    {
      getPlaceStartPosition (*it, x, y, inc_x);

      if (inc_x)
        {
          if (right)
            {
              (*it)->move (x_real - (*it)->getWidth (), y);
              x_real -= -(*it)->getWidth ();
            }
          else
            {
              (*it)->move (x_real, y);
              x_real += (*it)->getWidth ();
            }
        }
      else
        {
          if (right)
            {
              (*it)->move (x, y_real - (*it)->getHeight ());
              y_real -= (*it)->getHeight ();
            }
          else
            {
              (*it)->move (x, y_real);
              y_real += (*it)->getHeight ();
            }
        }
    }
}

//! @brief
void
Harbour::placeDockAppInsideScreen(DockApp *da)
{
	Geometry head;
#ifdef HAVE_XINERAMA
	PScreen::instance()->getHeadInfo(Config::instance()->getHarbourHead(), head);
#else // !HAVE_XINEAMA
	PScreen::instance()->getHeadInfo(0, head);
#endif // HAVE_XINERAMA

	uint pos = Config::instance()->getHarbourPlacement();

	// top or bottom placement
	if ((pos == TOP) || (pos == BOTTOM)) {
		// check horizontal position
		if (da->getX() < head.x) {
			da->move(head.x, da->getY());
		} else if (da->getRX() > signed(head.x + head.width)) {
			da->move(head.x + head.width - da->getWidth(), da->getY());
		}

		// check vertical position
		if ((pos == TOP) && (da->getY() < head.y)) {
			da->move(da->getX(), head.y);
		} else if ((pos == BOTTOM) && (da->getBY() > signed(head.y + head.height))) {
			da->move(da->getX(), head.y + head.height - da->getHeight());
		}

	// left or right placement
	} else {
		// check vertical position
		if (da->getY() < head.y) {
			da->move(da->getX(), head.y);
		} else if (da->getBY() > signed(head.y + head.height)) {
			da->move(da->getX(), head.y + head.height - da->getHeight());
		}

		// check horizontal position
		if ((pos == LEFT) && (da->getX() < head.x)) {
			da->move(head.x, da->getY());
		} else if ((pos == RIGHT) && (da->getRX() > signed(head.x + head.width))) {
			da->move(head.x + head.width - da->getWidth(), da->getY());
		}
	}
}

//! @brief
void
Harbour::getPlaceStartPosition(DockApp *da, int &x, int &y, bool &inc_x)
{
  if (!da)
    return;

  Geometry head;
#ifdef HAVE_XINERAMA
  PScreen::instance()->getHeadInfo(Config::instance()->getHarbourHead(), head);
#else // !HAVE_XINEAMA
  PScreen::instance()->getHeadInfo(0, head);
#endif // HAVE_XINERAMA

  bool right = (Config::instance()->getHarbourOrientation() == BOTTOM_TO_TOP);

  switch (Config::instance()->getHarbourPlacement()) {
  case TOP:
    inc_x = true;
    x = right ? head.x + head.width : head.x;
    y = head.y;
    break;
  case BOTTOM:
    inc_x = true;
    x = right ? head.x + head.width : head.x;
    y = head.y + head.height - da->getHeight();
    break;
  case LEFT:
    x = head.x;
    y = right ? head.y + head.height : head.y;
    break;
  case RIGHT:
    x = head.x + head.width - da->getWidth();
    y = right ? head.y + head.height : head.y;
    break;
  }
}


//! @brief Inserts DockApp in sorted order in the list
void
Harbour::insertDockAppSorted(DockApp *da)
{
  list<DockApp*>::iterator it(_da_list.begin());

  // The order of this list doesn't make much sense to me when
  // it comes to representing it in code, however it's perfectly sane
  // for representing the order in the config files.
  // anyway, order goes as follows: 1 2 3 0 0 0 -3 -2 -1

  // Middle of the list.
  if (da->getPosition () == 0)
    {
      for (; (it != _da_list.end ()) && ((*it)->getPosition () >= 0); ++it)
        ;
  // Beginning of the list.
  } else if (da->getPosition () > 0) {
      for (; it != _da_list.end (); ++it)
        {
          if (((*it)->getPosition () < 1) || // got to 0 or less
              (da->getPosition () <= (*it)->getPosition ()))
            {
              break;
            }
        }
  // end of the list
  } else {
    for (; (it != _da_list.end ()) && ((*it)->getPosition () >= 0); ++it)
      ;
    for (; (it != _da_list.end ()) && (da->getPosition () >= (*it)->getPosition ()); ++it)
      ;
  }

  _da_list.insert (it, da);
}

#endif // HARBOUR


syntax highlighted by Code2HTML, v. 0.9.1