/* -*-c-*- */
/*
 * FvwmButtons, copyright 1996, Jarl Totland
 *
 * This module, and the entire GoodStuff program, and the concept for
 * interfacing this module to the Window Manager, are all original work
 * by Robert Nation
 *
 * Copyright 1993, Robert Nation. No guarantees or warantees or anything
 * are provided or implied in any way whatsoever. Use this program at your
 * own risk. Permission to use this program for any purpose is given,
 * as long as the copyright is kept intact.
 *
 */

/* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"

#include <ctype.h>
#include <stdio.h>

#include <X11/Xlib.h>
#include <X11/Intrinsic.h>

#include "libs/fvwmlib.h"
#include "libs/FScreen.h"
#include "libs/Module.h"
#include "libs/Colorset.h"
#include "libs/Parse.h"
#include "libs/Strings.h"
#include "FvwmButtons.h"
#include "button.h"
#include "parse.h"

extern int w, h, x, y, xneg, yneg; /* used in ParseConfigLine */
extern char *config_file;
extern int button_width;
extern int button_height;
extern int has_button_geometry;
extern int screen;

/* contains the character that terminated the last string from seekright */
static char terminator = '\0';

/* ----------------------------------- macros ------------------------------ */

static char *trimleft(char *s)
{
	while (s && isspace((unsigned char)*s))
	{
		s++;
	}
	return s;
}

/**
*** seekright()
***
*** split off the first continous section of the string into a new allocated
*** string and move the old pointer forward. Accepts and strips quoting with
*** ['"`], and the current quote q can be escaped inside the string with \q.
**/
static char *seekright(char **s)
{
	char *token = NULL;
	char *line = *s;

	line = DoGetNextToken(line, &token, NULL, "),", &terminator);
	if (*s != NULL && line == NULL)
	{
		line = strchr(*s, '\0');
	}
	*s = line;

	return token;
}

static char *my_get_font(char **s)
{
	char *option;
	int len;

	*s = GetNextFullOption(*s, &option);
	if (option)
	{
		for (len = strlen(option) - 1; len >= 0 && isspace(option[len]); len--)
		{
			/* remove trailing whitespace */
			option[len] = 0;
		}
		for ( ; *option && isspace(*option); option++)
		{
			/* remove leading whitespace */
		}
	}

	len = strlen(option) -1;
	if (option[len] == ')' )
	{
		option[len] = 0;
	}

	return option;
}

/**
*** ParseBack()
*** Parses the options possible to Back
*** This function appears to be obsolete & should be removed -- SS.
**/
static int ParseBack(char **ss)
{
	char *opts[] =
	{
		"icon",
		NULL
	};
	char *t, *s = *ss;
	int r = 0;

	while (*s && *s != ')')
	{
		s = trimleft(s);
		if (*s == ',')
		{
			s++;
			continue;
		}

		switch (GetTokenIndex(s, opts, -1, &s))
		{
		case 0: /* Icon */
			r = 1;
			fprintf(stderr,
				"%s: Back(Icon) not supported yet\n",
				MyName);
			break;
		default:
			t = seekright(&s);
			fprintf(stderr,
				"%s: Illegal back option \"%s\"\n",
				MyName, (t) ? t : "");
			if (t)
			{
				free(t);
			}
		}
	}
	if (*s)
	{
		s++;
	}
	*ss = s;

	return r;
}

/**
*** ParseBoxSize()
*** Parses the options possible to BoxSize
**/
static void ParseBoxSize(char **ss, flags_type *flags)
{
	char *opts[] =
	{
		"dumb",
		"fixed",
		"smart",
		NULL
	};
	char *s = *ss;
	int m;

	if (!s)
	{
		return;
	}
	*ss = GetNextTokenIndex(*ss, opts, 0, &m);
	switch (m)
	{
	case 0:
		flags->b_SizeFixed = 0;
		flags->b_SizeSmart = 0;
		break;
	case 1:
		flags->b_SizeFixed = 1;
		flags->b_SizeSmart = 0;
		break;
	case 2:
		flags->b_SizeSmart = 1;
		flags->b_SizeFixed = 0;
		break;
	default:
		flags->b_SizeFixed = 0;
		flags->b_SizeSmart = 0;
		fprintf(stderr,
			"%s: Illegal boxsize option \"%s\"\n",
			MyName, s);
		break;
	}
	return;
}

/**
*** ParseTitle()
*** Parses the options possible to Title
**/
static void ParseTitle(char **ss, byte *flags, byte *mask)
{
	char *titleopts[] =
	{
		"left",
		"right",
		"center",
		"side",
		NULL
	};
	char *t, *s = *ss;

	while (*s && *s != ')')
	{
		s = trimleft(s);
		if (*s == ',')
		{
			s++;
			continue;
		}

		switch (GetTokenIndex(s, titleopts, -1, &s))
		{
		case 0: /* Left */
			*flags &= ~b_TitleHoriz;
			*flags &= ~3;
			*mask |= b_TitleHoriz;
			break;
		case 1: /* Right */
			*flags &= ~b_TitleHoriz;
			*flags &= ~3;
			*flags |= 2;
			*mask |= b_TitleHoriz;
			break;
		case 2: /* Center */
			*flags &= ~b_TitleHoriz;
			*flags &= ~3;
			*flags |= 1;
			*mask |= b_TitleHoriz;
			break;
		case 3: /* Side */
			*flags |= b_Horizontal;
			*flags &= ~3;
			*mask |= b_Horizontal;
			break;
		default:
			t = seekright(&s);
			fprintf(stderr,
				"%s: Illegal title option \"%s\"\n",
				MyName, (t) ? t : "");
			if (t)
			{
				free(t);
			}
		}
	}
	if (*s)
	{
		s++;
	}
	*ss = s;
}

