#include "global.hh"

#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <ctype.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <sys/types.h>
#include <sys/time.h>

#include "menus.hh"

#include "anim.hh"
#include "commands.hh"
#include "config.hh"
#include "events.hh"
#include "main.hh"
#include "misc.hh"
#include "windows.hh"

MenuStyle menu_style;
MenuList menu_list;

Menu *mm;
Menu *last_up = NULL;

//********************************************************************

MenuStyle::MenuStyle ()
{
	back = black;
	text = grey;
	hiback = grey;
	hiback_fn = GXcopy;
	hitext = black;
	rule = grey;
	title = white;
	bw = 1;
}

int MenuStyle::init ()
{
	item_font.init ();
	title_font.init ();

	// load colors
	back.init ();
	text.init ();
	hiback.init ();
	hitext.init ();
	rule.init ();
	title.init ();

	// create GCs
#ifdef FONTS_XFT
	gcm = GCForeground;
	gcv.foreground = rule.pixel;
	ruleGC = XCreateGC (dpy, root_window, gcm, &gcv);
	
	gcv.foreground = text.pixel;
	textGC = XCreateGC (dpy, root_window, gcm, &gcv);
	
	gcv.foreground = hitext.pixel;
	hitextGC = XCreateGC (dpy, root_window, gcm, &gcv);
	
	gcm |= GCFunction;
	gcv.function = hiback_fn;
	gcv.foreground = hiback.pixel;
	hibackGC = XCreateGC (dpy, root_window, gcm, &gcv);
#elif defined FONTS_XCORE
	gcm = GCFont | GCForeground;
	gcv.font = font.font->fid;
	gcv.foreground = text.pixel;
	textGC = XCreateGC (dpy, root_window, gcm, &gcv);

	gcv.foreground = hitext.pixel;
	hitextGC = XCreateGC (dpy, root_window, gcm, &gcv);
	
	gcv.foreground = rule.pixel;
	ruleGC = XCreateGC (dpy, root_window, gcm, &gcv);

	gcm |= GCFunction;
	gcv.function = hiback_fn;
	gcv.foreground = hiback.pixel;
	hibackGC = XCreateGC (dpy, root_window, gcm, &gcv);

	gcv.font = titlefont.font->fid;
	gcv.foreground = title.pixel;
	titleGC = XCreateGC (dpy, root_window, gcm, &gcv);
#endif

	XSync (dpy, 0);

	// load pixmap
	bkg.load (bkg_xpm);
}

//********************************************************************

MenuList::MenuList ()
{
	first = NULL;
}

Menu *winops_menu = NULL;
Menu *pager_winops_menu = NULL;

int
MenuList::add (Menu *tmp)
{
	if (strncasecmp (tmp->name, "winops", 6) == 0)
		winops_menu = tmp;

	if (strncasecmp (tmp->name, "pagerwinops", 6) == 0)
		pager_winops_menu = tmp;

	tmp->next = first;
	first = tmp;
}

int
MenuList::done ()
{
	Menu *t;
	while (first) {
		t = first;
		first = first->next;
		delete t;
	}
}

int
MenuList::find (Menu *&t, char *name)
{
	for (t = first; t; t = t->next)
		if (strcasecmp (t->name, name) == 0)
			break;
}

int
MenuList::init ()
{
	// set style on all windows - we have completed the parsing of
	// config file and we know the style now
	Menu *i;
	for (i = first; i; i = i->next) i->set_style ();
}

//********************************************************************

Menu::Menu ()
{
	focus = NULL;
	it_last = NULL;
	//focus = NULL;
	//  wd = ht = 0;
	parent = NULL;
	name = NULL;
	in_use = 0;

	// create the menu's window
	XSetWindowAttributes at;
	at.event_mask = MENU_EVENT_MASK;
	vm = CWEventMask;

	w = XCreateWindow (dpy, root_window, 0, 0, 1, 1, 0, CopyFromParent, InputOutput, CopyFromParent, vm, &at);
}

