//-*-c++-*-
// ----------------------------------------------
// RCS data:
// $Date: 2003/07/09 09:07:36 $
// $Revision: 1.6 $
// $Source: /cvsroot/miwm/miwm/miwm/menu.cc,v $
// $Id: menu.cc,v 1.6 2003/07/09 09:07:36 sgbeal Exp $
// $RCSfile: menu.cc,v $
// -------------------------------------------
// Copyright by Ben Paul Wise.
// -------------------------------------------
// 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 this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
// -------------------------------------------
// these are very simple menus: no icons, no cascading
// -------------------------------------------

#include "miwm.h"
#include "mimenu.h"
#include "ws.h"
#include "EPropertyList.h"
#include "miwm_framework.h"
// ----------------------------------------------

Menu::Menu() {
  enterAction = NULL;
  exitAction = NULL;
  releaseAction = NULL;

  items = new NodeList(); // data of type MenuItem

  // totally arbitrary start up defaults, but they are always
  // the most recent position of the popup menu
  x = 100;
  y = 100;
  iWidth = 60;
  iHeight = 10;
  length = 4;

  mHeight = iHeight * length;
  mWidth = iWidth;

  currItem = -1;
  prevItem = -1;
  currMI = NULL;
  prevMI = NULL;


}

Menu::~Menu() {
  if (items != NULL)
    obliterateNodeList(items);
}

// ----------------------------------------------

void
Menu::setDimensions() {
  int w = 0;	// Widest string so far.
  Node *miND = NULL;
  MenuItem *mi = NULL;
  iWidth = 0;
  iHeight = 0;
  length = items->length();

  if (0 == length)
    return;

  w = 0;
  for (miND = items->first;
       miND != NULL;
       miND = items->nextNode(miND)) {
    mi = ((MenuItem*) miND->data);
    w = imax(w, XTextWidth(popup_font, mi->label.c_str(), mi->label.size() + 1) );
  }

  iWidth = w + border;
  iHeight = popup_font->ascent + popup_font->descent + 1;

  mWidth = iWidth;
  mHeight = length * iHeight;
  return;
}

// ----------------------------------------------
// menu operations

void 
Menu::startMenuing(XButtonEvent *xbe) {
  //   cout << "Start menuing" << endl << flush;
  int xNew, yNew;
//   float N = 10;
  assert (wmMenuing == theWM->mode);
  if (-1 == currItem) // if user did not set anything, guess at a default
    currItem = 0;

  XChangeActivePointerGrab(dpy, 
			   theWM->moving_event_mask, 
			   menu_cursor, CurrentTime);


  if (items->length() > 0)
    currMI = ((MenuItem*) items->first->data);
  else
    currMI = NULL;
  
  prevItem = -1;
  prevMI = NULL;

  if (items->length() == 0) {
    //     cout << "No items to menu. Stopping menuing" << endl;
    theWM->mode = wmReady;
    return;
  }
  
  // we place the menu to the right, with the current item chosen
  y = ((int)(xbe->y_root - iHeight * (currItem + 0.5)));
//   x = ((int)(xbe->x_root - iWidth / N));
  x = ((int)(xbe->x_root - border/2));

  if ((x < border) || (x + iWidth > (display_width - border))) {
    // we adjust the menu to be completely on the screen horizontally.
    // this is not as critical as the vertical dimension, so
    // we do not bother warping the pointer
    x = imax(border, imin ( x, (display_width - border) - iWidth));
  }
      

  if ((y < border) || (y + ( iHeight * items->length() ) > (display_height - border))) {
    // we adjust the menu to be completely on the screen vertically,
    // and warp the pointer appropriately
    y = imax(border, imin( y, (display_height - border) -  iHeight * items->length()));
  }
    xNew = x + border/2;
    yNew = iround(y + iHeight * (currItem + 0.5));
    XWarpPointer(dpy, // obvious
		 None,  // unknown meaning and function
		 root,  // window coordinate system to use
		 0, 0, 0, 0, // unknown meaning and function
		 xNew, yNew);

  XMoveResizeWindow(dpy, popup, x, y, mWidth, mHeight);
  // do not highlight the chosen item - menu::expose will do that
  XMapRaised(dpy, popup);
  return;
}

