// Frame.C

#define FL_INTERNALS 1
#include "config.h"
#include "Frame.H"
#include "Desktop.H"
#include <string.h>
#include <stdio.h>
#include <FL/fl_draw.H>

static Atom wm_state = 0;
static Atom wm_change_state;
static Atom wm_protocols;
static Atom wm_delete_window;
static Atom wm_take_focus;
static Atom wm_save_yourself;
static Atom wm_colormap_windows;
static Atom _motif_wm_hints;
static Atom kwm_win_decoration;
#if DESKTOPS
static Atom kwm_win_desktop;
static Atom kwm_win_sticky;
#endif
//static Atom wm_client_leader;
static Atom _wm_quit_app;

// these are set by initialize in main.C:
Atom _win_hints;
Atom _win_state;
#if DESKTOPS
extern Atom _win_workspace;
#endif

#ifdef SHOW_CLOCK
extern char clock_buf[];
extern int clock_alarm_on;
#endif

static const int XEventMask =
ExposureMask|StructureNotifyMask
|KeyPressMask|KeyReleaseMask|KeymapStateMask|FocusChangeMask
|ButtonPressMask|ButtonReleaseMask
|EnterWindowMask|LeaveWindowMask
|PointerMotionMask|SubstructureRedirectMask|SubstructureNotifyMask;

extern Fl_Window* Root;

Frame* Frame::active_;
Frame* Frame::first;

static inline int max(int a, int b) {return a > b ? a : b;}
static inline int min(int a, int b) {return a < b ? a : b;}

////////////////////////////////////////////////////////////////
// The constructor is by far the most complex part, as it collects
// all the scattered pieces of information about the window that
// X has and uses them to initialize the structure, position the
// window, and then finally create it.

int dont_set_event_mask = 0; // used by FrameWindow

// "existing" is a pointer to an XWindowAttributes structure that is
// passed for an already-existing window when the window manager is
// starting up.  If so we don't want to alter the state, size, or
// position.  If null than this is a MapRequest of a new window.
Frame::Frame(XWindow window, XWindowAttributes* existing) :
  Fl_Window(0,0),
  window_(window),
  state_flags_(0),
  flags_(0),
  transient_for_xid(None),
  transient_for_(0),
  revert_to(active_),
  colormapWinCount(0),
  close_button(BUTTON_LEFT,BUTTON_TOP,BUTTON_W,BUTTON_H,"X"),
  iconize_button(BUTTON_LEFT,BUTTON_TOP,BUTTON_W,BUTTON_H,"i"),
  max_h_button(BUTTON_LEFT,BUTTON_TOP+3*BUTTON_H,BUTTON_W,BUTTON_H,"h"),
  max_w_button(BUTTON_LEFT,BUTTON_TOP+BUTTON_H,BUTTON_W,BUTTON_H,"w"),
  min_w_button(BUTTON_LEFT,BUTTON_TOP+2*BUTTON_H,BUTTON_W,BUTTON_H,"W")
{
#if FL_MAJOR_VERSION > 1
  clear_double_buffer();
#endif
  close_button.callback(button_cb_static);
  iconize_button.callback(button_cb_static);
  max_h_button.type(FL_TOGGLE_BUTTON);
  max_h_button.callback(button_cb_static);
  max_w_button.type(FL_TOGGLE_BUTTON);
  max_w_button.callback(button_cb_static);
  min_w_button.type(FL_TOGGLE_BUTTON);
  min_w_button.callback(button_cb_static);
  end();
  box(FL_NO_BOX); // relies on background color erasing interior
  labelcolor(FL_FOREGROUND_COLOR);
  next = first;
  first = this;

  // do this asap so we don't miss any events...
  if (!dont_set_event_mask)
    XSelectInput(fl_display, window_,
		 ColormapChangeMask | PropertyChangeMask | FocusChangeMask
		 );

  if (!wm_state) {
    // allocate all the atoms if this is the first time
    wm_state		= XInternAtom(fl_display, "WM_STATE",		0);
    wm_change_state	= XInternAtom(fl_display, "WM_CHANGE_STATE",	0);
    wm_protocols	= XInternAtom(fl_display, "WM_PROTOCOLS",	0);
    wm_delete_window	= XInternAtom(fl_display, "WM_DELETE_WINDOW",	0);
    wm_take_focus	= XInternAtom(fl_display, "WM_TAKE_FOCUS",	0);
    wm_save_yourself	= XInternAtom(fl_display, "WM_SAVE_YOURSELF",	0);
    wm_colormap_windows	= XInternAtom(fl_display, "WM_COLORMAP_WINDOWS",0);
    _motif_wm_hints	= XInternAtom(fl_display, "_MOTIF_WM_HINTS",	0);
    kwm_win_decoration	= XInternAtom(fl_display, "KWM_WIN_DECORATION",	0);
#if DESKTOPS
    kwm_win_desktop	= XInternAtom(fl_display, "KWM_WIN_DESKTOP",	0);
    kwm_win_sticky	= XInternAtom(fl_display, "KWM_WIN_STICKY",	0);
#endif
//  wm_client_leader	= XInternAtom(fl_display, "WM_CLIENT_LEADER",	0);
    _wm_quit_app	= XInternAtom(fl_display, "_WM_QUIT_APP",	0);
  }

  label_y = label_h = label_w = 0;
  getLabel();
  // getIconLabel();

  {XWindowAttributes attr;
  if (existing) attr = *existing;
  else {
    // put in some legal values in case XGetWindowAttributes fails:
    attr.x = attr.y = 0; attr.width = attr.height = 100;
    attr.colormap = fl_colormap;
    attr.border_width = 0;
    XGetWindowAttributes(fl_display, window, &attr);
  }
  left = top = dwidth = dheight = 0; // pretend border is zero-width for now
  app_border_width = attr.border_width;
  x(attr.x+app_border_width); restore_x = x();
  y(attr.y+app_border_width); restore_y = y();
  w(attr.width); restore_w = w();
  h(attr.height); restore_h = h();
  colormap = attr.colormap;}

  getColormaps();

  //group_ = 0;
  {XWMHints* hints = XGetWMHints(fl_display, window_);
  if (hints) {
    if ((hints->flags & InputHint) && !hints->input) set_flag(NO_FOCUS);
    //if (hints && hints->flags&WindowGroupHint) group_ = hints->window_group;
  }
  switch (getIntProperty(wm_state, wm_state, 0)) {
  case NormalState:
    state_ = NORMAL; break;
  case IconicState:
    state_ = ICONIC; break;
  // X also defines obsolete values ZoomState and InactiveState
  default:
    if (hints && (hints->flags&StateHint) && hints->initial_state==IconicState)
      state_ = ICONIC;
    else
      state_ = NORMAL;
  }
  if (hints) XFree(hints);}
  // Maya sets this, seems to mean the same as group:
  // if (!group_) group_ = getIntProperty(wm_client_leader, XA_WINDOW);

  XGetTransientForHint(fl_display, window_, &transient_for_xid);

  getProtocols();

  getMotifHints();

  // get Gnome hints:
  int p = getIntProperty(_win_hints, XA_CARDINAL);
  if (p&1) set_flag(NO_FOCUS);		// WIN_HINTS_SKIP_FOCUS
  // if (p&2)				// WIN_HINTS_SKIP_WINLIST
  // if (p&4)				// WIN_HINTS_SKIP_TASKBAR
  // if (p&8) ...			// WIN_HINTS_GROUP_TRANSIENT
  if (p&16) set_flag(CLICK_TO_FOCUS);	// WIN_HINTS_FOCUS_ON_CLICK

  // get KDE hints:
  p = getIntProperty(kwm_win_decoration, kwm_win_decoration, 1);
  if (!(p&3)) set_flag(NO_BORDER);
  else if (p & 2) set_flag(THIN_BORDER);
  if (p & 256) set_flag(NO_FOCUS);

  fix_transient_for();

  if (transient_for()) {
    if (state_ == NORMAL) state_ = transient_for()->state_;
#if DESKTOPS
    desktop_ = transient_for()->desktop_;
#endif
  }
#if DESKTOPS
  // see if anybody thinks window is "sticky:"
  else if ((getIntProperty(_win_state, XA_CARDINAL) & 1) // WIN_STATE_STICKY
      || getIntProperty(kwm_win_sticky, kwm_win_sticky)) {
    desktop_ = 0;
  } else {
    // get the desktop from either Gnome or KDE (Gnome takes precedence):
    p = getIntProperty(_win_workspace, XA_CARDINAL, -1) + 1; // Gnome desktop
    if (p <= 0) p = getIntProperty(kwm_win_desktop, kwm_win_desktop);
    if (p > 0 && p < 25)
      desktop_ = Desktop::number(p, 1);
    else
      desktop_ = Desktop::current();
  }
  if (desktop_ && desktop_ != Desktop::current())
    if (state_ == NORMAL) state_ = OTHER_DESKTOP;
#endif

  int autoplace = getSizes();
  // some Motif programs assumme this will force the size to conform :-(
  if (w() < min_w || h() < min_h) {
    if (w() < min_w) w(min_w);
    if (h() < min_h) h(min_h);
    XResizeWindow(fl_display, window_, w(), h());
  }

  // try to detect programs that think "transient_for" means "no border":
  if (transient_for_xid && !label() && !flag(NO_BORDER)) {
    set_flag(THIN_BORDER);
  }
  updateBorder();
  show_hide_buttons();

  if (autoplace && !existing && !(transient_for() && (x() || y()))) {
    place_window();
  }

  // move window so contents and border are visible:
  x(force_x_onscreen(x(), w()));
  y(force_y_onscreen(y(), h()));

  // guess some values for the "restore" fields, if already maximized:
  if (max_w_button.value()) {
    restore_w = min_w + ((w()-dwidth-min_w)/2/inc_w) * inc_w;
    restore_x = x()+left + (w()-dwidth-restore_w)/2;
  }
  if (max_h_button.value()) {
    restore_h = min_h + ((h()-dheight-min_h)/2/inc_h) * inc_h;
    restore_y = y()+top + (h()-dheight-restore_h)/2;
  }

  const int mask = CWBorderPixel | CWColormap | CWEventMask | CWBitGravity
    | CWBackPixel | CWOverrideRedirect;
  XSetWindowAttributes sattr;
  sattr.event_mask = XEventMask;
  sattr.colormap = fl_colormap;
  sattr.border_pixel = fl_xpixel(FL_GRAY0);
  sattr.bit_gravity = NorthWestGravity;
  sattr.override_redirect = 1;
  sattr.background_pixel = fl_xpixel(FL_GRAY);
  Fl_X::set_xid(this, XCreateWindow(fl_display,
				    RootWindow(fl_display,fl_screen),
			     x(), y(), w(), h(), 0,
			     fl_visual->depth,
			     InputOutput,
			     fl_visual->visual,
			     mask, &sattr));

  setStateProperty();

  if (!dont_set_event_mask) XAddToSaveSet(fl_display, window_);
  if (existing) set_state_flag(IGNORE_UNMAP);
  XReparentWindow(fl_display, window_, fl_xid(this), left, top);
  XSetWindowBorderWidth(fl_display, window_, 0);
  if (state_ == NORMAL) XMapWindow(fl_display, window_);
  sendConfigureNotify(); // many apps expect this even if window size unchanged

#if CLICK_RAISES || CLICK_TO_TYPE
  if (!dont_set_event_mask)
    XGrabButton(fl_display, AnyButton, AnyModifier, window, False,
		ButtonPressMask, GrabModeSync, GrabModeAsync, None, None);
#endif

  if (state_ == NORMAL) {
    XMapWindow(fl_display, fl_xid(this));
    if (!existing) activate_if_transient();
  }
  set_visible();
}