/**
*** ParseSwallow()
*** Parses the options possible to Swallow
**/
static void ParseSwallow(
	char **ss, unsigned int *flags, unsigned int *mask, button_info *b)
{
	char *swallowopts[] =
	{
		"nohints", "hints",
		"nokill", "kill",
		"noclose", "close",
		"respawn", "norespawn",
		"useold", "noold",
		"usetitle", "notitle",
		"fvwmmodule", "nofvwmmodule",
		"swallownew",
		NULL
	};
	char *t, *s = *ss;

	while (*s && *s != ')')
	{
		s = trimleft(s);
		if (*s == ',')
		{
			s++;
			continue;
		}

		switch (GetTokenIndex(s, swallowopts, -1, &s))
		{
		case 0: /* NoHints */
			*flags |= b_NoHints;
			*mask |= b_NoHints;
			break;
		case 1: /* Hints */
			*flags &= ~b_NoHints;
			*mask |= b_NoHints;
			break;
		case 2: /* NoKill */
			*flags &= ~b_Kill;
			*mask |= b_Kill;
			break;
		case 3: /* Kill */
			*flags |= b_Kill;
			*mask |= b_Kill;
			break;
		case 4: /* NoClose */
			*flags |= b_NoClose;
			*mask |= b_NoClose;
			break;
		case 5: /* Close */
			*flags &= ~b_NoClose;
			*mask |= b_NoClose;
			break;
		case 6: /* Respawn */
			*flags |= b_Respawn;
			*mask |= b_Respawn;
			b->newflags.do_swallow_new = 0;
			break;
		case 7: /* NoRespawn */
			*flags &= ~b_Respawn;
			*mask |= b_Respawn;
			b->newflags.do_swallow_new = 0;
			break;
		case 8: /* UseOld */
			*flags |= b_UseOld;
			*mask |= b_UseOld;
			break;
		case 9: /* NoOld */
			*flags &= ~b_UseOld;
			*mask |= b_UseOld;
			break;
		case 10: /* UseTitle */
			*flags |= b_UseTitle;
			*mask |= b_UseTitle;
			break;
		case 11: /* NoTitle */
			*flags &= ~b_UseTitle;
			*mask |= b_UseTitle;
			break;
		case 12: /* FvwmModule */
			*flags |= b_FvwmModule;
			*mask |= b_FvwmModule;
			break;
		case 13: /* NoFvwmModule */
			*flags &= ~b_FvwmModule;
			*mask |= b_FvwmModule;
			break;
		case 14: /* SwallowNew */
			*flags &= ~b_Respawn;
			*mask |= b_Respawn;
			b->newflags.do_swallow_new = 1;
			break;
		default:
			t = seekright(&s);
			fprintf(stderr,
				"%s: Illegal Swallow option \"%s\"\n", MyName,
				(t) ? t : "");
			if (t)
			{
				free(t);
			}
		}
	}
	if (*s)
	{
		s++;
	}
	*ss = s;
}

/**
*** ParsePanel()
*** Parses the options possible to Panel
**/
static void ParsePanel(
	char **ss, unsigned int *flags, unsigned int *mask, char *direction,
	int *steps, int *delay, panel_flags_type *panel_flags,
	int *indicator_size, int *rela_x, int *rela_y, char *position,
	char *context)
{
	char *swallowopts[] =
	{
		"nohints", "hints",
		"nokill", "kill",
		"noclose", "close",
		"respawn", "norespawn",
		"useold", "noold",
		"usetitle", "notitle",
		"up", "down", "left", "right",
		"steps",
		"delay",
		"smooth",
		"noborder",
		"indicator",
		"position",
		NULL
	};

	char *positionopts[] =
	{
		"button", "module", "root", "center", "left", "top", "right",
		"bottom", "noplr", "noptb", "mlr", "mtb", NULL
	};

	char *t, *s = *ss;
	int n;

	while (*s && *s != ')')
	{
		s = trimleft(s);
		if (*s == ',')
		{
			s++;
			continue;
		}

		switch (GetTokenIndex(s, swallowopts, -1, &s))
		{
		case 0: /* NoHints */
			*flags |= b_NoHints;
			*mask |= b_NoHints;
			break;
		case 1: /* Hints */
			*flags &= ~b_NoHints;
			*mask |= b_NoHints;
			break;
		case 2: /* NoKill */
			*flags &= ~b_Kill;
			*mask |= b_Kill;
			break;
		case 3: /* Kill */
			*flags |= b_Kill;
			*mask |= b_Kill;
			break;
		case 4: /* NoClose */
			*flags |= b_NoClose;
			*mask |= b_NoClose;
			break;
		case 5: /* Close */
			*flags &= ~b_NoClose;
			*mask |= b_NoClose;
			break;
		case 6: /* Respawn */
			*flags |= b_Respawn;
			*mask |= b_Respawn;
			break;
		case 7: /* NoRespawn */
			*flags &= ~b_Respawn;
			*mask |= b_Respawn;
			break;
		case 8: /* UseOld */
			*flags |= b_UseOld;
			*mask |= b_UseOld;
			break;
		case 9: /* NoOld */
			*flags &= ~b_UseOld;
			*mask |= b_UseOld;
			break;
		case 10: /* UseTitle */
			*flags |= b_UseTitle;
			*mask |= b_UseTitle;
			break;
		case 11: /* NoTitle */
			*flags &= ~b_UseTitle;
			*mask |= b_UseTitle;
			break;
		case 12: /* up */
			*direction = SLIDE_UP;
			break;
		case 13: /* down */
			*direction = SLIDE_DOWN;
			break;
		case 14: /* left */
			*direction = SLIDE_LEFT;
			break;
		case 15: /* right */
			*direction = SLIDE_RIGHT;
			break;
		case 16: /* steps */
			sscanf(s, "%d%n", steps, &n);
			s += n;
			break;
		case 17: /* delay */
			sscanf(s, "%d%n", delay, &n);
			s += n;
			break;
		case 18: /* smooth */
			(*panel_flags).smooth = 1;
			break;
		case 19: /* noborder */
			(*panel_flags).ignore_lrborder = 1;
			(*panel_flags).ignore_tbborder = 1;
			break;
		case 20: /* indicator */
			n = 0;
			(*panel_flags).panel_indicator = 1;
			*indicator_size = 0;
			sscanf(s, "%d%n", indicator_size, &n);
			if (*indicator_size < 0 || *indicator_size > 100)
			{
				*indicator_size = 0;
			}
			s += n;
			break;
		case 21: /* position */
			n = 0;
			*rela_x = *rela_y = 0;
			while (*s != ',' && *s != ')' && *s)
			{
				s = trimleft(s);
				/* get x and y offset */
				if (isdigit(*s) || *s == '+' || *s == '-')
				{
					sscanf(s, "%i%n", rela_x, &n);
					s += n;
					if (*s == 'p' || *s == 'P')
					{
						(*panel_flags).relative_x_pixel = 1;
						s++;
					}
					n = 0;
					s = trimleft(s);
					sscanf(s, "%i%n", rela_y, &n);
					s += n;
					if (*s == 'p' || *s == 'P')
					{
						(*panel_flags).relative_y_pixel = 1;
						s++;
					}
					s = trimleft(s);
				}
				switch (GetTokenIndex(s, positionopts, -1, &s))
				{
				case 0: /* button */
					*context = SLIDE_CONTEXT_PB;
					break;
				case 1: /* module */
					*context = SLIDE_CONTEXT_MODULE;
					break;
				case 2: /* root */
					*context = SLIDE_CONTEXT_ROOT;
					break;
				case 3: /* center */
					*position = SLIDE_POSITION_CENTER;
					break;
				case 4: /* left */
					*position = SLIDE_POSITION_LEFT_TOP;
					break;
				case 5: /* top  */
					*position = SLIDE_POSITION_LEFT_TOP;
					break;
				case 6: /* right */
					*position = SLIDE_POSITION_RIGHT_BOTTOM;
					break;
				case 7: /* bottom */
					*position = SLIDE_POSITION_RIGHT_BOTTOM;
					break;
				case 8: /* noplr */
					(*panel_flags).ignore_lrborder = 1;
					break;
				case 9: /* noptb */
					(*panel_flags).ignore_tbborder = 1;
					break;
				case 10: /* mlr */
					(*panel_flags).buttons_lrborder = 1;
					break;
				case 11: /* mtb */
					(*panel_flags).buttons_tbborder = 1;
					break;
				default:
					t = seekright(&s);
					s--;
					if (t)
					{
						fprintf(stderr,
							"%s: Illegal Panel "
							"position option %s\n",
							MyName,	(t) ? t : "");
						free(t);
					}
				}
			}
			break;
		default:
			t = seekright(&s);
			fprintf(stderr,
				"%s: Illegal Panel option \"%s\"\n", MyName,
				(t) ? t : "");
			if (t)
			{
				free(t);
			}
		}
	}
	if (*s)
	{
		s++;
	}
	*ss = s;
}

