/*
 * blwm.cc
 *
 * Copyright (C) 1995, 1996, 1997, 1997, 1998, 1999, 2000, 2001, 2002 Kenichi Kourai
 * Copyright (C) 1999, 2000, 2001, 2002 Luiz Blanes
 *
 * 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 of the License, 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 blwm; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
#include <X11/extensions/shape.h>
#include <X11/Xatom.h>
#include "main.h"
#include "misc.h"
#include "blwm.h"
#include "util.h"
#include "fbutton.h"
#include "tbutton.h"
#include "sbutton.h"
#include "menu.h"
#include "startmenu.h"
#include "event.h"
#include "taskbar.h"
#include "icon.h"
#include "blwmrc.h"
#include "switcher.h"
#include "paging.h"
#include "pager.h"
#include "mini.h"
#include "key.h"
#include "indicator.h"
#include "focus_mgr.h"
#include "desktop.h"
#include "mwm.h"
#include "gnome.h"
#include "exec.h"
#include "image.h"
#include "session.h"
#include "tooltip.h"

Bool		Blwm::umReserved = False;
XContext	Blwm::context;

int Blwm::TITLE_HEIGHT;
int Blwm::BORDER_WIDTH;
int Blwm::TOP_BORDER;

/*
 * Blwm class constructor --
 *   Position the window at that point if usrPos is True;
 *   otherwise, the position of the window obeys a given rule.
 */
Blwm::Blwm(Window w, Bool usrPos)
: wOrig(w)
{
  flags = 0;
  status = 0;
  mapped = False;
  imgLarge = NULL;
  imgSmall = NULL;
  nCmapWins = 0;
  name = shortname = NULL;
  isFirstMapped = True;
#ifdef USE_XSMP
  smClientId = NULL;
  wmRole = NULL;
#endif

  XGrabServer(display);

  /*
   * Get some hints.
   */
  wmHints = XGetWMHints(display, w);
  GetWindowSizeHints();
  Gnome::GetHints(this);
  Mwm::GetHints(this);
  GetWindowClassHints();
  FetchWMProtocols();
  GetTransientForHint();

  Gnome::SetInitStates(this);

  GetWindowAttrs(usrPos);

  if (Intersect(rc, rootBlwm->GetRect()))
    Gnome::ResetState(this, WIN_STATE_HID_WORKSPACE);
  else
    Gnome::SetState(this, WIN_STATE_HID_WORKSPACE);

  Rect rcCtrl, rcButton[3];

  CalcFramePos(rc, &rcParent, &rcTitle, rcCorner, rcSide, rcEdge, &rcCtrl,
	       rcButton);

  CreateFrame(rc);
  CreateParent(rcParent);
  CreateSides(rcSide);
  CreateCorners(rcCorner);  
  CreateEdges(rcEdge);
  CreateTitle(rcTitle);
  CreateCtrlButton(rcCtrl);

  if (CheckFlags(THIN_BORDER))
    XSetWindowBorderWidth(display, frame, 1);

  /*
   * Create three kinds of frame button.
   */
  fButton[0] = new FrameButton1(this, title, rcButton[0]);
  fButton[0]->ChangeImage(FrameButton::MINIMIZE);
  fButton[1] = new FrameButton2(this, title, rcButton[1]);
  fButton[1]->ChangeImage(FrameButton::MAXIMIZE);
  fButton[2] = new FrameButton3(this, title, rcButton[2]);
  fButton[2]->ChangeImage(FrameButton::CLOSE);
  
  if (imgLarge == NULL)
    imgLarge = GetFixedIcon(32);
  if (imgSmall == NULL)
    imgSmall = GetFixedIcon(16);

  Rect rect(-50, -50, 50, 50);
  BlImage* img = imgSmall->Duplicate();
  tButton = new TaskbarButton(this, rect, img);

  ctrlMenu = ::ctrlMenu;

  ChangeOrigWindow();
  SendConfigureEvent();
  int bShaped, cShaped;
  int xbs, ybs, xcs, ycs;
  unsigned int wbs, hbs, wcs, hcs;

  if (shapeSupport) {
    XShapeSelectInput(display, w, ShapeNotifyMask);
    XShapeQueryExtents(display, w, &bShaped, &xbs, &ybs, &wbs, &hbs,
		       &cShaped, &xcs, &ycs, &wcs, &hcs);
    if (bShaped) {
      SetFlags(SHAPED);
      SetShape();
    }
  }

  if (UsePager)
    mini = new Miniature(this, rc);

  scKey->GrabKeys(frame);

  SetTransient();

  XUngrabServer(display);
}