void 
Menu::motionNotify(XEvent*) {
  assert (wmMenuing == theWM->mode);

  //   cout << "Continue menuing" << endl << flush;

  // a silly little demo to show I know what I am doing:
  //   mWidth = (mWidth + 1) % 20;
  //   mWidth = imax(5, mWidth);
  //   mHeight = (mHeight + 1) % 60;
  //   mHeight = imax(mHeight, 15);
  //   getMouse (&x, &y);
  //   XMoveResizeWindow(dpy, popup, x, y, mWidth, mHeight);
  Node *nd = NULL;
  Client *hc = NULL;
  int i = 0;
  prevItem = currItem;
  prevMI = currMI;
  currItem = whichMenuItem();
  currMI = NULL;
  if (currItem >= 0) { // on the menu
    for (nd = items->first, i = 0; 
	 ((nd != NULL) && (currMI == NULL)) ; 
	 nd = items->nextNode(nd), i = i+1) {
      if (i == currItem)
 	currMI = ((MenuItem*) nd->data);
    }
  }

  if (currItem != prevItem) {
    //      cout << "You are now on item " << currItem << endl;
    //      cout << "     " << currMI << endl;


    // Unhighlight previous item if on menu.
    if (prevItem >= 0 && prevItem < length) {
      XFillRectangle(dpy, popup, menu_gc, 0, prevItem * iHeight,
		     iWidth, iHeight);
    }

    // Highlight current item if on menu.
    if (currItem >= 0 && currItem < length) {
      XFillRectangle(dpy, popup, menu_gc, 0, currItem * iHeight,
		     iWidth, iHeight);
    }
  }

  if ((hiddenClientMenu == theWM->whichMenu) && (1 == theWM->previewCP)) {
    if (currItem != prevItem) {
      // exit action
      if (NULL != prevMI) {
	hc = ((Client*) prevMI->data);
	theWM->undoFullPreview(hc);
      }
      // enter action
      if (NULL != currMI) {
	hc = ((Client*) currMI->data);
	theWM->doFullPreview(hc);
      }
    }
  }

  return;
}


void 
Menu::buttonRelease() {
  assert (wmMenuing == theWM->mode);
  Client* c = NULL;
  Node* cND = NULL;
  WorkSpace* ws = NULL;
  WMOperation op = NULL;
  //   cout << "End menuing" << endl << flush;
  //   cout << "You released on item " << currItem << endl;

  if (NULL != currMI)  // we were actually on the menu
    switch (theWM->whichMenu) {
    case hiddenClientMenu:
      c = ((Client*) currMI->data);
      cND = theWM->hiddenClients->inList((void*) c);
      if (cND != NULL) 
	theWM->hiddenClients->remove(cND);
      theWM->unhideClient(c);
      break;

    case operationsMenu:
      op = ((WMOperation) currMI->data);
      op();
      break; 

    case changeWSMenu:
      ws =  ((WorkSpace*) currMI->data);
      theWM->changeWorkSpace(theWM->workSpace, ws);
			
      break;

    case sendCtoWSMenu:
      ws =  ((WorkSpace*) currMI->data);
      if (ws != theWM->workSpace)
	theWM->sendClientToWorkSpace(current, ws);
      break;

    case commandMenu:
      theWM->spawn((char*) currMI->data);
      break;

    default:
      break;
    }
//   cout << "Menuing mode released = " << theWM->whichMenu << endl << flush;
  theWM->clearMenu();  // wipe out that pesky data!
  obliterateNodeList(items);
  items = NULL;

  theWM->whichMenu = noMenu;

  // this is very important: you have to keep the popup displayed and
  // receiving XEvents until the release-action is completed.
  // For example, on sending a client to another WS:
  // if you pop it down right away, then the client grabs focus and
  // stays on the WS. This way, the client goes away first, THEN the
  // popup is unmapped.
  XUnmapWindow(dpy, popup);
  return;
}