#if SMART_PLACEMENT
// Helper functions for "smart" window placement.
int overlap1(int p1, int l1, int p2, int l2) {
  int ret = 0;
  if(p1 <= p2 && p2 <= p1 + l1) {
    ret = min(p1 + l1 - p2, l2);
  } else if (p2 <= p1 && p1 <= p2 + l2) {
    ret = min(p2 + l2 - p1, l1);
  } 
  return ret;
}

int overlap(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2) {
  return (overlap1(x1, w1, x2, w2) * overlap1(y1, h1, y2, h2));
}

// Compute the overlap with existing windows.
// For normal windows the overlapping area is taken into account plus a 
// constant value for every overlapping window.
// The active window counts twice.
// For iconic windows half the overlapping area is taken into account.
int getOverlap(int x, int y, int w, int h, Frame *first, Frame *self) {
  int ret = 0;
  short state;
  for (Frame* f = first; f; f = f->next) {
    if (f != self) {
      state = f->state();
      if (state == NORMAL || state == ICONIC) {
	int o = overlap(x, y, w, h, f->x(), f->y(), f->w(), f->h());
	if (state == NORMAL) {
	  ret = ret + o + (o>0?40000:0) + (o * f->active());
	} else if (state == ICONIC) {
	  ret = ret + o/2;
	}
      }
    }
  }
  return ret;
}

// autoplacement (brute force version for now)
void Frame::place_window() {
  int min_overlap = -1;
  int tmp_x, tmp_y, tmp_o;
  int best_x = 0;
  int best_y = 0;
  int _w = w();
  int _h = h();
  int max_x = Root->x() + Root->w();
  int max_y = Root->y() + Root->h();
  
  Frame *f1 = Frame::first;
  for(int i=0;; i++) {
    if (i==0) {
      tmp_x = 0;
    } else if (i==1) {
      tmp_x = max_x - _w;
    } else {
      if (f1 == this) {
	f1 = f1->next;
      }
      if (!f1) {
	break;
      }
      tmp_x = f1->x() + f1->w();
      f1 = f1->next;
    }
    Frame *f2 = Frame::first;
    for(int j=0;; j++) {
      if (j==0) {
	tmp_y = 0;
      } else if (j==1) {
	tmp_y = max_y - _h;
      } else {
	if (f2 == this) {
	  f2 = f2->next;
	}
	if (!f2) {
	  break;
	}
	tmp_y = f2->y() + f2->h();
	f2 = f2->next;
      }

      if ((tmp_x + _w <= max_x) && (tmp_y + _h <= max_y)) {
	tmp_o = getOverlap(tmp_x, tmp_y, _w, _h, Frame::first, this);
	if(tmp_o < min_overlap || min_overlap < 0) {
	  best_x = tmp_x;
	  best_y = tmp_y;
	  min_overlap = tmp_o;
	  if (min_overlap == 0) {
	    break;
	  }
	}
      }
    }
    if (min_overlap == 0) {
      break;
    } 
  }
  x(best_x);
  y(best_y);
}

#else

// autoplacement (stupid version for now)
void Frame::place_window() {
  x(Root->x()+(Root->w()-w())/2);
  y(Root->y()+(Root->h()-h())/2);
  // move it until it does not hide any existing windows:
  const int delta = TITLE_WIDTH+LEFT;
    for (Frame* f = next; f; f = f->next) {
      if (f->x()+delta > x() && f->y()+delta > y() &&
	  f->x()+f->w()-delta < x()+w() && f->y()+f->h()-delta < y()+h()) {
	x(max(x(),f->x()+delta));
	y(max(y(),f->y()+delta));
	f = this;
      }
    }
}
#endif

// modify the passed X & W to a legal horizontal window position
int Frame::force_x_onscreen(int X, int W) {
  // force all except the black border on-screen:
  X = min(X, Root->x()+Root->w()+1-W);
  X = max(X, Root->x()-1);
  // force the contents on-screen:
  X = min(X, Root->x()+Root->w()-W+dwidth-left);
  if (W-dwidth > Root->w() || h()-dheight > Root->h())
    // windows bigger than the screen need title bar so they can move
    X = max(X, Root->x()-LEFT);
  else
    X = max(X, Root->x()-left);
  return X;
}

// modify the passed Y & H to a legal vertical window position:
int Frame::force_y_onscreen(int Y, int H) {
  // force border (except black edge) to be on-screen:
  Y = min(Y, Root->y()+Root->h()+1-H);
  Y = max(Y, Root->y()-1);
  // force contents to be on-screen:
  Y = min(Y, Root->y()+Root->h()-H+dheight-top);
  Y = max(Y, Root->y()-top);
  return Y;
}

////////////////////////////////////////////////////////////////
// destructor
// The destructor is called on DestroyNotify, so I don't have to do anything
// to the contained window, which is already been destroyed.

#if FL_MAJOR_VERSION < 2 && FL_MINOR_VERSION < 3
// fltk bug: it does not clear these pointers when window is deleted,
// causing flwm to crash on window close sometimes:
extern Fl_Window *fl_xfocus;
extern Fl_Window *fl_xmousewin;
#endif

Frame::~Frame() {

  // It is possible for the frame to be destroyed while the menu is
  // popped-up, and the menu will still contain a pointer to it.  To
  // fix this the menu checks the state_ location for a legal and
  // non-withdrawn state value before doing anything.  This should
  // be reliable unless something reallocates the memory and writes
  // a legal state value to this location:
  state_ = UNMAPPED;

#if FL_MAJOR_VERSION < 2 && FL_MINOR_VERSION < 3
  // fix fltk bug:
  fl_xfocus = 0;
  fl_xmousewin = 0;
  Fl::focus_ = 0;
#endif

  // remove any pointers to this:
  Frame** cp; for (cp = &first; *cp; cp = &((*cp)->next))
    if (*cp == this) {*cp = next; break;}
  for (Frame* f = first; f; f = f->next) {
    if (f->transient_for_ == this) f->transient_for_ = transient_for_;
    if (f->revert_to == this) f->revert_to = revert_to;
  }
  throw_focus(1);

  if (colormapWinCount) {
    XFree((char *)colormapWindows);
    delete[] window_Colormaps;
  }
  //if (iconlabel()) XFree((char*)iconlabel());
  if (label()) XFree((char*)label());
}

