/* src/menus.c - menu management
 
  Copyright (C) 1999, 2000, 2001 Martin Grabmueller <mgrabmue@cs.tu-berlin.de>
 
  This 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 software 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 package; see the file COPYING.  If not, write to the
  Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
  MA 02111-1307, USA.  */

#if HAVE_CONFIG_H
# include <config.h>
#endif 

#include <assert.h>
#include <stdlib.h>
#include <string.h>

#include "alloc.h"
#include "menus.h"
#include "display.h"
#include "screen.h"
#include "events.h"
#include "workset.h"
#include "wimpwm.h"

/* MENUS holds state necessary to draw and handle the various menus.  */
struct menu menus[5];

/* The size of a menu entry.  */
static int entry_height;

/* Initialize the menu pointed to by MENU.  */
static void
init_one_menu (menu_t menu)
{
  menu->window = None;
  menu->x = 0;
  menu->y = 0;
  menu->width = 250;
  menu->height = 100;
  menu->title = NULL;
  menu->menu_item_list = NULL;
  menu->menu_items = 0;
  menu->menu_cleared = 0;
  menu->last_highlight = -1;
}

/* Initialize the menu data structures.  */
void
init_menus (void)
{
  int x;
  
  for (x = 0; x < 5; x++)
    {
      init_one_menu (menus + x);
      zzz_protect_global (&(menus[x].menu_item_list));
    }
}

/* Create the menu for application launching. */
static result_t
create_application_menu (int menu_number)
{
  menus[menu_number].menu_item_list = application_list;
  menus[menu_number].title = application_menu_title;
  return RESULT_SUCCESS;
}

/* Create the menu for window selection.  */
static result_t
create_window_menu (int menu_number)
{
  client_t c;
  zzz_scm_t * mip;

  if (null_p (menus[menu_number].menu_item_list))
    {
      mip = &menus[menu_number].menu_item_list;
      c = client_root;
      while (c != NULL)
	{
/*  	  printf ("%s\n", c->name); */
	  if (c->name)
	    {
	      zzz_scm_t title;
	      if (get_wm_state(c) == IconicState)
		{
		  char buffer[2048];
		  unsigned int len = strlen (c->name);
		  if (len > sizeof (buffer) - 2)
		    len = sizeof (buffer) - 2;
		  buffer[0] = '(';
		  buffer[len + 1] = ')';
		  buffer[len + 2] = '\0';
		  memmove (buffer + 1, c->name, len);
		  title = zzz_make_string (buffer, len + 2);
		}
	      else
		{
		  title = zzz_make_string (c->name, -1);
		}
	      *mip = zzz_cons (zzz_make_list (title,
					      zzz_make_integer (c->parent),
					      NULL),
			       NULL);
	      mip = cdr_addr (*mip);
	    }
	  c = c->next;
	}
    }
  menus[menu_number].title = window_menu_title;
  return RESULT_SUCCESS;
}

/* Create the menu for working set selection.  */
static result_t
create_workset_menu (int menu_number)
{
  menus[menu_number].menu_item_list = working_set_list;
  menus[menu_number].title = working_set_menu_title;
  return RESULT_SUCCESS;
}