int Menu::set_style ()
{
	// create the menu's window
	XSetWindowAttributes at;
	at.event_mask = MENU_EVENT_MASK;
	at.cursor = cursor;
	//  at.save_under = TRUE;
	at.border_pixel = menu_style.rule.pixel;
	vm = CWEventMask | CWCursor | CWBorderPixel;// | CWSaveUnder;
	if (menu_style.bkg.pm) {
		at.background_pixmap = menu_style.bkg.pm;
		vm |= CWBackPixmap;
	}
	else {
		at.background_pixel = menu_style.back.pixel;
		vm |= CWBackPixel;
	};

	XChangeWindowAttributes (dpy, w, vm, &at);
}

int
Menu::select (MItem *i)
{
	//  printf ("select %s\n", name);
	if (focus == i) return 0;
	if (focus) focus->off ();
	focus = i;
	if (focus) focus->on ();
}

int
Menu::pop_up (int flags=0)
{
	if (in_use)
		return 0;

	in_use = 1;

	parent = last_up;
	last_up = this;

	// prepare the menu
	build ();
	align ();

	//  int x, y;
	// determine the position
	if (flags & MPOP_CENTER)
	{
		wr.x = (root.wd - hbox->wd) / 2;
		wr.y = (root.ht - hbox->ht) / 2;
	}
	else if (parent)
	{
		wr.x = parent->wr.x + parent->focus->x + parent->focus->wd;
		// 2*menu_style.bw - no border-overlap (but if it's two column, the
		// submenu of the left column's item will get unnecessarily
		// indented by one additional menu_style.bw)
		wr.y = parent->wr.y + parent->focus->y;
	}
	else
	{
		ppos.save (); // could be ppos.query, but sometimes i use
		// the ppos.restore later

		// pop up with pointer in center
		//    wr.x = ppos.rx - hbox->wd / 2;
		//    wr.y = ppos.ry - hbox->ht / 2;
		wr.x = ppos.rx;
		wr.y = ppos.ry;
	}

	focus = NULL;

	// clip to screen
	if (wr.x + hbox->wd > root.wd) wr.x = root.wd - hbox->wd;
	if (wr.x < 0) wr.x = 0;
	if (wr.y + hbox->ht > root.ht) wr.y = root.ht - hbox->ht;
	if (wr.y < 0) wr.y = 0;

	update ();

	/*
	if (parent && flags & MPOP_ANIM_FROM_PARENT)
	{
		anim.from.set (parent->x + parent->focus->x + parent->focus->wd,
			 parent->y + parent->focus->y, 5, 5);
		anim.to.set (x, y, wd, ht);
		anim.run ();
	}
	else
	*/
	if (flags & MPOP_ANIM)
	{
		//anim.from.start ();
		anim.from.ppos ();
		anim.to.set (wr);
		anim.type = ANIM_AD;
		anim.run ();
	}

	XMapRaised (dpy, w);

	if (flags & MPOP_CENTER)
	{
		// the first menu popped up - don't want the mouse to select the
		// item under it, if the menu was popped up by a keypress, so eat
		// the first EnterWindowEvent for the menu's window
		ppos.query ();
		if (wr.contains (ppos.rx, ppos.ry)) event->window (w, EnterWindowMask);
	}
}

int
Menu::pop_down (int flags)
{
	if (focus) {
#ifdef NOTE_WARN
#warning IMPROVE_ME_BOXW_EXCLAMATION
#endif
		Window oboxw = boxw;
		Menu *omm = mm;

		mm = this;
		boxw = w;

		focus->off (); // this pops down eventual submenus (e.g. you
		// pressed escape in root menu when submenu was poped up)

		// did not worked right when selected inet with mouse, then with
		// keyboard selected connect from submenu and then moved the mouse
		// to other item in root menu
		focus = NULL;

		mm = omm;
		boxw = oboxw;
	};

	XUnmapWindow (dpy, w);

	in_use = 0;
	last_up = parent;

	clear ();
	
	if (!parent && flags & MPOP_ANIM) {
		XSync (dpy, 0);

		anim.from.set (wr);
		//    anim.to.end ();
		anim.to.ppos ();
		anim.type = ANIM_AD;
		anim.run ();
	}
}

//********************************************************************