void 
Menu::expose() {
  assert (theWM->mode == wmMenuing);
  Node *miND = NULL;
  MenuItem *mi = NULL;
  int tx, ty;
  int i = 0;
  for (miND = items->first; miND != NULL; miND = items->nextNode(miND)) {
    mi = ((MenuItem*) miND->data);
    tx = (iWidth - XTextWidth(popup_font, mi->label.c_str(),
				 mi->label.size())) / 2;
    ty = i * iHeight + popup_font->ascent + 1;
    i = i + 1; 

    XDrawString(dpy, popup, menu_gc, tx, ty, mi->label.c_str(),
		mi->label.size());
  }
  // Highlight current item if on menu. 
  if (currItem >= 0 && currItem < length)
    XFillRectangle(dpy, popup, menu_gc, 0, currItem * iHeight,
		   iWidth, iHeight);

  return;
}

int
Menu::whichMenuItem() {
  int mx, my;
  int item;
  getMouse (&mx, &my);  // root coords

  mx = mx - x; // now menu coords
  my = my - y; // now menu coords
  if ((mx < 0) || (my < 0) || (mx > mWidth) || (my >= mHeight))
    item =  -1;
  else
    item = my / iHeight;

//   cout << "you are on item " << item << endl << flush;
  return item;
 }

// ----------------------------------------------

MenuItem::MenuItem() {
  label = std::string();
  data = NULL;
}

MenuItem::MenuItem(const char* lbl, const void* dt) {
  label = std::string(lbl);
  data = dt;
}

MenuItem::~MenuItem() {
//   cout << "deleting menu item " << label << endl << flush;
  // leave data alone, except in one special case
  if ((wmMenuing == theWM->mode) && (commandMenu == theWM->whichMenu)) {
//     cout << "deleting data"<<endl << flush;
    // in this one special case, we know the 'data' is char*

    // this is 'const', and should not be deleted
    //    delete ((char*) data);

    data = NULL;
  }
}


// ----------------------------------------------

void
WindowManager::clearMenu() {
  Node* nd = NULL;
  MenuItem* mi = NULL;

  if (NULL == menu)
    menu = new Menu();

  if (NULL != menu->items) {
    for ( nd = menu->items->first; nd != NULL; nd = menu->items->nextNode(nd)) {
      mi = ((MenuItem*) nd->data);
      delete mi;
      nd->data = NULL;
    }
  }
  if (NULL != menu->items) {
    obliterateNodeList(menu->items);
    menu->items = new NodeList();
  }
  else
    menu->items = new NodeList();

  assert (menu->items->length() == 0);
  menu->currItem = 0; // point at top workspace, initially
  return;
}

void 
WindowManager::setWSMenu() {
  WorkSpace* ws;
  int i;
  MenuItem *mi;
  clearMenu();
  menu->currItem = 0; // point at top workspace, initially

  for (i=0; i<numWorkSpaces; i++) {
    ws = allWorkSpaces[i];
    if (ws == workSpace)
      menu->currItem = i; // point at current WS
    mi = new MenuItem (ws->label.c_str(), (void*) ws);
    menu->items->append((void*) mi);
  }

  menu->setDimensions();
  whichMenu = noMenu; // you must say which purpose it will serve!
  return;
}
// ----------------------------------------------

void 
WindowManager::setClientOpsMenu() {
  MenuItem* mi = NULL;
  clearMenu();
  menu->currItem = 0; // point at first operation

  if (0 == current->sticky)
    mi = new MenuItem("Make Sticky", (void*) toggleSticky);
  else
    mi = new MenuItem("Make Unsticky", (void*) toggleSticky);
  menu->items->append(mi);

  mi = new MenuItem("ID", (void*) idCurrent);
  menu->items->append(mi);

  mi = new MenuItem("Kill", (void*) terminateCurrent);
  menu->items->append(mi);

  menu->setDimensions();
  whichMenu = operationsMenu; 
  return;
}
// ----------------------------------------------