/*
 * Blwm class constructor --
 *   For only root window.
 */
Blwm::Blwm(Window root)
: wOrig(root), frame(root)
{
  Rect rect(0, 0, BUTTON_WIDTH, BUTTON_HEIGHT);

  rc = Rect(0, 0,
	    DisplayWidth(display, screen), DisplayHeight(display, screen));

  parent = title = ctrl = None;
  for (int i = 0; i < 4; i++) {
    corner[i] = None;
    side[i]= None;
  }
  mapped = True;
  flags = 0L;
  status = 0;
  nCmapWins = 0;
  name = new char[10];
  strcpy(name, "blwm root");
  shortname = NULL;
  toolTip = NULL;

#ifdef USE_XSMP
  smClientId = NULL;
  wmRole = NULL;
#endif

  XRectangle ink, log;

  XmbTextExtents(fsTaskbar, StartButtonTitle, strlen(StartButtonTitle),
                 &ink, &log);
  log.width += 2 + StartButton::SYMBOL_SIZE + 4 + 2 + 4;

  taskBar = new Taskbar(this, log.width + 4, TaskbarRows);
  tButton = new StartButton(this, log.width);

  rect = Rect(0, 0, 0, 0);
  ctrlMenu = new Menu(DesktopMenuItem, fsCtrlMenu, NULL, this);

  XSaveContext(display, root, context, (caddr_t)this);

  focusBlwm = this;

  scKey->GrabKeys(root);
}

Blwm::~Blwm()
{
  int i;

  if (this != rootBlwm) {
    XDeleteContext(display, frame, context);
    XDeleteContext(display, parent, context);
    XDeleteContext(display, wOrig, context);
    XDeleteContext(display, title, context);
    for (i = 0; i < 4; i++) {
      XDeleteContext(display, corner[i], context);
      XDeleteContext(display, side[i], context);
    }
    XDeleteContext(display, ctrl, context);

    BlImage::Destroy(imgLarge);
    BlImage::Destroy(imgSmall);

    if (TitlebarImage)
      BlImage::Destroy(imgTitle);
    if (TitlebarActiveImage)
      BlImage::Destroy(imgActiveTitle);

    if (FrameImage) {
      for (i = 0; i < 4; i++) {
	BlImage::Destroy(imgSide[i]);
	BlImage::Destroy(imgCorner[i]);
      }
    }
    if (FrameActiveImage) {
      for (i = 0; i < 4; i++) {
	BlImage::Destroy(imgActiveSide[i]);
	BlImage::Destroy(imgActiveCorner[i]);
      }
    }

    if (GradTitlebar) {
      if (pixGrad)
	XFreePixmap(display, pixGrad);
      if (pixActiveGrad)
	XFreePixmap(display, pixActiveGrad);
    }

    delete toolTip;

    if (wmHints)
      XFree(wmHints);

    if (classHints.res_name)
      XFree(classHints.res_name);
    if (classHints.res_class)
      XFree(classHints.res_class);

#ifdef USE_XSMP
    if (wmRole)
      XFree(wmRole);
    if (smClientId)
      XFree(smClientId);
#endif

    /*
     * Delete frame buttons before the parent window(frame) is destroyed.
     */
    for (i = 0; i < 3; i++)
      delete fButton[i];
    delete tButton;

    /*
     * When this is a transient window.
     */
    if (CheckFlags(TRANSIENT) && blMain) {
      List<Blwm>::Iterator li(&blMain->trList);
      Blwm* blWm = li.GetHead();

      while (blWm) {
	if (blWm == this) {
	  blMain->trList.Remove(this);
	  break;
	}
	blWm = li.GetNext();
      }
    }
    else {
      /*
       * When this is a main window which has some transient windows.
       */
      List<Blwm>::Iterator li(&trList);
      Blwm* blTrans = li.GetHead();

      while (blTrans) {
	blTrans->blMain = NULL;
	blTrans = li.GetNext();
      }
    }

    if (CheckFlags(ONTOP))
      desktop.GetOnTopList().Remove(this);

    if (UsePager)
      delete mini;

    XDestroyWindow(display, frame);
  }
  else
    delete ctrlMenu;

  delete [] name;
  delete [] shortname;
}