/* Create and map the window for menu number MENU_NUMBER.  */
result_t
create_menu_window (int menu_number)
{
  switch (menu_number)
    {
    case BUTTON_1_MENU:
      create_application_menu (menu_number);
      break;
    case BUTTON_2_MENU:
      create_window_menu (menu_number);
      break;
    case BUTTON_3_MENU:
      create_workset_menu (menu_number);
      break;
    default:
      return RESULT_INVALID;
    }

  menus[menu_number].menu_items =
    zzz_list_length (menus[menu_number].menu_item_list);
  if (menus[menu_number].menu_items < 0)
    menus[menu_number].menu_items = 0;

  entry_height = font->ascent + font->descent + 5;

  menus[menu_number].width = 220;
  menus[menu_number].height = entry_height
    * (menus[menu_number].menu_items + 1);

  get_mouse_position (&menus[menu_number].x, &menus[menu_number].y);

  if (menus[menu_number].x + menus[menu_number].width >
      DisplayWidth(display, default_screen))
    menus[menu_number].x = DisplayWidth(display, default_screen)
      - menus[menu_number].width;
  if (menus[menu_number].y + menus[menu_number].height >
      DisplayHeight(display, default_screen))
    menus[menu_number].y = DisplayHeight(display, default_screen)
      - menus[menu_number].height;

  if (menus[menu_number].window == None)
    {
      menus[menu_number].window =
	XCreateSimpleWindow(display, root_window, 
			    menus[menu_number].x,
			    menus[menu_number].y, 
			    menus[menu_number].width, 
			    menus[menu_number].height, 1,
			    BlackPixel(display, default_screen), 
			    WhitePixel(display, default_screen));
      if (!menus[menu_number].window) 
	{
	  error ("cannot create menu window");
	  return RESULT_NOMEM;
	}

      XDefineCursor (display, menus[menu_number].window,
		     get_font_cursor (XC_arrow));
      XStoreName(display, menus[menu_number].window, "wimpwm Menu");
      XSetIconName(display, menus[menu_number].window, "wimpwm Menu");
      XSelectInput (display, menus[menu_number].window, ExposureMask);

    }
  else
    XMoveResizeWindow (display, menus[menu_number].window,
		       menus[menu_number].x, menus[menu_number].y,
		       menus[menu_number].width, menus[menu_number].height);
  XMapRaised (display, menus[menu_number].window);

  menu_draw (-1, menu_number);

  return RESULT_SUCCESS;
}

/* Unmap the menu window for menu number MENU_NUMBER and discard all
   menu items for that menu.  */
result_t
destroy_menu_window (int menu_number)
{
  if (menus[menu_number].window != None)
    XUnmapWindow (display, menus[menu_number].window);
  init_one_menu (menus + menu_number);
  return RESULT_SUCCESS;
}

/* Draw menu number MENU_NUMBER, taking HIGHLIGHT as the index of the
   menu item to be highlighted.  When HIGHLIGHT is -1, no item will be
   highlighted.  */
result_t
menu_draw (int highlight, int menu_number)
{
  zzz_scm_t mi = menus[menu_number].menu_item_list;
  zzz_scm_t text;
  /*    menu_item_t mi = menus[menu_number].menu_item_root; */
  int i = 0;
  XPoint points0[3];

  if (highlight == -1 && menus[menu_number].last_highlight == -1 &&
      menus[menu_number].menu_cleared)
    return RESULT_SUCCESS;

  if (!menus[menu_number].menu_cleared)
    {
      XFillRectangle(display, menus[menu_number].window, menu_gc,
		     0, 0,
		     menus[menu_number].width, entry_height);
      XDrawString(display, menus[menu_number].window, active_gc, 8,
		  2 + font->ascent, menus[menu_number].title,
		  strlen (menus[menu_number].title));

      points0[0].x = 0;
      points0[0].y = entry_height;
      points0[1].x = 0;
      points0[1].y = 0;
      points0[2].x = menus[menu_number].width - 1;
      points0[2].y = 0;
      XDrawLines(display, menus[menu_number].window,
		 highlight_gc, points0, 3, CoordModeOrigin);
      points0[0].x = menus[menu_number].width - 1;
      points0[0].y = 1;
      points0[1].x = menus[menu_number].width - 1;
      points0[1].y = entry_height - 2;
      points0[2].x = 0;
      points0[2].y = entry_height - 2;
      XDrawLines(display, menus[menu_number].window, shadow_gc,
		 points0, 3, CoordModeOrigin);
      XDrawLine (display, menus[menu_number].window,
		 border_gc, 0, entry_height - 1,
		 menus[menu_number].width, entry_height - 1);
      menus[menu_number].menu_cleared = 1;
    }
  while (cons_p (mi))
    {
      text = car (mi);
      if (cons_p (text))
	text = car (text);
      if (string_p (text))
	{
	  if (i == highlight)
	    {
	      /*  	  XFillRectangle(display, menus[menu_number].window, inactive_gc, */
	      /*  			 0, 0 + (i + 1) * entry_height, */
	      /*  			 menus[menu_number].width - 0, entry_height); */
	      XDrawRectangle(display, menus[menu_number].window, shadow_gc,
			     1, 0 + (i + 1) * entry_height + 1,
			     menus[menu_number].width - 3, entry_height - 3);
	      XDrawString(display, menus[menu_number].window, menu_gc, 8,
			  2 + (i + 1) * entry_height + font->ascent,
			  string_val (text),
			  string_len (text));
	    }
	  else if (menus[menu_number].last_highlight == -1 ||
		   menus[menu_number].last_highlight == i)
	    {
	      XFillRectangle(display, menus[menu_number].window, active_gc,
			     0, 0 + (i + 1) * entry_height,
			     menus[menu_number].width - 0, entry_height);
	      XDrawString(display, menus[menu_number].window, menu_gc, 8,
			  2 + (i + 1) * entry_height + font->ascent,
			  string_val (text),
			  string_len (text));
	    }
	  i++;
	}
      mi = cdr (mi);
    }
  menus[menu_number].last_highlight = highlight;
  return RESULT_SUCCESS;
}