void 
WindowManager::setWMOpsMenu() {
  MenuItem* mi = NULL;
  clearMenu();
  menu->currItem = 0; // point at first operation

  mi = new MenuItem("Retile WS", (void*) retileWS);
  menu->items->append(mi);
  mi = new MenuItem("Contract WS", (void*) contractWS);
  menu->items->append(mi);
  mi = new MenuItem("Step WS", (void*) stepWSup);
  menu->items->append(mi);
  mi = new MenuItem("Rotate Clients", (void*) rotateClients);
  menu->items->append(mi);

  if (1 == theWM->protectTopLeft)
    mi = new MenuItem("Allow off-screen", (void*) toggleTopLeft);
  else
    mi = new MenuItem("Force on-screen", (void*) toggleTopLeft);
  menu->items->append(mi);

  if (1 == theWM->raiseOnFocusP)
    mi = new MenuItem("No Autoraise", (void*) toggleRaiseOnFocus);
  else
    mi = new MenuItem("Raise on Focus", (void*) toggleRaiseOnFocus);
  menu->items->append(mi);

#ifdef USE_PIXMAPS
  if (1 == theWM->usePixmapsP)
    mi = new MenuItem("No Pixmaps", (void*) togglePixmaps);
  else
    mi = new MenuItem("Use Pixmaps", (void*) togglePixmaps);
  menu->items->append(mi);
#endif


  if (1 == theWM->tileP)
    mi = new MenuItem("No Tiling", (void*) toggleTiling);
  else
    mi = new MenuItem("Tile New", (void*) toggleTiling);
  menu->items->append(mi);

  if (1 == theWM->previewCP)
    mi = new MenuItem("No Preview Clients", (void*) toggleClientPreview);
  else 
    mi = new MenuItem("Preview Clients", (void*) toggleClientPreview);
  menu->items->append(mi);
  mi = new MenuItem("Restart miwm", (void*) restartWM);
  menu->items->append(mi);
  mi = new MenuItem("QUIT miwm", (void*) terminateWM);
  menu->items->append(mi);

  menu->setDimensions();
  whichMenu = operationsMenu; 
  return;
}
// ----------------------------------------------

void 
WindowManager::setHiddenClientMenu() {
  Node *cND;
  Client *c;
  MenuItem *mi;
  clearMenu();
  menu->currItem = 0; // point at last one hidden

  if (hiddenClients->length() > 0) {
    for (cND = hiddenClients->first; cND != NULL;	 cND = hiddenClients->nextNode(cND)) {
      c = ((Client*) cND->data);
      if (c->workSpace == workSpace) {
	getWindowName(c);
 	mi = new MenuItem(choose_label(c), ((void*) c));
	menu->items->append((void*) mi);
      }
    }
  }

  menu->setDimensions();

  assert (menu->items->length() <= hiddenClients->length());
  whichMenu = hiddenClientMenu;
  return;
}


// ----------------------------------------------


void 
WindowManager::setShellCmdMenu() {
        MenuItem *mi = NULL;
        clearMenu();
        menu->currItem = 0; // point at top command
        EPropertyList list;
        if( ! list.load( miwm::homePath() + "/" + "miwm.usermenu" ) ) return;
        typedef EPropertyList::string_list PSLIST;

        PSLIST::const_iterator it = list.orderedKeys().begin();
        PSLIST::const_iterator eit = list.orderedKeys().end();
        std::string val;
        while( it != eit )
        {
                val = list.getString( (*it) );
                if( val.empty() ) { ++it; continue; } // no point, is there?
                mi = new MenuItem();
                mi->label = (*it);
                mi->data = (const void *)  val.c_str();
                menu->items->append((void*) mi);
                ++it;
        }
        menu->setDimensions();
        whichMenu = commandMenu;
        return;
}


// ----------------------------------------------
// end of menu.cc
// ----------------------------------------------

