/* -*-c-*- */
/* 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 "libs/fvwmlib.h"
#include "libs/FShape.h"
#include "libs/Module.h"
#include "FvwmIconMan.h"
#include "x.h"
#include "xmanager.h"
#include "libs/PictureGraphics.h"
#include "libs/PictureUtils.h"
#include "libs/Rectangles.h"
#include "libs/Grab.h"
#include "libs/Graphics.h"
#include "libs/Strings.h"
#include "libs/wild.h"

extern FlocaleWinString *FwinString;

/* button dirty bits: */
#define ICON_STATE_CHANGED  1
#define STATE_CHANGED       2
#define PICTURE_CHANGED     4
#define WINDOW_CHANGED      8
#define STRING_CHANGED      16
#define REDRAW_BUTTON       32
#define GEOMETRY_CHANGED    64

/* manager dirty bits: */
/*      GEOMETRY_CHANGED    64 same as with button */
#define MAPPING_CHANGED     2
#define SHAPE_CHANGED       4
#define REDRAW_MANAGER      8
#define REDRAW_BG           16

/* ButtonArray dirty bits: */
#define NUM_BUTTONS_CHANGED 1
#define NUM_WINDOWS_CHANGED 2

#define ALL_CHANGED         0x7f /* high bit is special */

typedef struct {
  int button_x, button_y, button_h, button_w; /* dim's of the whole button */
  int icon_x, icon_y, icon_h, icon_w;         /* what denotes icon state */
  int text_x, text_y, text_h, text_w;         /* text field */
  int text_base;                              /* text baseline */
} ButtonGeometry;

static void print_button_info(Button *b);
static void insert_windows_button(WinData *win);

/*
 * Utility leaf functions
 */

static int selected_button_in_man(WinManager *man)
{
  assert(man);
  ConsoleDebug(X11, "selected_button_in_man: %p\n",
		globals.select_win);
  if (globals.select_win && globals.select_win->button &&
    globals.select_win->manager == man) {
    return globals.select_win->button->index;
  }
  return -1;
}

#if 0
static void ClipRectangle(WinManager *man, int context,
			   int x, int y, int w, int h)
{
  XRectangle r;

  r.x = x;
  r.y = y;
  r.width = w;
  r.height = h;

  XSetClipRectangles(theDisplay, man->hiContext[context], 0, 0, &r, 1,
		     YXBanded);
}
#endif

static Region GetRegion(int x, int y, int w, int h)
{
  Region region;
  XRectangle rectangle;

  rectangle.x = x;
  rectangle.y = y;
  rectangle.width = w;
  rectangle.height = h;

  region = XCreateRegion();
  XUnionRectWithRegion(&rectangle, region, region);
  return region;
}

static int num_visible_rows(int n, int cols)
{
  return (n - 1) / cols + 1;
}

static int first_row_len(int n, int cols)
{
	int ret = n % cols;
	if (ret == 0)
		ret = cols;

	return ret;
}

static int index_to_box(WinManager *man, int index)
{
  int first_len, n, cols;

  if (man->geometry.dir & GROW_DOWN) {
    return index;
  }
  else {
    n = man->buttons.num_windows;
    cols = man->geometry.cols;
    first_len = first_row_len(n, cols);
    if (index >= first_len)
      index += cols - first_len;
    index += (man->geometry.rows - num_visible_rows(n, cols)) * cols;
    return index;
  }
}

static int box_to_index(WinManager *man, int box)
{
  int first_len, n, cols;
  int index = box;

  if (man->geometry.dir & GROW_DOWN)
  {
	  return index;
  }
  else
  {
	  n = man->buttons.num_windows;
	  cols = man->geometry.cols;
	  first_len = first_row_len(n, cols);

	  index = box - (man->geometry.rows - num_visible_rows(n, cols)) * cols;
	  if (!((index >= 0 && index < first_len) || index >= cols))
	  {
		  index = -1;
	  }
	  if (index >= first_len)
	  {
		  index -= cols - first_len;
	  }

  }

  return index;
}

static int index_to_row(WinManager *man, int index)
{
  int row;

  row = index_to_box(man, index) / man->geometry.cols;

  return row;
}

static int index_to_col(WinManager *man, int index)
{
  int col;

  col = index_to_box(man, index) % man->geometry.cols;

  return col;
}

static int rects_equal(XRectangle *x, XRectangle *y)
{
  return (x->x == y->x) && (x->y == y->y) && (x->width == y->width) &&
    (x->height == y->height);
}

static int top_y_coord(WinManager *man)
{
  if (man->buttons.num_windows > 0 && (man->geometry.dir & GROW_UP)) {
    return index_to_row(man, 0) * man->geometry.boxheight;
  }
  else {
    return 0;
  }
}

static ManGeometry *figure_geometry(WinManager *man)
{
	/* Given the number of wins in icon_list and width x height, compute
	   new geometry. */
	/* if GROW_FIXED is set, don't change window geometry */

	static ManGeometry ret;
	ManGeometry *g = &man->geometry;
	int n = man->buttons.num_windows;
	Bool noWindow = False;

	ret = *g;

	ConsoleDebug(
		X11, "figure_geometry: %s: %d, %d %d %d %d\n",
		man->titlename, n,
		ret.width, ret.height, ret.cols, ret.rows);

	if (n == 0)
	{
		n = 1;
		noWindow = True;
	}

	if (man->geometry.dir & GROW_FIXED)
	{
		ret.cols = num_visible_rows(n, g->rows);
		ret.boxwidth = ret.width / ret.cols;
		if (man->max_button_width_columns > 0)
		{
			man->max_button_width =
				ret.width / man->max_button_width_columns;
		}
		if (!noWindow && man->max_button_width > 0 &&
		    ret.boxwidth > man->max_button_width)
		{
			int i = 1;
			while(i <= g->rows)
			{
				if (n*man->max_button_width <= ret.width)
				{
					ret.cols = n;
					break;
				}
				else if ((n/i)*man->max_button_width <=
					 ret.width)
				{
					ret.cols =
						ret.width /
						man->max_button_width;
					break;
				}
				i++;
			}
			ret.boxwidth = man->max_button_width;
		}
		if (ret.boxwidth < 1)
		{
			ret.boxwidth = 1;
		}
	}
	else
	{
		if (man->geometry.dir & GROW_VERT)
		{
			if (g->cols)
			{
				ret.rows = num_visible_rows(n, g->cols);
			}
			else
			{
				ConsoleMessage(
					"Internal error in figure_geometry\n");
				ret.rows = 1;
			}
			ret.height = ret.rows * g->boxheight;
			ret.width  = ret.cols * g->boxwidth;
			if (ret.boxwidth < 1)
			{
				ret.boxwidth = 1;
			}
		}
		else
		{
			/* need to set resize inc  */
			if (g->rows)
			{
				ret.cols = num_visible_rows(n, g->rows);
			}
			else
			{
				ConsoleMessage(
					"Internal error in figure_geometry\n");
				ret.cols = 1;
			}
			ret.height = ret.rows * g->boxheight;
			ret.width = ret.cols * g->boxwidth;
			ret.boxwidth = ret.width / ret.cols;
			if (ret.boxwidth < 1)
			{
				ret.boxwidth = 1;
			}
		}
	}

	ConsoleDebug(
		X11, "figure_geometry: %d %d %d %d %d\n",
		n, ret.width, ret.height, ret.cols, ret.rows);

	return &ret;
}

static ManGeometry *query_geometry(WinManager *man)
{
  XWindowAttributes frame_attr, win_attr;
  int off_x, off_y;
  static ManGeometry g;

  assert(man->window_mapped);

  off_x = 0;
  off_y = 0;
  man->theFrame = find_frame_window(man->theWindow, &off_x, &off_y);
  if (XGetWindowAttributes(theDisplay, man->theFrame, &frame_attr))
  {
    g.x = frame_attr.x + off_x + frame_attr.border_width;
    g.y = frame_attr.y + off_y + frame_attr.border_width;
  }
  else
  {
    g.x = off_x;
    g.y = off_y;
    fprintf(stderr, "%s: query_geometry: failed to get frame attributes.\n",
	    MyName);
  }
  if (XGetWindowAttributes(theDisplay, man->theWindow, &win_attr))
  {
    g.width = win_attr.width;
    g.height = win_attr.height;
  }
  else
  {
    g.width = 1;
    g.height = 1;
    fprintf(stderr, "%s: query_geometry: failed to get window attributes.\n",
	    MyName);
  }

  return &g;
}

static void fix_manager_size(WinManager *man, int w, int h)
{
  XSizeHints size;
  long mask;

  if (man->geometry.dir & GROW_FIXED)
    return;

  XGetWMNormalHints(theDisplay, man->theWindow, &size, &mask);
  size.min_width = w;
  size.max_width = w;
  size.min_height = h;
  size.max_height = h;
  XSetWMNormalHints(theDisplay, man->theWindow, &size);
}