int
Menu::exec ()
{
	if (swm_mode != MODE_RUN) return 0;
	if (!grab_setup (root_window)) return 0;
	pop_up (MPOP_ANIM);

	int pressed = 0;
	swm_mode = MODE_MENU;

	while (swm_mode == MODE_MENU)
	{
		// block until there is an event
		// see also Menu::create () - at.event_mask
		event->mask (MENU_EVENT_MASK);
		last_up->handle_event ();

		if (!event->handled) 
		{
			switch (event->x.type)
			{
			case ButtonPress:
				//	if (event->x.xany.window == root)
				pressed = 1;
				event->handled = 1;
				break;
			case ButtonRelease:
				if (pressed) {
					// pressing button outside the menu will cause it to put off
					ppos.save ();
					swm_mode = MODE_RUN;
					event->handled = 1;
				};
				break;
			case KeyPress:
				handle_KeyPress ();
				break;
			case EnterNotify:
				event->handled = 1; // otherwise winops won't work
				                    // (event->handle () would focus to none == root)
				break;
			}
		}

		if (!event->handled) event->handle ();
	};

	while (last_up) last_up->pop_down (MPOP_ANIM); // pop down all the menus
	grab_cancel ();
	//ppos.restore ();
}

int
Menu::handle_event ()
{
	if (event->x.type == KeyPress) {
		// handle keypresses only by the last_up menu
		mm = this;
		boxw = w;

		hbox->handle_event ();
	}
	else if (event->x.xany.window == w) {
		mm = this;
		boxw = w;

		switch (event->x.type) {
		case MotionNotify:
			if (focus) focus->handle_event (); // faster (?)
			break;
		case LeaveNotify:
			if (this == last_up && focus) select (NULL);
			break;
		};

		if (!event->handled) hbox->handle_event ();
	}
	else
		if (parent) parent->handle_event ();
}

int
Menu::handle_KeyPress ()
{
	switch (event->keysym)
	{
	case XK_Return:
	case XK_space:
		if (last_up->focus) last_up->focus->action ();
		event->handled = 1;
		break;
	case XK_Escape:
		ppos.save ();
		swm_mode = MODE_RUN;
		// now we should return focus back to the window that had it
		// before the menu was poped up (beacuse ungrabem() will generate
		// some EnterNotifies)
		if (win_list.focus)
			cq.add (new WinCmd (win_list.focus, CW_FOCUS));
		// *** FIXED ***
		// *** FIXME *** doesn't work now because of the (present) event
		// processing procedure: command queue is executed *before* other
		// events are processed - the closure of the menu's window
		// (?and/or the ungrabbing of the pointer) generates an
		// EnterNotify event immediately after it - and in the event
		// handler, the command queue is generated -> the focus is at
		// given to wl.focus (as we want), but after it the EnterNotify
		// handling gives the focus to a window (generally) different from
		// wl.focus

		event->handled = 1;
		break;
	case XK_Up:
		last_up->go_up ();
		event->handled = 1;
		break;
	case XK_Down:
		last_up->go_down ();
		event->handled = 1;
		break;
	};
}

int
Menu::go_up ()
{
	if (focus) select (focus->it_prev);
	else if (it_last) select (it_last->it_next);
}

int
Menu::go_down ()
{
	if (focus) select (focus->it_next);
	else if (it_last) select (it_last->it_next);
}

//********************************************************************

int
Menu::clear ()
{
	if (hbox)
	{
		delete hbox;
		hbox = NULL;
		it_last = NULL;
	}
}

int
Menu::it_add (MItem *it)
{
	if (!it_last)
		it->it_next = it->it_prev = it;
	else
	{
		it->it_next = it_last->it_next;
		it_last->it_next->it_prev = it;

		it_last->it_next = it;
		it->it_prev = it_last;
	};

	it_last = it;
	vbox->aadd (it);
}

//********************************************************************

struct MenuParser: LineParser // abstract class
{
	Menu *m;
	int parse ();
	virtual int next_line () {};
	virtual int parse_line ();
};

struct MenuSrcParser: MenuParser
{
	MenuSrcParser (Menu *);
	~MenuSrcParser ();
	int reset_line ();
	int next_line ();
	int parse ();
	int parse_line ();
};

struct MenuShellParser: MenuParser
{
	FILE *pipe_fp;
	char *cmd;

