/*
 * taskbar.cc
 *
 * Copyright (C) 1995-2001 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 <string.h>
#include <unistd.h>
#include <time.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xlocale.h>
#include "main.h"
#include "misc.h"
#include "qvwm.h"
#include "taskbar.h"
#include "tbutton.h"
#include "sbutton.h"
#include "startmenu.h"
#include "event.h"
#include "icon.h"
#include "paging.h"
#include "pager.h"
#include "qvwmrc.h"
#include "indicator.h"
#include "desktop.h"
#include "tooltip.h"
#include "callback.h"
#include "timer.h"

int Taskbar::BASE_HEIGHT;
int Taskbar::INC_HEIGHT;

Taskbar::Taskbar(Qvwm* qvWm, int width, unsigned int rows)
: hiding(False)
{
  XSetWindowAttributes attributes;
  unsigned long valueMask;
  Dim dRoot(DisplayWidth(display, screen), DisplayHeight(display, screen));
  Dim base;
  char str[128];
  int len;
  XRectangle ink, log;

  if (UseClock) {
    /*
     * Calculate clock width.
     */
    time_t t;
    struct tm* tm;
    
    time(&t);
    tm = localtime(&t);
    
    // set values with max font width
    tm->tm_sec = 59;
    tm->tm_min = 59;
    tm->tm_hour = 23;
    tm->tm_mday = 31;
    tm->tm_yday = 365;
    
    // if ClockLocaleName is not specified in the configuration files
    if (ClockLocaleName == NULL)
      ClockLocaleName = LocaleName;

    setlocale(LC_TIME, ClockLocaleName);

    strftime(str, sizeof(str), ClockFormat, tm);

    len = strlen(str);
    XmbTextExtents(fsTaskbar, str, len, &ink, &log);
    clockWidth = log.width;

    timer->SetTimeout(1000,
		      new Callback<Taskbar>(this, &Taskbar::AdvanceClock));
  }
  else
    clockWidth = 0;
  indWidth = 0;

  dTbox.width = clockWidth + TBOX_MARGIN * 2;
  dTbox.height = TaskbarButton::BUTTON_HEIGHT;

  base.width = (width > clockWidth + 6) ? width : clockWidth + 6;
  base.height = (rows - 1) * INC_HEIGHT + BASE_HEIGHT;
  if (base.height > dRoot.height / 2)
    base.height = (dRoot.height / 2 - BASE_HEIGHT) / INC_HEIGHT * INC_HEIGHT
      + BASE_HEIGHT;
 
  rc[BOTTOM] = Rect(0, dRoot.height - base.height, dRoot.width, base.height);
  rc[TOP] = Rect(0, 0, dRoot.width, base.height);
  rc[LEFT] = Rect(0, 0, base.width, dRoot.height);
  rc[RIGHT] = Rect(dRoot.width - base.width, 0, base.width, dRoot.height);

  /*
   * Create taskbar.
   */
  attributes.background_pixel = TaskbarColor.pixel;
  attributes.override_redirect = True;
  attributes.event_mask = ExposureMask | ButtonPressMask |
                          ButtonReleaseMask | Button1MotionMask |
			  EnterWindowMask | LeaveWindowMask;
  valueMask = CWBackPixel | CWOverrideRedirect | CWEventMask;

  frame = XCreateWindow(display, root,
			-10, -10, 10, 10,
			0, CopyFromParent, InputOutput, CopyFromParent,
			valueMask, &attributes);

  attributes.event_mask = ExposureMask | ButtonPressMask | ButtonReleaseMask |
                          Button1MotionMask;
  attributes.background_pixel = TaskbarColor.pixel;
  attributes.cursor = cursor[SYS];
  valueMask = CWBackPixel | CWCursor | CWEventMask;

  w = XCreateWindow(display, frame,
		    0, 0, 10, 10,
		    0, CopyFromParent, InputOutput, CopyFromParent,
		    valueMask, &attributes);

  /*
   * Create taskbar box.
   */
  attributes.event_mask = ExposureMask | EnterWindowMask | LeaveWindowMask |
                          PointerMotionMask;
  valueMask = CWBackPixel | CWEventMask;

  tbox = XCreateWindow(display, w,
		       0, 0, dTbox.width, dTbox.height,
		       0, CopyFromParent, InputOutput, CopyFromParent,
		       valueMask, &attributes);

  XMapWindow(display, w);
  XMapWindow(display, tbox);

  if (TaskbarImage) {
    imgTaskbar = CreateImageFromFile(TaskbarImage, timer);
    if (imgTaskbar) {
      imgTaskbar->SetBackground(frame);
      imgTWin = NULL;
      imgTBox = NULL;
    }
    else {
      delete [] TaskbarImage;
      TaskbarImage = NULL;
    }
  }

  ctrlMenu = new Menu(TaskbarMenuItem, fsCtrlMenu, NULL, qvWm);

  toolTip = new Tooltip();
}

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

  if (TaskbarImage) {
    QvImage::Destroy(imgTaskbar);
    QvImage::Destroy(imgTWin);
    QvImage::Destroy(imgTBox);
  }

  delete toolTip;
  delete ctrlMenu;
}