/* Like XMoveResizeWindow(), but can move in arbitary directions */
static void resize_window(WinManager *man)
{
  ManGeometry *g;
  int x_changed, y_changed, dir;

  dir = man->geometry.dir;
  fix_manager_size(man, man->geometry.width, man->geometry.height);

  if ((dir & GROW_DOWN) && (dir & GROW_RIGHT)) {
    XResizeWindow(theDisplay, man->theWindow, man->geometry.width,
		   man->geometry.height);
  }
  else {
    MyXGrabServer(theDisplay);
    g = query_geometry(man);
    x_changed = y_changed = 0;
    if (dir & GROW_LEFT) {
      man->geometry.x = g->x + g->width - man->geometry.width;
      x_changed = 1;
    }
    else {
      man->geometry.x = g->x;
    }
    if (dir & GROW_UP) {
      man->geometry.y = g->y + g->height - man->geometry.height;
      y_changed = 1;
    }
    else {
      man->geometry.y = g->y;
    }

    ConsoleDebug(X11, "queried: y: %d, h: %d, queried: %d\n", g->y,
		  man->geometry.height, g->height);

    if (x_changed || y_changed) {
      XMoveResizeWindow(theDisplay, man->theWindow,
			 man->geometry.x,
			 man->geometry.y,
			 man->geometry.width, man->geometry.height);
    }
    else {
      XResizeWindow(theDisplay, man->theWindow, man->geometry.width,
		     man->geometry.height);
    }
    MyXUngrabServer(theDisplay);
  }
}

static char *make_display_string(WinData *win, char *format, int len)
{
#define MAX_DISPLAY_SIZE 1024
#define COPY(field)                                       \
  temp_p = win->field;                                    \
  if (temp_p)                                             \
    while (*temp_p && out_p - buf < len - 1) \
      *out_p++ = *temp_p++;                               \
  in_p++;

  static char buf[MAX_DISPLAY_SIZE];
  char *string, *in_p, *out_p, *temp_p;

  in_p = format;
  out_p = buf;

  if (len > MAX_DISPLAY_SIZE || len <= 0)
    len = MAX_DISPLAY_SIZE;

  while (*in_p && out_p - buf < len - 1) {
    if (*in_p == '%') {
      switch (*(++in_p)) {
      case 'i':
	COPY(visible_icon_name);
	break;

      case 't':
	COPY(visible_name);
	break;

      case 'r':
	COPY(resname);
	break;

      case 'c':
	COPY(classname);
	break;

      default:
	*out_p++ = *in_p++;
	break;
      }
    }
    else {
      *out_p++ = *in_p++;
    }
  }

  *out_p++ = '\0';

  string = buf;
  return string;

#undef COPY
#undef MAX_DISPLAY_SIZE
}

Button *button_above(WinManager *man, Button *b)
{
  int n = man->buttons.num_windows, cols = man->geometry.cols;
  int i = -1;

  if (b) {
    i = box_to_index(man, index_to_box(man, b->index) - cols);
  }
  if (i < 0 || i >= n)
    return b;
  else
    return man->buttons.buttons[i];
}

Button *button_below(WinManager *man, Button *b)
{
  int n = man->buttons.num_windows;
  int i = -1;

  if (b) {
    i = box_to_index(man, index_to_box(man, b->index) + man->geometry.cols);
  }
  if (i < 0 || i >= n)
    return b;
  else
    return man->buttons.buttons[i];
}

Button *button_right(WinManager *man, Button *b)
{
  int i = -1;

  if (index_to_col(man, b->index) < man->geometry.cols - 1) {
    i = box_to_index(man, index_to_box(man, b->index) + 1);
  }

  if (i < 0 || i >= man->buttons.num_windows)
    return b;
  else
    return man->buttons.buttons[i];
}

Button *button_left(WinManager *man, Button *b)
{
  int i = -1;
  if (index_to_col(man, b->index) > 0) {
    i = box_to_index(man, index_to_box(man, b->index) - 1);
  }

  if (i < 0 || i >= man->buttons.num_windows)
    return b;
  else
    return man->buttons.buttons[i];
}

Button *button_next(WinManager *man, Button *b)
{
  int i = b->index + 1;

  if (i >= 0 && i < man->buttons.num_windows)
    return man->buttons.buttons[i];
  else
    return b;
}

Button *button_prev(WinManager *man, Button *b)
{
  int i = b->index - 1;

  if (i >= 0 && i < man->buttons.num_windows)
    return man->buttons.buttons[i];
  else
    return b;
}

Button *xy_to_button(WinManager *man, int x, int y)
{
	ManGeometry *g;
	int row, col, box, index;

	g = figure_geometry(man);
	row = y / g->boxheight;
	col = x / g->boxwidth;

	if (x >= 0 && x <= g->width &&
	    y >= 0 && y <= g->height && col < g->cols)
	{
		box = row * g->cols + col;
		index = box_to_index(man, box);
		if (index >= 0 && index < man->buttons.num_windows)
		{
			return man->buttons.buttons[index];
		}
	}

	return NULL;
}

/*
 * Routines which change dirtyable state
 */

static void set_button_geometry(WinManager *man, Button *box)
{
	ManGeometry *g;

	g = figure_geometry(man);
	box->x = index_to_col(man, box->index) * g->boxwidth;
	box->y = index_to_row(man, box->index) * g->boxheight;
	box->w = g->boxwidth;
	box->h = g->boxheight;
	box->drawn_state.dirty_flags |= GEOMETRY_CHANGED;
}

static void clear_button(Button *b)
{
  assert(b);
  b->drawn_state.win = NULL;
  b->drawn_state.dirty_flags = REDRAW_BUTTON;
  b->drawn_state.display_string = NULL;
  b->drawn_state.ew = 0;
}

static void set_window_button(WinData *win, int index)
{
  Button *b;

  assert(win->manager && index < win->manager->buttons.num_buttons);

  b = win->manager->buttons.buttons[index];

  /* Can optimize here */
  if (FMiniIconsSupported)
  {
    b->drawn_state.pic = win->pic;
  }
  b->drawn_state.win = win;
  copy_string(&b->drawn_state.display_string, win->display_string);
  b->drawn_state.iconified = win->iconified;
  b->drawn_state.state = win->state;
  b->drawn_state.dirty_flags = ALL_CHANGED;

  win->button = b;
}

static void set_num_buttons(ButtonArray *buttons, int n)
{
  int i;

  ConsoleDebug(X11, "set_num_buttons: %d %d\n", buttons->num_buttons, n);

  if (n > buttons->num_buttons) {
    buttons->dirty_flags |= NUM_BUTTONS_CHANGED;
    buttons->buttons = (Button **)saferealloc((void *)buttons->buttons,
					   n * sizeof(Button *));

    for (i = buttons->num_buttons; i < n; i++) {
      buttons->buttons[i] = (Button *)safemalloc(sizeof(Button));
      memset(buttons->buttons[i], 0, sizeof(Button));
      buttons->buttons[i]->drawn_state.display_string = NULL;
      buttons->buttons[i]->index = i;
    }

    buttons->dirty_flags |= NUM_BUTTONS_CHANGED;
    buttons->num_buttons = n;
  }
}

static void increase_num_windows(ButtonArray *buttons, int off)
{
  int n;

  if (off != 0) {
    buttons->num_windows += off;
    buttons->dirty_flags |= NUM_WINDOWS_CHANGED;

    if (buttons->num_windows > buttons->num_buttons) {
      n = buttons->num_windows + 10;
      set_num_buttons(buttons, n);
    }
  }
}

static void set_man_gravity_origin(WinManager *man)
{
  if (man->gravity == NorthWestGravity || man->gravity == NorthEastGravity)
    man->geometry.gravity_y = 0;
  else
    man->geometry.gravity_y = man->geometry.height;
  if (man->gravity == NorthWestGravity || man->gravity == SouthWestGravity)
    man->geometry.gravity_x = 0;
  else
    man->geometry.gravity_x = man->geometry.width;
}

static void set_man_geometry(WinManager *man, ManGeometry *new)
{
  int n;

  if (man->geometry.width != new->width ||
      man->geometry.height != new->height ||
      man->geometry.rows != new->rows ||
      man->geometry.cols != new->cols ||
      man->geometry.boxheight != new->boxheight ||
      man->geometry.boxwidth != new->boxwidth) {
    man->dirty_flags |= GEOMETRY_CHANGED;
  }

  man->geometry = *new;
  set_man_gravity_origin(man);
  n = man->geometry.rows * man->geometry.cols;
  if (man->buttons.num_buttons < n)
    set_num_buttons(&man->buttons, n + 10);
}

void set_manager_width(WinManager *man, int width)
{
  if (width != man->geometry.width) {
    ConsoleDebug(X11, "set_manager_width: %d -> %d, %d -> %d\n",
		  man->geometry.width, width, man->geometry.boxwidth,
		  width / man->geometry.cols);
    man->geometry.width = width;
    man->geometry.boxwidth = width / man->geometry.cols;
    if (man->geometry.boxwidth < 1)
      man->geometry.boxwidth = 1;
    man->dirty_flags |= GEOMETRY_CHANGED;
  }
}

void force_manager_redraw(WinManager *man)
{
  man->dirty_flags |= REDRAW_MANAGER;
  draw_manager(man);
}

void set_win_picture(WinData *win, Pixmap picture, Pixmap mask, Pixmap alpha,
		      unsigned int depth, unsigned int width,
		      unsigned int height)
{
  if (!FMiniIconsSupported)
  {
    return;
  }
  if (win->button)
    win->button->drawn_state.dirty_flags |= PICTURE_CHANGED;
  win->old_pic = win->pic;
  win->pic.picture = picture;
  win->pic.mask = mask;
  win->pic.alpha = alpha;
  win->pic.width = width;
  win->pic.height = height;
  win->pic.depth = depth;
}