	MenuShellParser (Menu *, char *, char *);
	~MenuShellParser ();
	int next_line ();
	int parse ();
};

int
MenuSrcParser::reset_line ()
{
	while (*p == '\n' && p > line) p--;
	while (*p != '\n' && p > line) p--;
	if (*p == '\n') p++;
}

int
MenuParser::parse_line ()
{
	jmp ('"'); inc (); jmp ('"'); inc (); skip_space ();

	//      MItem *it;
	//      Box *b;

	if (match ("title"))
		m->vbox->aadd (new MTitle);
	// endmatch ("title") ...
	else if (match ("popup") || match("clickpopup"))
		m->it_add (new MPopup);
	else
	{
		reset_line ();
		skip_space ();
		if (match ("\""))	m->it_add (new MCommand);
		else if (match ("-")) m->vbox->aadd (new MHRule);
		else if (match ("|"))
		{
			m->hbox->aadd (new MVRule);
			m->hbox->aadd (m->vbox = new VBox);
		}
		else
		{
			// the mess below ensures that ::error() will print only one line

			// reminder: here all lines are stacked into one long
			// string, no nulls on the end of the lines; without this,
			// ::error() would print all remaining lines of the menu
			// definition, and not only the current one

			int repl;
			char *pp = p;
			char c[2];
			while (*pp != '\n' && *pp != 0)
				pp++;

			repl = *pp;
			if (repl) {
				memcpy (c, pp, 2);
				*pp = '\n'; *(pp+1) = 0;
			};
			error ("menu parse error: ");
			if (repl) {
				memcpy (pp, c, 2);
			};
			m->vbox->aadd (new MTitle ((char *) "<parse error>"));
		}
	}
}

int
MenuSrcParser::parse ()
{
	LineParser *lps_orig = lps;
	lps = this;

	p = line;
	do parse_line (); while (next_line ());

	lps = lps_orig;
}

MenuSrcParser::MenuSrcParser (Menu *im)
{
	m = im;
	line = m->src;

	parse ();
}

MenuSrcParser::~MenuSrcParser ()
{
	line = NULL; // we don't want the menu's definition to be deleted
	// (~LineParser is called after this descructor)
}

int
MenuSrcParser::next_line ()
{
	while (*p != '\n' && *p != 0) p++;
	while (*p == '\n') p++;
	if (*p == 0) return 0; else return 1;
}

int
MenuSrcParser::parse_line ()
{
	if (match ("%%shell%%"))
	{
		//    allow_shell = 0; // don't allow recursive shell inlines

		char *cb, *ce; // shell command beginning and end
		cb = p;
	
		int found_end = 0;
	
		while (!found_end && next_line ()) {
			ce = p;
			if (match ("%%shell%%")) found_end = 1;
		};
		
		if (!found_end) {
			error ("missing end of the shell command (\"%%%%shell%%%%\" at the beginning of a line)");
			m->vbox->aadd (new MTitle ((char *) "<missing end of the command>"));
		}
		else
			delete (new MenuShellParser (m, cb, ce));
	}
	else
		MenuParser::parse_line ();
}

MenuShellParser::MenuShellParser (Menu *im, char *cb, char *ce)
{
	m = im;

	// copy the bash command(s) to cmd
	int cl = ce - cb;
	cmd = new char[cl+1];
	strncpy (cmd, cb, cl);
	cmd[cl] = 0;

	parse ();
}

MenuShellParser::~MenuShellParser ()
{
	delete cmd;
}

int
MenuShellParser::next_line ()
{
	do
	{
		if (getline (&line, &line_size, pipe_fp) == -1) return 0;
		p = line;
		skip_space ();
	}
	while (eol () || *p == '/' || *p == '#' || *p == ';'); // discard empty and commented lines
	
	//  printf ("%s", p); // debugging
	//  printf ("%s", line); // debugging
	
	return 1;
}

int
MenuShellParser::parse ()
{
	LineParser *lps_orig = lps;
	lps = this;

	// Create one way pipe line with call to popen ()
	//  printf ("\n\n\nexecuting cmd:\n%s",cmd);
	if (pipe_fp = popen (cmd, "r")) {
		//    printf ("\ndone.\n\n\n");
		while (next_line ())
			parse_line ();

		pclose (pipe_fp);
	}
	else {
		error ("shell command execution failed: %s\n", cmd);
		m->vbox->aadd (new MTitle ((char *) "<cmd failed>"));
	};

	lps = lps_orig;
}

