/*
 * pager.cc
 *
 * Copyright (C) 1995-2000 Kenichi Kourai
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with qvwm; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "main.h"
#include "misc.h"
#include "qvwm.h"
#include "event.h"
#include "qvwmrc.h"
#include "paging.h"
#include "pager.h"
#include "util.h"
#include "taskbar.h"
#include "callback.h"

Pager::Pager(InternGeom geom)
: rcOrig(geom.rc), gravity(geom.gravity)
{
  XSetWindowAttributes attributes;
  unsigned long valueMask;

  /*
   * If width or height is 0, set default value.
   */
  if (rcOrig.width == 0)
    rcOrig.width = 48;
  if (rcOrig.height == 0)
    rcOrig.height = 48;

  /*
   * Adjust pager size and position.
   */
  rcOrig.width = RoundDown(rcOrig.width, paging->rcVirt.width);
  rcOrig.height = RoundDown(rcOrig.height, paging->rcVirt.height);
  if (gravity.x == EAST)
    rcOrig.x += geom.rc.width - rcOrig.width;
  if (gravity.y == SOUTH)
    rcOrig.y += geom.rc.height - rcOrig.height;

  CalcPagerPos();

  /*
   * Create the frame window for pager.
   */
  attributes.background_pixel = PagerColor.pixel;
  attributes.override_redirect = True;
  attributes.event_mask = ExposureMask;
  valueMask = CWBackPixel | CWEventMask | CWOverrideRedirect;
  
  frame = XCreateWindow(display, root,
			rc.x, rc.y, rc.width, rc.height,
			0, CopyFromParent, InputOutput, CopyFromParent,
			valueMask, &attributes);
  
  /*
   * Create the title window.
   */
  attributes.background_pixel = TitlebarActiveColor.pixel;
  attributes.event_mask = ButtonPressMask | ButtonReleaseMask |
                          Button1MotionMask;
  valueMask = CWBackPixel | CWEventMask;
  
  title = XCreateWindow(display, frame,
			BORDER_WIDTH, BORDER_WIDTH, rcOrig.width, TITLE_HEIGHT,
			0, CopyFromParent, InputOutput, CopyFromParent,
			valueMask, &attributes);
  
  if (GradTitlebar) {
    Pixmap pixGrad = CreateGradPixmap(gradActivePattern, rcOrig.width, title);
    XSetWindowBackgroundPixmap(display, title, pixGrad);
  }

  /*
   * Create the window for the miniature of whole virtual screen.
   */
  attributes.background_pixel = PagerColor.pixel;
  attributes.event_mask = ButtonPressMask | ExposureMask;
  valueMask = CWBackPixel | CWEventMask;
  
  pages = XCreateWindow(display, frame,
			BORDER_WIDTH, TITLE_HEIGHT + BORDER_WIDTH + 1,
			rcOrig.width, rcOrig.height,
			0, CopyFromParent, InputOutput, CopyFromParent,
			valueMask, &attributes);
  
  if (PagerImage) {
    imgPager = CreateImageFromFile(PagerImage, timer);
    if (imgPager)
      imgPager->SetBackground(pages);
  }

  ASSERT(paging);

  pageSize = Dim(rcOrig.width / paging->rcVirt.width,
		 rcOrig.height / paging->rcVirt.height);
  
  /*
   * Create the window standing for current page.
   */
  attributes.background_pixel = PagerActiveColor.pixel;
  attributes.event_mask = ButtonPressMask | ButtonReleaseMask |
                          Button3MotionMask;
  valueMask = CWBackPixel | CWEventMask;
  
  visual = XCreateWindow(display, pages,
			 0, 0, pageSize.width - 2, pageSize.height - 2,
			 0, CopyFromParent, InputOutput, CopyFromParent,
			 valueMask, &attributes);
  
  XMapWindow(display, frame);
  XMapWindow(display, title);
  XMapWindow(display, pages);
  XMapWindow(display, visual);
  
  RaisePager();

  DrawVisualPage();
}