void set_win_iconified(WinData *win, int iconified)
{
	/* This change has become necessary because with colorsets we don't
	 * know the background colour of the button (gradient background).
	 * Thus the button has to be redrawn completely, we can not just draw
	 * the square in the background colour. */
	char string[256];
	WinData *man_data = NULL;
	Bool do_animate = True;

	if (win->button != NULL && (win->iconified != iconified) )
	{
		if (win->manager->flags.is_shaded)
		{
			man_data = id_to_win(win->manager->theWindow);
			if (!man_data->geometry_set)
			{
				do_animate = False;
			}
		}
		else if (win->manager->swallowed)
		{
			if (win->manager->swallower_win &&
			    (man_data =
			     id_to_win(win->manager->swallower_win)) &&
			    man_data->geometry_set)
			{
				/* ok */
				if (!IS_SHADED(man_data))
				{
					/* animate as usual */
					man_data = NULL;
				}
			}
			else
			{
				do_animate = False;
			}
		}
		/* we check the win->button width and height because they
		 * will only be == zero on init, and if we didn't check we
		 * would get animations of all iconified windows to the far
		 * left of whereever thae manager would eventually be. */
		if (do_animate && win->manager->AnimCommand &&
		    (win->manager->AnimCommand[0] != 0)
		    && IS_ICON_SUPPRESSED(win) && (win->button->w != 0)
		    && (win->button->h !=0))
		{
			int abs_x, abs_y, w, h;
			Window junkw;

			XTranslateCoordinates(
				theDisplay, win->manager->theWindow,
				theRoot, win->button->x, win->button->y,
				&abs_x, &abs_y, &junkw);
			w = win->button->w;
			h = win->button->h;
			if (man_data)
			{
				if (w > man_data->real_g.width)
				{
					w = 1;
				}
				if (h > man_data->real_g.height)
				{
					h = 1;
				}
				if (abs_x < man_data->real_g.x ||
				    abs_x > man_data->real_g.x +
				    man_data->real_g.width)
				{
					abs_x = man_data->real_g.x;
				}
				if (abs_y < man_data->real_g.y ||
				    abs_y > man_data->real_g.y +
				    man_data->real_g.height)
				{
					abs_y = man_data->real_g.y;
				}
			}
			if (iconified)
			{
				sprintf(string, "%s %d %d %d %d %d %d %d %d",
					win->manager->AnimCommand,
					(int)win->x, (int)win->y,
					(int)win->width, (int)win->height,
					abs_x, abs_y, w, h);
			}
			else
			{
				sprintf(string, "%s %d %d %d %d %d %d %d %d",
					win->manager->AnimCommand,
					abs_x, abs_y, w, h,
					(int)win->x, (int)win->y,
					(int)win->width, (int)win->height);
			}
			SendText(fvwm_fd, string, 0);
		}
		win->button->drawn_state.dirty_flags |= ICON_STATE_CHANGED;
	}
	win->iconified = iconified;
	if (!iconified)
	{
		win->state = PLAIN_CONTEXT;
		if (globals.select_win == win)
		{
			add_win_state(win, SELECT_CONTEXT);
		}
		if (globals.focus_win == win)
		{
			add_win_state(win, FOCUS_CONTEXT);
		}
	}
}

void set_win_state(WinData *win, int state)
{
	if (win->button && win->state != state)
		win->button->drawn_state.dirty_flags |= STATE_CHANGED;
	win->state = state;
}

/* this is "broken" */
void add_win_state(WinData *win, int flag)
{
	int old_state = win->state;

	if (flag == FOCUS_CONTEXT)
	{
		if (win->state == SELECT_CONTEXT ||
		    win->state == FOCUS_SELECT_CONTEXT)
		{
			win->state = FOCUS_SELECT_CONTEXT;
		}
		else
		{
			win->state = FOCUS_CONTEXT;
		}
	}
	else if (flag == SELECT_CONTEXT)
	{
		if (win->state == FOCUS_CONTEXT ||
		    win->state == FOCUS_SELECT_CONTEXT)
		{
			win->state = FOCUS_SELECT_CONTEXT;
		}
		else
		{
			win->state = SELECT_CONTEXT;
		}
	}
	else
	{
		/* add_win_state should not be called  */
		win->state = flag;
	}
	if (win->button && (old_state != win->state))
	{
		win->button->drawn_state.dirty_flags |= STATE_CHANGED;
	}
	ConsoleDebug(X11, "add_win_state: %s 0x%x\n", win->titlename, flag);
}

/* this is "broken" */
void del_win_state(WinData *win, int flag)
{

	if (flag == FOCUS_CONTEXT)
	{
		if (win->state == FOCUS_SELECT_CONTEXT)
		{
			win->state = SELECT_CONTEXT;
		}
		else if (win->state == FOCUS_CONTEXT)
		{
			if (win->iconified)
			{
				win->state = ICON_CONTEXT;
			}
			else
			{
				win->state = PLAIN_CONTEXT;
			}
		}
		else
		{
			/* nothing */
		}
	}
	else if (flag == SELECT_CONTEXT)
	{
		if (win->state == FOCUS_SELECT_CONTEXT)
		{
			win->state = FOCUS_CONTEXT;
		}
		else if (win->state == SELECT_CONTEXT)
		{
			if (win->iconified)
			{
				win->state = ICON_CONTEXT;
			}
			else
			{
				win->state = PLAIN_CONTEXT;
			}
		}
		else
		{
			/* */
		}
	}
	else
	{
		/* del_win_state should not be called  */
		/*win->state = PLAIN_CONTEXT;*/
	}
	if (win->button)
	{
		win->button->drawn_state.dirty_flags |= STATE_CHANGED;
	}
	ConsoleDebug(X11, "del_win_state: %s 0x%x\n", win->titlename, flag);
}

void set_win_displaystring(WinData *win)
{
  WinManager *man = win->manager;
  int maxlen;
  char *tmp;

  if (man && win->button && win->button == man->tipped_button)
  {
	  tips_update_label(man);
  }

  if (!man || ((man->format_depend & CLASS_NAME) && !win->classname)
      || ((man->format_depend & ICON_NAME) && !win->visible_icon_name)
      || ((man->format_depend & TITLE_NAME) && !win->visible_name)
      || ((man->format_depend & RESOURCE_NAME) && !win->resname)) {
    return;
  }

  if (man->window_up) {
    assert(man->geometry.width && man->fontwidth);
    maxlen = man->geometry.width / man->fontwidth + 2 /* fudge factor */;
  }
  else {
    maxlen = 0;
  }

  tmp = make_display_string(win, man->formatstring, maxlen);
  if ((tmp == NULL && win->display_string == NULL) ||
      (tmp != NULL && win->display_string != NULL &&
       strcmp(tmp, win->display_string) == 0))
  {
	  return;
  }
  copy_string(&win->display_string, tmp);
  if (win->button)
    win->button->drawn_state.dirty_flags |= STRING_CHANGED;
}

/* This function is here and not with the other static utility functions
   because it basically is the inverse of set_shape() */

static void clear_empty_region(WinManager *man)
{
  XRectangle rects[3];
  int num_rects = 0, n = man->buttons.num_windows, cols = man->geometry.cols;
  int rows = man->geometry.rows;
  int boxwidth = man->geometry.boxwidth;
  int boxheight = man->geometry.boxheight;

  if (man->shaped)
    return;

  rects[1].x = rects[1].y = rects[1].width = rects[1].height = 0;

  if (n == 0 || rows * cols == 0 /* just be to safe */)
  {
    rects[0].x = 0;
    rects[0].y = 0;
    rects[0].width = man->geometry.width;
    rects[0].height = man->geometry.height;
    num_rects = 1;
  }
  else if (man->geometry.dir & GROW_DOWN)
  {
    assert(cols);
    if (n % cols == 0)
    {
      rects[0].x = 0;
      rects[0].y = num_visible_rows(n, cols) * man->geometry.boxheight;
      rects[0].width = man->geometry.width;
      rects[0].height = man->geometry.height - rects[0].y;
      num_rects = 1;
    }
    else
    {
      rects[0].x = (n % cols) * man->geometry.boxwidth;
      rects[0].y = (num_visible_rows(n, cols) - 1) * man->geometry.boxheight;
      rects[0].width = man->geometry.width - rects[0].x;
      rects[0].height = boxheight;
      rects[1].x = 0;
      rects[1].y = rects[0].y + rects[0].height;
      rects[1].width = man->geometry.width;
      rects[1].height = man->geometry.height - rects[0].y;
      num_rects = 2;
    }
  }
  else
  {
    assert(cols);
    /* for shaped windows, we won't see this part of the window */
    if (n % cols == 0)
    {
      rects[0].x = 0;
      rects[0].y = 0;
      rects[0].width = man->geometry.width;
      rects[0].height = top_y_coord(man);
      num_rects = 1;
    }
    else
    {
      rects[0].x = 0;
      rects[0].y = 0;
      rects[0].width = man->geometry.width;
      rects[0].height = top_y_coord(man);
      rects[1].x = (n % cols) * man->geometry.boxwidth;
      rects[1].y = rects[0].height;
      rects[1].width = man->geometry.width - rects[1].x;
      rects[1].height = boxheight;
      num_rects = 2;
    }
  }

  if (n != 0 && rows * cols != 0)
  {
    rects[num_rects].x = cols * boxwidth;
    rects[num_rects].y = 0;
    rects[num_rects].width = man->geometry.width - cols * boxwidth;
    rects[num_rects].height = man->geometry.height;
    num_rects++;
  }

  ConsoleDebug(X11, "Clearing: %d: (%d, %d, %d, %d) + (%d, %d, %d, %d)\n",
		num_rects,
		rects[0].x, rects[0].y, rects[0].width, rects[0].height,
		rects[1].x, rects[1].y, rects[1].width, rects[1].height);

  if (CSET_IS_TRANSPARENT_PR_PURE(man->colorsets[DEFAULT]))
  {
    for(n=0; n < num_rects; n++)
    {
	    if (rects[n].width > 0 && rects[n].height > 0)
	    {
		    XClearArea(
			    theDisplay, man->theWindow, rects[n].x, rects[n].y,
			    rects[n].width, rects[n].height, False);
	    }
    }
  }
  else
  {
    XFillRectangles(theDisplay, man->theWindow,
		     man->backContext[DEFAULT], rects, num_rects);
  }
}