/**
*** ParseContainer()
*** Parses the options possible to Container
**/
static void ParseContainer(char **ss,button_info *b)
{
	char *conts[] =
	{
		"columns",
		"rows",
		"font",
		"frame",
		"back",
		"fore",
		"padding",
		"title",
		"swallow",
		"nosize",
		"size",
		"boxsize",
		"colorset",
		NULL
	};
	char *t, *o, *s = *ss;
	int i, j;

	while (*s && *s != ')')
	{
		s = trimleft(s);
		if (*s == ',')
		{
			s++;
			continue;
		}

		switch (GetTokenIndex(s, conts, -1, &s))
		{
		case 0: /* Columns */
			b->c->num_columns = max(1, strtol(s, &t, 10));
			s = t;
			break;
		case 1: /* Rows */
			b->c->num_rows = max(1, strtol(s, &t, 10));
			s = t;
			break;
		case 2: /* Font */
			if (b->c->font_string)
			{
				free(b->c->font_string);
			}
			b->c->font_string = my_get_font(&s);
			b->c->flags.b_Font = (b->c->font_string ? 1 : 0);
			break;
		case 3: /* Frame */
			b->c->framew = strtol(s, &t, 10);
			b->c->flags.b_Frame = 1;
			s = t;
			break;
		case 4: /* Back */
			s = trimleft(s);
			if (*s == '(' && s++ && ParseBack(&s))
			{
				b->c->flags.b_IconBack = 1;
			}
			if (b->c->back)
			{
				free(b->c->back);
			}
			b->c->back = seekright(&s);
			if (b->c->back)
			{
				b->c->flags.b_Back = 1;
			}
			else
			{
				b->c->flags.b_IconBack = 0;
				b->c->flags.b_Back = 0;
			}
			break;
    case 5: /* Fore */
      if (b->c->fore) free(b->c->fore);
      b->c->fore = seekright(&s);
      b->c->flags.b_Fore = (b->c->fore ? 1 : 0);
      break;
    case 6: /* Padding */
      i = strtol(s, &t, 10);
      if (t > s)
      {
	b->c->xpad = b->c->ypad = i;
	s = t;
	i = strtol(s, &t, 10);
	if (t > s)
	{
	  b->c->ypad = i;
	  s = t;
	}
	b->c->flags.b_Padding = 1;
      }
      else
	fprintf(stderr,"%s: Illegal padding argument\n",MyName);
      break;
    case 7: /* Title - flags */
      s = trimleft(s);
      if (*s == '(' && s++)
      {
	b->c->justify = 0;
	b->c->justify_mask = 0;
	ParseTitle(&s, &b->c->justify, &b->c->justify_mask);
	if (b->c->justify_mask)
	{
	  b->c->flags.b_Justify = 1;
	}
      }
      else
      {
	char *temp;
	fprintf(stderr,
		"%s: Illegal title in container options\n",
		MyName);
	temp = seekright(&s);
	if (temp)
	{
	  free(temp);
	}
      }
      break;
    case 8: /* Swallow - flags */
      {
	Bool failed = False;

	s = trimleft(s);
	if (b->c->flags.b_Swallow || b->c->flags.b_Panel)
	{
	  fprintf(stderr, "%s: Multiple Swallow or Panel options are not"
		" allowed in a single button", MyName);
	  failed = True;
	}
	else if (*s == '(' && s++)
	{
	  b->c->swallow = 0;
	  b->c->swallow_mask = 0;
	  ParseSwallow(&s, &b->c->swallow, &b->c->swallow_mask, b);
	  if (b->c->swallow_mask)
	  {
	    b->c->flags.b_Swallow = 1;
	  }
	}
	else
	{
	  fprintf(stderr,
		  "%s: Illegal swallow or panel in container options\n",
		  MyName);
	  failed = True;
	}
	if (failed)
	{
	  char *temp;

	  temp = seekright(&s);
	  if (temp)
	    free(temp);
	}
      }
      break;
    case 9: /* NoSize */
      b->c->flags.b_Size = 1;
      b->c->minx = b->c->miny = 0;
      break;

    case 10: /* Size */
      i = strtol(s, &t, 10);
      j = strtol(t, &o, 10);
      if (t > s && o > t)
      {
	b->c->minx = i;
	b->c->miny = j;
	b->c->flags.b_Size = 1;
	s = o;
      }
      else
	fprintf(stderr,"%s: Illegal size arguments\n",MyName);
      break;

    case 11: /* BoxSize */
      ParseBoxSize(&s, &b->c->flags);
      break;

    case 12: /* Colorset */
      i = strtol(s, &t, 10);
      if (t > s)
      {
	b->c->colorset = i;
	b->c->flags.b_Colorset = 1;
	AllocColorset(i);
	s = t;
      }
      else
      {
	b->c->flags.b_Colorset = 0;
      }
      break;

    default:
      t = seekright(&s);
      fprintf(stderr,"%s: Illegal container option \"%s\"\n",MyName, (t)?t:"");
      if (t)
	free(t);
    }
  }
  if (*s)
  {
    s++;
  }
  *ss = s;
}