////////////////////////////////////////////////////////////////

void Frame::getLabel(int del) {
  char* old = (char*)label();
  char* nu = del ? 0 : (char*)getProperty(XA_WM_NAME);
  if (nu) {
    // since many window managers print a default label when none is
    // given, many programs send spaces to make a blank label.  Detect
    // this and make it really be blank:
    char* c = nu; while (*c == ' ') c++;
    if (!*c) {XFree(nu); nu = 0;}
  }
  if (old) {
    if (nu && !strcmp(old,nu)) {XFree(nu); return;}
    XFree(old);
  } else {
    if (!nu) return;
  }
  Fl_Widget::label(nu);
  if (nu) {
    fl_font(TITLE_FONT_SLOT, TITLE_FONT_SIZE);
    label_w = int(fl_width(nu))+6;
  } else
    label_w = 0;
  if (shown() && label_h > 3 && left > 3)
    XClearArea(fl_display, fl_xid(this), 1, label_y+3, left-1, label_h-3, 1);
}

////////////////////////////////////////////////////////////////

int Frame::getGnomeState(int &) {
// values for _WIN_STATE property are from Gnome WM compliance docs:
#define WIN_STATE_STICKY          (1<<0) /*everyone knows sticky*/
#define WIN_STATE_MINIMIZED       (1<<1) /*Reserved - definition is unclear*/
#define WIN_STATE_MAXIMIZED_VERT  (1<<2) /*window in maximized V state*/
#define WIN_STATE_MAXIMIZED_HORIZ (1<<3) /*window in maximized H state*/
#define WIN_STATE_HIDDEN          (1<<4) /*not on taskbar but window visible*/
#define WIN_STATE_SHADED          (1<<5) /*shaded (MacOS / Afterstep style)*/
#define WIN_STATE_HID_WORKSPACE   (1<<6) /*not on current desktop*/
#define WIN_STATE_HID_TRANSIENT   (1<<7) /*owner of transient is hidden*/
#define WIN_STATE_FIXED_POSITION  (1<<8) /*window is fixed in position even*/
#define WIN_STATE_ARRANGE_IGNORE  (1<<9) /*ignore for auto arranging*/
  // nyi
  return 0;
}

////////////////////////////////////////////////////////////////

// Read the sizeHints, and try to remove the vast number of mistakes
// that some applications seem to do writing them.
// Returns true if autoplace should be done.

int Frame::getSizes() {

  XSizeHints sizeHints;
  long junk;
  if (!XGetWMNormalHints(fl_display, window_, &sizeHints, &junk))
    sizeHints.flags = 0;

  // get the increment, use 1 if none or illegal values:
  if (sizeHints.flags & PResizeInc) {
    inc_w = sizeHints.width_inc; if (inc_w < 1) inc_w = 1;
    inc_h = sizeHints.height_inc; if (inc_h < 1) inc_h = 1;
  } else {
    inc_w = inc_h = 1;
  }

  // get the current size of the window:
  int W = w()-dwidth;
  int H = h()-dheight;
  // I try a lot of places to get a good minimum size value.  Lots of
  // programs set illegal or junk values, so getting this correct is
  // difficult:
  min_w = W;
  min_h = H;

  // guess a value for minimum size in case it is not set anywhere:
  min_w = min(min_w, 4*BUTTON_H);
  min_w = ((min_w+inc_w-1)/inc_w) * inc_w;
  min_h = min(min_h, 4*BUTTON_H);
  min_h = ((min_h+inc_h-1)/inc_h) * inc_h;
  // some programs put the minimum size here:
  if (sizeHints.flags & PBaseSize) {
    junk = sizeHints.base_width; if (junk > 0) min_w = junk;
    junk = sizeHints.base_height; if (junk > 0) min_h = junk;
  }
  // finally, try the actual place the minimum size should be:
  if (sizeHints.flags & PMinSize) {
    junk = sizeHints.min_width; if (junk > 0) min_w = junk;
    junk = sizeHints.min_height; if (junk > 0) min_h = junk;
  }

  max_w = max_h = 0; // default maximum size is "infinity"
  if (sizeHints.flags & PMaxSize) {
    // Though not defined by ICCCM standard, I interpret any maximum
    // size that is less than the minimum to mean "infinity".  This
    // allows the maximum to be set in one direction only:
    junk = sizeHints.max_width;
    if (junk >= min_w && junk <= W) max_w = junk;
    junk = sizeHints.max_height;
    if (junk >= min_h && junk <= H) max_h = junk;
  }

  // set the maximize buttons according to current size:
  max_w_button.value(W == maximize_width());
  max_h_button.value(H == maximize_height());

  // Currently only 1x1 aspect works:
  if (sizeHints.flags & PAspect
      && sizeHints.min_aspect.x == sizeHints.min_aspect.y)
    set_flag(KEEP_ASPECT);

  // another fix for gimp, which sets PPosition to 0,0:
  if (x() <= 0 && y() <= 0) sizeHints.flags &= ~PPosition;

  return !(sizeHints.flags & (USPosition|PPosition));
}

int max_w_switch;
// return width of contents when maximize button pressed:
int Frame::maximize_width() {
  int W = max_w_switch; if (!W) W = Root->w();
  return ((W-TITLE_WIDTH-min_w)/inc_w) * inc_w + min_w;
}

int max_h_switch;
int Frame::maximize_height() {
  int H = max_h_switch; if (!H) H = Root->h();
  return ((H-min_h)/inc_h) * inc_h + min_h;
}

////////////////////////////////////////////////////////////////

void Frame::getProtocols() {
  int n; Atom* p = (Atom*)getProperty(wm_protocols, XA_ATOM, &n);
  if (p) {
    clear_flag(DELETE_WINDOW_PROTOCOL|TAKE_FOCUS_PROTOCOL|QUIT_PROTOCOL);
    for (int i = 0; i < n; ++i) {
      if (p[i] == wm_delete_window) {
	set_flag(DELETE_WINDOW_PROTOCOL);
      } else if (p[i] == wm_take_focus) {
	set_flag(TAKE_FOCUS_PROTOCOL);
      } else if (p[i] == wm_save_yourself) {
	set_flag(SAVE_PROTOCOL);
      } else if (p[i] == _wm_quit_app) {
	set_flag(QUIT_PROTOCOL);
      }
    }
  }
  XFree((char*)p);
}

////////////////////////////////////////////////////////////////

int Frame::getMotifHints() {
  long* prop = (long*)getProperty(_motif_wm_hints, _motif_wm_hints);
  if (!prop) return 0;

  // see /usr/include/X11/Xm/MwmUtil.h for meaning of these bits...
  // prop[0] = flags (what props are specified)
  // prop[1] = functions (all, resize, move, minimize, maximize, close, quit)
  // prop[2] = decorations (all, border, resize, title, menu, minimize,
  //                        maximize)
  // prop[3] = input_mode (modeless, primary application modal, system modal,
  //                       full application modal)
  // prop[4] = status (tear-off window)

  // Fill in the default value for missing fields:
  if (!(prop[0]&1)) prop[1] = 1;
  if (!(prop[0]&2)) prop[2] = 1;

  // The low bit means "turn the marked items off", invert this.
  // Transient windows already have size & iconize buttons turned off:
  if (prop[1]&1) prop[1] = ~prop[1] & (transient_for_xid ? ~0x58 : -1);
  if (prop[2]&1) prop[2] = ~prop[2] & (transient_for_xid ? ~0x60 : -1);

  int old_flags = flags();

  // see if they are trying to turn off border:
  if (!(prop[2])) set_flag(NO_BORDER); else clear_flag(NO_BORDER);

  // see if they are trying to turn off title & close box:
  if (!(prop[2]&0x18)) set_flag(THIN_BORDER); else clear_flag(THIN_BORDER);

  // some Motif programs use this to disable resize :-(
  // and some programs change this after the window is shown (*&%$#%)
  if (!(prop[1]&2) || !(prop[2]&4))
    set_flag(NO_RESIZE); else clear_flag(NO_RESIZE);

  // and some use this to disable the Close function.  The commented
  // out test is it trying to turn off the mwm menu button: it appears
  // programs that do that still expect Alt+F4 to close them, so I
  // leave the close on then:
  if (!(prop[1]&0x20) /*|| !(prop[2]&0x10)*/)
    set_flag(NO_CLOSE); else clear_flag(NO_CLOSE);

  // see if they set "input hint" to non-zero:
  // prop[3] should be nonzero but the only example of this I have
  // found is Netscape 3.0 and it sets it to zero...
  if (!shown() && (prop[0]&4) /*&& prop[3]*/) set_flag(::MODAL);

  // see if it is forcing the iconize button back on.  This makes
  // transient_for act like group instead...
  if ((prop[1]&0x8) || (prop[2]&0x20)) set_flag(ICONIZE);

  // Silly 'ol Amazon paint ignores WM_DELETE_WINDOW and expects to
  // get the SGI-specific "_WM_QUIT_APP".  It indicates this by trying
  // to turn off the close box. SIGH!!!
  if (flag(QUIT_PROTOCOL) && !(prop[1]&0x20))
    clear_flag(DELETE_WINDOW_PROTOCOL);

  XFree((char*)prop);
  return (flags() ^ old_flags);
}