void set_shape(WinManager *man)
{
  if (FShapesSupported)
  {
    int n;
    XRectangle rects[2];
    int cols = man->geometry.cols;

    if (man->shaped == 0)
      return;

    ConsoleDebug(X11, "in set_shape: %s\n", man->titlename);

    n = man->buttons.num_windows;
    if (n == 0)
    {
      man->shape.num_rects = 1;
      rects[0].x = -1;
      rects[0].y = -1;
      rects[0].width = 1;
      rects[0].height = 1;
      if (man->shape.num_rects != 0) {
	man->dirty_flags |= SHAPE_CHANGED;
      }
      man->shape.rects[0] = rects[0];
      return;
    }

    if (cols == 0 || n % cols == 0) {
      rects[0].x = 0;
      rects[0].y = top_y_coord(man);
      rects[0].width = cols * man->geometry.boxwidth;
      rects[0].height = num_visible_rows(n, cols) * man->geometry.boxheight;
      if (man->shape.num_rects != 1 || !rects_equal(rects, man->shape.rects)) {
	man->dirty_flags |= SHAPE_CHANGED;
      }
      man->shape.num_rects = 1;
      man->shape.rects[0] = rects[0];
    }
    else {
      if (man->geometry.dir & GROW_DOWN) {
	rects[0].x = 0;
	rects[0].y = 0;
	rects[0].width = cols * man->geometry.boxwidth;
	rects[0].height =
	  (num_visible_rows(n, cols) - 1) * man->geometry.boxheight;
	rects[1].x = 0;
	rects[1].y = rects[0].height;
	rects[1].width = (n % cols) * man->geometry.boxwidth;
	rects[1].height = man->geometry.boxheight;
      }
      else {
	rects[0].x = 0;
	rects[0].y = top_y_coord(man);
	rects[0].width = (n % cols) * man->geometry.boxwidth;
	rects[0].height = man->geometry.boxheight;
	rects[1].x = 0;
	rects[1].y = rects[0].y + rects[0].height;
	rects[1].width = man->geometry.width;
	rects[1].height = (num_visible_rows(n, cols) - 1) *
	  man->geometry.boxheight;
      }
      if (man->shape.num_rects != 2 ||
	  !rects_equal(rects, man->shape.rects) ||
	  !rects_equal(rects + 1, man->shape.rects + 1)) {
	man->dirty_flags |= SHAPE_CHANGED;
      }
      man->shape.num_rects = 2;
      man->shape.rects[0] = rects[0];
      man->shape.rects[1] = rects[1];
    }
  }
}

void set_manager_window_mapping(WinManager *man, int flag)
{
  if (flag != man->window_mapped) {
    man->window_mapped = flag;
    man->dirty_flags |= MAPPING_CHANGED;
  }
}

/*
 * Major exported functions
 */

void init_button_array(ButtonArray *array)
{
  memset(array, 0, sizeof(ButtonArray));
}

/* Pretty much like resize_manager, but used only to figure the
   correct size when creating the window */

void size_manager(WinManager *man)
{
  ManGeometry *new;
  int oldwidth, oldheight, w, h;

  new = figure_geometry(man);

  assert(new->width && new->height);

  w = new->width;
  h = new->height;

  oldwidth = man->geometry.width;
  oldheight = man->geometry.height;

  set_man_geometry(man, new);

  if (oldheight != h || oldwidth != w) {
    if (man->geometry.dir & GROW_UP)
      man->geometry.y -= h - oldheight;
    if (man->geometry.dir & GROW_LEFT)
      man->geometry.x -= w - oldwidth;
  }

  ConsoleDebug(X11, "size_manager %s: %d %d %d %d\n", man->titlename,
		man->geometry.x, man->geometry.y, man->geometry.width,
		man->geometry.height);
}

static void resize_manager(WinManager *man, int force)
{
  ManGeometry *new;
  int oldwidth, oldheight, oldrows, oldcols, old_boxwidth, old_boxheight;
  int dir;
  int i;

  if (man->flags.is_shaded)
  {
    man->flags.needs_resize_after_unshade = 1;
    return;
  }
  if (man->can_draw == 0)
    return;

  oldwidth = man->geometry.width;
  oldheight = man->geometry.height;
  oldrows = man->geometry.rows;
  oldcols = man->geometry.cols;
  dir = man->geometry.dir;
  old_boxwidth = man->geometry.boxheight;
  old_boxheight = man->geometry.boxwidth;

  if (dir & GROW_FIXED) {
    new = figure_geometry(man);
    set_man_geometry(man, new);
    set_shape(man);
    if (force || oldrows != new->rows || oldcols != new->cols ||
	oldwidth != new->width || oldheight != new->height) {
      man->dirty_flags |= GEOMETRY_CHANGED;
    }
  }
  else {
    new = figure_geometry(man);
    set_man_geometry(man, new);
    set_shape(man);
    if (force || oldrows != new->rows || oldcols != new->cols ||
	oldwidth != new->width || oldheight != new->height) {
      resize_window(man);
    }
  }

#if 1 /* not sure that this is needed */
  if (oldwidth != new->width || oldheight != new->height)
  {
    for (i = 0; i < NUM_CONTEXTS; i++)
    {
	    if (man->colorsets[i] >=0 &&
		(i == DEFAULT || CSET_IS_TRANSPARENT(man->colorsets[i])) &&
		Colorset[man->colorsets[i]].pixmap)
	    {
		    recreate_background(man, i);
	    }
    }
  }

  if (old_boxwidth != new->boxwidth || old_boxheight != new->boxheight)
  {
    for (i = 0; i < NUM_CONTEXTS; i++)
    {
	    if (man->colorsets[i] >= 0 && i != DEFAULT &&
		!CSET_IS_TRANSPARENT(man->colorsets[i]) &&
		Colorset[man->colorsets[i]].pixmap)
	    {
		    recreate_background(man, i);
	    }
    }
  }
#endif
}

static int center_padding(int h1, int h2)
{
  return (h2 - h1) / 2;
}

static void get_title_geometry(WinManager *man, ButtonGeometry *g)
{
  int text_pad;
  assert(man);
  g->button_x = 0;
  g->button_y = 0;
  g->button_w = man->geometry.boxwidth;
  g->button_h = man->geometry.boxheight;
  g->text_x = g->button_x + g->button_h / 2;
  g->text_w = g->button_w - 4 - (g->text_x - g->button_x);
  if (g->text_w <= 0)
    g->text_w = 1;
  g->text_h = man->fontheight;
  text_pad = center_padding(man->fontheight, g->button_h);

  g->text_y = g->button_y + text_pad;
  g->text_base = g->text_y + man->FButtonFont->ascent;
}

static void get_button_geometry(WinManager *man, Button *button,
				 ButtonGeometry *g)
{
  int icon_pad, text_pad;
  WinData *win;

  assert(man);

  win = button->drawn_state.win;

  g->button_x = button->x;
  g->button_y = button->y;

  g->button_w = button->w;
  g->button_h = button->h;

/* [BV 16-Apr-97] Mini Icons work on black-and-white too */
  if (FMiniIconsSupported && man->draw_icons && win && win->pic.picture) {
    /* If no window, then icon_* aren't used, so doesn't matter what
       they are */
    g->icon_w = min(win->pic.width, g->button_h);
    g->icon_h = min(g->button_h - 4, win->pic.height);
    icon_pad  = center_padding(g->icon_h, g->button_h);
    g->icon_x = g->button_x + 4;
    g->icon_y = g->button_y + icon_pad;
  }
  else {
    g->icon_h = man->geometry.boxheight - 8;
    g->icon_w = g->icon_h;

    icon_pad = center_padding(g->icon_h, g->button_h);
    g->icon_x = g->button_x + icon_pad;
    g->icon_y = g->button_y + icon_pad;
  }

  g->text_x = g->icon_x + g->icon_w + 2;
  g->text_w = g->button_w - 4 - (g->text_x - g->button_x);
  if (g->text_w <= 0)
    g->text_w = 1;
  g->text_h = man->fontheight;

  text_pad = center_padding(man->fontheight, g->button_h);

  g->text_y = g->button_y + text_pad;
  g->text_base = g->text_y + man->FButtonFont->ascent;
}