int
Menu::build ()
{
	vbox = new VBox;
	hbox = new HBox;
	hbox->aadd (vbox);

	// build menu from the stored source
	delete (new MenuSrcParser (this));
	
	hbox->add_border (menu_style.bw, menu_style.ruleGC);
}

//********************************************************************

MItem::MItem ()
{
	it_prev = it_next = NULL;
	state = 0;
}

int
MItem::fill (GC gc)
{
	XFillRectangle (dpy, boxw, gc, x, y, wd, ht);
}

int
MItem::cl ()
{
	XClearArea (dpy, boxw, x, y, wd, ht, FALSE);
}

int
MItem::handle_event ()
{
	switch (event->x.type) {
	case Expose:
		if (overlap (event->r)) draw ();
		break;

	case EnterNotify:
		if (contains (event->x.xcrossing.x, event->x.xcrossing.y)) {
			mm->select (this);
			event->handled = 1;
		};
		break;
	case MotionNotify:
		if (contains (event->x.xmotion.x, event->x.xmotion.y)) {
			mm->select (this);
			event->handled = 1;
		};
		break;
	case ButtonRelease:
		if (contains (event->x.xbutton.x, event->x.xbutton.y)) {
			action ();
			event->handled = 1;
		};
		break;
	}
}

int
MItem::on ()
{
	//  parent->focus = this;
	if (!state)	{
		state = 1;
		draw ();
	}
}

int
MItem::off ()
{
	if (state) {
		state = 0;
		draw ();
	}
}

//********************************************************************
int
MText::ia ()
{
#ifdef FONTS_XFT
	wd = 2*menu_text_padding + XftTextWidthUtf8 (menu_style.item_font.font, text, len);
#elif defined FONTS_XCORE
	wd = 2*menu_text_padding + XTextWidth (menu_style.item_font.font, text, len);
#endif
	ht = menu_style.item_font.ht + menu_text_padding;
}

int
MText::fa ()
{
	xoff = menu_text_padding;
	yoff = (ht - menu_style.item_font.ht) / 2 + menu_style.item_font.y;
}

#ifdef FONTS_XFT
int
MText::draw_text (MyColor color, MyFont font)
{
	XftDrawChange (xft_draw, boxw);
	XftDrawStringUtf8 (xft_draw, &color.xftcolor, font.font, x+xoff, y+yoff, (XftChar8*) text, len);
}
#elif defined FONTS_XCORE
int
MText::draw_text (GC gc)
{
	XDrawString (dpy, boxw, gc, x+xoff, y + yoff, text, len);
}
#endif

MText::~MText ()
{
	delete text;
}

//********************************************************************
int
MSelectable::parse_hotkeys ()
{
	char *i, *j;

	if (!text) return 0;

	for (i = text; *i; i++)
		if (*i == '&')
			if (*(i + 1) == '&')
				// just an escaped & -- move the rest
				for (j = i; *j; j++) *j = *(j+1);
			else {
				hotkey = *(i+1);
				if (isupper (hotkey)) hotkey = tolower (hotkey);
				hotoff = (1+(i - text));
				// move the rest
				for (j = i; *j; j++) *j = *(j+1);
				return 0;
			};
	
	hotkey = 0;
	hotoff = 0;
}

int
MSelectable::draw ()
{
#ifdef FONTS_XFT
	if (state) {
		fill (menu_style.hibackGC);
		draw_text (menu_style.hitext, menu_style.item_font);
	}
	else {
		cl ();
		draw_text (menu_style.text, menu_style.item_font);
	}
#elif defined FONTS_XCORE
	GC gc;
	cl ();
	
	// background
	if (state) {
		fill (menu_style.hibackGC);
		gc = menu_style.hitextGC;
	}
	else
		gc = menu_style.textGC;

	// text
	draw_text (gc);
	
	// if the item has a hot key, underline it
	if (hotoff > 0) {
		int x1 = xoff + XTextWidth (menu_style.font.font, text, hotoff - 1);
		int x2 = xoff + XTextWidth (menu_style.font.font, text, hotoff) - 1;
		XDrawLine (dpy, boxw, gc, x + x1, y + yoff + 1, x + x2, y + yoff + 1);
	}
#endif
}