void Blwm::GetWindowAttrs(Bool usrPos)
{
  XWindowAttributes attr;

  XGetWindowAttributes(display, wOrig, &attr);

  /*
   * Preserve original border width and set border width to 0.
   */
  bwOrig = attr.border_width;
  XSetWindowBorderWidth(display, wOrig, 0);

#ifdef USE_XSMP
  /*
   * Restore properties and attributes from the previous session
   */
  GetSMClientId(); // initialize smClientId
  GetWMRole(); // initialize wmRole
  int ax, ay, aw, ah;
  if (session->SearchProto(smClientId, wmRole,
			   classHints.res_class,
			   classHints.res_name,
			   name, &ax, &ay, &aw, &ah)) {
    usrPos = True; // force place
    attr.x = ax;
    attr.y = ay;
    attr.width = aw;
    attr.height = ah;
  }
#endif // USE_XSMP

  Point pageoff;
  ExecPending* pending = ExecPending::LookInList(name, classHints);

  if (pending) {
    pageoff = pending->GetPageOff();
    ExecPending::m_pendingList.Remove(pending);
    delete pending;
  }
  else
    pageoff = paging->origin;

  /*
   * Calculate the window position.
   */
  if (usrPos || (flags & (TRANSIENT | FIXED_POS)) ||
      (hints.flags & (USPosition | PPosition))) {
     if (CheckFlags(GEOMETRY)) {
       if (geom->bitmask & WidthValue)
 	attr.width = geom->rc.width;
       if (geom->bitmask & HeightValue)
 	attr.height = geom->rc.height;
     }

    if (CheckFlags(STICKY))
      rcOrig = Rect(attr.x, attr.y, attr.width, attr.height);
    else
      rcOrig = Rect(attr.x + pageoff.x, attr.y + pageoff.y,
		    attr.width, attr.height);
    CalcWindowPos(rcOrig);
  }
  else {
     if (CheckFlags(GEOMETRY)) {
       if (geom->bitmask & WidthValue)
 	attr.width = geom->rc.width;
       if (geom->bitmask & HeightValue)
 	attr.height = geom->rc.height;
 
       if (geom->bitmask & XNegative)
 	attr.x = DisplayWidth(display, screen) + geom->rc.x - attr.width;
       else if (geom->bitmask & XValue)
 	attr.x = geom->rc.x;
 
       if (geom->bitmask & YNegative)
 	attr.y = DisplayHeight(display, screen) + geom->rc.y - attr.height;
       else if (geom->bitmask & YValue)
 	attr.y = geom->rc.y;
 
       if (CheckFlags(STICKY))
 	rcOrig = Rect(attr.x, attr.y, attr.width, attr.height);
       else
 	rcOrig = Rect(attr.x + pageoff.x, attr.y + pageoff.y,
 		      attr.width, attr.height);
       
       CalcWindowPos(rcOrig);
     }
     else {
       int borderWidth, topBorder, titleHeight, titleEdge;
  
       GetBorderAndTitle(borderWidth, topBorder, titleHeight, titleEdge);
  
       rc.width = attr.width + borderWidth * 2;
       rc.height = attr.height + borderWidth + topBorder + titleHeight
 	+ titleEdge;
 
       Point pt = desktop.GetNextPlace(Dim(rc.width, rc.height), pageoff);
 
       rc.x = pt.x;
       rc.y = pt.y;
 
       if (CheckFlags(STICKY))
 	rcOrig = Rect(rc.x - rcScreen.x - pageoff.x,
 		      rc.y - rcScreen.y - pageoff.y,
 		      attr.width,
 		      attr.height);
       else
 	rcOrig = Rect(rc.x - rcScreen.x, rc.y - rcScreen.y,
 		      attr.width, attr.height);
     }

  }
}