static void draw_button_background(
	WinManager *man, XRectangle bounding, Contexts button_state)
{
	int cset = man->colorsets[button_state];

	if (CSET_IS_TRANSPARENT_PR_PURE(cset) ||
	    man->backContext[button_state] == None)
	{
		XClearArea(
			theDisplay, man->theWindow,
			bounding.x, bounding.y,
			bounding.width, bounding.height,
			False);
	}
	else if (CSET_IS_TRANSPARENT_PR_TINT(cset))
	{
		SetRectangleBackground(
			theDisplay, man->theWindow,
			bounding.x, bounding.y,
			bounding.width, bounding.height,
			&Colorset[cset], Pdepth, man->backContext[button_state]);
	}
	else
	{
		XFillRectangle(
			theDisplay, man->theWindow,
			man->backContext[button_state],
			bounding.x, bounding.y,
			bounding.width, bounding.height);
	}
}

static void draw_3d_icon(WinManager *man, int box, ButtonGeometry *g,
			  int iconified, Contexts contextId)
{
  if ((iconified == 0) || (man->relief_thickness == 0))
    return;
  else
    RelieveRectangle(theDisplay, man->theWindow, g->icon_x, g->icon_y,
		      g->icon_w - 1, g->icon_h - 1,
		      man->reliefContext[contextId],
		      man->shadowContext[contextId], man->relief_thickness);
}


  /* this routine should only be called from draw_button() */
static void iconify_box(WinManager *man, WinData *win, int box,
			 ButtonGeometry *g, int iconified, Contexts contextId,
			 int button_already_cleared, XRectangle bounding)
{
	XRectangle inter;
	int cset = man->colorsets[contextId];
	Bool erase = False;

	if (!man->window_up)
	{
		return;
	}
	frect_get_intersection(
		bounding.x, bounding.y, bounding.width, bounding.height,
		g->icon_x, g->icon_y, g->icon_w, g->icon_h,
		&inter);

	if (inter.width <= 0 || inter.height <= 0)
	{
		return;
	}

        /* [BV 16-Apr-97] Mini Icons work on black-and-white too */
	if (FMiniIconsSupported && man->draw_icons && win->pic.picture)
	{
		if (iconified == 0 && man->draw_icons != 2)
		{
			erase = True;
		}
		else
		{
			FvwmRenderAttributes fra;
			int p_x = 0, p_y = 0;

			fra.mask = FRAM_DEST_IS_A_WINDOW;
			if (cset >= 0)
			{
				fra.mask |= FRAM_HAVE_ICON_CSET;
				fra.colorset = &Colorset[cset];
			}
			if (inter.x > g->icon_x)
			{
				p_x = inter.x - g->icon_x;
			}
			if (inter.y > g->icon_y)
			{
				p_y = inter.y - g->icon_y;
			}
			PGraphicsRenderPicture(
				theDisplay, man->theWindow, &win->pic, &fra,
				man->theWindow, man->hiContext[contextId],
				None, None,
				p_x, p_y, inter.width, inter.height,
				inter.x, inter.y, inter.width, inter.height,
				False);
		}
	}
	else
	{
		if (!PictureUseBWOnly())
		{
			draw_3d_icon(man, box, g, iconified, contextId);
		}
		else
		{
			if (iconified == 0)
			{
				erase = True;
			}
			else
			{
				XFillArc(
					theDisplay, man->theWindow,
					man->hiContext[contextId],
					g->icon_x, g->icon_y, g->icon_w,
					g->icon_h, 0, 360 * 64);
			}
		}
	}
	if (erase)
	{
		draw_button_background(man, inter, contextId);
	}
}

int change_windows_manager(WinData *win)
{
  WinManager *oldman;
  WinManager *newman;

  ConsoleDebug(X11, "change_windows_manager: %s\n", win->titlename);

  oldman = win->manager;
  newman = figure_win_manager(win, ALL_NAME);
  if (oldman && newman != oldman && win->button) {
    delete_windows_button(win);
  }
  win->manager = newman;
  set_win_displaystring(win);
  check_win_complete(win);
  check_in_window(win);
  ConsoleDebug(X11, "change_windows_manager: returning %d\n",
		newman != oldman);
  return (newman != oldman);
}

void check_in_window(WinData *win)
{
  int in_viewport;
  int is_state_selected;

  if (win->complete) {
    WinManager *oldman;
    WinManager *newman;

    ConsoleDebug(X11, "change_windows_manager: %s\n", win->titlename);

    oldman = win->manager;
    newman = figure_win_manager(win, ALL_NAME);
    if (oldman && newman != oldman && win->button) {
      oldman->we_are_drawing = 1;
      delete_windows_button(win);
    }
    win->manager = newman;
    set_win_displaystring(win);
  }

  if (win->manager && win->complete) {
    is_state_selected =
	    ((!win->manager->showonlyiconic || win->iconified) &&
	     (!win->manager->shownoiconic || !win->iconified) &&
	     (win->manager->showtransient || !IS_TRANSIENT(win)));
    in_viewport = win_in_viewport(win);
    if (win->manager->usewinlist && DO_SKIP_WINDOW_LIST(win))
      in_viewport = 0;
    if ((win->manager->showonlyfocused && win->state != FOCUS_CONTEXT) &&
	(win->manager->showonlyfocused && win->state != FOCUS_SELECT_CONTEXT))
	in_viewport = 0;
    if (win->button == NULL && in_viewport && is_state_selected) {
      insert_windows_button(win);
      if (win->manager->window_up == 0 && globals.got_window_list)
	create_manager_window(win->manager->index);
    }
    if (win->button && (!in_viewport || !is_state_selected)) {
      if (win->button->drawn_state.display_string)
	Free(win->button->drawn_state.display_string);
      delete_windows_button(win);
    }
  }
}

static void get_gcs(WinManager *man, int state, int iconified,
		     GC *context1, GC *context2)
{
  GC gc1, gc2;

  switch (man->buttonState[state]) {
  case BUTTON_FLAT:
    gc1 = man->flatContext[state];
    gc2 = man->flatContext[state];
    break;

  case BUTTON_UP:
  case BUTTON_EDGEUP:
    gc1 = man->reliefContext[state];
    gc2 = man->shadowContext[state];
    break;

  case BUTTON_DOWN:
  case BUTTON_EDGEDOWN:
    gc1 = man->shadowContext[state];
    gc2 = man->reliefContext[state];
    break;

  default:
    ConsoleMessage("Internal error in get_gcs\n");
    return;
  }

  if (((man->rev == REVERSE_ICON) && iconified)
      || ((man->rev == REVERSE_NORMAL) && !iconified)) {
    *context1 = gc2;
    *context2 = gc1;
  } else {
    *context1 = gc1;
    *context2 = gc2;
  }
  return;
}

static void draw_relief(WinManager *man, int button_state, ButtonGeometry *g,
			 GC context1, GC context2)
{
  int state;
  int relief;

  state = man->buttonState[button_state];
  relief = man->relief_thickness;

  /* Make sure that the relief isn't too large for the button... */
  if ((abs(relief) > (man->geometry.boxheight / 2)) ||
      (abs(relief) > (man->geometry.boxwidth / 2))) {
      relief = min(man->geometry.boxheight/2, man->geometry.boxwidth/2);
      if (man->relief_thickness < 0)
        relief *= -1;
  }

  if (state == BUTTON_FLAT || relief == 0)
    return;
  if (state == BUTTON_EDGEUP || state == BUTTON_EDGEDOWN) {
    RelieveRectangle(theDisplay, man->theWindow, g->button_x, g->button_y,
		      g->button_w - 1, g->button_h - 1, context1, context2,
		      relief);
    RelieveRectangle(theDisplay, man->theWindow, g->button_x+2, g->button_y+2,
		      g->button_w - 5, g->button_h - 5, context2, context1,
		      relief);
  }
  else {
    RelieveRectangle(theDisplay, man->theWindow, g->button_x, g->button_y,
		      g->button_w - 1, g->button_h - 1, context1, context2,
		      relief);
  }
}