////////////////////////////////////////////////////////////////

void Frame::getColormaps(void) {
  if (colormapWinCount) {
    XFree((char *)colormapWindows);
    delete[] window_Colormaps;
  }
  int n;
  XWindow* cw = (XWindow*)getProperty(wm_colormap_windows, XA_WINDOW, &n);
  if (cw) {
    colormapWinCount = n;
    colormapWindows = cw;
    window_Colormaps = new Colormap[n];
    for (int i = 0; i < n; ++i) {
      if (cw[i] == window_) {
	window_Colormaps[i] = colormap;
      } else {
	XWindowAttributes attr;
	XSelectInput(fl_display, cw[i], ColormapChangeMask);
	XGetWindowAttributes(fl_display, cw[i], &attr);
	window_Colormaps[i] = attr.colormap;
      }
    }
  } else {
    colormapWinCount = 0;
  }
}

void Frame::installColormap() const {
  for (int i = colormapWinCount; i--;)
    if (colormapWindows[i] != window_ && window_Colormaps[i])
      XInstallColormap(fl_display, window_Colormaps[i]);
  if (colormap)
    XInstallColormap(fl_display, colormap);
}

////////////////////////////////////////////////////////////////

// figure out transient_for(), based on the windows that exist, the
// transient_for and group attributes, etc:
void Frame::fix_transient_for() {
  Frame* p = 0;
  if (transient_for_xid && !flag(ICONIZE)) {
    for (Frame* f = first; f; f = f->next) {
      if (f != this && f->window_ == transient_for_xid) {p = f; break;}
    }
    // loops are illegal:
    for (Frame* q = p; q; q = q->transient_for_) if (q == this) {p = 0; break;}
  }
  transient_for_ = p;
}

int Frame::is_transient_for(const Frame* f) const {
  if (f)
    for (Frame* p = transient_for(); p; p = p->transient_for())
      if (p == f) return 1;
  return 0;
}

// When a program maps or raises a window, this is called.  It guesses
// if this window is in fact a modal window for the currently active
// window and if so transfers the active state to this:
// This also activates new main windows automatically
int Frame::activate_if_transient() {
  if (!Fl::pushed())
    if (!transient_for() || is_transient_for(active_)) return activate(1);
  return 0;
}

////////////////////////////////////////////////////////////////

int Frame::activate(int warp) {
  // see if a modal & newer window is up:
  for (Frame* c = first; c && c != this; c = c->next)
    if (c->flag(::MODAL) && c->transient_for() == this)
      if (c->activate(warp)) return 1;
  // ignore invisible windows:
  if (state() != NORMAL || w() <= dwidth) return 0;
  // always put in the colormap:
  installColormap();
  // move the pointer if desired:
  // (note that moving the pointer is pretty much required for point-to-type
  // unless you know the pointer is already in the window):
  if (!warp || Fl::event_state() & (FL_BUTTON1|FL_BUTTON2|FL_BUTTON3)) {
    ;
  } else if (warp==2) {
    // warp to point at title:
    XWarpPointer(fl_display, None, fl_xid(this), 0,0,0,0, left/2+1,
                 min(label_y+label_w/2+1,h()/2));
  } else {
    warp_pointer();
  }
  // skip windows that don't want focus:
  if (flag(NO_FOCUS)) return 0;
  // set this even if we think it already has it, this seems to fix
  // bugs with Motif popups:
  XSetInputFocus(fl_display, window_, RevertToPointerRoot, fl_event_time);
  if (active_ != this) {
    if (active_) active_->deactivate();
    active_ = this;
#ifdef ACTIVE_COLOR
    XSetWindowAttributes a;
    a.background_pixel = fl_xpixel(FL_SELECTION_COLOR);
    XChangeWindowAttributes(fl_display, fl_xid(this), CWBackPixel, &a);
    labelcolor(contrast(FL_FOREGROUND_COLOR, FL_SELECTION_COLOR));
    XClearArea(fl_display, fl_xid(this), 2, 2, w()-4, h()-4, 1);
#else
#ifdef SHOW_CLOCK
    redraw();
#endif
#endif
    if (flag(TAKE_FOCUS_PROTOCOL))
      sendMessage(wm_protocols, wm_take_focus);
  }
  return 1;
}

// this private function should only be called by constructor and if
// the window is active():
void Frame::deactivate() {
#ifdef ACTIVE_COLOR
    XSetWindowAttributes a;
    a.background_pixel = fl_xpixel(FL_GRAY);
    XChangeWindowAttributes(fl_display, fl_xid(this), CWBackPixel, &a);
    labelcolor(FL_FOREGROUND_COLOR);
    XClearArea(fl_display, fl_xid(this), 2, 2, w()-4, h()-4, 1);
#else
#ifdef SHOW_CLOCK
    redraw();
#endif
#endif
}

#if CLICK_RAISES || CLICK_TO_TYPE
// After the XGrabButton, the main loop will get the mouse clicks, and
// it will call here when it gets them:
void click_raise(Frame* f) {
  f->activate();
#if CLICK_RAISES
  if (fl_xevent->xbutton.button <= 1) f->raise();
#endif
  XAllowEvents(fl_display, ReplayPointer, CurrentTime);
}
#endif

// get rid of the focus by giving it to somebody, if possible:
void Frame::throw_focus(int destructor) {
  if (!active()) return;
  if (!destructor) deactivate();
  active_ = 0;
  if (revert_to && revert_to->activate()) return;
  for (Frame* f = first; f; f = f->next)
    if (f != this && f->activate()) return;
}

////////////////////////////////////////////////////////////////

// change the state of the window (this is a private function and
// it ignores the transient-for or desktop information):

void Frame::state(short newstate) {
  short oldstate = state();
  if (newstate == oldstate) return;
  state_ = newstate;
  switch (newstate) {
  case UNMAPPED:
    throw_focus();
    XUnmapWindow(fl_display, fl_xid(this));
    //set_state_flag(IGNORE_UNMAP);
    //XUnmapWindow(fl_display, window_);
    XRemoveFromSaveSet(fl_display, window_);
    break;
  case NORMAL:
    if (oldstate == UNMAPPED) XAddToSaveSet(fl_display, window_);
    if (w() > dwidth) XMapWindow(fl_display, window_);
    XMapWindow(fl_display, fl_xid(this));
    clear_state_flag(IGNORE_UNMAP);
    break;
  default:
    if (oldstate == UNMAPPED) {
      XAddToSaveSet(fl_display, window_);
    } else if (oldstate == NORMAL) {
      throw_focus();
      XUnmapWindow(fl_display, fl_xid(this));
      //set_state_flag(IGNORE_UNMAP);
      //XUnmapWindow(fl_display, window_);
    } else {
      return; // don't setStateProperty IconicState multiple times
    }
    break;
  }
  setStateProperty();
}

void Frame::setStateProperty() const {
  long data[2];
  switch (state()) {
  case UNMAPPED :
    data[0] = WithdrawnState; break;
  case NORMAL :
  case OTHER_DESKTOP :
    data[0] = NormalState; break;
  default :
    data[0] = IconicState; break;
  }
  data[1] = (long)None;
  XChangeProperty(fl_display, window_, wm_state, wm_state,
		  32, PropModeReplace, (unsigned char *)data, 2);
}

////////////////////////////////////////////////////////////////
// Public state modifiers that move all transient_for(this) children
// with the frame and do the desktops right:

