/*
 * switcher.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 <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include "main.h"
#include "qvwm.h"
#include "switcher.h"
#include "qvwmrc.h"
#include "timer.h"
#include "key.h"
#include "focus_mgr.h"
#include "timer.h"

Timer* TaskSwitcher::swTimer;

TaskSwitcher::TaskSwitcher()
{
  XSetWindowAttributes attributes;
  unsigned long valueMask;
  
  /*
   * Create frame window.
   */
  attributes.background_pixel = SwitcherColor.pixel;
  attributes.override_redirect = True;
  attributes.event_mask = KeyPressMask | KeyReleaseMask | ExposureMask;
  valueMask = CWBackPixel | CWOverrideRedirect | CWEventMask;

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

  /*
   * Create the window displaying title name.
   */
  valueMask = CWBackPixel;

  titleFrame = XCreateWindow(display, frame, 14, 0, 322, 24,
			     0, CopyFromParent, InputOutput, CopyFromParent,
			     valueMask, &attributes);

  title = XCreateWindow(display, titleFrame, 2, 2, 318, 20,
			0, CopyFromParent, InputOutput, CopyFromParent,
			valueMask, &attributes);

  if (SwitcherImage) {
    imgSwitcher = CreateImageFromFile(SwitcherImage, swTimer);
    if (imgSwitcher) {
      imgSwitcher->SetBackground(frame);
      imgTitle = NULL;
    }
    else {
      delete [] SwitcherImage;
      SwitcherImage = NULL;
    }
  }

  iterMap = new List<Qvwm>::Iterator(&focusMgr.GetMapList());
  iterUnmap = new List<Qvwm>::Iterator(&focusMgr.GetUnmapList());
}

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

  if (SwitcherImage) {
    QvImage::Destroy(imgSwitcher);
    QvImage::Destroy(imgTitle);
  }
}

void TaskSwitcher::MapSwitcher(int winNum)
{
  int col = (winNum + 6) / 7;

  ASSERT(rootQvwm);
  Rect rcRoot = rootQvwm->GetRect();

  rc = Rect((rcRoot.width - BASE_WIDTH) / 2,
	    (rcRoot.height - BASE_HEIGHT - col * 43) / 2,
	    BASE_WIDTH,
	    BASE_HEIGHT + col * 43);

  XMoveResizeWindow(display, frame, rc.x, rc.y, rc.width, rc.height);
  XMoveWindow(display, titleFrame, 14, 29 + col * 43);

  if (SwitcherImage) {
    QvImage::Destroy(imgTitle);
    imgTitle = imgSwitcher->GetOffsetImage(Point(14 + 2, 29 + col * 43 + 2));
    imgTitle->SetBackground(title);
  }

  XMapWindow(display, titleFrame);
  XMapWindow(display, title);
  XMapRaised(display, frame);
}

/*
 * SwitchTask --
 *   Display task switcher and switch focus window.
 *   This routine must be called by modifier + code.
 *   mod is with LockMask.
 */
void TaskSwitcher::SwitchTask(Bool forward, KeyCode code, unsigned int mod)
{
  int winNum = focusMgr.GetAllNum();
  Qvwm* qvWm;
  
  if (winNum == 0)
    return;
  else if (winNum == 1) {
    qvWm = focusMgr.Top();
    if (qvWm == NULL)
      qvWm = focusMgr.TopUnmapList();

    qvWm->SetFocus();
    if (qvWm->CheckMapped())
      qvWm->RaiseWindow(True);
    else
      qvWm->RestoreWindow();
    qvWm->AdjustPage();
    return;
  }

  direct = forward;

  MapSwitcher(winNum);
  XSetInputFocus(display, frame, RevertToParent, CurrentTime);

  EventLoop(winNum, code, mod);
}