static void draw_button(WinManager *man, int button, int force)
{
	Button *b;
	WinData *win;
	ButtonGeometry g, old_g;
	GC context1, context2;
	Contexts button_state;
	int cleared_button = 0, dirty;
	int draw_background = 0, draw_icon = 0, draw_string = 0;
	int clear_old_pic = 0;

	assert(man);

	if (!man->window_up)
	{
		ConsoleMessage("draw_button: manager not up yet\n");
		return;
	}

	b = man->buttons.buttons[button];
	win = b->drawn_state.win;
	dirty = b->drawn_state.dirty_flags;

	if (win && win->button != b)
	{
		ConsoleMessage("Internal error in draw_button.\n");
		return;
	}

	if (!win)
	{
		return;
	}

	if (force || (dirty & REDRAW_BUTTON))
	{
		ConsoleDebug(X11, "draw_button: %d forced\n", b->index);
		draw_background = 1;
		draw_icon = 1;
		draw_string = 1;
	}

	/* figure out what we have to draw */
	if (dirty)
	{
		ConsoleDebug(X11, "draw_button: %d dirty\n", b->index);
		/* humm ... this should be optimised one day ! */
		if (dirty & GEOMETRY_CHANGED)
		{
			ConsoleDebug(X11, "\tGeometry changed\n");
			/* Determine if geometry has changed relative
			 * to the window gravity */
			if (b->w != b->drawn_state.w ||
			    b->h != b->drawn_state.h ||
			    b->x - man->geometry.gravity_x !=
			    b->drawn_state.x -
			    man->drawn_geometry.gravity_x ||
			    b->y - man->geometry.gravity_y !=
			    b->drawn_state.y -
			    man->drawn_geometry.gravity_y)
			{
				draw_background = 1;
				draw_icon = 1;
				draw_string = 1;
			}
		}
		if (dirty & STATE_CHANGED)
		{
			ConsoleDebug(X11, "\tState changed\n");
			b->drawn_state.state = win->state;
			draw_background = 1;
			draw_icon = 1;
			draw_string = 1;
		}
		if (FMiniIconsSupported && (dirty & PICTURE_CHANGED))
		{
			FvwmPicture tpic;

			ConsoleDebug(X11, "\tPicture changed\n");
			tpic = win->pic;
			win->pic = win->old_pic;
			get_button_geometry(man, b, &old_g);
			win->pic = tpic;
			b->drawn_state.pic = win->pic;
			draw_icon = 1;
			draw_string = 1;
			draw_background = 1;
		}
		if ((dirty & ICON_STATE_CHANGED) &&
		    b->drawn_state.iconified != win->iconified)
		{
			ConsoleDebug(X11, "\tIcon changed\n");
			b->drawn_state.iconified = win->iconified;
			draw_icon = 1;
			draw_background = 1;
			draw_string = 1;
		}
		if (dirty & STRING_CHANGED)
		{
			ConsoleDebug(
				X11, "\tString changed: %s\n",
				win->display_string);
			copy_string(
				&b->drawn_state.display_string,
				win->display_string);
			assert(b->drawn_state.display_string);
			draw_icon = 1;
			draw_background = 1;
			draw_string = 1;
		}
	}

	if (draw_background || draw_icon || draw_string)
	{
		XRectangle bounding;
		Region b_region;

		get_button_geometry(man, b, &g);
		if (b->drawn_state.ew > 0 && b->drawn_state.eh > 0)
		{
			/* we redraw from an expose event */
			bounding.x =  b->drawn_state.ex;
			bounding.y =  b->drawn_state.ey;
			bounding.width =  b->drawn_state.ew;
			bounding.height =  b->drawn_state.eh;
		}
		else
		{
			bounding.x = g.button_x;
			bounding.y = g.button_y;
			bounding.width = g.button_w;
			bounding.height = g.button_h;
		}
		b_region = GetRegion(
			bounding.x, bounding.y, bounding.width, bounding.height);
		ConsoleDebug(
			X11, "\tgeometry: %d %d %d %d\n", g.button_x, g.button_y,
			g.button_w, g.button_h);
		button_state = b->drawn_state.state;
		if (b->drawn_state.iconified && button_state == PLAIN_CONTEXT)
		{
			button_state = ICON_CONTEXT;
		}
		if (draw_background)
		{
			ConsoleDebug(X11, "\tDrawing background\n");
			draw_button_background(man, bounding, button_state);
			cleared_button = 1;
		}
		if (clear_old_pic)
		{
			ConsoleDebug(X11, "\tClearing old picture\n");
			if (!cleared_button)
			{
				XRectangle r;

				frect_get_intersection(
					bounding.x, bounding.y,
					bounding.width, bounding.height,
					g.icon_x, g.icon_y, g.icon_w, g.icon_h,
					&r);
				if (r.width > 0)
				{
					draw_button_background(
						man, r, button_state);
				}
			}
		}
		if (draw_icon)
		{
			ConsoleDebug(X11, "\tDrawing icon\n");
			iconify_box(
				man, win, button, &g, win->iconified,
				button_state, cleared_button, bounding);
		}
		/* Draw reliefs after icon since the icon might overlay
		   part of it. */
		if (draw_background)
		{
			ConsoleDebug(X11, "\tDrawing reliefs\n");
			if (!PictureUseBWOnly())
			{
				get_gcs(
					man, button_state, win->iconified,
					&context1, &context2);
				draw_relief(
					man, button_state, &g, context1,
					context2);
			}
			else if (button_state & SELECT_CONTEXT)
			{
				XDrawRectangle(
					theDisplay, man->theWindow,
					man->hiContext[button_state],
					g.button_x + 2, g.button_y + 1,
					g.button_w - 4, g.button_h - 2);
			}
		}
		if (draw_string)
		{
			Region tr;

			ConsoleDebug(
				X11, "\tDrawing text: %s\n",
				b->drawn_state.display_string);

			tr = GetRegion(
				g.text_x, g.text_y, g.text_w, g.text_h);
			XIntersectRegion(tr, b_region, tr);
			XSetRegion(
				theDisplay, man->hiContext[button_state], tr);
			if (!cleared_button)
			{
				XRectangle r;

				frect_get_intersection(
					bounding.x, bounding.y,
					bounding.width, bounding.height,
					g.text_x, g.text_y, g.text_w, g.text_h,
					&r);
				draw_button_background(man, r, button_state);
			}
			FwinString->str =  b->drawn_state.display_string;
			FwinString->win = man->theWindow;
			FwinString->gc = man->hiContext[button_state];
			if (man->colorsets[button_state] >= 0)
			{
				FwinString->colorset =
					&Colorset[man->colorsets[button_state]];
				FwinString->flags.has_colorset = True;
			}
			else
			{
				FwinString->flags.has_colorset = False;
			}
			FwinString->flags.has_clip_region = True;
			FwinString->clip_region = tr;
			FwinString->x = g.text_x;
			FwinString->y = g.text_base;
			FlocaleDrawString(
				theDisplay, man->FButtonFont, FwinString, 0);
			XDestroyRegion(tr);
			XSetClipMask(
				theDisplay, man->hiContext[button_state], None);
		}
		XDestroyRegion(b_region);
	}

	b->drawn_state.dirty_flags = 0;
	b->drawn_state.x = b->x;
	b->drawn_state.y = b->y;
	b->drawn_state.w = b->w;
	b->drawn_state.h = b->h;
	b->drawn_state.ew = 0;
	b->drawn_state.eh = 0;
	XFlush(theDisplay);
}

void draw_managers(void)
{
  int i;
  for (i = 0; i < globals.num_managers; i++)
    draw_manager(&globals.managers[i]);
}

static void draw_empty_manager(WinManager *man)
{
	GC context1, context2;
	int state = TITLE_CONTEXT;
	ButtonGeometry g;
	int len = strlen(man->titlename);
	Region region;

	ConsoleDebug(X11, "draw_empty_manager\n");
	clear_empty_region(man);
	get_title_geometry(man, &g);

	/* FIXME: should bound when exposed */
	if (len > 0)
	{
		XRectangle r;

		r.x = g.button_x;
		r.y = g.button_y;
		r.width = g.button_w;
		r.height = g.button_h;
		draw_button_background(man, r, state);
	}
	if (!PictureUseBWOnly())
	{
		get_gcs(man, state, 0, &context1, &context2);
		draw_relief(man, state, &g, context1, context2);
	}
	region = GetRegion(g.text_x, g.text_y, g.text_w, g.text_h);
	XSetRegion(theDisplay, man->hiContext[state], region);
	FwinString->str = man->titlename;
	FwinString->win = man->theWindow;
	FwinString->gc = man->hiContext[state];
	if (man->colorsets[state] >= 0)
	{
		FwinString->colorset = &Colorset[man->colorsets[state]];
		FwinString->flags.has_colorset = True;
	}
	else
	{
		FwinString->flags.has_colorset = False;
	}
	FwinString->flags.has_clip_region = True;
	FwinString->clip_region = region;
	FwinString->x = g.text_x;
	FwinString->y = g.text_base;
	FlocaleDrawString(theDisplay, man->FButtonFont, FwinString, 0);
	XSetClipMask(theDisplay, man->hiContext[state], None);
}