/**
*** ParseButton()
***
*** parse a buttonconfig string.
*** *FvwmButtons: (option[ options]) title iconname command
**/
/*#define DEBUG_PARSER*/
static void ParseButton(button_info **uberb, char *s)
{
	button_info *b, *ub = *uberb;
	int i, j;
	char *t, *o;

	b = alloc_button(ub, (ub->c->num_buttons)++);
	s = trimleft(s);

	if (*s == '(' && s++)
	{
		char *opts[] =
		{
			"back",
			"fore",
			"font",
			"title",
			"icon",
			"frame",
			"padding",
			"swallow",
			"panel",
			"actionignoresclientwindow",
			"actiononpress",
			"container",
			"end",
			"nosize",
			"size",
			"left",
			"right",
			"center",
			"colorset",
			"action",
			"id",
			"activeicon",
			"activetitle",
			"pressicon",
			"presstitle",
			"activecolorset",
			"presscolorset",
			"top",
			NULL
		};
		s = trimleft(s);
		while (*s && *s != ')')
		{
			Bool is_swallow = False;

			if (*s == ',')
			{
				s++;
				s = trimleft(s);
				continue;
			}
			if (isdigit(*s) || *s == '+' || *s == '-')
			{
				char *geom;
				int x, y, flags;
				unsigned int w, h;
				geom = seekright(&s);
				if (geom)
				{
					flags = XParseGeometry(geom, &x, &y, &w, &h);
					if (flags&WidthValue)
					{
						b->BWidth = w;
					}
					if (flags&HeightValue)
					{
						b->BHeight = h;
					}
					if (flags & XValue)
					{
						b->BPosX = x;
						b->flags.b_PosFixed = 1;
					}
					if (flags & YValue)
					{
						b->BPosY = y;
						b->flags.b_PosFixed = 1;
					}
					if (flags & XNegative)
					{
						b->BPosX = -1 - x;
					}
					if (flags & YNegative)
					{
						b->BPosY = -1 - y;
					}
					free(geom);
				}
				s = trimleft(s);
				continue;
			}
			switch (GetTokenIndex(s, opts, -1, &s))
			{
			case 0: /* Back */
				s = trimleft(s);
				if (*s == '(' && s++ && ParseBack(&s))
				{
					b->flags.b_IconBack = 1;
				}
				if (b->flags.b_Back && b->back)
				{
					free(b->back);
				}
				b->back = seekright(&s);
				if (b->back)
				{
					b->flags.b_Back = 1;
				}
				else
				{
					b->flags.b_IconBack = 0;
					b->flags.b_Back = 0;
				}
				break;

			case 1: /* Fore */
				if (b->flags.b_Fore && b->fore)
				{
					free(b->fore);
				}
				b->fore = seekright(&s);
				b->flags.b_Fore = (b->fore ? 1 : 0);
				break;

			case 2: /* Font */
				if (b->flags.b_Font && b->font_string)
				{
					free(b->font_string);
				}
				b->font_string = my_get_font(&s);
				b->flags.b_Font = (b->font_string ? 1 : 0);
				break;

			/* ----------------- title --------------- */

			case 3: /* Title */
				s = trimleft(s);
				if (*s == '(' && s++)
				{
					b->justify = 0;
					b->justify_mask = 0;
					ParseTitle(
						&s, &b->justify,
						&b->justify_mask);
					if (b->justify_mask)
					{
						b->flags.b_Justify = 1;
					}
				}
				t = seekright(&s);
				if (t && *t && (t[0] != '-' || t[1] != 0))
				{
					if (b->title)
					{
						free(b->title);
					}
					b->title = t;
#ifdef DEBUG_PARSER
					fprintf(stderr,
						"PARSE: Title \"%s\"\n",
						b->title);
#endif
					b->flags.b_Title = 1;
				}
				else
				{
					fprintf(stderr,
						"%s: Missing title argument\n",
						MyName);
					if (t)
					{
						free(t);
					}
				}
				break;

			/* ------------------ icon --------------- */

			case 4: /* Icon */
				t = seekright(&s);
				if (t && *t && (t[0] != '-' || t[1] != 0))
				{
					if (b->flags.b_Swallow)
					{
						fprintf(stderr,
							"%s: a button can not "
							"have an icon and a "
							"swallowed window at "
							"the same time. "
							"Ignoring icon\n",
							MyName);
					}
					else
					{
						if (b->icon_file)
						{
							free(b->icon_file);
						}
						b->icon_file = t;
						b->flags.b_Icon = 1;
					}
				}
				else
				{
					fprintf(stderr,
						"%s: Missing Icon argument\n",
						MyName);
					if (t)
					{
						free(t);
					}
				}
				break;

			/* ----------------- frame --------------- */

			case 5: /* Frame */
				i = strtol(s, &t, 10);
				if (t > s)
				{
					b->flags.b_Frame = 1;
					b->framew = i;
					s = t;
				}
				else
				{
					fprintf(stderr,
						"%s: Illegal frame argument\n",
						MyName);
				}
				break;

			/* ---------------- padding -------------- */

			case 6: /* Padding */
				i = strtol(s,&t,10);
				if (t>s)
				{
					b->xpad = b->ypad = i;
					b->flags.b_Padding = 1;
					s = t;
					i = strtol(s, &t, 10);
					if (t > s)
					{
						b->ypad = i;
						s = t;
					}
				}
				else
				{
					fprintf(stderr,
						"%s: Illegal padding "
						"argument\n", MyName);
				}
				break;

			/* ---------------- swallow -------------- */

			case 7: /* Swallow */
				is_swallow = True;
				/* fall through */

			case 8: /* Panel */
				s = trimleft(s);
				if (is_swallow)
				{
					b->swallow = 0;
					b->swallow_mask = 0;
				}
				else
				{
					/* set defaults */
					b->swallow = b_Respawn;
					b->swallow_mask = b_Respawn;
					b->slide_direction = SLIDE_UP;
					b->slide_position = SLIDE_POSITION_CENTER;
					b->slide_context = SLIDE_CONTEXT_PB;
					b->relative_x = 0;
					b->relative_y = 0;
					b->slide_steps = 12;
					b->slide_delay_ms = 5;
				}
				if (*s == '(' && s++)
				{
					if (is_swallow)
					{
						ParseSwallow(
							&s, &b->swallow,
							&b->swallow_mask, b);
					}
					else
					{
						ParsePanel(
							&s, &b->swallow,
							&b->swallow_mask,
							&b->slide_direction,
							&b->slide_steps,
							&b->slide_delay_ms,
							&b->panel_flags,
							&b->indicator_size,
							&b->relative_x,
							&b->relative_y,
							&b->slide_position,
							&b->slide_context);
					}
				}
				t = seekright(&s);
				o = seekright(&s);
				if (t)
				{
					if (b->hangon)
					{
						free(b->hangon);
					}
					b->hangon = t;
					if (is_swallow)
					{
						if (b->flags.b_Icon)
						{
							fprintf(stderr,
							"%s: a button can not "
							"have an icon and a "
							"swallowed window at "
							"the same time. "
							"Ignoring icon\n",
							MyName);
							b->flags.b_Icon = 0;
						}

						b->flags.b_Swallow = 1;
						b->flags.b_Hangon = 1;
					}
					else
					{
						b->flags.b_Panel = 1;
						b->flags.b_Hangon = 1;
						b->newflags.is_panel = 1;
						b->newflags.panel_mapped = 0;
					}
					b->swallow |= 1;
					if (!(b->swallow & b_NoHints))
					{
						b->hints = (XSizeHints*)
						mymalloc(sizeof(XSizeHints));
					}
					if (o)
					{
						char *p;

						p = module_expand_action(
							Dpy, screen, o, NULL,
							UberButton->c->fore,
							UberButton->c->back);
						if (p)
						{
							if (!(buttonSwallow(b)&b_UseOld))
							{
								exec_swallow(p,b);
							}
							if (b->spawn)
							{
								free(b->spawn);
							}

							/* Might be needed if
							 * respawning sometime
							 */
							b->spawn = o;
							free(p);
						}
					}
				}
				else
				{
					fprintf(stderr,
						"%s: Missing swallow "
						"argument\n", MyName);
					if (t)
					{
						free(t);
					}
					if (o)
					{
						free(o);
					}
				}
				/* check if it is a module by command line inspection if
				 * this hints has not been given in the swallow option */
				if (is_swallow && !(b->swallow_mask & b_FvwmModule))
				{
					if (b->spawn != NULL &&
					    (strncasecmp(b->spawn, "module", 6) == 0 ||
					     strncasecmp(b->spawn, "fvwm", 4) == 0))
					{
						b->swallow |= b_FvwmModule;
					}
				}
				break;

			case 9: /* ActionIgnoresClientWindow */
				b->flags.b_ActionIgnoresClientWindow = 1;
				break;

			case 10: /* ActionOnPress */
				b->flags.b_ActionOnPress = 1;
				break;

			/* ---------------- container ------------ */

			case 11: /* Container */
				/*
				 * SS: This looks very buggy! The FvwmButtons
				 * man page suggests it's here to restrict the
				 * options used with "Container", but it only
				 * restricts those options appearing _before_
				 * the "Container" keyword for a button.
				 * b->flags&=b_Frame|b_Back|b_Fore|b_Padding|b_Action;
				 */

				MakeContainer(b);
				*uberb = b;
				s = trimleft(s);
				if (*s == '(' && s++)
				{
					ParseContainer(&s,b);
				}
				break;

			case 12: /* End */
				*uberb = ub->parent;
				ub->c->buttons[--(ub->c->num_buttons)] = NULL;
				free(b);
				if (!ub->parent)
				{
					fprintf(stderr,"%s: Unmatched END in config file\n",MyName);
					exit(1);
				}
				if (ub->parent->c->flags.b_Colorset ||
				    ub->parent->c->flags.b_ColorsetParent)
				{
					ub->c->flags.b_ColorsetParent = 1;
				}
				if (ub->parent->c->flags.b_IconBack || ub->parent->c->flags.b_IconParent)
				{
					ub->c->flags.b_IconParent = 1;
				}
				return;

			case 13: /* NoSize */
				b->flags.b_Size = 1;
				b->minx = b->miny = 0;
				break;

			case 14: /* Size */
				i = strtol(s, &t, 10);
				j = strtol(t, &o, 10);
				if (t > s && o > t)
				{
					b->minx = i;
					b->miny = j;
					b->flags.b_Size = 1;
					s = o;
				}
				else
				{
					fprintf(stderr,
						"%s: Illegal size arguments\n",
						MyName);
				}
				break;

			case 15: /* Left */
				b->flags.b_Left = 1;
				b->flags.b_Right = 0;
				break;

			case 16: /* Right */
				b->flags.b_Right = 1;
				b->flags.b_Left = 0;
				break;

			case 17: /* Center */
				b->flags.b_Right = 0;
				b->flags.b_Left = 0;
				break;

			case 18: /* Colorset */
				i = strtol(s, &t, 10);
				if (t > s)
				{
					b->colorset = i;
					b->flags.b_Colorset = 1;
					s = t;
					AllocColorset(i);
				}
				else
				{
					b->flags.b_Colorset = 0;
				}
				break;

			/* ----------------- action -------------- */

			case 19: /* Action */
				s = trimleft(s);
				i = 0;
				if (*s == '(')
				{
					s++;
					if (strncasecmp(s, "mouse", 5) != 0)
					{
						fprintf(stderr,
							"%s: Couldn't parse "
							"action\n", MyName);
					}
					s += 5;
					i = strtol(s, &t, 10);
					s = t;
					while (*s && *s != ')')
					{
						s++;
					}
					if (*s == ')')
					{
						s++;
					}
				}
				{
					char *r;
					char *u = s;

					s = GetQuotedString(s, &t, ",)", NULL, "(", ")");
					r = s;
					if (t && r > u + 1)
					{
						/* remove unquoted trailing spaces */
						r -= 2;
						while (r >= u && isspace(*r))
						{
							r--;
						}
						r++;
						if (isspace(*r))
						{
							t[strlen(t) - (s - r - 1)] = 0;
						}
					}
				}
				if (t)
				{
					AddButtonAction(b,i,t);
					free(t);
				}
				else
				{
					fprintf(stderr,
						"%s: Missing action argument\n",
						MyName);
				}
				break;

			case 20: /* Id */
				s = trimleft(s);
				s = DoGetNextToken(s, &t, NULL, ",)", &terminator);

				/* it should include the delimiter */
				if (s && terminator == ')')
				{
					s--;
				}

				if (t)
				{
					if (isalpha(t[0]))
					{
						/* should check for duplicate ids first... */
						b->flags.b_Id = 1;
						if (b->id)
						{
							free(b->id);
						}
						CopyString(&b->id, t);
					}
			        	else
					{
						fprintf(stderr,
							"%s: Incorrect id '%s' "
							"ignored\n", MyName, t);
					}
					free(t);
				}
				else
				{
					fprintf(stderr,
						"%s: Missing id argument\n",
						MyName);
				}
				break;

			/* ------------------ ActiveIcon --------------- */
			case 21: /* ActiveIcon */
				t = seekright(&s);
				if (t && *t && (t[0] != '-' || t[1] != 0))
				{
					if (b->flags.b_Swallow)
					{
						fprintf(stderr,
							"%s: a button can not "
							"have a ActiveIcon and "
							"a swallowed window at "
							"the same time. "
							"Ignoring ActiveIcon.\n",
							MyName);
					}
					else
					{
						if (b->active_icon_file)
						{
							free(b->active_icon_file);
						}
						b->active_icon_file = t;
						b->flags.b_ActiveIcon = 1;
					}
				}
				else
				{
					fprintf(stderr,
						"%s: Missing ActiveIcon "
						"argument\n", MyName);
					if (t)
					{
						free(t);
					}
				}
				break;

			/* --------------- ActiveTitle --------------- */
			case 22: /* ActiveTitle */
				s = trimleft(s);
				if (*s == '(')
				{
					fprintf(stderr,
						"%s: justification not allowed "
						"for ActiveTitle.\n", MyName);
				}
				t = seekright(&s);
				if (t && *t && (t[0] != '-' || t[1] != 0))
				{
					if (b->activeTitle)
					{
						free(b->activeTitle);
					}
					b->activeTitle = t;
#ifdef DEBUG_PARSER
					fprintf(stderr,
						"PARSE: ActiveTitle \"%s\"\n",
						b->activeTitle);
#endif
					b->flags.b_ActiveTitle = 1;
				}
				else
				{
					fprintf(stderr,
						"%s: Missing ActiveTitle "
						"argument\n", MyName);
					if (t)
					{
						free(t);
					}
				}
				break;

			/* --------------- PressIcon --------------- */
			case 23: /* PressIcon */
				t = seekright(&s);
				if (t && *t && (t[0] != '-' || t[1] != 0))
				{
					if (b->flags.b_Swallow)
					{
						fprintf(stderr,
							"%s: a button can not "
							"have a PressIcon and "
							"a swallowed window at "
							"the same time. "
							"Ignoring PressIcon.\n",
							MyName);
					}
					else
					{
						if (b->press_icon_file)
						{
							free(b->press_icon_file);
						}
						b->press_icon_file = t;
						b->flags.b_PressIcon = 1;
					}
				}
				else
				{
					fprintf(stderr,
						"%s: Missing PressIcon "
						"argument\n", MyName);
					if (t)
					{
						free(t);
					}
				}
				break;

			/* --------------- PressTitle --------------- */
			case 24: /* PressTitle */
				s = trimleft(s);
				if (*s == '(')
				{
					fprintf(stderr,
						"%s: justification not allowed "
						"for PressTitle.\n", MyName);
				}
				t = seekright(&s);
				if (t && *t && (t[0] != '-' || t[1] != 0))
				{
					if (b->pressTitle)
					{
						free(b->pressTitle);
					}
					b->pressTitle = t;
#ifdef DEBUG_PARSER
					fprintf(stderr,
						"PARSE: PressTitle \"%s\"\n",
						b->pressTitle);
#endif
					b->flags.b_PressTitle = 1;
				}
				else
				{
					fprintf(stderr,
						"%s: Missing PressTitle "
						"argument\n", MyName);
					if (t)
					{
						free(t);
					}
				}
				break;

			/* --------------- --------------- */
			case 25: /* ActiveColorset */
				i = strtol(s, &t, 10);
				if (t > s)
				{
					b->activeColorset = i;
					b->flags.b_ActiveColorset = 1;
					s = t;
					AllocColorset(i);
				}
				else
				{
					b->flags.b_ActiveColorset = 0;
				}
				break;

			/* --------------- --------------- */
			case 26: /* PressColorset */
				i = strtol(s, &t, 10);
				if (t > s)
				{
					b->pressColorset = i;
					b->flags.b_PressColorset = 1;
					s = t;
					AllocColorset(i);
				}
				else
				{
					b->flags.b_PressColorset = 0;
				}
				break;

			case 27: /* top */
				b->flags.b_Top = 1;
				break;
			/* --------------- --------------- */
			default:
				t = seekright(&s);
				fprintf(stderr,
					"%s: Illegal button option \"%s\"\n",
					MyName, (t) ? t : "");
				if (t)
				{
					free(t);
				}
				break;
			} /* end switch */
			s = trimleft(s);
		}
		if (s && *s)
		{
			s++;
			s = trimleft(s);
		}
	}

	/* get title and iconname */
	if (!b->flags.b_Title)
	{
		b->title = seekright(&s);
		if (b->title && *b->title &&
			((b->title)[0] != '-' || (b->title)[1] != 0))
		{
			b->flags.b_Title = 1;
		}
		else if (b->title)
		{
			free(b->title);
		}
	}
	else
	{
		char *temp;
		temp = seekright(&s);
		if (temp)
		{
			free(temp);
		}
	}

	if (!b->flags.b_Icon)
	{
		b->icon_file = seekright(&s);
		if (b->icon_file && b->icon_file &&
			 ((b->icon_file)[0] != '-'||(b->icon_file)[1] != 0))
		{
			b->flags.b_Icon = 1;
		}
		else if (b->icon_file)
		{
			free(b->icon_file);
		}
	}
	else
	{
		char *temp;
		temp = seekright(&s);
		if (temp)
		{
			free(temp);
		}
	}

	s = trimleft(s);

	/* Swallow hangon command */
	if (strncasecmp(s, "swallow", 7) == 0 || strncasecmp(s, "panel", 7) == 0)
	{
		if (b->flags.b_Swallow || b->flags.b_Panel)
		{
			fprintf(stderr,
				"%s: Illegal with both old and new swallow!\n",
				MyName);
			exit(1);
		}
		s += 7;
		/*
		 * Swallow old 'swallowmodule' command
		 */
		if (strncasecmp(s, "module", 6) == 0)
		{
			s += 6;
		}
		if (b->hangon)
		{
			free(b->hangon);
		}
		b->hangon = seekright(&s);
		if (!b->hangon)
		{
			b->hangon = safestrdup("");
		}
		if (tolower(*s) == 's')
		{
			b->flags.b_Swallow = 1;
			b->flags.b_Hangon = 1;
		}
		else
		{
			b->flags.b_Panel = 1;
			b->flags.b_Hangon = 1;
		}
		b->swallow |= 1;
		s = trimleft(s);
		if (!(b->swallow & b_NoHints))
		{
			b->hints = (XSizeHints*)mymalloc(sizeof(XSizeHints));
		}
		if (*s)
		{
			if (!(buttonSwallow(b) & b_UseOld))
			{
				SendText(fd, s, 0);
			}
			b->spawn = safestrdup(s);
		}
	}
	else if (*s)
	{
		AddButtonAction(b, 0, s);
	}
	return;
}