void Frame::raise() {
  Frame* newtop = 0;
  Frame* previous = 0;
  int previous_state = state_;
  Frame** p;
  // Find all the transient-for windows and this one, and raise them,
  // preserving stacking order:
  for (p = &first; *p;) {
    Frame* f = *p;
    if (f == this || (f->is_transient_for(this) && f->state() != UNMAPPED)) {
      *p = f->next; // remove it from list
      if (previous) {
	XWindowChanges w;
	w.sibling = fl_xid(previous);
	w.stack_mode = Below;
	XConfigureWindow(fl_display, fl_xid(f), CWSibling|CWStackMode, &w);
	previous->next = f;
      } else {
	XRaiseWindow(fl_display, fl_xid(f));
	newtop = f;
      }
#if DESKTOPS
      if (f->desktop_ && f->desktop_ != Desktop::current())
       f->state(OTHER_DESKTOP);
      else
#endif
	f->state(NORMAL);
      previous = f;
    } else {
      p = &((*p)->next);
    }
  }
  previous->next = first;
  first = newtop;
#if DESKTOPS
  if (!transient_for() && desktop_ && desktop_ != Desktop::current()) {
    // for main windows we also must move to the current desktop
    desktop(Desktop::current());
  }
#endif
  if (previous_state != NORMAL && newtop->state_==NORMAL)
    newtop->activate_if_transient();
}

void Frame::lower() {
  Frame* t = transient_for(); if (t) t->lower();
  if (!next || next == t) return; // already on bottom
  // pull it out of the list:
  Frame** p = &first;
  for (; *p != this; p = &((*p)->next)) {}
  *p = next;
  // find end of list:
  Frame* f = next; while (f->next != t) f = f->next;
  // insert it after that:
  f->next = this; next = t;
  // and move the X window:
  XWindowChanges w;
  w.sibling = fl_xid(f);
  w.stack_mode = Below;
  XConfigureWindow(fl_display, fl_xid(this), CWSibling|CWStackMode, &w);
}

void Frame::iconize() {
  for (Frame* c = first; c; c = c->next) {
    if (c == this || (c->is_transient_for(this) && c->state() != UNMAPPED))
      c->state(ICONIC);
  }
}

#if DESKTOPS
void Frame::desktop(Desktop* d) {
  if (d == desktop_) return;
  // Put all the relatives onto the desktop as well:
  for (Frame* c = first; c; c = c->next) {
    if (c == this || c->is_transient_for(this)) {
      c->desktop_ = d;
      c->setProperty(_win_state, XA_CARDINAL, !d);
      c->setProperty(kwm_win_sticky, kwm_win_sticky, !d);
      if (d) {
	c->setProperty(kwm_win_desktop, kwm_win_desktop, d->number());
	c->setProperty(_win_workspace, XA_CARDINAL, d->number()-1);
      }
      if (!d || d == Desktop::current()) {
	if (c->state() == OTHER_DESKTOP) c->state(NORMAL);
      } else {
	if (c->state() == NORMAL) c->state(OTHER_DESKTOP);
      }
    }
  }
}
#endif

////////////////////////////////////////////////////////////////

// Resize and/or move the window.  The size is given for the frame, not
// the contents.  This also sets the buttons on/off as needed:

void Frame::set_size(int nx, int ny, int nw, int nh, int warp) {
  int dx = nx-x(); x(nx);
  int dy = ny-y(); y(ny);
  if (!dx && !dy && nw == w() && nh == h()) return;
  int unmap = 0;
  int remap = 0;
  // use XClearArea to cause correct damage events:
  if (nw != w()) {
    max_w_button.value(nw-dwidth == maximize_width());
    min_w_button.value(nw <= dwidth);
    if (nw <= dwidth) {
      unmap = 1;
    } else {
      if (w() <= dwidth) remap = 1;
    }
    int minw = (nw < w()) ? nw : w();
    XClearArea(fl_display, fl_xid(this), minw-RIGHT, 0, RIGHT, nh, 1);
    w(nw);
  }
  if (nh != h()) {
    max_h_button.value(nh-dheight == maximize_height());
    int minh = (nh < h()) ? nh : h();
    XClearArea(fl_display, fl_xid(this), 0, minh-BOTTOM, w(), BOTTOM, 1);
    // see if label or close box moved, erase the minimum area:
//     int old_label_y = label_y;
//     int old_label_h = label_h;
    h(nh); show_hide_buttons();
#if 1 //def SHOW_CLOCK
    int t = label_y + 3; // we have to clear the entire label area
#else
    int t = nh;
    if (label_y != old_label_y) {
      t = label_y; if (old_label_y < t) t = old_label_y;
    } else if (label_y+label_h != old_label_y+old_label_h) {
      t = label_y+label_h;
      if (old_label_y+old_label_h < t) t = old_label_y+old_label_h;
    }
#endif
    if (t < nh && left>LEFT)
      XClearArea(fl_display,fl_xid(this), 1, t, left-1, nh-t, 1);
  }
  // for maximize button move the cursor first if window gets smaller
  if (warp == 1 && (dx || dy))
    XWarpPointer(fl_display, None,None,0,0,0,0, dx, dy);
  // for configure request, move the cursor first
  if (warp == 2 && active() && !Fl::pushed()) warp_pointer();
  XMoveResizeWindow(fl_display, fl_xid(this), nx, ny, nw, nh);
  if (nw <= dwidth) {
    if (unmap) {
      set_state_flag(IGNORE_UNMAP);
      XUnmapWindow(fl_display, window_);
    }
  } else {
    XResizeWindow(fl_display, window_, nw-dwidth, nh-dheight);
    if (remap) {
      XMapWindow(fl_display, window_);
#if CLICK_TO_TYPE
      if (active()) activate();
#else
      activate();
#endif
    }
  }
  // for maximize button move the cursor second if window gets bigger:
  if (warp == 3 && (dx || dy))
    XWarpPointer(fl_display, None,None,0,0,0,0, dx, dy);
  if (nw > dwidth) sendConfigureNotify();
  XSync(fl_display,0);
}

void Frame::sendConfigureNotify() const {
  XConfigureEvent ce;
  ce.type   = ConfigureNotify;
  ce.event  = window_;
  ce.window = window_;
  ce.x = x()+left-app_border_width;
  ce.y = y()+top-app_border_width;
  ce.width  = w()-dwidth;
  ce.height = h()-dheight;
  ce.border_width = app_border_width;
  ce.above = None;
  ce.override_redirect = 0;
  XSendEvent(fl_display, window_, False, StructureNotifyMask, (XEvent*)&ce);
}

// move the pointer inside the window:
void Frame::warp_pointer() {
  int X,Y; Fl::get_mouse(X,Y);
  X -= x();
  int Xi = X;
  if (X <= 0) X = left/2+1;
  if (X >= w()) X = w()-(RIGHT/2+1);
  Y -= y();
  int Yi = Y;
  if (Y < 0) Y = TOP/2+1;
  if (Y >= h()) Y = h()-(BOTTOM/2+1);
  if (X != Xi || Y != Yi)
    XWarpPointer(fl_display, None, fl_xid(this), 0,0,0,0, X, Y);
}

// Resize the frame to match the current border type:
void Frame::updateBorder() {
  int nx = x()+left;
  int ny = y()+top;
  int nw = w()-dwidth;
  int nh = h()-dheight;
  if (flag(NO_BORDER)) {
    left = top = dwidth = dheight = 0;
  } else {
    left = flag(THIN_BORDER) ? LEFT : LEFT+TITLE_WIDTH;
    dwidth = left+RIGHT;
    top = TOP;
    dheight = TOP+BOTTOM;
  }
  nx -= left;
  ny -= top;
  nw += dwidth;
  nh += dheight;
  if (x()==nx && y()==ny && w()==nw && h()==nh) return;
  x(nx); y(ny); w(nw); h(nh);
  if (!shown()) return; // this is so constructor can call this
  // try to make the contents not move while the border changes around it:
  XSetWindowAttributes a;
  a.win_gravity = StaticGravity;
  XChangeWindowAttributes(fl_display, window_, CWWinGravity, &a);
  XMoveResizeWindow(fl_display, fl_xid(this), nx, ny, nw, nh);
  a.win_gravity = NorthWestGravity;
  XChangeWindowAttributes(fl_display, window_, CWWinGravity, &a);
  // fix the window position if the X server didn't do the gravity:
  XMoveWindow(fl_display, window_, left, top);
}