void draw_manager(WinManager *man)
{
  int i, force_draw = 0, update_geometry = 0, redraw_all = 0;
  int shape_changed = 0;

  assert(man->buttons.num_buttons >= 0 && man->buttons.num_windows >= 0);

  if (!man->window_up)
    return;
  ConsoleDebug(X11, "Drawing Manager: %s\n", man->titlename);
  redraw_all = man->dirty_flags & REDRAW_MANAGER;

  if (!man->flags.is_shaded && man->flags.needs_resize_after_unshade) {
    ConsoleDebug(X11, "\tresizing manager\n");
    resize_manager(man, 1);
    update_geometry = 1;
    force_draw = 1;
  } else if (redraw_all || (man->buttons.dirty_flags & NUM_WINDOWS_CHANGED)) {
    ConsoleDebug(X11, "\tresizing manager\n");
    resize_manager(man, redraw_all);
    update_geometry = 1;
    force_draw = 1;
  }

  if (redraw_all || (man->dirty_flags & MAPPING_CHANGED)) {
    force_draw = 1;
    ConsoleDebug(X11, "manager %s: mapping changed\n", man->titlename);
  }

  if (FShapesSupported && man->shaped)
  {
    if (redraw_all || (man->dirty_flags & SHAPE_CHANGED)) {
      FShapeCombineRectangles(
	theDisplay, man->theWindow, FShapeBounding, 0, 0, man->shape.rects,
	man->shape.num_rects, FShapeSet, Unsorted);
      FShapeCombineRectangles(
	theDisplay, man->theWindow, FShapeClip, 0, 0, man->shape.rects,
	man->shape.num_rects, FShapeSet, Unsorted);
      shape_changed = 1;
      update_geometry = 1;
    }
  }

  if (redraw_all || (man->dirty_flags & GEOMETRY_CHANGED)) {
    ConsoleDebug(X11, "\tredrawing all buttons\n");
    update_geometry = 1;
  }

  if (update_geometry) {
    for (i = 0; i < man->buttons.num_windows; i++)
      set_button_geometry(man, man->buttons.buttons[i]);
  }

  if (force_draw || update_geometry || (man->dirty_flags & REDRAW_BG)) {
    ConsoleDebug(X11, "\tredrawing background\n");
    clear_empty_region(man);
  }
  if (force_draw || update_geometry)
  {
    /* FIXME: maybe not useful but safe */
    if (CSET_IS_TRANSPARENT_PR_PURE(man->colorsets[DEFAULT]))
      force_draw = True;
  }

  man->dirty_flags = 0;
  man->buttons.dirty_flags = 0;
  man->buttons.drawn_num_buttons = man->buttons.num_buttons;
  man->buttons.drawn_num_windows = man->buttons.num_windows;

  if (man->buttons.num_windows == 0) {
    if (force_draw)
      draw_empty_manager(man);
  }
  else {
    /* I was having the problem where when the shape changed the manager
       wouldn't get redrawn. It appears we weren't getting the expose.
       How can I tell when I am going to reliably get an expose event? */

    if (1 || shape_changed) {
      /* if shape changed, we'll catch it on the expose */
      for (i = 0; i < man->buttons.num_buttons; i++) {
	draw_button(man, i, force_draw);
      }
    }
  }
  man->drawn_geometry = man->geometry;
  XFlush(theDisplay);
}

Bool draw_transparent_buttons(
	WinManager *man, Bool only_moved, Bool clear_only)
{
	Button **bp;
	Contexts button_state;
	int i,cset;
	Bool r = False;
	Bool man_bg_transparent = False;

	if (CSET_IS_TRANSPARENT(man->colorsets[DEFAULT]))
	{
		clear_empty_region(man);
		man_bg_transparent = True;
	}

	if (!man->buttons.num_windows)
	{
		if (CSET_IS_TRANSPARENT(man->colorsets[TITLE_CONTEXT]) ||
		    CSET_IS_TRANSPARENT_PR_TINT(man->colorsets[DEFAULT]))
		{
			draw_empty_manager(man);
		}
		return 0;
	}

	bp = man->buttons.buttons;
	for (i = 0; i < man->buttons.num_windows; i++)
	{
		button_state = bp[i]->drawn_state.state;
		if (bp[i]->drawn_state.iconified &&
		    button_state == PLAIN_CONTEXT)
		{
			button_state = ICON_CONTEXT;
		}
		cset = man->colorsets[button_state];

		if ((!CSET_IS_TRANSPARENT(cset) &&
		     !CSET_IS_TRANSPARENT_PR_TINT(man->colorsets[DEFAULT]))
		    ||
		    (only_moved && !man_bg_transparent &&
		     !CSET_IS_TRANSPARENT_ROOT(cset)))
		{
			continue;
		}
		if (clear_only)
		{
			XClearArea(
				theDisplay, man->theWindow,
				bp[i]->x, bp[i]->y, bp[i]->w, bp[i]->h,
				True);
			r = True;
		}
		else
		{
			bp[i]->drawn_state.dirty_flags |= REDRAW_BUTTON;
			draw_button(man, i, 0);
		}
	}
	return r;
}

static int compute_weight(WinData *win)
{
  WinManager *man;
  WeightedSort *sort;
  int i;

  man = win->manager;
  for (i = 0; i < man->weighted_sorts_len; i++) {
    sort = &man->weighted_sorts[i];
    if (sort->resname && !matchWildcards(sort->resname, win->resname)) {
      continue;
    }
    if (sort->classname && !matchWildcards(sort->classname, win->classname)) {
      continue;
    }
    if (sort->titlename && !matchWildcards(sort->titlename, win->titlename)) {
      continue;
    }
    if (sort->iconname && !matchWildcards(sort->iconname, win->iconname)) {
      continue;
    }
    return sort->weight;
  }
  return 0;
}

static int compare_windows(SortType type, WinData *a, WinData *b)
{
	int wa, wb;

	if (type == SortId)
	{
		return a->app_id - b->app_id;
	}
	else if (type == SortName)
	{
		return strcasecmp(
			(a->display_string)? a->display_string:"",
			(b->display_string)? b->display_string:"");
	}
	else if (type == SortNameCase)
	{
		return strcmp((a->display_string)? a->display_string:"",
			       (b->display_string)? b->display_string:"");
	}
	else if (type == SortWeighted)
	{
		wa = compute_weight(a);
		wb = compute_weight(b);
		if (wa != wb)
		{
			return wa - wb;
		}
		return strcmp((a->display_string)? a->display_string:"",
			      (b->display_string)? b->display_string:"");
	}
	else
	{
		ConsoleMessage("Internal error in compare_windows\n");
		return 0;
	}
}

/* find_windows_spot: returns index of button to stick the window in.
 *                    checks win->button to see if it's already in manager.
 *                    if it isn't, then gives spot at which it should be,
 *                    if it were.
 */

static int find_windows_spot(WinData *win)
{
  WinManager *man = win->manager;
  int num_windows = man->buttons.num_windows;

  if (man->sort != SortNone) {
    int i, cur, start, finish, cmp_dir, correction;
    Button **bp;

    bp = man->buttons.buttons;
    if (win->button) {
      /* start search from our current location */
      cur = win->button->index;

      if (cur - 1 >= 0 &&
	  compare_windows(man->sort,
			   win, bp[cur - 1]->drawn_state.win) < 0) {
	start = cur - 1;
	finish = -1;
	cmp_dir = -1;
	correction = 1;
      }
      else if (cur < num_windows - 1 &&
	       compare_windows(man->sort,
				win, bp[cur + 1]->drawn_state.win) > 0) {
	start = cur + 1;
	finish = num_windows;
	cmp_dir = 1;
	correction = -1;
      }
      else {
	return cur;
      }
    }
    else {
      start = 0;
      finish = num_windows;
      cmp_dir = 1;
      correction = 0;
    }
    for (i = start; i != finish && bp[i]->drawn_state.win && cmp_dir *
	     compare_windows(man->sort, win, bp[i]->drawn_state.win) > 0;
	 i = i + cmp_dir)
      ;
    i += correction;
    ConsoleDebug(X11, "find_windows_spot: %s %d\n", win->display_string, i);
    return i;
  }
  else {
    if (win->button) {
      /* already have a perfectly fine spot */
      return win->button->index;
    }
    else {
      return num_windows;
    }
  }

  /* shouldn't get here */
  return -1;
}

static void move_window_buttons(WinManager *man, int start, int finish,
				 int offset)
{
  int n = man->buttons.num_buttons, i;
  Button **bp;

  ConsoleDebug(X11, "move_window_buttons: %s(%d): (%d, %d) + %d\n",
		man->titlename, n, start, finish, offset);

  if (finish >= n || finish + offset >= n || start < 0 || start + offset < 0) {
    ConsoleMessage("Internal error in move_window_buttons\n");
    ConsoleMessage("\tn = %d, start = %d, finish = %d, offset = %d\n",
		    n, start, finish, offset);
    return;
  }
  bp = man->buttons.buttons;
  if (offset > 0)
  {
    for (i = finish; i >= start; i--)
    {
      bp[i + offset]->drawn_state = bp[i]->drawn_state;
      bp[i + offset]->drawn_state.dirty_flags = ALL_CHANGED;
      if (bp[i + offset]->drawn_state.win)
	bp[i + offset]->drawn_state.win->button = bp[i + offset];
    }
    for (i = 0; i < offset; i++)
      clear_button(bp[start + i]);
  }
  else if (offset < 0)
  {
    for (i = start; i <= finish; i++)
    {
      bp[i + offset]->drawn_state = bp[i]->drawn_state;
      bp[i + offset]->drawn_state.dirty_flags = ALL_CHANGED;
      if (bp[i + offset]->drawn_state.win)
	bp[i + offset]->drawn_state.win->button = bp[i + offset];
    }
    for (i = 0; i > offset; i--)
      clear_button(bp[finish + i]);
  }
}

static void insert_windows_button(WinData *win)
{
  int spot;
  int selected_index = -1;
  WinManager *man = win->manager;
  ButtonArray *buttons;

  ConsoleDebug(X11, "insert_windows_button: %s\n", win->titlename);

  assert(man);
  selected_index = selected_button_in_man(man);

  if (win->button) {
    ConsoleDebug(X11, "insert_windows_button: POSSIBLE BUG: "
		  "already have a button\n");
    return;
  }

  if (!win || !win->complete || !man) {
    ConsoleMessage("Internal error in insert_windows_button\n");
    ShutMeDown(1);
  }

  buttons = &man->buttons;

  spot = find_windows_spot(win);

  increase_num_windows(buttons, 1);
  move_window_buttons(man, spot, buttons->num_windows - 2, 1);

  set_window_button(win, spot);
  if (selected_index >= 0) {
    ConsoleDebug(X11, "insert_windows_button: selected_index = %d, moving\n",
		  selected_index);
    move_highlight(man, man->buttons.buttons[selected_index]);
  }
}