/*
 * ChangeOrigWindow --
 *   Change parent and attributes of original window.
 */
void Blwm::ChangeOrigWindow()
{
  XSetWindowAttributes attributes;
  unsigned long valueMask;

  XReparentWindow(display, wOrig, parent, 0, 0);

  attributes.event_mask = StructureNotifyMask | PropertyChangeMask |
    EnterWindowMask | LeaveWindowMask | ColormapChangeMask;

  attributes.do_not_propagate_mask = ButtonPressMask | ButtonReleaseMask;
  valueMask = CWEventMask | CWDontPropagate;

  XChangeWindowAttributes(display, wOrig, valueMask, &attributes);

  XSaveContext(display, wOrig, context, (caddr_t)this);
  XAddToSaveSet(display, wOrig);

}

/*
 * SetTransient
 *   Search pre-mapped transient windows for this window to handle
 *   a transient window properly even if the main window is mapped
 *   after the transient window is mapped first.
 */
void Blwm::SetTransient()
{
  List<Blwm>::Iterator i(&desktop.GetBlwmList());
  Window tWin;

  for (Blwm* blWm = i.GetHead(); blWm; blWm = i.GetNext()) {
    if (blWm->CheckFlags(TRANSIENT) && blWm->blMain == NULL)
      if (XGetTransientForHint(display, blWm->GetWin(), &tWin))
	if (tWin == wOrig) {
	  blWm->blMain = this;  // blWm is a transient window of this
	  if (CheckFlags(ONTOP) && !blWm->CheckFlags(ONTOP)) {
	    blWm->SetFlags(ONTOP);
	    desktop.GetOnTopList().InsertTail(blWm);
	    Gnome::ChangeLayer(blWm, WIN_LAYER_ONTOP);
	  }
	  trList.InsertTail(blWm);
	}
  }
}

/*
 * SendMessage --
 *   Send the messeage to this window.
 */
void Blwm::SendMessage(Atom atom)
{
  XClientMessageEvent cev;

  cev.type = ClientMessage;
  cev.window = wOrig;
  cev.message_type = _XA_WM_PROTOCOLS;
  cev.format = 32;
  cev.data.l[0] = atom;
  cev.data.l[1] = CurrentTime;

#ifdef DEBUG
  if (atom == _XA_WM_DELETE_WINDOW)
    printf("Send delete msg to '%s'\n", name);
#endif

  XSendEvent(display, wOrig, False, 0L, (XEvent *)&cev);
}

/*
 * AdjustPage --
 *   Switch to the page where the window center point exists.
 */
void Blwm::AdjustPage()
{
  if (this == rootBlwm)
    return;

  if (CheckFlags(STICKY))
    return;

  Rect rect = GetRect();
  Point ptCenter(rect.x+rect.width/2, rect.y+rect.height/2);
  Rect rcRoot = rootBlwm->GetRect();
  
  if (ptCenter.x >= 0)
    ptCenter.x =  ptCenter.x / rcRoot.width * rcRoot.width;
  else
    ptCenter.x = (ptCenter.x / rcRoot.width - 1) * rcRoot.width;

  if (ptCenter.y >= 0)
    ptCenter.y = ptCenter.y / rcRoot.height * rcRoot.height;
  else 
    ptCenter.y = (ptCenter.y / rcRoot.height - 1) * rcRoot.height;
  
  ASSERT(paging);

  paging->PagingProc(ptCenter, False);
}