#define wait5 timer = add_timer (5); while (timer->t); rm_timer (timer);

int
MSelectable::blink ()
{
	Timer *timer;

	if (mm->focus == this)
	{
		mm->select (NULL);
		XSync (dpy, 0);
		wait5;
	};

	for (int i=0; i<3; i++)
	{
		mm->select (this);
		XSync (dpy, 0);
		wait5;
		mm->select (NULL);
		XSync (dpy, 0);
		wait5;
	}
}

#undef wait5
	
int
MSelectable::handle_event ()
{
	switch (event->x.type)
	{
	case KeyPress:
		if (event->keysym == hotkey)
		{
			blink ();
			mm->select (this);
			
			action ();
			event->handled = 1;
		};
		break;
	default:
		MItem::handle_event ();
		break;
	}
}

//********************************************************************
MCommand::MCommand ()
{
	lps->reset_line ();
	lps->stripcpy2 (text);
	parse_hotkeys ();
	len = strlen (text);
	lps->cmd (cmd);
	del = 1;
}

MCommand::MCommand (char *itext, Cmd *icmd)
{
	text = new char[strlen(itext) + 1];
	strcpy (text, itext);
	parse_hotkeys ();
	len = strlen (text);
	cmd = icmd;
	del = 1;
}

MCommand::~MCommand ()
{
	// *** IMPROVE ME ***
	if (del)
		delete cmd;
}

int
MCommand::action ()
{
	del = 0; // will be deleted (after execution) by command queue (cq)
	if (win_list.focus)
		cq.add (new WinCmd (win_list.focus, CW_FOCUS));
	cq.add (cmd);
	swm_mode = MODE_RUN;
}

//********************************************************************

MPopup::MPopup ()
{
	flag=0;

	lps->reset_line ();
	lps->stripcpy2 (text);
	parse_hotkeys ();
	len = strlen (text);
	
	lps->skip_space();
	if (lps->match("clickpopup")) flag |= MP_CLICKABLE;

	char *menu_name;
	lps->stripcpy2 (menu_name);
	menu_list.find (menu, menu_name);
	if (!menu)
		lps->error ("bad menu name");
	delete menu_name;
}

int
MPopup::ia ()
{
	MText::ia ();

	// (!?) doesn't work right! virtual functions are in a fact pointers
	// to a function (??), which are filled when new object (structure)
	// is created, and call of such function is a call of a function
	// pointer (yes! with the fixed value, e. g. with MText::ia () for
	// "new MText", and with MPopup::ia for "new MPopup"; so it does
	// work right

	wd += menu_style.item_font.ht; // add space for triangle
}

int
MPopup::draw ()
{
	MSelectable::draw ();

	if (state)
		draw_triangle (menu_style.hitextGC);
	else
		draw_triangle (menu_style.textGC);
}

int
MPopup::draw_triangle (GC gc)
{
	int d, dd, xx, yy;

	// draw a little triangle on right side
	d = ht / 6;
	dd = d * 2;
	xx = x + wd - d - menu_style.item_font.ht / 2;
	yy = y + ht / 2;
	XDrawLine (dpy, boxw, gc, xx, yy, xx-dd, yy-d);
	XDrawLine (dpy, boxw, gc, xx-dd, yy-d, xx-dd, yy+d);
	XDrawLine (dpy, boxw, gc, xx-dd, yy+d, xx, yy);
}

int
MPopup::handle_event ()
{
//	printf ("%d ", event->x.type); fflush(stdout);
	switch (event->x.type)
	{
	case MotionNotify:
		if (contains (event->x.xmotion.x, event->x.xmotion.y))
		{
			mm->select (this);
		
			if (!(flag & MP_CLICKABLE) && event->x.xmotion.x > x + wd / 2)
				if (!menu->in_use) menu->pop_up();
			event->handled = 1;
		}
		break;
	
	case KeyPress:
		if (event->keysym == hotkey)
		{
			mm->select (this);
			
			action ();
			event->handled = 1;
		};
		break;

	default:
		MItem::handle_event ();
		break;
	}
}