/**
*** ParseConfigLine
**/
static void ParseConfigLine(button_info **ubb, char *s)
{
	button_info *ub = *ubb;
	char *opts[] =
	{
		"geometry",
		"buttongeometry",
		"font",
		"padding",
		"columns",
		"rows",
		"back",
		"fore",
		"frame",
		"file",
		"pixmap",
		"boxsize",
		"colorset",
		"activecolorset",
		"presscolorset",
		NULL
	};
	int i, j, k;

	switch (GetTokenIndex(s, opts, -1, &s))
	{
	case 0:/* Geometry */
	{
		char geom[64];

		i = sscanf(s, "%63s", geom);
		if (i == 1)
		{
			parse_window_geometry(geom, 0);
		}
		break;
	}
	case 1:/* ButtonGeometry */
	{
		char geom[64];

		i = sscanf(s, "%63s", geom);
		if (i == 1)
		{
			parse_window_geometry(geom, 1);
		}
		break;
	}
	case 2:/* Font */
		if (ub->c->font_string)
		{
			free(ub->c->font_string);
		}
		CopyStringWithQuotes(&ub->c->font_string, s);
		break;
	case 3:/* Padding */
		i = sscanf(s, "%d %d", &j, &k);
		if (i > 0)
		{
			ub->c->xpad = ub->c->ypad = j;
		}
		if (i > 1)
		{
			ub->c->ypad = k;
		}
		break;
	case 4:/* Columns */
		i = sscanf(s, "%d", &j);
		if (i > 0)
		{
			ub->c->num_columns = j;
		}
		break;
	case 5:/* Rows */
		i = sscanf(s, "%d", &j);
		if (i > 0)
		{
			ub->c->num_rows = j;
		}
		break;
	case 6:/* Back */
		if (ub->c->back)
		{
			free(ub->c->back);
		}
		CopyString(&(ub->c->back), s);
		break;
	case 7:/* Fore */
		if (ub->c->fore)
		{
			free(ub->c->fore);
		}
		CopyString(&(ub->c->fore), s);
		break;
	case 8:/* Frame */
		i = sscanf(s,"%d",&j);
		if (i > 0)
		{
			ub->c->framew = j;
		}
		break;
	case 9:/* File */
		s = trimleft(s);
		if (config_file)
		{
			free(config_file);
		}
		config_file = seekright(&s);
		break;
	case 10:/* Pixmap */
		s = trimleft(s);
		if (strncasecmp(s, "none", 4) == 0)
		{
			ub->c->flags.b_TransBack = 1;
		}
		else
		{
			if (ub->c->back_file)
			{
				free(ub->c->back_file);
			}
			CopyString(&(ub->c->back_file),s);
		}
		ub->c->flags.b_IconBack = 1;
		break;
	case 11: /* BoxSize */
		ParseBoxSize(&s, &ub->c->flags);
		break;
	case 12: /* Colorset */
		i = sscanf(s, "%d", &j);
		if (i > 0)
		{
			ub->c->colorset = j;
			ub->c->flags.b_Colorset = 1;
			AllocColorset(j);
		}
		else
		{
			ub->c->flags.b_Colorset = 0;
		}
		break;
	case 13: /* ActiveColorset */
		i = sscanf(s, "%d", &j);
		if (i > 0)
		{
			ub->c->activeColorset = j;
			ub->c->flags.b_ActiveColorset = 1;
			AllocColorset(j);
		}
		else
		{
			ub->c->flags.b_ActiveColorset = 0;
		}
		break;
	case 14: /* PressColorset */
		i = sscanf(s, "%d", &j);
		if (i > 0)
		{
			ub->c->pressColorset = j;
			ub->c->flags.b_PressColorset = 1;
			AllocColorset(j);
		}
		else
		{
			ub->c->flags.b_PressColorset = 0;
		}
		break;

	default:
		s = trimleft(s);
		ParseButton(ubb, s);
		break;
	}
}