void delete_windows_button(WinData *win)
{
  int spot;
  int selected_index = -1;
  WinManager *man = (win->manager);
  ButtonArray *buttons;

  ConsoleDebug(X11, "delete_windows_button: %s\n", win->titlename);

  assert(man);

  buttons = &win->manager->buttons;

  assert(win->button);
  assert(buttons->buttons);

  selected_index = selected_button_in_man(man);
  ConsoleDebug(X11, "delete_windows_button: selected_index = %d\n",
		selected_index);

  spot = win->button->index;

  tips_cancel(man);
  move_window_buttons(win->manager, spot + 1, buttons->num_windows - 1, -1);
  increase_num_windows(buttons, -1);
  win->button = NULL;
  if (globals.focus_win == win) {
    globals.focus_win = NULL;
  }
  if (selected_index >= 0) {
    ConsoleDebug(X11, "delete_windows_button: selected_index = %d, moving\n",
		  selected_index);
    move_highlight(man, man->buttons.buttons[selected_index]);
  }
  win->state = PLAIN_CONTEXT;
}

void resort_windows_button(WinData *win)
{
  int new_spot, cur_spot;
  int selected_index = -1;
  WinManager *man = win->manager;

  assert(win->button && man);

  ConsoleDebug(X11, "In resort_windows_button: %s\n", win->resname);

  selected_index = selected_button_in_man(man);

  new_spot = find_windows_spot(win);
  cur_spot = win->button->index;

  print_button_info(win->button);

  if (new_spot != cur_spot) {
    ConsoleDebug(X11, "resort_windows_button: win moves from %d to %d\n",
		    cur_spot, new_spot);
    if (new_spot < cur_spot) {
      move_window_buttons(man, new_spot, cur_spot - 1, +1);
    }
    else {
      move_window_buttons(man, cur_spot + 1, new_spot, -1);
    }
    set_window_button(win, new_spot);

    if (selected_index >= 0) {
      move_highlight(man, man->buttons.buttons[selected_index]);
    }
  }
}

void move_highlight(WinManager *man, Button *b)
{
  WinData *old;

  assert(man);

  ConsoleDebug(X11, "move_highlight\n");

  old = globals.select_win;

  if (old && old->button) {
    del_win_state(old, SELECT_CONTEXT);
    old->manager->select_button = NULL;
    draw_button(old->manager, old->button->index, 0);
  }
  if (b && b->drawn_state.win) {
    add_win_state(b->drawn_state.win, SELECT_CONTEXT);
    draw_button(man, b->index, 0);
    globals.select_win = b->drawn_state.win;
  }
  else {
    globals.select_win = NULL;
  }

  man->select_button = b;
}

void man_exposed(WinManager *man, XEvent *theEvent)
{
	rectangle r1, r2;
	int i;
	Button **bp;

	ConsoleDebug(X11, "manager: %s, got expose\n", man->titlename);

	r1.x = theEvent->xexpose.x;
	r1.y = theEvent->xexpose.y;
	r1.width = theEvent->xexpose.width;
	r1.height = theEvent->xexpose.height;

	r2.width = man->geometry.boxwidth;
	r2.height = man->geometry.boxheight;

	bp = man->buttons.buttons;

	/* Background must be redrawn. */
	/* olicha: I do not think so */
	/*man->dirty_flags |= REDRAW_BG;*/

	if (0 && FHaveShapeExtension && man->shaped)
	{
		/* There's some weird problem where if we change window shapes,
		 * we can't draw into buttons in the area NewShape intersect
		 * (not OldShape) until we get our Expose event. So, for now,
		 * just redraw everything when we get Expose events. This has
		 * the disadvantage of drawing buttons twice, but avoids having
		 * to match which expose event results from which shape change.*/
		/* seems fixed ? olicha */
		if (man->buttons.num_windows)
		{
			for (i = 0; i < man->buttons.num_windows; i++)
			{
				bp[i]->drawn_state.dirty_flags |= REDRAW_BUTTON;
			}
		}
		else
		{
			draw_empty_manager(man);
		}

		return;
	}

	if (CSET_IS_TRANSPARENT_PR(man->colorsets[DEFAULT]))
	{
		clear_empty_region(man);
	}
	if (man->buttons.num_windows)
	{
		for (i = 0; i < man->buttons.num_windows; i++)
		{
			r2.x = index_to_col(man, i) * r2.width;
			r2.y = index_to_row(man, i) * r2.height;
			if (fvwmrect_do_rectangles_intersect(
				&r1, &r2))
			{
				bp[i]->drawn_state.ex = max(r1.x,r2.x);
				bp[i]->drawn_state.ey = max(r1.y,r2.y);
				bp[i]->drawn_state.ew =
					min(r1.x+r1.width,r2.x+r2.width) - 
						max(r1.x,r2.x);
				bp[i]->drawn_state.eh =
					min(r1.y+r1.height, r2.y+r2.height) -
						max(r1.y,r2.y);
				bp[i]->drawn_state.dirty_flags |= REDRAW_BUTTON;
			}
		}
	}
	else
	{
		draw_empty_manager(man);
	}
}

/*
 * tips routine
 */

static char *get_tips(WinManager *man, Button *b)
{
	ButtonGeometry g;
	char *s;
	static char *free_str = NULL;

	if (man->tips == TIPS_NEVER || b == NULL)
	{
		return NULL;
	}

	if (free_str != NULL)
	{
		free(free_str);
		free_str = NULL;
	}

	if (man->tips_formatstring && b->drawn_state.win)
	{
		CopyString(
			&s, make_display_string(
				b->drawn_state.win, man->tips_formatstring,
				0));
		free_str = s;
	}
	else
	{
		s = b->drawn_state.display_string;
	}

	if (s == NULL)
	{
		return NULL;
	}

	if (man->tips == TIPS_ALWAYS)
	{
		return s;
	}

	/* TIPS_NEEDED */
	if (free_str != NULL &&
	    (b->drawn_state.display_string == NULL ||
	     strcmp(s, b->drawn_state.display_string)))
	{
		return s;
	}

	get_button_geometry(man, b, &g);

	if (g.text_x + FlocaleTextWidth(man->FButtonFont, s, strlen(s))
	    > g.button_x + g.button_w - 4)
	{
		return s;
	}

	return NULL;
}

void tips_cancel(WinManager *man)
{
	int j;

	if (man)
	{
		man->tipped_button = NULL;
	}
	else
	{
		for (j = 0; j < globals.num_managers; j++)
		{
			man = &globals.managers[j];
			man->tipped_button = NULL;
		}
	}
	FTipsCancel(theDisplay);
}

void tips_on(WinManager *man, Button *b)
{
	char *tips_str;

	if ((tips_str = get_tips(man, b)) != NULL)
	{
		 FTipsOn(
			 theDisplay, man->theWindow, man->tips_conf, (void *)b,
			 tips_str, b->x, b->y, b->w, b->h);
		 man->tipped_button = b;
	}
	else
	{
		tips_cancel(man);
	}
}

void tips_update_label(WinManager *man)
{
	char *tips_str;

	if ((tips_str = get_tips(man, man->tipped_button)) != NULL)
	{
		 FTipsUpdateLabel(theDisplay, tips_str);
	}
	else
	{
		tips_cancel(man);
	}
}


/*
 * Debugging routines
 */

void check_managers_consistency(void)
{
#ifdef FVWM_DEBUG_MSGS
  int i, j;
  Button **b;

  for (i = 0; i < globals.num_managers; i++) {
    for (j = 0, b = globals.managers[i].buttons.buttons;
	 j < globals.managers[i].buttons.num_buttons; j++, b++) {
      if ((*b)->drawn_state.win && (*b)->drawn_state.win->button != *b) {
	ConsoleMessage("manager %d, button %d is confused\n", i, j);
	abort();
      }
      else if ((*b)->drawn_state.win &&
	       j >= globals.managers[i].buttons.num_windows) {
	ConsoleMessage("manager %d: button %d has window and shouldn't\n",
			i, j);
	abort();
      }
    }
  }
#endif
}

static void print_button_info(Button *b)
{
#ifdef FVWM_DEBUG_MSGS
  ConsoleMessage("button: %d\n", b->index);
  ConsoleMessage("win: 0x%x\n", (unsigned int) b->drawn_state.win);
  ConsoleMessage("dirty: 0x%x\n", b->drawn_state.dirty_flags);
  if (b->drawn_state.win) {
    ConsoleMessage("name: %s\n", b->drawn_state.display_string);
    ConsoleMessage("iconified: %d state %d\n", b->drawn_state.iconified,
		    b->drawn_state.state);
    ConsoleMessage("win->button: 0x%x\n",
		    (unsigned int) b->drawn_state.win->button);
  }
#endif
}


syntax highlighted by Code2HTML, v. 0.9.1