int
MPopup::action ()
{
	// pop a sub-menu
	if (!menu->in_use) menu->pop_up(); else menu->pop_down();
}

int
MPopup::off ()
{
	if (menu->in_use)
		menu->pop_down ();

	MItem::off ();
}

MTitle::MTitle ()
{
	lps->reset_line ();
	lps->stripcpy2 (text);
	len = strlen (text);
}

MTitle::MTitle (char *itext)
{
	text = new char[strlen(itext) + 1];
	strcpy (text, itext);
	len = strlen (text);
}

int
MTitle::ia ()
{
#ifdef FONTS_XFT
	wd = 2*menu_text_padding + XftTextWidthUtf8 (menu_style.title_font.font, text, len);
#elif defined FONTS_XCORE
	wd = 2*menu_text_padding + XTextWidth (menu_style.title_font.font, text, len);
#endif
	ht = menu_style.title_font.ht + menu_text_padding;
}

int
MTitle::fa ()
{
	MText::fa ();
#ifdef FONTS_XFT
	xoff = (wd - XftTextWidthUtf8 (menu_style.title_font.font, text, len))/2;
#elif defined FONTS_XCORE
	xoff = (wd - XTextWidth (menu_style.title_font.font, text, len))/2;
#endif
	printf ("MTitle::fa-textwd-wd,xoff: %d,%d\n", wd, XftTextWidthUtf8 (menu_style.title_font.font, text, len), xoff);
}

int
MTitle::draw ()
{
	cl ();
#ifdef FONTS_XFT
	draw_text (menu_style.title, menu_style.title_font);
	printf ("title-draw-wd,xoff: %d,%d\n", wd, xoff);
#elif defined FONTS_XCORE
	draw_text (menu_style.titleGC);
#endif
}

MHRule::MHRule ()
{
	x = 0;
	wd = 0;
	ht = menu_style.item_font.ht / 4;
}

int
MHRule::ia ()
{
	wd = 0;
}

int
MHRule::draw ()
{
	cl ();
	XDrawLine (dpy, boxw, menu_style.ruleGC, x+ht, y+ht/2, x+wd-ht, y+ht/2);
}

MVRule::MVRule ()
{
	x = 0;
	wd = menu_style.item_font.ht / 4;
	ht = 0;
}

int
MVRule::ia ()
{
	ht = 0;
}

int
MVRule::draw ()
{
	cl ();
	XDrawLine (dpy, boxw, menu_style.ruleGC, x+wd/2, y+wd, x+wd/2, y+ht-wd);
}

//********************************************************************

int
ConfigParser::parse_menu_style ()
{
	skip (".");

	/**/ if (match ("font")) menu_style.item_font.parse ();
	else if (match ("titlefont")) menu_style.title_font.parse ();
	else if (match ("text")) menu_style.text.parse ();
	else if (match ("pixmap")) get_filename (menu_style.bkg_xpm);
	else if (match ("back")) menu_style.back.parse ();
	else if (match ("hitext")) menu_style.hitext.parse ();
	else if (match ("hiback")) {
		menu_style.hiback.parse ();
		num (menu_style.hiback_fn);
	}
	else if (match ("rule")) menu_style.rule.parse ();
	else if (match ("title")) menu_style.title.parse ();
	else if (match ("bw")) num (menu_style.bw);
	else error ("bad MenuStyle command: ");
}

//********************************************************************

int
ConfigParser::parse_menu ()
{
	Menu *menu = new Menu;
	stripcpy2 (menu->name);
	
	// count how many bytes we need
	fpos_push ();
	int len = 0; // overall length of the definition
	while (get_line ()  && (mode & CM_SHELL || !match ("end"))) len += strlen (line);
	fpos_pop ();

	// copy the definition
	menu->src = new char[len+1];
	menu->src[len] = 0; // the terminating \0
	int loc = 0; // location
	while (get_line ()  && (mode & CM_SHELL || !match ("end"))) {
		memcpy (menu->src+loc, line, strlen (line));
		loc += strlen (line);
	}

	menu_list.add (menu);
}