/* Change menu number MENU_NUMBER so that the highlighted menu item
   matches the mouse motion reported in E.  */
result_t
menu_highlight (XMotionEvent * e, int menu_number)
{
  int x = 0;
  int y = 0;
  int which;
  zzz_scm_t mi;
  int i;

  if (e != NULL)
    {
      x = e->x - menus[menu_number].x;
      y = e->y - menus[menu_number].y;
      which = (y - entry_height) / entry_height;

      if (y < entry_height)
	{
	  menu_draw (-1, menu_number);
	}
      else
	{
	  mi = menus[menu_number].menu_item_list;
	  i = 0;
	  while (which-- > 0 && cons_p (mi))
	    {
	      mi = cdr (mi);
	      i++;
	    }
	  
	  if (i != menus[menu_number].last_highlight)
	    menu_draw (i, menu_number);
	}
    }
  else
    menu_draw (-1, menu_number);
  return RESULT_SUCCESS;
}

/* Select the highlighted menu item in menu number MENU_NUMBER and
 perform the corresponding action. Do nothing, if no menu item is
 currently highlighted.  */
result_t
menu_select (XButtonEvent * e, int menu_number)
{
  client_t old_client;
  zzz_scm_t mi;
  int i = 0;
  zzz_scm_t action;
  zzz_scm_t temp;

  if (menus[menu_number].last_highlight != -1)
    {
      mi = menus[menu_number].menu_item_list;
      while (cons_p (mi) && i < menus[menu_number].last_highlight)
	{
	  mi = cdr (mi);
	  i++;
	}
      if (cons_p (mi))
	{
	  if (cons_p (car (mi)) && cons_p (cdr (car (mi))))
	    action = car (cdr (car (mi)));
	  else
	    action = NULL;
	  switch (menu_number)
	    {
	    case BUTTON_1_MENU:
	      if (symbol_p (action))
		{
		  if (action == zzz_make_symbol ("exit", -1))
		      exit (0);
		  else if (action == zzz_make_symbol ("restart", -1))
		    restart ();
		  else if (action == zzz_make_symbol ("reload", -1))
		    read_user_init_file ();
		  else
		    {
		      error ("invalid action in menu definition: ");
		      zzz_port_print (zzz_stderr_fport, action,
				      &zzz_state.print_state, &temp);
		      zzz_port_putc ('\n', zzz_stderr_fport, &temp);
		    }
		  }
	      else if (string_p (action))
		execute (string_val (action));
	      break;

	    case BUTTON_2_MENU:
	      old_client = current_client;
	      current_client = find_client(integer_val (action), 1);
	      if (current_client)
		{
		  if (current_client->workset != current_workset &&
		      current_client->workset != -1)
		    {
		      if (e->state & ControlMask)
			{
			  workset_select (current_client->workset);
			}
		      else
			{
			  make_client_visible (current_client);
			  current_client->workset = current_workset;
			}
		    }
		  if (get_wm_state(current_client) == IconicState)
		    {
		      unhide_client (current_client);
		    }
		  client_raise (current_client);
		  transfer_focus (current_client, old_client);
		}
	      else
		current_client = old_client;
	      break;

	    case BUTTON_3_MENU:
	      if (e->state & ControlMask)
		{
		  client_t client;
		  int workset = i;

		  client = manually_select_window();
		  if (client && workset != current_workset)
		    {
		      client->workset = workset;
		      make_client_invisible (client);
		      if (client == current_client)
			{
/*  			  current_client = NULL; */
/*  			  worksets[current_workset].last_focused = None; */
/*  			  focus_any_client (); */
			}
		    }
		}
	      else
		workset_select (i);
	      break;
	    }
	}
      menus[menu_number].last_highlight = -1;
    }
  return RESULT_SUCCESS;
}