/**
*** ParseConfigFile()
*** Parses optional separate configuration file for FvwmButtons
**/
static void ParseConfigFile(button_info *ub)
{
	char s[1024], *t;
	FILE *f = fopen(config_file, "r");
	int l;
	if (!f)
	{
		fprintf(stderr,
			"%s: Couldn't open config file %s\n", MyName,
			config_file);
		return;
	}

	while (fgets(s, 1023, f))
	{
		/* Allow for line continuation: */
		while ((l = strlen(s)) < sizeof(s)
			&& l >= 2 && s[l - 1] == '\n' && s[l - 2] == '\\')
		{
			fgets(s + l - 2, sizeof(s) - l, f);
		}

		/* And comments: */
		t = s;
		while (*t)
		{
			if (*t == '#' && (t == s || *(t - 1) != '\\'))
			{
				*t = 0;
				break;
			}
			t++;
		}
		t = s;
		t = trimleft(t);
		if (*t)
		{
			ParseConfigLine(&ub, t);
		}
	}

	fclose(f);
}

void parse_window_geometry(char *geom, int is_button_geometry)
{
	int flags;
	int g_x;
	int g_y;
	unsigned int width;
	unsigned int height;

	flags = FScreenParseGeometry(geom, &g_x, &g_y, &width, &height);
	UberButton->w = 0;
	UberButton->h = 0;
	UberButton->x = 0;
	UberButton->y = 0;
	if (is_button_geometry)
	{
		if (flags&WidthValue)
		{
			button_width = width;
		}
		if (flags&HeightValue)
		{
			button_height = height;
		}
	}
	else
	{
		if (flags&WidthValue)
		{
			w = width;
		}
		if (flags&HeightValue)
		{
			h = height;
		}
	}
	if (flags&XValue)
	{
		UberButton->x = g_x;
	}
	if (flags&YValue)
	{
		UberButton->y = g_y;
	}
	if (flags&XNegative)
	{
		UberButton->w = 1;
	}
	if (flags&YNegative)
	{
		UberButton->h = 1;
	}
	has_button_geometry = is_button_geometry;

	return;
}

/**
*** ParseOptions()
**/
void ParseConfiguration(button_info *ub)
{
	char *s;
	char *items[] =
	{
		NULL, /* filled out below */
		"imagepath",
		"colorset",
		XINERAMA_CONFIG_STRING,
		NULL
	};

	items[0] = mymalloc(strlen(MyName) + 2);
	sprintf(items[0], "*%s", MyName);

	/* send config lines with MyName */
	InitGetConfigLine(fd, items[0]);
	GetConfigLine(fd, &s);
	while (s && s[0])
	{
		char *rest;
		switch (GetTokenIndex(s,items,-1,&rest))
		{
		case -1:
			break;
		case 0:
			if (rest && rest[0] && !config_file)
			{
				ParseConfigLine(&ub, rest);
			}
			break;
		case 1:
			if (imagePath)
			{
				free(imagePath);
			}
			CopyString(&imagePath, rest);
			break;
		case 2:
			/* store colorset sent by fvwm */
			LoadColorset(rest);
			break;
		case 3:
			/* Xinerama state */
			FScreenConfigureModule(rest);
			break;
		}
		GetConfigLine(fd,&s);
	}

	if (config_file)
	{
		ParseConfigFile(ub);
	}

	free(items[0]);
	return;
}