Pager::~Pager()
{
  XDestroyWindow(display, frame);

  if (PagerImage)
    QvImage::Destroy(imgPager);
}

/*
 * IsPagerWindows --
 *   Return True if the window is pager window.
 */
Bool Pager::IsPagerWindows(Window win)
{
  if (win == pages || win == visual || win == frame || win == title)
    return True;
  else
    return False;
}

void Pager::CalcPagerPos()
{
  Rect rect;

  if (UseTaskbar) {
    if (TaskbarAutoHide)
      rect = taskBar->GetScreenRectOnHiding();
    else
      rect = taskBar->GetScreenRectOnShowing();
  }

  rc.x = rcOrig.x + rect.x + BORDER_WIDTH * gravity.x;
  rc.y = rcOrig.y + rect.y;
  if (gravity.y == CENTER)
    rc.y -= BORDER_WIDTH + TITLE_HEIGHT + 1;
  else if (gravity.y == SOUTH)
    rc.y -= BORDER_WIDTH * 2 + TITLE_HEIGHT + 1;

  Rect rcRoot = rootQvwm->GetRect();

  if (UseTaskbar) {
    switch (taskBar->GetPos()) {
    case Taskbar::BOTTOM:
    case Taskbar::TOP:
      // South??Gravity
      if (gravity.y == SOUTH)
	rc.y -= rcRoot.height - rect.height;
      break;
      
    case Taskbar::LEFT:
    case Taskbar::RIGHT:
      // ??EastGravity
      if (gravity.x == EAST) {
	rc.x -= rcRoot.width - rect.width;
      }
      break;
    }
  }

  rc.width = rcOrig.width + BORDER_WIDTH * 2;
  rc.height = rcOrig.height + BORDER_WIDTH * 2 + TITLE_HEIGHT + 1;
}

/*
 * RecalcPager --
 *   Recalc the position of pager according to rcScreen and move the pager.
 */
void Pager::RecalcPager()
{
  CalcPagerPos();
  XMoveWindow(display, frame, rc.x, rc.y);
}

/*
 * ConvertToPagerPos
 *   Convert real position to position in pager.
 */
Point Pager::ConvertToPagerPos(const Point& pt)
{
  Rect rcRoot = rootQvwm->GetRect();
  Point ptMini;

  ASSERT(paging);

  ptMini.x = pt.x * pageSize.width / rcRoot.width -
    paging->rcVirt.x * pageSize.width;
  ptMini.y = pt.y * pageSize.height / rcRoot.height -
    paging->rcVirt.y * pageSize.height;

  return (ptMini);
}

/*
 * ConvertToPagerSize
 *   Convert real size to size in pager.
 */
Rect Pager::ConvertToPagerSize(const Rect& rect)
{
  Rect rcRoot = rootQvwm->GetRect();
  Rect rcMini;

  ASSERT(paging);

  rcMini.x = rect.x * pageSize.width / rcRoot.width -
    paging->rcVirt.x * pageSize.width;
  rcMini.y = rect.y * pageSize.height / rcRoot.height -
    paging->rcVirt.y * pageSize.height;
  rcMini.width = rect.width * pageSize.width / rcRoot.width;
  if (rcMini.width == 0)
    rcMini.width = 1;
  rcMini.height = rect.height * pageSize.height / rcRoot.height;
  if (rcMini.height == 0)
    rcMini.height = 1;

  return (rcMini);
}

/*
 * ConvertToRealPos
 *   Convert position in pager to real position.
 */
Point Pager::ConvertToRealPos(const Point& pt)
{
  Rect rcRoot = rootQvwm->GetRect();
  Point ptReal;

  ASSERT(paging);

  ptReal.x = pt.x * rcRoot.width / pageSize.width +
    paging->rcVirt.x * rcRoot.width;
  ptReal.y = pt.y * rcRoot.height / pageSize.height +
    paging->rcVirt.y * rcRoot.height;

  return (ptReal);
}

/*
 * DrawPages --
 *   Draw pages.
 */