/*
 * Exposure --
 *   Process exposure event.
 */
void Blwm::Exposure(Window win)
{
  if (win == ctrl)
    DrawCtrlMenuMark();
  else if (win == title)
    DrawTitle(False);
  else
    DrawFrame(win);  // win may not be a part of frame windows
}

/*
 * Button1Press --
 *   Process the press of button1(mouse left button).
 */
void Blwm::Button1Press(Window win, Time clickTime, const Point& ptRoot,
			unsigned int state)
{
  if (this != rootBlwm)
    toolTip->Disable();

  if (UseTaskbar && TaskbarAutoHide && !taskBar->IsHiding())
    taskBar->HideTaskbar();

  if (win == ctrl)
    CtrlButton1Press(clickTime, ptRoot, state);
  else {
    Menu::UnmapAllMenus();
    
    if (!CheckFocus())
      SetFocus();

    if (Icon::focusIcon != NULL)
      Icon::focusIcon->ResetFocus();

    if (ClickingRaises || win != wOrig)
      RaiseWindow(True);

    /*
     * Title bar.
     */
    if (win == title)
      TitleButton1Press(clickTime, ptRoot);

    SetStatus(PRESS_FRAME);
  }

  if (AutoRaise && focusBlwm == this && activeBlwm != this)
    RaiseWindow(True);
}

/*
 * Button1Release --
 *   Process the release of button1(mouse left button).
 */
void Blwm::Button1Release()
{
  ResetStatus(PRESS_FRAME);

  /*
   * Unmap control menu if the menu is shown and menu button is pressed.
   */
  if (umReserved) {
    ASSERT(ctrlMenu);
    ctrlMenu->UnmapMenu();
    umReserved = False;
  }
}    

void Blwm::Button2Press(Window win)
{
  if (this != rootBlwm)
    toolTip->Disable();

  if (win == side[0] || win == side[1] || win == side[2] || win == side[3] ||
      win == corner[0] || win == corner[1] || win == corner[2] ||
      win == corner[3])
    SetStatus(PRESS_FRAME);

  if (AutoRaise && focusBlwm == this && activeBlwm != this)
    RaiseWindow(True);
}

void Blwm::Button2Release(Window win)
{
  ResetStatus(PRESS_FRAME);
  if (win != frame)
    LowerWindow();
}

void Blwm::Button3Press()
{
  if (this != rootBlwm)
    toolTip->Disable();

  if (AutoRaise && focusBlwm == this && activeBlwm != this)
    RaiseWindow(True);
}

/*
 * Button3Release --
 *   Process the release of button3(mouse right button).
 */