// position and show the buttons according to current border, size,
// and other state information:
void Frame::show_hide_buttons() {
  if (flag(THIN_BORDER|NO_BORDER)) {
    iconize_button.hide();
    max_w_button.hide();
    min_w_button.hide();
    max_h_button.hide();
    close_button.hide();
    return;
  }
  int by = BUTTON_TOP;
  if (transient_for()) {
    iconize_button.hide();
    min_w_button.hide();
  } else {
    iconize_button.position(BUTTON_LEFT,by);
    iconize_button.show();
    by += BUTTON_H;
#if MINIMIZE_BOX
    min_w_button.position(BUTTON_LEFT,by);
    min_w_button.show();
    by += BUTTON_H;
#else
    min_w_button.hide();
#endif
  }
  if (min_h == max_h || flag(KEEP_ASPECT|NO_RESIZE) ||
      (!max_h_button.value() && by+label_w+2*BUTTON_H > h()-BUTTON_BOTTOM)) {
    max_h_button.hide();
  } else {
    max_h_button.position(BUTTON_LEFT,by);
    max_h_button.show();
    by += BUTTON_H;
  }
  if (min_w == max_w || flag(KEEP_ASPECT|NO_RESIZE) ||
      (!max_w_button.value() && by+label_w+2*BUTTON_H > h()-BUTTON_BOTTOM)) {
    max_w_button.hide();
  } else {
    max_w_button.position(BUTTON_LEFT,by);
    max_w_button.show();
    by += BUTTON_H;
  }
  if (label_y != by && shown())
    XClearArea(fl_display,fl_xid(this), 1, by, left-1, label_h+label_y-by, 1);
  label_y = by;
#if CLOSE_BOX
  if (by+BUTTON_H > h()-BUTTON_BOTTOM || flag(NO_CLOSE)) {
#endif
    label_h = h()-BOTTOM-by;
    close_button.hide();
#if CLOSE_BOX
  } else {
    close_button.show();
    close_button.position(BUTTON_LEFT,h()-(BUTTON_BOTTOM+BUTTON_H));
    label_h = close_button.y()-by;
  }
#endif
}

// make sure fltk does not try to set the window size:
void Frame::resize(int, int, int, int) {}
// For fltk2.0:
void Frame::layout() {
#if FL_MAJOR_VERSION>1 
  layout_damage(0); // actually this line is not needed in newest cvs fltk2.0
#endif
}

////////////////////////////////////////////////////////////////

void Frame::close() {
  if (flag(DELETE_WINDOW_PROTOCOL))
    sendMessage(wm_protocols, wm_delete_window);
  else if (flag(QUIT_PROTOCOL))
    sendMessage(wm_protocols, _wm_quit_app);
  else
    kill();
}

void Frame::kill() {
  XKillClient(fl_display, window_);
}

// this is called when window manager exits:
void Frame::save_protocol() {
  Frame* f;
  for (f = first; f; f = f->next) if (f->flag(SAVE_PROTOCOL)) {
    f->set_state_flag(SAVE_PROTOCOL_WAIT);
    f->sendMessage(wm_protocols, wm_save_yourself);
  }
  double t = 10.0; // number of seconds to wait before giving up
  while (t > 0.0) {
    for (f = first; ; f = f->next) {
      if (!f) return;
      if (f->flag(SAVE_PROTOCOL) && f->state_flags_&SAVE_PROTOCOL_WAIT) break;
    }
    t = Fl::wait(t);
  }
}

////////////////////////////////////////////////////////////////
// Drawing code:
#if FL_MAJOR_VERSION>1
# include <fltk/Box.h>
#endif

void Frame::draw() {
  if (flag(NO_BORDER)) return;
  if (!flag(THIN_BORDER)) Fl_Window::draw();
  if (damage() != FL_DAMAGE_CHILD) {
#ifdef ACTIVE_COLOR
    fl_frame2(active() ? "AAAAJJWW" : "AAAAJJWWNNTT",0,0,w(),h());
    if (active()) {
      fl_color(FL_GRAY_RAMP+('N'-'A'));
      fl_xyline(2, h()-3, w()-3, 2);
    }
#else
    fl_frame("AAAAWWJJTTNN",0,0,w(),h());
#endif
    if (!flag(THIN_BORDER) && label_h > 3) {
      fl_push_clip(1, label_y, left, label_h);
#ifdef SHOW_CLOCK
      if (active()) {
	  int clkw = int(fl_width(clock_buf));
	  if (clock_alarm_on) {
	      fl_font(TITLE_FONT_SLOT, TITLE_FONT_SIZE);
	      fl_rectf(LEFT-1, label_y + label_h - 3 - clkw, TITLE_WIDTH, clkw,
		       (ALARM_BG_COLOR>>16)&0xff,
		       (ALARM_BG_COLOR>>8)&0xff,
		       ALARM_BG_COLOR&0xff);
	      fl_color((ALARM_FG_COLOR>>16)&0xff,
		       (ALARM_FG_COLOR>>8)&0xff,
		       ALARM_FG_COLOR&0xff);
	  } else
	      fl_font(MENU_FONT_SLOT, TITLE_FONT_SIZE);
	  // This might overlay the label if the label is long enough
	  // and the window height is short enough.  For now, we'll
	  // assume this is not enough of a problem to be concerned
	  // about.
          fl_draw(90, clock_buf,
                  (left + fl_height() + 1)/2 - fl_descent(),
                  label_y+label_h-3);
      } else
	  // Only show the clock on the active frame.
	  XClearArea(fl_display, fl_xid(this), 1, label_y+3,
		     left-1, label_h-3, 0);
#endif      
      fl_color(labelcolor());
      fl_font(TITLE_FONT_SLOT, TITLE_FONT_SIZE);
      if (label() && *label())
	fl_draw(90, label(),
		(left + fl_height() + 1)/2 - fl_descent(),
		label_y+3+fl_width(label()));
      fl_pop_clip();
    }
  }
}

#ifdef SHOW_CLOCK
void Frame::redraw_clock() {
    double clkw = fl_width(clock_buf);
    XClearArea(fl_display, fl_xid(this),
	       1, label_y+label_h-3-(int)clkw,
	       left-1, (int)clkw, 1);
}
#endif

void FrameButton::draw() {
#if FL_MAJOR_VERSION>1
  const int x = value()?1:0;
  const int y = x;
  drawstyle(style(),flags()|fltk::OUTPUT);
  FL_UP_BOX->draw(Rectangle(w(),h()));
#else
  const int x = this->x();
  const int y = this->y();
  Fl_Widget::draw_box(value() ? FL_DOWN_FRAME : FL_UP_FRAME, FL_GRAY);
#endif
  fl_color(parent()->labelcolor());
  switch (label()[0]) {
  case 'W':
#if MINIMIZE_ARROW
    fl_line (x+2,y+(h())/2,x+w()-4,y+h()/2);
    fl_line (x+2,y+(h())/2,x+2+4,y+h()/2+4);
    fl_line (x+2,y+(h())/2,x+2+4,y+h()/2-4);
#else
    fl_rect(x+(h()-7)/2,y+3,2,h()-6);
#endif
    return;
  case 'w':
    fl_rect(x+2,y+(h()-7)/2,w()-4,7);
    return;
  case 'h':
    fl_rect(x+(h()-7)/2,y+2,7,h()-4);
    return;
  case 'X':
#if CLOSE_X
    fl_line(x+2,y+3,x+w()-5,y+h()-4);
    fl_line(x+3,y+3,x+w()-4,y+h()-4);
    fl_line(x+2,y+h()-4,x+w()-5,y+3);
    fl_line(x+3,y+h()-4,x+w()-4,y+3);
#endif
#if CLOSE_HITTITE_LIGHTNING
    fl_arc(x+3,y+3,w()-6,h()-6,0,360);
    fl_line(x+7,y+3, x+7,y+11);
#endif
    return;
  case 'i':
#if ICONIZE_BOX
    fl_rect(x+w()/2-1,y+h()/2-1,3,3);
#endif
    return;
  }
}

////////////////////////////////////////////////////////////////
// User interface code:

// this is called when user clicks the buttons:
void Frame::button_cb(Fl_Button* b) {
  switch (b->label()[0]) {
  case 'W':	// minimize button
    if (b->value()) {
      if (!max_w_button.value()) {
        restore_x = x()+left;
	restore_y = y()+top;
#if MINIMIZE_HEIGHT
	restore_w=w()-dwidth;
	restore_h = h()-dwidth;
#endif
      }
#if MINIMIZE_HEIGHT
      set_size(x(), y(), dwidth-1,
	       min(h(),min(350,label_w+3*BUTTON_H+BUTTON_TOP+BUTTON_BOTTOM)),
	       1);
#else
      set_size(x(), y(), dwidth-1, h(), 1);
#endif
    } else {
#if MINIMIZE_HEIGHT
      set_size(x(), y(), restore_w+dwidth, restore_h+dwidth, 1);
#else
      set_size(x(), y(), restore_w+dwidth, h(), 1);
#endif
    }
    show_hide_buttons();
    break;
  case 'w':	// max-width button
    if (b->value()) {
      if (!min_w_button.value()) {restore_x=x()+left; restore_w=w()-dwidth;}
      int W = maximize_width()+dwidth;
      int X = force_x_onscreen(x() + (w()-W)/2, W);
      set_size(X, y(), W, h(), 3);
    } else {
      set_size(restore_x-left, y(), restore_w+dwidth, h(), 1);
    }
    show_hide_buttons();
    break;
  case 'h':	// max-height button
    if (b->value()) {
      restore_y = y()+top;
      restore_h = h()-dwidth;
      int H = maximize_height()+dheight;
      int Y = force_y_onscreen(y() + (h()-H)/2, H);
      set_size(x(), Y, w(), H, 3);
    } else {
      set_size(x(), restore_y-top, w(), restore_h+dwidth, 1);
    }
    break;
  case 'X':
    close();
    break;
  default: // iconize button
    iconize();
    break;
  }
}