/*
 * MapTaskbar --
 *   Map the taskbar.
 */
void Taskbar::MapTaskbar()
{
  XMapWindow(display, frame);

  RaiseTaskbar();

  ASSERT(rootQvwm);
  ASSERT(rootQvwm->tButton);

  rootQvwm->tButton->MapButton();         // Start menu

  if (TaskbarAutoHide) {
    BasicCallback* cb;
    cb = new Callback<Taskbar>(this, &Taskbar::HideTaskbar);
    timer->SetTimeout(TaskbarHideDelay, cb);
  }
    
  if (!hiding)
    paging->RaisePagingBelt();
}

/*
 * UnmapTaskbar --
 *   Unmap the taskbar.
 */
void Taskbar::UnmapTaskbar()
{
  XUnmapWindow(display, frame);
}

/*
 * MoveTaskbar --
 *   Move the taskbar to the tp position.
 */
void Taskbar::MoveTaskbar(TaskbarPos tp)
{
  pos = tp;

  switch (pos) {
  case BOTTOM:
    XDefineCursor(display, frame, cursor[Y_RESIZE]);
    XMoveResizeWindow(display, w, 0, 4, rc[BOTTOM].width, rc[BOTTOM].height-4);

    TaskbarButton::rcTButton.x = 2;
    TaskbarButton::rcTButton.y = 0;

    if (TaskbarImage) {
      QvImage::Destroy(imgTWin);
      imgTWin = imgTaskbar->GetOffsetImage(Point(0, 4));
      imgTWin->SetBackground(w);
    }
    break;

  case TOP:
    XDefineCursor(display, frame, cursor[Y_RESIZE]);
    XMoveResizeWindow(display, w, 0, 0, rc[TOP].width, rc[TOP].height-4);

    TaskbarButton::rcTButton.x = 2;
    TaskbarButton::rcTButton.y = 2;

    if (TaskbarImage) {
      QvImage::Destroy(imgTWin);
      imgTWin = imgTaskbar->GetOffsetImage(Point(0, 0));
      imgTWin->SetBackground(w);
    }
    break;

  case LEFT:
    XDefineCursor(display, frame, cursor[X_RESIZE]);
    XMoveResizeWindow(display, w, 0, 0, rc[LEFT].width-4, rc[LEFT].height);

    TaskbarButton::rcTButton.x = 2;
    TaskbarButton::rcTButton.y = 2;

    if (TaskbarImage) {
      QvImage::Destroy(imgTWin);
      imgTWin = imgTaskbar->GetOffsetImage(Point(0, 0));
      imgTWin->SetBackground(w);
    }
    break;

  case RIGHT:
    XDefineCursor(display, frame, cursor[X_RESIZE]);
    XMoveResizeWindow(display, w, 4, 0, rc[RIGHT].width-4, rc[RIGHT].height);

    TaskbarButton::rcTButton.x = 0;
    TaskbarButton::rcTButton.y = 2;

    if (TaskbarImage) {
      QvImage::Destroy(imgTWin);
      imgTWin = imgTaskbar->GetOffsetImage(Point(4, 0));
      imgTWin->SetBackground(w);
    }
    break;
  }

  if (TaskbarAutoHide)
    rcScreen = GetScreenRectOnHiding();
  else
    rcScreen = GetScreenRectOnShowing();

  // relocate the contents of taskbar box
  MoveResizeTaskbarBox();
  DrawTaskbarBox();

  RedrawAllTaskbarButtons();

  XMoveResizeWindow(display, frame, rc[pos].x, rc[pos].y, rc[pos].width,
		    rc[pos].height);

  desktop.RecalcAllWindows();
  desktop.RedrawAllIcons();
  if (UsePager) {
    ASSERT(pager);
    pager->RecalcPager();
  }

  ASSERT(paging);

  if (!hiding)
    paging->RaisePagingBelt();
}

/*
 * MoveTaskbar --
 *   Drag the taskbar to move.
 */