void Blwm::Button3Release(Window win, const Point& ptRoot)
{
  Point pt;
  int dir;

  if (this == rootBlwm) {
    Menu::UnmapAllMenus();
    
    if (!CheckFocus())
      SetFocus();
    
    ASSERT(ctrlMenu);
    pt = ctrlMenu->GetFixedMenuPos(ptRoot, dir);
    ctrlMenu->MapMenu(pt.x, pt.y, dir);
  }
  else {
    Menu::UnmapAllMenus();
    
    if (!CheckFocus())
      SetFocus();

    if (CheckMapped())
      RaiseWindow(True);
    else
      RestoreWindow();

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

/*
 * Button1Motion --
 *   Move or resize according to mouse movement.
 */
void Blwm::Button1Motion(Window win, const Point& ptRoot)
{
  if (win == title && !CheckStatus(MAXIMIZE_WINDOW))
    MoveWindow(ptRoot, True);
  else if (win == side[0])
    ResizeWindow(P_LEFT, True);
  else if (win == side[1])
    ResizeWindow(P_RIGHT, True);
  else if (win == side[2])
    ResizeWindow(P_TOP, True);
  else if (win == side[3])
    ResizeWindow(P_BOTTOM, True);
  else if (win == corner[0])
    ResizeWindow(P_LEFT | P_TOP, True);
  else if (win == corner[1])
    ResizeWindow(P_RIGHT | P_TOP, True);
  else if (win == corner[2])
    ResizeWindow(P_LEFT | P_BOTTOM, True);
  else if (win == corner[3])
    ResizeWindow(P_RIGHT | P_BOTTOM, True);
}

void Blwm::Button2Motion(Window win, const Point& ptRoot)
{
  if (!CheckStatus(MAXIMIZE_WINDOW))
    MoveWindow(ptRoot, True);
}

void Blwm::Enter(Window win, unsigned int state)
{
  if (win == title) {
    if (strcmp(name, shortname) != 0)
      toolTip->SetTimer();
  }

  if ((state & NoFocusChangeMask) == NoFocusChangeMask)
    return;

  if (!ClickToFocus && !(NoDesktopFocus && this == rootBlwm) &&
      !CheckStatus(MINIMIZE_WINDOW) && !CheckFlags(FOCUS_ON_CLICK)) {
    if (!CheckFocus() && !Menu::CheckAnyMenusMapped()) {
      if (AutoRaise)
	RaiseWindow(False);
      SetFocus();
    }
  }
}

void Blwm::Leave(Window win)
{
  if (win == title)
    toolTip->Disable();
}

void Blwm::PointerMotion(Window win)
{
  if (win == title) {
    if (!toolTip->IsMapped())
      toolTip->ResetTimer();
  }
}

void Blwm::Initialize()
{
  context = XUniqueContext();

  TITLE_HEIGHT = FrameTitleHeight;
  BORDER_WIDTH = FrameBorderWidth + 4;
  TOP_BORDER = FrameBorderWidth + 2;

  CreateTitlebarPixmap();
  CreateFramePixmap();
}

#ifdef USE_XSMP
/*
 * SavePropertiesToFile --
 *   Save the state of this window to the FILE for XSMP
 */
Bool Blwm::SavePropertiesToFile(int idx, FILE* fp)
{
  if (blMain) {
    // this window is a transient win.
    return True;
  }
  GetSMClientId();
  GetWMRole();
  // WM_CLASS  (if WM_WINDOW_ROLE is not specified)
  XClassHint wm_class;        // STRING
  if (wmRole == NULL) {
    if (XGetClassHint(display, wOrig, &wm_class) == 0)
      return False;
  }

  // Write out to the 'fp'
  if (smClientId != NULL)
    fprintf(fp, "%d,id=%s\n", idx, smClientId);       // SM_CLIENT_ID

  fprintf(fp, "%d,wOrig=%x\n", idx, (int)wOrig);
  fprintf(fp, "%d,frame=%x\n", idx, (int)frame);
  if (wmRole)
    fprintf(fp, "%d,role=%s\n", idx, wmRole); // WM_WINDOW_ROLE
  else {
    fprintf(fp, "%d,class=%s\n", idx, wm_class.res_class);// WM_CLASS
    fprintf(fp, "%d,rname=%s\n", idx, wm_class.res_name);
    fprintf(fp, "%d,name=%s\n", idx, name);   // WM_NAME
  }
  fprintf(fp, "%d,pos=%d,%d\n", idx, rcOrig.x, rcOrig.y);
  fprintf(fp, "%d,size=%d,%d\n", idx, rcOrig.width, rcOrig.height);

  if (wmRole == NULL) {
    XFree(wm_class.res_class);
    XFree(wm_class.res_name);
  }
  return True;
}
#endif // USE_XSMP


syntax highlighted by Code2HTML, v. 0.9.1