void TaskSwitcher::EventLoop(int winNum, KeyCode code, unsigned int mod)
{
  Qvwm* qvWm = GetFirstFocus();
  int focusWin;
  XEvent ev;
  int fd = ConnectionNumber(display);
  fd_set fds;
  struct timeval tm;

  if (Qvwm::focusQvwm == rootQvwm)
    focusWin = 0;
  else
    focusWin = direct ? 1 : winNum - 1;

  XGrabServer(display);

  while (1) {
    FD_ZERO(&fds);
    FD_SET(fd, &fds);

    while (XPending(display) != 0) {
      XNextEvent(display, &ev);

      switch (ev.type) {
      case Expose:
	DrawTaskSwitcher();
	ASSERT(qvWm);
	DrawTitle(qvWm->name);
	DrawPixmaps(winNum);
	DrawRect(focusWin, winNum);
    
	while (XCheckWindowEvent(display, ev.xexpose.window, ExposureMask,
				 &ev))
	  ;
	break;
    
      case KeyPress:
	if (ev.xkey.keycode == code && ev.xkey.state == mod) {
	  qvWm = direct ? GetNextFocus() : GetPrevFocus();
	  ASSERT(qvWm);
	  DrawTitle(qvWm->name);
	  
	  if (direct) {
	    focusWin++;
	    if (focusWin == winNum)
	      focusWin = 0;
	  }
	  else {
	    focusWin--;
	    if (focusWin == -1)
	      focusWin = winNum - 1;
	  }
	  DrawRect(focusWin, winNum);
	}
	if (XKeycodeToKeysym(display, ev.xkey.keycode, 0) == XK_Escape) {
	  XUnmapWindow(display, frame);
	  XUngrabServer(display);
	  
	  if (ClickToFocus)
	    XSetInputFocus(display, qvWm->GetWin(), RevertToParent,
			   CurrentTime);
	  else
	    XSetInputFocus(display, root, RevertToParent, CurrentTime);
	  return;
	}
	break;

      case KeyRelease:
	if (mod & ShiftMask) {
	  if (XKeycodeToKeysym(display, ev.xkey.keycode, 0) != XK_Shift_L &&
	      XKeycodeToKeysym(display, ev.xkey.keycode, 0) != XK_Shift_R)
	    break;
	}
	else if (mod & ControlMask) {
	  if (XKeycodeToKeysym(display, ev.xkey.keycode, 0) != XK_Control_L &&
	      XKeycodeToKeysym(display, ev.xkey.keycode, 0) != XK_Control_R)
	    break;
	}
	else if (mod & ShortCutKey::getAltMask()) {
	  if (XKeycodeToKeysym(display, ev.xkey.keycode, 0) != XK_Alt_L &&
	      XKeycodeToKeysym(display, ev.xkey.keycode, 0) != XK_Alt_R)
	    break;
	}
	else if (mod & ShortCutKey::getMetaMask()) {
	  if (XKeycodeToKeysym(display, ev.xkey.keycode, 0) != XK_Meta_L &&
	      XKeycodeToKeysym(display, ev.xkey.keycode, 0) != XK_Meta_R)
	    break;
	}

	XUnmapWindow(display, frame);
	XUngrabServer(display);
      
	if (qvWm->CheckMapped()) {
	  qvWm->SetFocus();
	  /*
	   * If a new active window has already have a focus, XSetInputFocus
	   * doesn't call and the window with a mouse pointer gets an input
	   * focus.  To avoid this problem, qvwm calls XSetInputFocus below.
	   */
	  XSetInputFocus(display, qvWm->GetWin(), RevertToParent, CurrentTime);
	  qvWm->RaiseWindow(True);
	}
	else
	  qvWm->RestoreWindow();
	qvWm->AdjustPage();
	return;
      }

      swTimer->CheckTimeout(&tm);
    }

    if (!swTimer->CheckTimeout(&tm)) {
      tm.tv_sec = 1;
      tm.tv_usec = 0;
	
      XFlush(display);
    }

#if defined(__hpux) && !defined(_XPG4_EXTENDED)
    if (select(fd + 1, (int *)&fds, 0, 0, &tm) == 0) {    // timeout
#else
    if (select(fd + 1, &fds, 0, 0, &tm) == 0) {    // timeout
#endif
      swTimer->CheckTimeout(&tm);
    }
  }
}

/*
 * Called firstly once
 */
Qvwm* TaskSwitcher::GetFirstFocus()
{
  Qvwm* qvWm;

  qvWm = iterMap->GetHead();
  if (qvWm) {
    LookMapList = True;
    if (Qvwm::focusQvwm == qvWm)
      qvWm = direct ? GetNextFocus() : GetPrevFocus();
  }
  else {
    qvWm = iterUnmap->GetHead();
    ASSERT(qvWm);
    LookMapList = False;
  }

  return qvWm;
}

/*
 * Get next window in the focus stack, including unmapped windows
 */
Qvwm* TaskSwitcher::GetNextFocus()
{
  Qvwm* qvWm;

  if (LookMapList) {
    qvWm = iterMap->GetNext();
    if (qvWm == NULL) {
      qvWm = iterUnmap->GetHead();
      if (qvWm)
	LookMapList = False;
      else {
	qvWm = iterMap->GetHead();
	ASSERT(qvWm);
      }
    }
  }
  else {
    qvWm = iterUnmap->GetNext();
    if (qvWm == NULL) {
      qvWm = iterMap->GetHead();
      if (qvWm)
	LookMapList = True;
      else {
	qvWm = iterUnmap->GetHead();
	ASSERT(qvWm);
      }
    }
  }

  return qvWm;
}

/*
 * Get prev window in the focus stack, including unmapped windows
 */
Qvwm* TaskSwitcher::GetPrevFocus()
{
  Qvwm* qvWm;

  if (LookMapList) {
    qvWm = iterMap->GetPrev();
    if (qvWm == NULL) {
      qvWm = iterUnmap->GetTail();
      if (qvWm)
	LookMapList = False;
      else {
	qvWm = iterMap->GetTail();
	ASSERT(qvWm);
      }
    }
  }
  else {
    qvWm = iterUnmap->GetPrev();
    if (qvWm == NULL) {
      qvWm = iterMap->GetTail();
      if (qvWm)
	LookMapList = True;
      else {
	qvWm = iterUnmap->GetTail();
	ASSERT(qvWm);
      }
    }
  }

  return qvWm;
}

/*
 * DrawTaskSwitcher --
 *   Draw task switcher.
 */
void TaskSwitcher::DrawTaskSwitcher()
{
  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, gray.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, darkGrey.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);
  
  /*
   * The frame of title area.
   */
  xp[0].x = TITLE_WIDTH - 2;
  xp[0].y = 0;
  xp[1].x = 0;
  xp[1].y = 0;
  xp[2].x = 0;
  xp[2].y = TITLE_HEIGHT - 2;

  XSetForeground(display, gc, darkGray.pixel);
  XDrawLines(display, titleFrame, gc, xp, 3, CoordModeOrigin);
  
  xp[0].x = TITLE_WIDTH - 1;
  xp[0].y = 0;
  xp[1].x = TITLE_WIDTH - 1;
  xp[1].y = TITLE_HEIGHT - 1;
  xp[2].x = 0;
  xp[2].y = TITLE_HEIGHT - 1;
  
  XSetForeground(display, gc, white.pixel);
  XDrawLines(display, titleFrame, gc, xp, 3, CoordModeOrigin);
  
  xp[0].x = TITLE_WIDTH - 3;
  xp[0].y = 1;
  xp[1].x = 1;
  xp[1].y = 1;
  xp[2].x = 1;
  xp[2].y = TITLE_HEIGHT - 3;
  
  XSetForeground(display, gc, darkGrey.pixel);
  XDrawLines(display, titleFrame, gc, xp, 3, CoordModeOrigin);
  
  xp[0].x = TITLE_WIDTH - 2;
  xp[0].y = 1;
  xp[1].x = TITLE_WIDTH - 2;
  xp[1].y = TITLE_HEIGHT - 2;
  xp[2].x = 1;
  xp[2].y = TITLE_HEIGHT - 2;
  
  XSetForeground(display, gc, SwitcherColor.pixel);
  XDrawLines(display, titleFrame, gc, xp, 3, CoordModeOrigin);
}