void Taskbar::MoveTaskbar(const Point& ptRoot)
{
  XEvent ev;
  Point ptNew;
  Rect rcRoot = rootQvwm->GetRect();
  Bool pointer = False;

  if (DisableDesktopChange)
    return;

  /*
   * There is parts that cannot let taskbar to move.
   */
  switch (pos) {
  case BOTTOM:
    if (ptRoot.y == rcRoot.height-1 || ptRoot.y == rcRoot.height-2)
      return;
    break;

  case TOP:
    if (ptRoot.y == 0 || ptRoot.y == 1)
      return;
    break;

  case LEFT:
    if (ptRoot.x == 0 || ptRoot.x == 1)
      return;
    break;

  case RIGHT:
    if (ptRoot.x == rcRoot.width-1 || ptRoot.x == rcRoot.width-2)
      return;
    break;
  }
  
  if (!OpaqueMove) {
    XGrabServer(display);
    XDrawRectangle(display, root, gcXor, rc[pos].x, rc[pos].y,
		   rc[pos].width, rc[pos].height);
  }

  while (1) {
    XMaskEvent(display,
	       Button1MotionMask | ButtonReleaseMask | ExposureMask |
	       ButtonPressMask | PointerMotionMask,
	       &ev);
    switch (ev.type) {
    case MotionNotify:
      if (!OpaqueMove)
	XDrawRectangle(display, root, gcXor, rc[pos].x, rc[pos].y,
		       rc[pos].width, rc[pos].height);

      ptNew = Point(ev.xbutton.x_root, ev.xbutton.y_root);
      pos = desktop.GetDesktopArea(ptNew);

      if (OpaqueMove)
	MoveTaskbar(pos);
      else
	XDrawRectangle(display, root, gcXor, rc[pos].x, rc[pos].y,
		       rc[pos].width, rc[pos].height);
      break;

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

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

decide:
  if (!OpaqueMove) {
    XDrawRectangle(display, root, gcXor, rc[pos].x, rc[pos].y,
		   rc[pos].width, rc[pos].height);
    XUngrabServer(display);
  }

  ptNew = Point(ev.xbutton.x_root, ev.xbutton.y_root);
  pos = desktop.GetDesktopArea(ptNew);
  MoveTaskbar(pos);
}  

/*
 * ResizeTaskbar --
 *   Resize the taskbar.
 */
void Taskbar::ResizeTaskbar(const Point& ptRoot)
{
  RectPt resize;
  XEvent ev;
  Rect rcNew = rc[pos], rcOld = rc[pos];
  Point ptNew;
  Rect rcRoot = rootQvwm->GetRect();
  Bool pointer = False;
  int ctype;

  if (DisableDesktopChange)
    return;

  if (!OpaqueResize) {
    XGrabServer(display);
    XDrawRectangle(display, root, gcXor,
		   rcNew.x, rcNew.y, rcNew.width, rcNew.height);
  }

  while (1) {
    XMaskEvent(display,
	       Button1MotionMask | ButtonReleaseMask | ExposureMask |
	       ButtonPressMask | PointerMotionMask,
	       &ev);
    switch (ev.type) {
    case MotionNotify:
      if (!OpaqueResize)
	XDrawRectangle(display, root, gcXor,
		       rcNew.x, rcNew.y, rcNew.width, rcNew.height);

      ptNew = Point(ev.xbutton.x_root, ev.xbutton.y_root);
      resize = RectPt(0, 0, 0, 0);

      switch (pos) {
      case BOTTOM:
	resize.top = ptNew.y - ptRoot.y;
	break;

      case TOP:
	resize.bottom = ptNew.y - ptRoot.y;
	break;

      case LEFT:
	resize.right = ptNew.x - ptRoot.x;
	break;

      case RIGHT:
	resize.left = ptNew.x - ptRoot.x;
	break;
      }
      
      rcNew.x = rcOld.x + resize.left;
      rcNew.y = rcOld.y + resize.top;
      rcNew.width = rcOld.width - resize.left + resize.right;
      rcNew.height = rcOld.height - resize.top + resize.bottom;
      
      /*
       * Adjust.
       */
      switch (pos) {
      case BOTTOM:
      case TOP:
	if (rcNew.height > rcRoot.height/2)
	  rcNew.height = rcRoot.height / 2;
	rcNew.height = rcNew.height / INC_HEIGHT * INC_HEIGHT + 3;
	if (rcNew.height < 6)
	  rcNew.height = 6;
	break;

      case LEFT:
      case RIGHT:
	if (rcNew.width > rcRoot.width/2)
	  rcNew.width = rcRoot.width / 2;
	if (rcNew.width < 6)
	  rcNew.width = 6;
	break;
      }
      
      if (pos == BOTTOM)
	rcNew.y = rcRoot.height - rcNew.height;
      if (pos == RIGHT)
	rcNew.x = rcRoot.width - rcNew.width;
      
      if (OpaqueResize) {
	rc[pos] = rcNew;
	MoveTaskbar(pos);
      }
      else 
	XDrawRectangle(display, root, gcXor,
		       rcNew.x, rcNew.y, rcNew.width, rcNew.height);
      break;
      
    case ButtonRelease:
      if (LockDragState) {
	pointer = True;
	if (pos == BOTTOM || pos == TOP)
	  ctype = Y_RESIZE;
	else
	  ctype = X_RESIZE;
	XGrabPointer(display, root, True, ButtonPressMask | PointerMotionMask,
		     GrabModeAsync, GrabModeAsync, root, cursor[ctype],
		     CurrentTime);
	break;
      }
      else
	goto decide;

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

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

decide:
  if (!OpaqueResize) {
    XDrawRectangle(display, root, gcXor,
		   rcNew.x, rcNew.y, rcNew.width, rcNew.height);
    XUngrabServer(display);
  }

  rc[pos] = rcNew;
  MoveTaskbar(pos);
}  

/*
 * RaiseTaskbar --
 *   Raise the taskbar.
 */
void Taskbar::RaiseTaskbar()
{
  Window win[2];
  int nwindows = 0;
  Window menuFrame = Menu::GetMappedMenuFrame();

  if (OnTopTaskbar) {
    if (menuFrame != None)
      win[nwindows++] = menuFrame;
  }
  else
    win[nwindows++] = desktop.GetTopWindow();
  win[nwindows++] = frame;
  
  ASSERT(paging);

  if (hiding) {
    paging->RaisePagingBelt();
    if (OnTopTaskbar)
      XRaiseWindow(display, frame);
    XRestackWindows(display, win, nwindows);
  }
  else {
    if (OnTopTaskbar)
      XRaiseWindow(display, frame);
    XRestackWindows(display, win, nwindows);
    paging->RaisePagingBelt();
  }
}

/*
 * MoveResizeTaskbarBox --
 *    Move and resize taskbar box.
 */
void Taskbar::MoveResizeTaskbarBox()
{
  Rect rcRoot = rootQvwm->GetRect();
  StartButton *sButton = (StartButton *)rootQvwm->tButton;
  Point pt;

  indWidth = Indicator::RedrawAllIndicators();
  dTbox.width = indWidth + IC_MARGIN + clockWidth + TBOX_MARGIN;

  switch (pos) {
  case BOTTOM:
    pt = Point(rc[BOTTOM].width - dTbox.width - 2, 0);
    XMoveResizeWindow(display, tbox, pt.x, pt.y,
		      dTbox.width, TaskbarButton::BUTTON_HEIGHT);
    buttonArea = rcRoot.width - (sButton->buttonWidth + dTbox.width + 21);

    if (TaskbarImage) {
      QvImage::Destroy(imgTBox);
      imgTBox = imgTaskbar->GetOffsetImage(Point(pt.x, pt.y + 4));
      imgTBox->SetBackground(tbox);
    }
    break;

  case TOP:
    pt = Point(rc[TOP].width - dTbox.width - 2, 2);
    XMoveResizeWindow(display, tbox, pt.x, pt.y,
		      dTbox.width, TaskbarButton::BUTTON_HEIGHT);
    buttonArea = rcRoot.width - (sButton->buttonWidth + dTbox.width + 21);

    if (TaskbarImage) {
      QvImage::Destroy(imgTBox);
      imgTBox = imgTaskbar->GetOffsetImage(pt);
      imgTBox->SetBackground(tbox);
    }
    break;

  case LEFT:
    if (rc[LEFT].width < sButton->buttonWidth + dTbox.width + 19) {
      if (rc[LEFT].width - 9 > dTbox.width) {
	pt = Point((rc[LEFT].width - dTbox.width - 4) / 2 + 1,
		   rc[LEFT].height - dTbox.height - 6);
	XMoveResizeWindow(display, tbox,
			  pt.x, pt.y, dTbox.width, dTbox.height);
      }
      else {
	dTbox.width = rc[LEFT].width - 9;
	if (dTbox.width <= 0)
	  dTbox.width = 1;
	pt = Point(3, rc[LEFT].height - dTbox.height - 6);
	XMoveResizeWindow(display, tbox,
			  pt.x, pt.y, dTbox.width, dTbox.height);
      }
    }
    else {
      pt = Point(rc[LEFT].width - dTbox.width - 8, TaskbarButton::rcTButton.y);
      XMoveResizeWindow(display, tbox,
			pt.x, pt.y, dTbox.width, TaskbarButton::BUTTON_HEIGHT);
    }

    buttonArea = rcRoot.height - (TaskbarButton::rcTButton.y +
				  TaskbarButton::BUTTON_HEIGHT + 12);
    if (rc[LEFT].width < sButton->buttonWidth + dTbox.width + 19)
      buttonArea -= dTbox.height + 9;

    if (TaskbarImage) {
      QvImage::Destroy(imgTBox);
      imgTBox = imgTaskbar->GetOffsetImage(pt);
      imgTBox->SetBackground(tbox);
    }
    break;
    
  case RIGHT:
    if (rc[RIGHT].width < sButton->buttonWidth + dTbox.width + 19) {
      if (rc[RIGHT].width - 9 > dTbox.width) {
	pt = Point((rc[RIGHT].width - dTbox.width - 4) / 2 - 1,
		   rc[RIGHT].height - dTbox.height - 6);
	XMoveResizeWindow(display, tbox,
			  pt.x, pt.y, dTbox.width, dTbox.height);
      }
      else {
	dTbox.width = rc[RIGHT].width - 9;
	if (dTbox.width <= 0)
	  dTbox.width = 1;
	pt = Point(4, rc[RIGHT].height - dTbox.height - 6);
	XMoveResizeWindow(display, tbox,
			  pt.x, pt.y, dTbox.width, dTbox.height);
      }
    }
    else {
      pt = Point(rc[RIGHT].width - dTbox.width - 8,
		 TaskbarButton::rcTButton.y);
      XMoveResizeWindow(display, tbox,
			pt.x, pt.y, dTbox.width, TaskbarButton::BUTTON_HEIGHT);
    }

    buttonArea = rcRoot.height - (TaskbarButton::rcTButton.y +
				  TaskbarButton::BUTTON_HEIGHT + 12);
    if (rc[RIGHT].width < sButton->buttonWidth + dTbox.width + 19)
      buttonArea -= dTbox.height + 9;

    if (TaskbarImage) {
      QvImage::Destroy(imgTBox);
      imgTBox = imgTaskbar->GetOffsetImage(Point(pt.x + 4, pt.y));
      imgTBox->SetBackground(tbox);
    }
    break;
  }
}

/*
 * DrawTaskbarFrame --
 *   Draw the taskbar frame.
 */
void Taskbar::DrawTaskbarFrame()
{
  if (hiding)
    return;

  switch (pos) {
  case BOTTOM:
    XSetForeground(display, gc, gray.pixel);
    XDrawLine(display, frame, gc, rc[BOTTOM].x, 0, rc[BOTTOM].width-1, 0);
    XSetForeground(display, gc, white.pixel);
    XDrawLine(display, frame, gc, rc[BOTTOM].x, 1, rc[BOTTOM].width-1, 1);
    break;

  case TOP:
    XSetForeground(display, gc, darkGray.pixel);
    XDrawLine(display, frame, gc, rc[TOP].x, rc[TOP].height-2,
	      rc[TOP].width-1, rc[TOP].height-2);
    XSetForeground(display, gc, darkGrey.pixel);
    XDrawLine(display, frame, gc, rc[TOP].x, rc[TOP].height-1,
	      rc[TOP].width-1, rc[TOP].height-1);
    break;

  case LEFT:
    XSetForeground(display, gc, darkGray.pixel);
    XDrawLine(display, frame, gc, rc[LEFT].width-2, rc[LEFT].y,
	      rc[LEFT].width-2, rc[LEFT].height-1);
    XSetForeground(display, gc, darkGrey.pixel);
    XDrawLine(display, frame, gc, rc[LEFT].width-1, rc[LEFT].y,
	      rc[LEFT].width-1, rc[LEFT].height-1);
    break;

  case RIGHT:
    XSetForeground(display, gc, gray.pixel);
    XDrawLine(display, frame, gc, 0, rc[RIGHT].y, 0, rc[RIGHT].height-1);
    XSetForeground(display, gc, white.pixel);
    XDrawLine(display, frame, gc, 1, rc[RIGHT].y, 1, rc[RIGHT].height-1);
    break;
  }
}

void Taskbar::DrawTaskbar()
{
  if (hiding)
    return;

  StartButton *sButton = (StartButton *)rootQvwm->tButton;
  int x, y;
  XPoint xp[3];

  XClearWindow(display, w);

  switch (pos) {
  case BOTTOM:
  case TOP:
    // between start button and taskbar buttons
    x = sButton->buttonWidth + 2;
    y = TaskbarButton::rcTButton.y;

    XSetForeground(display, gc, darkGray.pixel);
    XDrawLine(display, w, gc, x + 2, y, x + 2, y + rc[pos].height - 7);
    XSetForeground(display, gc, white.pixel);
    XDrawLine(display, w, gc, x + 3, y, x + 3, y + rc[pos].height - 7);

    xp[0].x = x + 7;
    xp[0].y = y + 2;
    xp[1].x = x + 6;
    xp[1].y = y + 2;
    xp[2].x = x + 6;
    xp[2].y = y + rc[pos].height - 9;
    XSetForeground(display, gc, white.pixel);
    XDrawLines(display, w, gc, xp, 3, CoordModeOrigin);

    xp[0].x = x + 8;
    xp[0].y = y + 2;
    xp[1].x = x + 8;
    xp[1].y = y + rc[pos].height - 9;
    xp[2].x = x + 6;
    xp[2].y = y + rc[pos].height - 9;
    XSetForeground(display, gc, darkGray.pixel);
    XDrawLines(display, w, gc, xp, 3, CoordModeOrigin);

    // line next to taskbar box
    x = rc[pos].width - dTbox.width - 6;

    XSetForeground(display, gc, darkGray.pixel);
    XDrawLine(display, w, gc, x, y, x, y + rc[pos].height - 7);
    XSetForeground(display, gc, white.pixel); 
    XDrawLine(display, w, gc, x + 1, y, x + 1, y + rc[pos].height - 7);
    break;

  case LEFT:
  case RIGHT:
    // between start button and taskbar buttons
    x = TaskbarButton::rcTButton.x;
    y = TaskbarButton::BUTTON_HEIGHT + 2;

    XSetForeground(display, gc, darkGray.pixel);
    XDrawLine(display, w, gc, x, y + 1, x + rc[pos].width - 7, y + 1);
    XSetForeground(display, gc, white.pixel);
    XDrawLine(display, w, gc, x, y + 2, x + rc[pos].width - 7, y + 2);
    
    xp[0].x = rc[pos].width - 9;
    xp[0].y = y + 5;
    xp[1].x = x + 2;
    xp[1].y = y + 5;
    xp[2].x = x + 2;
    xp[2].y = y + 6;
    XSetForeground(display, gc, white.pixel);
    XDrawLines(display, w, gc, xp, 3, CoordModeOrigin);

    xp[0].x = x + 2;
    xp[0].y = y + 7;
    xp[1].x = x + rc[pos].width - 9;
    xp[1].y = y + 7;
    xp[2].x = x + rc[pos].width - 9;
    xp[2].y = y + 5;
    XSetForeground(display, gc, darkGray.pixel);
    XDrawLines(display, w, gc, xp, 3, CoordModeOrigin);

    if (rc[pos].width < sButton->buttonWidth + dTbox.width + 19) {
      // line next to taskbar box
      y = rc[pos].height - dTbox.height - 9;

      XSetForeground(display, gc, darkGray.pixel);
      XDrawLine(display, w, gc, x, y, x + rc[pos].width - 7, y);
      XSetForeground(display, gc, white.pixel); 
      XDrawLine(display, w, gc, x, y + 1, x + rc[pos].width - 7, y + 1);
    }
    break;
  }
}

/*
 * DrawTaskbarBox --
 *   Draw the indicators and the clock in the taskbar box.
 */
void Taskbar::DrawTaskbarBox()
{
  XPoint xp[3];

  DrawClock();

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

  XSetForeground(display, gc, darkGray.pixel);
  XDrawLines(display, tbox, gc, xp, 3, CoordModeOrigin);

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

  XSetForeground(display, gc, white.pixel);
  XDrawLines(display, tbox, gc, xp, 3, CoordModeOrigin);
}

/*
 * DrawClock --
 *   Draw the clock in the right or bottom side.
 */
void Taskbar::DrawClock()
{
  time_t t;
  struct tm *tm;
  int len;
  Point pt;
  XRectangle ink, log;

  XClearArea(display, tbox, 1, 1, dTbox.width - 2, dTbox.height - 2, False);

  time(&t);
  tm = localtime(&t);

  strftime(strClk, sizeof(strClk), ClockFormat, tm);
  len = strlen(strClk);

  XmbTextExtents(fsTaskbar, strClk, len, &ink, &log);
  pt.x = indWidth + IC_MARGIN + (clockWidth - log.width) / 2 - log.x;
  pt.y = (TaskbarButton::BUTTON_HEIGHT - log.height) / 2 - log.y;

  XSetForeground(display, gc, ClockStringColor.pixel);
  XmbDrawString(display, tbox, fsTaskbar, gc, pt.x, pt.y, strClk, len);
}

void Taskbar::AdvanceClock()
{
  time_t t;
  struct tm *tm;
  char str[128];

  time(&t);
  tm = localtime(&t);
  strftime(str, sizeof(str), ClockFormat, tm);
  
  if (strcmp(str, strClk) != 0)
    DrawClock();

  timer->SetTimeout(1000, new Callback<Taskbar>(this, &Taskbar::AdvanceClock));
}

/*
 * Exposure --
 *   Process expose event.
 */
void Taskbar::Exposure(Window win)
{
  if (win == frame)
    DrawTaskbarFrame();
  if (win == w)
    DrawTaskbar();
  else if (win == tbox)
    DrawTaskbarBox();
}

void Taskbar::Button1Press()
{
  Menu::UnmapAllMenus(False);

  if (!OnTopTaskbar) {
    RaiseTaskbar();

    if (UsePager && OnTopPager)
      pager->RaisePager();
  }
}

/*
 * Button1Motion --
 *   Process drag.
 */
void Taskbar::Button1Motion(Window win, const Point& ptRoot)
{
  if (win == w)
    MoveTaskbar(ptRoot);
  else if (win == frame)
    ResizeTaskbar(ptRoot);
}

void Taskbar::Button3Release(const Point& ptRoot)
{
  int dir;

  Menu::UnmapAllMenus(False);
  rootQvwm->SetFocus();

  Point pt = ctrlMenu->GetFixedMenuPos(ptRoot, dir);
  ctrlMenu->MapMenu(pt.x, pt.y, dir);
}

/*
 * IsTaskbarWindows --
 *   Return True if the window is taskbar window.
 */
Bool Taskbar::IsTaskbarWindows(Window win)
{
  if (win == w || win == frame || win == tbox)
    return True;

  return False;
}

void Taskbar::Enter(Window win, const Point& ptRoot, int detail)
{
  if (win == frame) {
    if (!TaskbarAutoHide)
      return;

    if (detail != NotifyInferior)
      if (!Menu::CheckAnyMenusMapped())
	if (hiding) {
	  BasicCallback* cb;
	  cb= new Callback<Taskbar>(this, &Taskbar::ShowTaskbar);
	  timer->SetTimeout(TaskbarShowDelay, cb);
	}
  }
  else if (win == tbox) {
    time_t t;
    struct tm *tm;
    
    time(&t);
    tm = localtime(&t);

    strftime(strTip, sizeof(strTip), ClockMessageFormat, tm);
    toolTip->SetString(strTip, &fsTaskbar);

    toolTip->SetTimer();
  }
}

void Taskbar::Leave(Window win, const Point& ptRoot, int detail)
{
  if (win == frame) {
    if (!TaskbarAutoHide)
      return;

    if (detail != NotifyInferior)
      if (!Menu::CheckAnyMenusMapped())
	if (!hiding)
	  if (InRect(ptRoot, GetScreenRectOnShowing())) {
	    BasicCallback* cb;
	    cb = new Callback<Taskbar>(this, &Taskbar::HideTaskbar);
	    timer->SetTimeout(TaskbarHideDelay, cb);
	  }
  }
  else if (win == tbox)
    toolTip->Disable();
}

void Taskbar::PointerMotion()
{
  if (!toolTip->IsMapped())
    toolTip->ResetTimer();
}

/*
 * Get the rectangle of screen except taskbar on taskbar showing.
 */
Rect Taskbar::GetScreenRectOnShowing() const
{
  Rect rcRoot = rootQvwm->GetRect();

  switch (pos) {
  case TOP:
    return Rect(0, rc[TOP].height, rcRoot.width,
		rcRoot.height - rc[TOP].height);

  case BOTTOM:
    return Rect(0, 0, rcRoot.width, rcRoot.height - rc[BOTTOM].height);

  case LEFT:
    return Rect(rc[LEFT].width, 0,
		rcRoot.width - rc[LEFT].width, rcRoot.height);

  case RIGHT:
    return Rect(0, 0, rcRoot.width - rc[RIGHT].width, rcRoot.height);
  }

  ASSERT(False);
  return Rect(0, 0, 0, 0);
}

/*
 * Get the rectangle of screen except taskbar on taskbar hiding.
 */
Rect Taskbar::GetScreenRectOnHiding() const
{
  Rect rcRoot = rootQvwm->GetRect();

  switch (pos) {
  case TOP:
    return Rect(0, 2, rcRoot.width, rcRoot.height - 2);

  case BOTTOM:
    return Rect(0, 0, rcRoot.width, rcRoot.height - 2);

  case LEFT:
    return Rect(2, 0, rcRoot.width - 2, rcRoot.height);

  case RIGHT:
    return Rect(0, 0, rcRoot.width - 2, rcRoot.height);
  }

  ASSERT(False);
  return Rect(0, 0, 0, 0);
}

void Taskbar::ShowTaskbar()
{
  if (!hiding)
    return;

  hiding = False;

  Rect rcRoot = rootQvwm->GetRect();
  Point pt;

  if (GradTaskbarMotion) {
    switch (pos) {
    case TOP:
      pt = Point(0, 2 - rc[TOP].height);
      break;
      
    case BOTTOM:
      pt = Point(0, rcRoot.height - 2);
      break;
      
    case LEFT:
      pt = Point(2 - rc[LEFT].width, 0);
      break;
      
    case RIGHT:
      pt = Point(rcRoot.width - 2, 0);
      break;
    }
    
    for (int i = GradTaskbarMotionSpeed - 1; i >= 0; i--) {
      XMoveWindow(display, frame,
		  rc[pos].x + (pt.x - rc[pos].x) * i / GradTaskbarMotionSpeed,
		  rc[pos].y + (pt.y - rc[pos].y) * i / GradTaskbarMotionSpeed);

      XFlush(display);
      usleep(10000);
    }
  }
  else
    XMoveWindow(display, frame, rc[pos].x, rc[pos].y);

  XClearWindow(display, frame);

  RaiseTaskbar();

  if (UsePager && OnTopPager)
    pager->RaisePager();
}

void Taskbar::HideTaskbar()
{
  if (hiding)
    return;

  if (startMenu && startMenu->CheckMapped())
    return;

  hiding = True;

  Rect rcRoot = rootQvwm->GetRect();
  Point pt;

  switch (pos) {
  case TOP:
    pt = Point(0, 2 - rc[TOP].height);
    break;

  case BOTTOM:
    pt = Point(0, rcRoot.height - 2);
    break;

  case LEFT:
    pt = Point(2 - rc[LEFT].width, 0);
    break;

  case RIGHT:
    pt = Point(rcRoot.width - 2, 0);
    break;
  }

  if (GradTaskbarMotion) {
    for (int i = GradTaskbarMotionSpeed - 1; i >= 0; i--) {
      XMoveWindow(display, frame,
		  pt.x + (rc[pos].x - pt.x) * i / GradTaskbarMotionSpeed,
		  pt.y + (rc[pos].y - pt.y) * i / GradTaskbarMotionSpeed);

      XFlush(display);
      usleep(10000);
    }
  }
  else
    XMoveWindow(display, frame, pt.x, pt.y);

  XClearWindow(display, frame);

  if (OnTopTaskbar)
    RaiseTaskbar();
  if (UsePager && OnTopPager)
    pager->RaisePager();
}

/*
 * RedrawAllTaskbarButtons --
 *   Redraw all taskbar buttons.
 */
void Taskbar::RedrawAllTaskbarButtons()
{
  List<Qvwm>::Iterator i(&desktop.GetQvwmList());
  Qvwm* tmpQvwm;
  int buttonWidth;
  int row, col;
  int buttonNum = 0, count = 0;
  StartButton *sButton = (StartButton *)rootQvwm->tButton;
  int x, y;
  Point offFrame;
  Rect rcCurPage(paging->origin.x, paging->origin.y,
		 rcScreen.width, rcScreen.height);

  // get offset between frame and w
  switch (pos) {
  case BOTTOM:
    offFrame = Point(0, 4);
    break;
    
  case TOP:
  case LEFT:
    offFrame = Point(0, 0);
    break;

  case RIGHT:
    offFrame = Point(4, 0);
    break;
  }

  ASSERT(sButton);
  ASSERT(rootQvwm->tButton);

  XMoveWindow(display, sButton->GetFrameWin(),
	      TaskbarButton::rcTButton.x, TaskbarButton::rcTButton.y);

  /*
   * Taskbar buttons are unvisible when the taskbar is shown minimumly.
   */
  if (rc[pos].height == 6)
    TaskbarButton::rcTButton.y = -10000;
  if (rc[pos].width == 6)
    TaskbarButton::rcTButton.x = -10000;

  /*
   * Adjust the position and the size of the start button.
   */
  Rect rect = sButton->GetRect();

  rect.x = TaskbarButton::rcTButton.x;
  rect.y = TaskbarButton::rcTButton.y;

  if (rc[pos].width > sButton->buttonWidth + 6)
    rect.width = sButton->buttonWidth;
  else {
    rect.width = rc[pos].width - 6;
    if (rect.width == 0)
      rect.width = 1;
  }

  sButton->MoveResizeButton(rect);

  if (TaskbarImage) {
    sButton->SetBgImage(imgTaskbar, offFrame + Point(rect.x, rect.y));
    sButton->SetBgActiveImage(imgTaskbar,
			      offFrame + Point(rect.x, rect.y - 1));
    sButton->DrawButton();
  }

  // Count the number of taskbar buttons.
  for (tmpQvwm = i.GetHead(); tmpQvwm; tmpQvwm = i.GetNext()) {
    if (!tmpQvwm->CheckFlags(TRANSIENT) && !tmpQvwm->CheckFlags(NO_TBUTTON) &&
	(tmpQvwm->CheckMapped() || tmpQvwm->CheckStatus(MINIMIZE_WINDOW))) {
      if (TaskbarButtonInScr) {
	// count only taskbar buttons in the current page
	if (!Intersect(tmpQvwm->GetRect(), rcCurPage))
	  continue;
      }

      buttonNum++;
    }
  }
     
  if (buttonNum == 0)
    return;

  /*
   * According to taskbar position, determine the way arranging buttons.
   */
  switch (pos) {
  case BOTTOM:
  case TOP:
    row = rc[pos].height / (TaskbarButton::rcTButton.height + BETWEEN_SPACE);
    if (row == 0)
      row = 1;  // quick hack
    buttonWidth = buttonArea * row / RoundUp(buttonNum, row) - BETWEEN_SPACE;
    if (buttonWidth > TaskbarButton::rcTButton.width)
      buttonWidth = TaskbarButton::rcTButton.width;
    if (buttonWidth == 0)
      buttonWidth = 1;
    col = buttonArea / buttonWidth;
    break;

  case LEFT:
  case RIGHT:
    row = buttonArea / (TaskbarButton::rcTButton.height + BETWEEN_SPACE);
    col = (buttonNum + row - 1) / row;
    buttonWidth = (rc[pos].width - 5) / col - BETWEEN_SPACE;
    if (buttonWidth == 0)
      buttonWidth = 1;
    break;

  default:
    col = row = buttonWidth = 0;  // XXX for warning
    ASSERT(False);
  }

  /*
   * Reposition all taskbar buttons.
   */
  for (tmpQvwm = i.GetHead(); tmpQvwm; tmpQvwm = i.GetNext()) {
    if (tmpQvwm->CheckFlags(TRANSIENT) || tmpQvwm->CheckFlags(NO_TBUTTON) ||
	(!tmpQvwm->CheckMapped() && !tmpQvwm->CheckStatus(MINIMIZE_WINDOW)))
      continue;

    TaskbarButton* tb = tmpQvwm->tButton;

    if (TaskbarButtonInScr) {
      // show only taskbar buttons in the current page
      if (!Intersect(tmpQvwm->GetRect(), rcCurPage)) {
	tb->MoveResizeButton(Rect(-1, -1, 1, 1));  // hide
	continue;
      }
    }

    switch (pos) {
    case BOTTOM:
    case TOP:
      x = TaskbarButton::rcTButton.x + sButton->buttonWidth + 13
	+ (buttonWidth + BETWEEN_SPACE) * (count % col);
      y = TaskbarButton::rcTButton.y
	+ (count / col) * (TaskbarButton::rcTButton.height + BETWEEN_SPACE);
      break;

    case LEFT:
    case RIGHT:
      x = TaskbarButton::rcTButton.x
	+ (count % col) * (buttonWidth + BETWEEN_SPACE);
      y = TaskbarButton::rcTButton.y + TaskbarButton::BUTTON_HEIGHT + 12
	+ (TaskbarButton::rcTButton.height + BETWEEN_SPACE) * (count / col);
      break;

    default:
      x = y = 0;  // XXX for warning
      ASSERT(False);
    }
    count++;
    
    tb->MoveResizeButton(Rect(x, y,
			      buttonWidth, TaskbarButton::rcTButton.height));

    if (TaskbarImage) {
      tb->SetBgImage(imgTaskbar, offFrame + Point(x, y));
      tb->SetBgActiveImage(imgTaskbar, offFrame + Point(x, y - 1));
      tb->DrawButton();
    }
  }
}                         

void Taskbar::Initialize()
{
  BASE_HEIGHT = TaskbarButton::BUTTON_HEIGHT + 6;
  INC_HEIGHT = TaskbarButton::BUTTON_HEIGHT + BETWEEN_SPACE;
}


syntax highlighted by Code2HTML, v. 0.9.1