void Pager::DrawPages()
{
  int i;

  ASSERT(paging);

  for (i = 1; i < paging->rcVirt.width; i++) {
    XSetForeground(display, gc, darkGray.pixel);
    XDrawLine(display, pages, gc,
	      pageSize.width*i - 1, 1,
	      pageSize.width * i - 1, rcOrig.height - 2);
    XSetForeground(display, gc, white.pixel);
    XDrawLine(display, pages, gc,
	      pageSize.width * i, 1,
	      pageSize.width * i, rcOrig.height - 2);
  }
  for (i = 1; i < paging->rcVirt.height; i++) {
    XSetForeground(display, gc, darkGray.pixel);
    XDrawLine(display, pages, gc,
	      1, pageSize.height * i - 1,
	      rcOrig.width - 2, pageSize.height * i - 1);
    XSetForeground(display, gc, white.pixel);
    XDrawLine(display, pages, gc,
	      1, pageSize.height * i,
	      rcOrig.width - 2, pageSize.height * i);
  }
}

/*
 * DrawVisualPage --
 *   Draw area standing for visual page.
 */
void Pager::DrawVisualPage()
{
  Rect rcRoot = rootQvwm->GetRect();
  Point ptVisual;

  ASSERT(paging);

  ptVisual.x = (paging->origin.x - paging->rcVirt.x * rcRoot.width)
    * rcOrig.width / (rcRoot.width * paging->rcVirt.width) + 1;
  ptVisual.y = (paging->origin.y - paging->rcVirt.y * rcRoot.height)
    * rcOrig.height / (rcRoot.height * paging->rcVirt.height) + 1;
  
  XMoveWindow(display, visual, ptVisual.x, ptVisual.y);
}

/*
 * DrawFrame --
 *   Draw pager frame.
 */
void Pager::DrawFrame()
{
  XPoint xp[3];

  xp[0].x = rc.width - 2;
  xp[0].y = 0;
  xp[1].x = 0;
  xp[1].y = 0;
  xp[2].x = 0;
  xp[2].y = rc.height - 2;
  
  XSetForeground(display, gc, lightGray.pixel);
  XDrawLines(display, frame, gc, xp, 3, CoordModeOrigin);

  xp[0].x = rc.width - 1;
  xp[0].y = 0;
  xp[1].x = rc.width - 1;
  xp[1].y = rc.height - 1;
  xp[2].x = 0;
  xp[2].y = rc.height - 1;

  XSetForeground(display, ::gc, black.pixel);
  XDrawLines(display, frame, ::gc, xp, 3, CoordModeOrigin);

  xp[0].x = rc.width - 3;
  xp[0].y = 1;
  xp[1].x = 1;
  xp[1].y = 1;
  xp[2].x = 1;
  xp[2].y = rc.height - 3;

  XSetForeground(display, ::gc, white.pixel);
  XDrawLines(display, frame, ::gc, xp, 3, CoordModeOrigin);
  
  xp[0].x = rc.width - 2;
  xp[0].y = 1;
  xp[1].x = rc.width - 2;
  xp[1].y = rc.height - 2;
  xp[2].x = 1;
  xp[2].y = rc.height - 2;

  XSetForeground(display, ::gc, darkGray.pixel);
  XDrawLines(display, frame, ::gc, xp, 3, CoordModeOrigin);
}

/*
 * Button1Press --
 *   Process the press of button1(mouse left button).
 */