/*
 * DrawTitle --
 *   Draw title name.
 */
void TaskSwitcher::DrawTitle(const char* name)
{
  XRectangle ink, log;
  Point pt;

  XSetBackground(display, gc, SwitcherColor.pixel);
  XClearWindow(display, title);

  XmbTextExtents(fsTitle, name, strlen(name), &ink, &log);
  pt.x = 2 - log.x;
  pt.y = (TITLE_HEIGHT - log.height) / 2 - log.y;

  XSetForeground(display, gc, SwitcherStringColor.pixel);
  XmbDrawString(display, title, fsTitle, gc, pt.x, pt.y, name, strlen(name));
}

/*
 * DrawRect --
 *   Draw the rectangle which indicates that the window is selected.
 */
void TaskSwitcher::DrawRect(int focusWin, int winNum)
{
  int base;
  Rect rect(0, 0, 42, 42);

  if (winNum < 7) {
    base = (BASE_WIDTH - winNum * 43 - 3) / 2 + 3;
    if (direct)
      rect.x = base + ((focusWin - 1 + winNum) % winNum) * 43 + 1;
    else
      rect.x = base + ((focusWin + 1) % winNum) * 43 + 1;
    rect.y = 19 + 1;
  }
  else {
    base = 26;
    if (direct) {
      if (focusWin == 0) {
	rect.x = base + ((winNum - 1) % 7) * 43 + 1;
	rect.y = 19 + ((winNum - 1) / 7) * 43 + 1;
      }
      else {
	rect.x = base + ((focusWin + 6) % 7) * 43 + 1;
	rect.y = 19 + ((focusWin - 1) / 7) * 43 + 1;
      }
    }
    else {
      if (focusWin == winNum - 1) {
	rect.x = base + 1;
	rect.y = 19 + 1;
      }
      else {
	rect.x = base + ((focusWin + 1) % 7) * 43 + 1;
	rect.y = 19 + ((focusWin + 1) / 7) * 43 + 1;
      }
    }
  }

  /*
   * Erase the previous rectangle.
   */
  XSetLineAttributes(display, gc, 2, LineSolid, CapButt, JoinMiter);

  if (SwitcherImage) {
    XClearArea(display, frame,
	       rect.x - 1, rect.y - 1, rect.width + 1, 2, False);
    XClearArea(display, frame,
	       rect.x - 1, rect.y - 1, 2, rect.height + 1, False);
    XClearArea(display, frame,
	       rect.x + rect.width - 1, rect.y - 1, 2, rect.height + 1, False);
    XClearArea(display, frame,
	       rect.x - 1, rect.y + rect.height - 1, rect.width + 1, 2, False);
  }
  else {
    XSetForeground(display, gc, SwitcherColor.pixel);
    XDrawRectangle(display, frame, gc,
		   rect.x, rect.y, rect.width, rect.height);
  }

  /*
   * Draw new rectangle.
   */
  rect.x = base + (focusWin % 7) * 43 + 1;
  rect.y = 19 + (focusWin / 7) * 43 + 1;

  XSetForeground(display, gc, SwitcherActiveColor.pixel);
  XDrawRectangle(display, frame, gc, rect.x, rect.y, rect.width, rect.height);

  /*
   * Restore line thickness.
   */
  XSetLineAttributes(display, gc, 1, LineSolid, CapButt, JoinMiter);
}