// static callback for fltk:
void Frame::button_cb_static(Fl_Widget* w, void*) {
  ((Frame*)(w->parent()))->button_cb((Fl_Button*)w);
}

// This method figures out what way the mouse will resize the window.
// It is used to set the cursor and to actually control what you grab.
// If the window cannot be resized in some direction this should not
// return that direction.
int Frame::mouse_location() {
  int x = Fl::event_x();
  int y = Fl::event_y();
  int r = 0;
  if (flag(NO_RESIZE)) return 0;
  if (min_h != max_h) {
    if (y < RESIZE_EDGE) r |= FL_ALIGN_TOP;
    else if (y >= h()-RESIZE_EDGE) r |= FL_ALIGN_BOTTOM;
  }
  if (min_w != max_w) {
#if RESIZE_LEFT
    if (x < RESIZE_EDGE) r |= FL_ALIGN_LEFT;
#else
    if (x < RESIZE_EDGE && r) r |= FL_ALIGN_LEFT;
#endif
    else if (x >= w()-RESIZE_EDGE) r |= FL_ALIGN_RIGHT;
  }
  return r;
}

// set the cursor correctly for a return value from mouse_location():
void Frame::set_cursor(int r) {
  Fl_Cursor c = r ? FL_CURSOR_ARROW : FL_CURSOR_MOVE;
  switch (r) {
  case FL_ALIGN_TOP:
  case FL_ALIGN_BOTTOM:
    c = FL_CURSOR_NS;
    break;
  case FL_ALIGN_LEFT:
  case FL_ALIGN_RIGHT:
    c = FL_CURSOR_WE;
    break;
  case FL_ALIGN_LEFT|FL_ALIGN_TOP:
  case FL_ALIGN_RIGHT|FL_ALIGN_BOTTOM:
    c = FL_CURSOR_NWSE;
    break;
  case FL_ALIGN_LEFT|FL_ALIGN_BOTTOM:
  case FL_ALIGN_RIGHT|FL_ALIGN_TOP:
    c = FL_CURSOR_NESW;
    break;
  }
#if FL_MAJOR_VERSION>1
  cursor(c);
#else
  static Frame* previous_frame;
  static Fl_Cursor previous_cursor;
  if (this != previous_frame || c != previous_cursor) {
    previous_frame = this;
    previous_cursor = c;
    cursor(c, CURSOR_FG_SLOT, CURSOR_BG_SLOT);
  }
#endif
}

#ifdef AUTO_RAISE
// timeout callback to cause autoraise:
void auto_raise(void*) {
  if (Frame::activeFrame() && !Fl::grab() && !Fl::pushed())
    Frame::activeFrame()->raise();
}
#endif

extern void ShowMenu();

// If cursor is in the contents of a window this is set to that window.
// This is only used to force the cursor to an arrow even though X keeps
// sending mysterious erroneous move events:
static Frame* cursor_inside = 0;

// Handle an fltk event.
int Frame::handle(int e) {
  static int what, dx, dy, ix, iy, iw, ih;
  // see if child widget handles event:
#if FL_MAJOR_VERSION > 1
  if (fltk::Group::handle(e) && e != FL_ENTER && e != FL_MOVE) {
    if (e == FL_PUSH) set_cursor(-1);
    return 1;
  }
#else
  if (Fl_Group::handle(e) && e != FL_ENTER && e != FL_MOVE) {
    if (e == FL_PUSH) set_cursor(-1);
    return 1;
  }
#endif
  switch (e) {

  case FL_SHOW:
  case FL_HIDE:
    return 0; // prevent fltk from messing things up

  case FL_ENTER:
#if !CLICK_TO_TYPE
    if (Fl::pushed() || Fl::grab()) return 1;
    if (activate()) {
#ifdef AUTO_RAISE
      Fl::remove_timeout(auto_raise);
      Fl::add_timeout(AUTO_RAISE, auto_raise);
#endif
    }
#endif
    goto GET_CROSSINGS;

  case FL_LEAVE:
#if !CLICK_TO_TYPE && !STICKY_FOCUS
    if (active()) {
      deactivate();
      XSetInputFocus(fl_display, PointerRoot, RevertToPointerRoot,
		     fl_event_time);
      active_ = 0;
    }
#endif
    goto GET_CROSSINGS;

  case FL_MOVE:
  GET_CROSSINGS:
    // set cursor_inside to true when the mouse is inside a window
    // set it false when mouse is on a frame or outside a window.
    // fltk mangles the X enter/leave events, we need the original ones:

    switch (fl_xevent->type) {
    case LeaveNotify:
      if (fl_xevent->xcrossing.detail == NotifyInferior) {
	// cursor moved from frame to interior
	cursor_inside = this;
	break;
      } else {
	// cursor moved to another window
	return 1;
      }

    case EnterNotify:
      // see if cursor skipped over frame and directly to interior:
      if (fl_xevent->xcrossing.detail == NotifyVirtual ||
	  fl_xevent->xcrossing.detail == NotifyNonlinearVirtual)
	cursor_inside = this;
      else {
	// cursor is now pointing at frame:
	cursor_inside = 0;
      }
    }
    if (Fl::belowmouse() != this || cursor_inside == this)
      set_cursor(-1);
    else
      set_cursor(mouse_location());
    return 1;

  case FL_PUSH:
    if (Fl::event_button() > 2) {
      set_cursor(-1);
      ShowMenu();
      return 1;
    }
    ix = x(); iy = y(); iw = w(); ih = h();
    if (!max_w_button.value() && !min_w_button.value()) {
      restore_x = ix+left; restore_w = iw-dwidth;
    }
#if MINIMIZE_HEIGHT
    if (!min_w_button.value())
#endif
    if (!max_h_button.value()) {
      restore_y = iy+top; restore_h = ih-dwidth;
    }
    what = mouse_location();
    if (Fl::event_button() > 1) what = 0; // middle button does drag
    dx = Fl::event_x_root()-ix;
    if (what & FL_ALIGN_RIGHT) dx -= iw;
    dy = Fl::event_y_root()-iy;
    if (what & FL_ALIGN_BOTTOM) dy -= ih;
    set_cursor(what);
    return 1;
  case FL_DRAG:
    if (Fl::event_is_click()) return 1; // don't drag yet
  case FL_RELEASE:
    if (Fl::event_is_click()) {
      if (Fl::grab()) return 1;
#if CLICK_TO_TYPE
      if (activate()) {
	if (Fl::event_button() <= 1) raise();
	return 1;
      }
#endif
      if (Fl::event_button() > 1) lower(); else raise();
    } else if (!what) {
      int nx = Fl::event_x_root()-dx;
      int W = Root->x()+Root->w();
      if (nx+iw > W && nx+iw < W+SCREEN_SNAP) {
	int t = W+1-iw;
	if (iw >= Root->w() || x() > t || nx+iw >= W+EDGE_SNAP)
	  t = W+(dwidth-left)-iw;
	if (t >= x() && t < nx) nx = t;
      }
      int X = Root->x();
      if (nx < X && nx > X-SCREEN_SNAP) {
	int t = X-1;
	if (iw >= Root->w() || x() < t || nx <= X-EDGE_SNAP) t = X-BUTTON_LEFT;
	if (t <= x() && t > nx) nx = t;
      }
      int ny = Fl::event_y_root()-dy;
      int H = Root->y()+Root->h();
      if (ny+ih > H && ny+ih < H+SCREEN_SNAP) {
	int t = H+1-ih;
	if (ih >= Root->h() || y() > t || ny+ih >= H+EDGE_SNAP)
	  t = H+(dheight-top)-ih;
	if (t >= y() && t < ny) ny = t;
      }
      int Y = Root->y();
      if (ny < Y && ny > Y-SCREEN_SNAP) {
	int t = Y-1;
	if (ih >= H || y() < t || ny <= Y-EDGE_SNAP) t = Y-top;
	if (t <= y() && t > ny) ny = t;
      }
      set_size(nx, ny, iw, ih);
    } else {
      int nx = ix;
      int ny = iy;
      int nw = iw;
      int nh = ih;
      if (what & FL_ALIGN_RIGHT)
	nw = Fl::event_x_root()-dx-nx;
      else if (what & FL_ALIGN_LEFT)
	nw = ix+iw-(Fl::event_x_root()-dx);
      else {nx = x(); nw = w();}
      if (what & FL_ALIGN_BOTTOM)
	nh = Fl::event_y_root()-dy-ny;
      else if (what & FL_ALIGN_TOP)
	nh = iy+ih-(Fl::event_y_root()-dy);
      else {ny = y(); nh = h();}
      if (flag(KEEP_ASPECT)) {
	if ((nw-dwidth > nh-dwidth
             && (what&(FL_ALIGN_LEFT|FL_ALIGN_RIGHT)))
	    || !(what&(FL_ALIGN_TOP|FL_ALIGN_BOTTOM)))
	  nh = nw-dwidth+dheight;
	else
	  nw = nh-dheight+dwidth;
      }
      int MINW = min_w+dwidth;
      if (nw <= dwidth && dwidth > TITLE_WIDTH) {
	nw = dwidth-1;
#if MINIMIZE_HEIGHT
	restore_h = nh;
#endif
      } else {
	if (inc_w > 1) nw = ((nw-MINW+inc_w/2)/inc_w)*inc_w+MINW;
	if (nw < MINW) nw = MINW;
	else if (max_w && nw > max_w+dwidth) nw = max_w+dwidth;
      }
      int MINH = min_h+dheight;
      const int MINH_B = BUTTON_H+BUTTON_TOP+BUTTON_BOTTOM;
      if (MINH_B > MINH) MINH = MINH_B;
      if (inc_h > 1) nh = ((nh-MINH+inc_h/2)/inc_h)*inc_h+MINH;
      if (nh < MINH) nh = MINH;
      else if (max_h && nh > max_h+dheight) nh = max_h+dheight;
      if (what & FL_ALIGN_LEFT) nx = ix+iw-nw;
      if (what & FL_ALIGN_TOP) ny = iy+ih-nh;
      set_size(nx,ny,nw,nh);
    }
    return 1;
  }
  return 0;
}