void Pager::Button1Press(Window win, const Point& ptRoot, const Point& ptWin)
{
  RaisePager();

  if (UseTaskbar && OnTopTaskbar && !OnTopPager)
    taskBar->RaiseTaskbar();

  if (UseTaskbar && !taskBar->IsHiding()) {
    ASSERT(paging);
    paging->RaisePagingBelt();
  }

  if (win == pages) {
    /*
     * Move the current page to the clicked area.
     */
    Point ptPage;
    Rect rcRoot = rootQvwm->GetRect();

    ptPage.x = ptWin.x * paging->rcVirt.width / rcOrig.width;
    ptPage.y = ptWin.y * paging->rcVirt.height / rcOrig.height;
    
    Point oldOrigin = paging->origin;
    paging->origin.x = (ptPage.x + paging->rcVirt.x) * rcRoot.width;
    paging->origin.y = (ptPage.y + paging->rcVirt.y) * rcRoot.height;

    paging->PagingAllWindows(oldOrigin);
  }
  else if (win == title) {
    /*
     * Move the pager.
     */
    XEvent ev;
    Point ptOld(ptRoot), ptNew, ptMove;
    Bool pointer = False;

    while (1) {
      XMaskEvent(display,
		 Button1MotionMask | ButtonReleaseMask | ExposureMask |
		 ButtonPressMask | PointerMotionMask,
		 &ev);
      switch (ev.type) {
      case MotionNotify:
	ptNew = Point(ev.xbutton.x_root, ev.xbutton.y_root);
	ptMove = ptNew - ptOld;
	rc.x += ptMove.x;
	rc.y += ptMove.y;
	rcOrig.x += ptMove.x;
	rcOrig.y += ptMove.y;
	ptOld = ptNew;
	XMoveWindow(display, frame, rc.x, rc.y);
	break;

      case ButtonRelease:
	if (LockDragState) {
	  pointer = True;
	  XGrabPointer(display, root, True,
		       ButtonPressMask | PointerMotionMask,
		       GrabModeAsync, GrabModeAsync, root, None, CurrentTime);
	  break;
	}
	else
	  return;

      case ButtonPress:
	if (pointer) {
	  XUngrabPointer(display, CurrentTime);
	  return;
	}
	break;

      case Expose:
	event.ExposeProc((const XExposeEvent &)ev);
	break;
      }
    }
  }
}

/*
 * Button3Press --
 *   Process the press of button3(mouse right button).
 *   You can move the visual page, or the current screen freely.
 */
void Pager::Button3Press(const Point& ptRoot)
{
  XEvent ev;
  Point ptOld, ptNew;
  Rect rcRoot = rootQvwm->GetRect();
  Bool pointer = False;
  
  RaisePager();

  if (UseTaskbar && !taskBar->IsHiding()) {
    ASSERT(paging);
    paging->RaisePagingBelt();
  }

  XGrabPointer(display, visual, True, Button3MotionMask | ButtonReleaseMask,
	       GrabModeAsync, GrabModeAsync, pages, None, CurrentTime);

  ptOld = ptRoot;

  while (1) {
    XMaskEvent(display,
	       Button3MotionMask | ButtonReleaseMask | ExposureMask |
	       ButtonPressMask | PointerMotionMask,
	       &ev);
    switch (ev.type) {
    case MotionNotify:
      {
	Point oldOrigin = paging->origin;

	ptNew = Point(ev.xbutton.x_root, ev.xbutton.y_root);
	paging->origin.x += (ptNew.x - ptOld.x) *
	  (rcRoot.width * paging->rcVirt.width / rcOrig.width);
	paging->origin.y += (ptNew.y - ptOld.y) *
	  (rcRoot.height * paging->rcVirt.height / rcOrig.height);
	ptOld = ptNew;
	paging->PagingAllWindows(oldOrigin);
	break;
      }

    case ButtonRelease:
      if (LockDragState) {
	pointer = True;
	XChangeActivePointerGrab(display, ButtonPressMask | PointerMotionMask,
				 None, CurrentTime);
	break;
      }
      else {
	XUngrabPointer(display, CurrentTime);
	return;
      }

    case ButtonPress:
      if (pointer) {
	XUngrabPointer(display, CurrentTime);
	return;
      }
      break;

    case Expose:
      event.ExposeProc((const XExposeEvent &)ev);
      break;
    }
  }
}

/*
 * Exposure --
 *   Process exposure event.
 */
void Pager::Exposure(Window win)
{
  if (win == frame)
    DrawFrame();
  else if (win == pages)
    DrawPages();
}

void Pager::RaisePager()
{
  if (OnTopPager)
    XRaiseWindow(display, frame);
  else {
    Window win[2];

    win[0] = desktop.GetTopWindow();
    win[1] = frame;
    XRestackWindows(display, win, 2);
  }
}