/*
 * DrawPixmaps --
 *   Draw pixmaps which indicate windows.
 */
void TaskSwitcher::DrawPixmaps(int winNum)
{
  int base = 26, num = 0;

  if (winNum < 7)
    base = (BASE_WIDTH - winNum * 43 - 3) / 2 + 3;

  // draw windows in the map list
  List<Qvwm>::Iterator i(&focusMgr.GetMapList());

  for (Qvwm* qvWm = i.GetHead(); qvWm; qvWm = i.GetNext()) {
    ASSERT(num < winNum);
    DrawPixmap(qvWm, num, base);
    num++;
  }

  // draw windows in the unmap list
  List<Qvwm>::Iterator j(&focusMgr.GetUnmapList());

  for (Qvwm* qvWm = j.GetHead(); qvWm; qvWm = j.GetNext()) {
    ASSERT(num < winNum);
    DrawPixmap(qvWm, num, base);
    num++;
  }
}

void TaskSwitcher::DrawPixmap(Qvwm* qvWm, int num, int base)
{
  Point pt;

  pt.x = base + (num % 7) * 43 + 5;
  pt.y = 19 + (num / 7) * 43 + 5;

  QvImage* img = qvWm->imgLarge;  // use only here (not need Duplicate)
  img->Display(frame, pt);
}

void TaskSwitcher::Initialize()
{
  swTimer = new Timer();
}


syntax highlighted by Code2HTML, v. 0.9.1