// Handle events that fltk did not recognize (mostly ones directed
// at the desktop):

int Frame::handle(const XEvent* ei) {

  switch (ei->type) {

  case ConfigureRequest: {
    const XConfigureRequestEvent* e = &(ei->xconfigurerequest);
    unsigned long mask = e->value_mask;
    if (mask & CWBorderWidth) app_border_width = e->border_width;
    // Try to detect if the application is really trying to move the
    // window, or is simply echoing it's postion, possibly with some
    // variation (such as echoing the parent window position), and
    // dont' move it in that case:
    int X = (mask & CWX && e->x != x()) ? e->x+app_border_width-left : x();
    int Y = (mask & CWY && e->y != y()) ? e->y+app_border_width-top : y();
    int W = (mask & CWWidth) ? e->width+dwidth : w();
    int H = (mask & CWHeight) ? e->height+dheight : h();
    // Generally we want to obey any application positioning of the
    // window, except when it appears the app is trying to position
    // the window "at the edge".
    if (!(mask & CWX) || (X >= -2*left && X < 0)) X = force_x_onscreen(X,W);
    if (!(mask & CWY) || (Y >= -2*top && Y < 0)) Y = force_y_onscreen(Y,H);
    // Fix Rick Sayre's program that resizes it's windows bigger than the
    // maximum size:
    if (W > max_w+dwidth) max_w = 0;
    if (H > max_h+dheight) max_h = 0;
    set_size(X, Y, W, H, 2);
    if (e->value_mask & CWStackMode && e->detail == Above && state()==NORMAL)
      raise();
    return 1;}

  case MapRequest: {
    //const XMapRequestEvent* e = &(ei->xmaprequest);
    raise();
    return 1;}

  case UnmapNotify: {
    const XUnmapEvent* e = &(ei->xunmap);
    if (e->window == window_ && !e->from_configure) {
      if (state_flags_&IGNORE_UNMAP) clear_state_flag(IGNORE_UNMAP);
      else state(UNMAPPED);
    }
    return 1;}

  case DestroyNotify: {
    //const XDestroyWindowEvent* e = &(ei->xdestroywindow);
    delete this;
    return 1;}

  case ReparentNotify: {
    const XReparentEvent* e = &(ei->xreparent);
    if (e->parent==fl_xid(this)) return 1; // echo
    if (e->parent==fl_xid(Root)) return 1; // app is trying to tear-off again?
    delete this; // guess they are trying to paste tear-off thing back?
    return 1;}

  case ClientMessage: {
    const XClientMessageEvent* e = &(ei->xclient);
    if (e->message_type == wm_change_state && e->format == 32) {
      if (e->data.l[0] == NormalState) raise();
      else if (e->data.l[0] == IconicState) iconize();
    } else
      // we may want to ignore _WIN_LAYER from xmms?
      Fl::warning("flwm: unexpected XClientMessageEvent, type 0x%lx, "
	      "window 0x%lx\n", e->message_type, e->window);
    return 1;}

  case ColormapNotify: {
    const XColormapEvent* e = &(ei->xcolormap);
    if (e->c_new) {  // this field is called "new" in the old C++-unaware Xlib
      colormap = e->colormap;
      if (active()) installColormap();
    }
    return 1;}

  case PropertyNotify: {
    const XPropertyEvent* e = &(ei->xproperty);
    Atom a = e->atom;

    // case XA_WM_ICON_NAME: (do something similar to name)
    if (a == XA_WM_NAME) {
      getLabel(e->state == PropertyDelete);

    } else if (a == wm_state) {
      // it's not clear if I really need to look at this.  Need to make
      // sure it is not seeing the state echoed by the application by
      // checking for it being different...
      switch (getIntProperty(wm_state, wm_state, state())) {
      case IconicState:
	if (state() == NORMAL || state() == OTHER_DESKTOP) iconize(); break;
      case NormalState:
	if (state() != NORMAL && state() != OTHER_DESKTOP) raise(); break;
      }

    } else if (a == wm_colormap_windows) {
      getColormaps();
      if (active()) installColormap();

    } else if (a == _motif_wm_hints) {
      // some #%&%$# SGI Motif programs change this after mapping the window!
      // :-( :=( :-( :=( :-( :=( :-( :=( :-( :=( :-( :=(
      if (getMotifHints()) { // returns true if any flags changed
	fix_transient_for();
	updateBorder();
	show_hide_buttons();
      }

    } else if (a == wm_protocols) {
      getProtocols();
      // get Motif hints since they may do something with QUIT:
      getMotifHints();

    } else if (a == XA_WM_NORMAL_HINTS || a == XA_WM_SIZE_HINTS) {
      getSizes();
      show_hide_buttons();

    } else if (a == XA_WM_TRANSIENT_FOR) {
      XGetTransientForHint(fl_display, window_, &transient_for_xid);
      fix_transient_for();
      show_hide_buttons();

    } else if (a == XA_WM_COMMAND) {
      clear_state_flag(SAVE_PROTOCOL_WAIT);

    }
    return 1;}

  }
  return 0;
}

////////////////////////////////////////////////////////////////
// X utility routines:

void* Frame::getProperty(Atom a, Atom type, int* np) const {
  return ::getProperty(window_, a, type, np);
}

void* getProperty(XWindow w, Atom a, Atom type, int* np) {
  Atom realType;
  int format;
  unsigned long n, extra;
  int status;
  uchar* prop;
  status = XGetWindowProperty(fl_display, w,
			      a, 0L, 256L, False, type, &realType,
			      &format, &n, &extra, &prop);
  if (status != Success) return 0;
  if (!prop) return 0;
  if (!n) {XFree(prop); return 0;}
  if (np) *np = (int)n;
  return (void*)prop;
}

int Frame::getIntProperty(Atom a, Atom type, int deflt) const {
  return ::getIntProperty(window_, a, type, deflt);
}

int getIntProperty(XWindow w, Atom a, Atom type, int deflt) {
  void* prop = getProperty(w, a, type);
  if (!prop) return deflt;
  int r = int(*(long*)prop);
  XFree(prop);
  return r;
}

void setProperty(XWindow w, Atom a, Atom type, int v) {
  long prop = v;
  XChangeProperty(fl_display, w, a, type, 32, PropModeReplace, (uchar*)&prop,1);
}

void Frame::setProperty(Atom a, Atom type, int v) const {
  ::setProperty(window_, a, type, v);
}

void Frame::sendMessage(Atom a, Atom l) const {
  XEvent ev;
  long mask;
  memset(&ev, 0, sizeof(ev));
  ev.xclient.type = ClientMessage;
  ev.xclient.window = window_;
  ev.xclient.message_type = a;
  ev.xclient.format = 32;
  ev.xclient.data.l[0] = long(l);
  ev.xclient.data.l[1] = long(fl_event_time);
  mask = 0L;
  XSendEvent(fl_display, window_, False, mask, &ev);
}
