1// Frame.C
2// Some modifications by Michael A. Losh, tagged "ML" below,
3//   or bracketed by TOPSIDE manifest constant
4// Last update: 2009-09-27
5
6#include "config.h"
7#include "Frame.H"
8#include "Desktop.H"
9#include <string.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <FL/fl_draw.H>
13#include "Rotated.H"
14
15
16static Atom wm_state = 0;
17static Atom wm_change_state;
18static Atom wm_protocols;
19static Atom wm_delete_window;
20static Atom wm_take_focus;
21static Atom wm_save_yourself;
22static Atom wm_colormap_windows;
23static Atom _motif_wm_hints;
24static Atom kwm_win_decoration;
25#if DESKTOPS
26static Atom kwm_win_desktop;
27static Atom kwm_win_sticky;
28#endif
29//static Atom wm_client_leader;
30static Atom _wm_quit_app;
31
32// these are set by initialize in main.C:
33Atom _win_hints;
34Atom _win_state;
35#if DESKTOPS
36extern Atom _win_workspace;
37#endif
38
39#ifdef SHOW_CLOCK
40extern char clock_buf[];
41extern int clock_alarm_on;
42#endif
43
44static const int XEventMask =
45ExposureMask|StructureNotifyMask
46|KeyPressMask|KeyReleaseMask|KeymapStateMask|FocusChangeMask
47|ButtonPressMask|ButtonReleaseMask
48|EnterWindowMask|LeaveWindowMask
49|PointerMotionMask|SubstructureRedirectMask|SubstructureNotifyMask;
50
51extern Fl_Window* Root;
52
53Frame* Frame::active_;
54Frame* Frame::first;
55
56static inline int max(int a, int b) {return a > b ? a : b;}
57static inline int min(int a, int b) {return a < b ? a : b;}
58
59////////////////////////////////////////////////////////////////
60// The constructor is by far the most complex part, as it collects
61// all the scattered pieces of information about the window that
62// X has and uses them to initialize the structure, position the
63// window, and then finally create it.
64
65int dont_set_event_mask = 0; // used by FrameWindow
66
67// "existing" is a pointer to an XWindowAttributes structure that is
68// passed for an already-existing window when the window manager is
69// starting up.  If so we don't want to alter the state, size, or
70// position.  If null than this is a MapRequest of a new window.
71Frame::Frame(XWindow window, XWindowAttributes* existing) :
72  Fl_Window(0,0),
73  window_(window),
74  state_flags_(0),
75  flags_(0),
76  transient_for_xid(None),
77  transient_for_(0),
78  revert_to(active_),
79  colormapWinCount(0),
80  close_button(BUTTON_LEFT,BUTTON_TOP,BUTTON_W,BUTTON_H,"X"),
81  iconize_button(BUTTON_LEFT,BUTTON_TOP,BUTTON_W,BUTTON_H,"i"),
82  max_h_button(BUTTON_LEFT,BUTTON_TOP+3*BUTTON_H,BUTTON_W,BUTTON_H,"h"),
83  max_w_button(BUTTON_LEFT,BUTTON_TOP+BUTTON_H,BUTTON_W,BUTTON_H,"w"),
84  min_w_button(BUTTON_LEFT,BUTTON_TOP+2*BUTTON_H,BUTTON_W,BUTTON_H,"W")
85{
86#if FL_MAJOR_VERSION > 1
87  clear_double_buffer();
88#endif
89  close_button.callback(button_cb_static);
90  iconize_button.callback(button_cb_static);
91  max_h_button.type(FL_TOGGLE_BUTTON);
92  max_h_button.callback(button_cb_static);
93  max_w_button.type(FL_TOGGLE_BUTTON);
94  max_w_button.callback(button_cb_static);
95  min_w_button.type(FL_TOGGLE_BUTTON);
96  min_w_button.callback(button_cb_static);
97  end();
98  box(FL_NO_BOX); // relies on background color erasing interior
99  // ML -------------------------------
100#ifdef ML_TITLEBAR_COLOR
101  char * titlebar_color_str = getenv("FLWM_TITLEBAR_COLOR");
102  int r = 0x90, g =  0x90, b = 0x90;
103  int fields;
104  if (titlebar_color_str) {
105	  fields = sscanf(titlebar_color_str, "%02X:%02X:%02X", &r, &g, &b);
106  }
107  if (titlebar_color_str && fields == 3) {
108	  Fl::set_color(FL_BACKGROUND2_COLOR, r, g, b);
109  }
110  else {
111	  Fl::set_color(FL_BACKGROUND2_COLOR, 0x90, 0x90, 0x90);
112  }
113#else
114  Fl::set_color(FL_BACKGROUND2_COLOR, 0x90, 0x90, 0x90);
115#endif
116  // --------------------------------ML
117  labelcolor(FL_FOREGROUND_COLOR);
118  next = first;
119  first = this;
120
121  // do this asap so we don't miss any events...
122  if (!dont_set_event_mask)
123    XSelectInput(fl_display, window_,
124		 ColormapChangeMask | PropertyChangeMask | FocusChangeMask
125		 );
126
127  if (!wm_state) {
128    // allocate all the atoms if this is the first time
129    wm_state		= XInternAtom(fl_display, "WM_STATE",		0);
130    wm_change_state	= XInternAtom(fl_display, "WM_CHANGE_STATE",	0);
131    wm_protocols	= XInternAtom(fl_display, "WM_PROTOCOLS",	0);
132    wm_delete_window	= XInternAtom(fl_display, "WM_DELETE_WINDOW",	0);
133    wm_take_focus	= XInternAtom(fl_display, "WM_TAKE_FOCUS",	0);
134    wm_save_yourself	= XInternAtom(fl_display, "WM_SAVE_YOURSELF",	0);
135    wm_colormap_windows	= XInternAtom(fl_display, "WM_COLORMAP_WINDOWS",0);
136    _motif_wm_hints	= XInternAtom(fl_display, "_MOTIF_WM_HINTS",	0);
137    kwm_win_decoration	= XInternAtom(fl_display, "KWM_WIN_DECORATION",	0);
138#if DESKTOPS
139    kwm_win_desktop	= XInternAtom(fl_display, "KWM_WIN_DESKTOP",	0);
140    kwm_win_sticky	= XInternAtom(fl_display, "KWM_WIN_STICKY",	0);
141#endif
142//  wm_client_leader	= XInternAtom(fl_display, "WM_CLIENT_LEADER",	0);
143    _wm_quit_app	= XInternAtom(fl_display, "_WM_QUIT_APP",	0);
144  }
145
146#ifdef TOPSIDE
147  label_x = label_h = label_w = 0;
148#else
149  label_y = label_h = label_w = 0;
150#endif
151  getLabel();
152  // getIconLabel();
153
154  {XWindowAttributes attr;
155  if (existing) attr = *existing;
156  else {
157    // put in some legal values in case XGetWindowAttributes fails:
158    attr.x = attr.y = 0; attr.width = attr.height = 100;
159    attr.colormap = fl_colormap;
160    attr.border_width = 0;
161    XGetWindowAttributes(fl_display, window, &attr);
162  }
163  left = top = dwidth = dheight = 0; // pretend border is zero-width for now
164  app_border_width = attr.border_width;
165  x(attr.x+app_border_width); restore_x = x();
166  y(attr.y+app_border_width); restore_y = y();
167  w(attr.width); restore_w = w();
168  h(attr.height); restore_h = h();
169  colormap = attr.colormap;}
170
171  getColormaps();
172
173  //group_ = 0;
174  {XWMHints* hints = XGetWMHints(fl_display, window_);
175  if (hints) {
176    if ((hints->flags & InputHint) && !hints->input) set_flag(NO_FOCUS);
177    //if (hints && hints->flags&WindowGroupHint) group_ = hints->window_group;
178  }
179  switch (getIntProperty(wm_state, wm_state, 0)) {
180  case NormalState:
181    state_ = NORMAL; break;
182  case IconicState:
183    state_ = ICONIC; break;
184  // X also defines obsolete values ZoomState and InactiveState
185  default:
186    if (hints && (hints->flags&StateHint) && hints->initial_state==IconicState)
187      state_ = ICONIC;
188    else
189      state_ = NORMAL;
190  }
191  if (hints) XFree(hints);}
192  // Maya sets this, seems to mean the same as group:
193  // if (!group_) group_ = getIntProperty(wm_client_leader, XA_WINDOW);
194
195  XGetTransientForHint(fl_display, window_, &transient_for_xid);
196
197  getProtocols();
198
199  getMotifHints();
200
201  // get Gnome hints:
202  int p = getIntProperty(_win_hints, XA_CARDINAL);
203  if (p&1) set_flag(NO_FOCUS);		// WIN_HINTS_SKIP_FOCUS
204  // if (p&2)				// WIN_HINTS_SKIP_WINLIST
205  // if (p&4)				// WIN_HINTS_SKIP_TASKBAR
206  // if (p&8) ...			// WIN_HINTS_GROUP_TRANSIENT
207  if (p&16) set_flag(CLICK_TO_FOCUS);	// WIN_HINTS_FOCUS_ON_CLICK
208
209  // get KDE hints:
210  p = getIntProperty(kwm_win_decoration, kwm_win_decoration, 1);
211  if (!(p&3)) set_flag(NO_BORDER);
212  else if (p & 2) set_flag(THIN_BORDER);
213  if (p & 256) set_flag(NO_FOCUS);
214
215  fix_transient_for();
216
217  if (transient_for()) {
218    if (state_ == NORMAL) state_ = transient_for()->state_;
219#if DESKTOPS
220    desktop_ = transient_for()->desktop_;
221#endif
222  }
223#if DESKTOPS
224  // see if anybody thinks window is "sticky:"
225  else if ((getIntProperty(_win_state, XA_CARDINAL) & 1) // WIN_STATE_STICKY
226      || getIntProperty(kwm_win_sticky, kwm_win_sticky)) {
227    desktop_ = 0;
228  } else {
229    // get the desktop from either Gnome or KDE (Gnome takes precedence):
230    p = getIntProperty(_win_workspace, XA_CARDINAL, -1) + 1; // Gnome desktop
231    if (p <= 0) p = getIntProperty(kwm_win_desktop, kwm_win_desktop);
232    if (p > 0 && p < 25)
233      desktop_ = Desktop::number(p, 1);
234    else
235      desktop_ = Desktop::current();
236  }
237  if (desktop_ && desktop_ != Desktop::current())
238    if (state_ == NORMAL) state_ = OTHER_DESKTOP;
239#endif
240
241  int autoplace = getSizes();
242  // some Motif programs assumme this will force the size to conform :-(
243  if (w() < min_w || h() < min_h) {
244    if (w() < min_w) w(min_w);
245    if (h() < min_h) h(min_h);
246    XResizeWindow(fl_display, window_, w(), h());
247  }
248
249  // try to detect programs that think "transient_for" means "no border":
250  if (transient_for_xid && !label() && !flag(NO_BORDER)) {
251    set_flag(THIN_BORDER);
252  }
253  updateBorder();
254  show_hide_buttons();
255
256  if (autoplace && !existing && !(transient_for() && (x() || y()))) {
257    place_window();
258  }
259
260  // move window so contents and border are visible:
261  x(force_x_onscreen(x(), w()));
262  y(force_y_onscreen(y(), h()));
263
264  // guess some values for the "restore" fields, if already maximized:
265  if (max_w_button.value()) {
266    restore_w = min_w + ((w()-dwidth-min_w)/2/inc_w) * inc_w;
267    restore_x = x()+left + (w()-dwidth-restore_w)/2;
268  }
269  if (max_h_button.value()) {
270    restore_h = min_h + ((h()-dheight-min_h)/2/inc_h) * inc_h;
271    restore_y = y()+top + (h()-dheight-restore_h)/2;
272  }
273
274  const int mask = CWBorderPixel | CWColormap | CWEventMask | CWBitGravity
275    | CWBackPixel | CWOverrideRedirect;
276  XSetWindowAttributes sattr;
277  sattr.event_mask = XEventMask;
278  sattr.colormap = fl_colormap;
279  sattr.border_pixel = fl_xpixel(FL_GRAY0);
280  sattr.bit_gravity = NorthWestGravity;
281  sattr.override_redirect = 1;
282  sattr.background_pixel = fl_xpixel(FL_GRAY);
283  Fl_X::set_xid(this, XCreateWindow(fl_display,
284				    RootWindow(fl_display,fl_screen),
285			     x(), y(), w(), h(), 0,
286			     fl_visual->depth,
287			     InputOutput,
288			     fl_visual->visual,
289			     mask, &sattr));
290
291  setStateProperty();
292
293  if (!dont_set_event_mask) XAddToSaveSet(fl_display, window_);
294  if (existing) set_state_flag(IGNORE_UNMAP);
295  XReparentWindow(fl_display, window_, fl_xid(this), left, top);
296  XSetWindowBorderWidth(fl_display, window_, 0);
297  if (state_ == NORMAL) XMapWindow(fl_display, window_);
298  sendConfigureNotify(); // many apps expect this even if window size unchanged
299
300#if CLICK_RAISES || CLICK_TO_TYPE
301  if (!dont_set_event_mask)
302    XGrabButton(fl_display, AnyButton, AnyModifier, window, False,
303		ButtonPressMask, GrabModeSync, GrabModeAsync, None, None);
304#endif
305
306  if (state_ == NORMAL) {
307    XMapWindow(fl_display, fl_xid(this));
308    if (!existing) activate_if_transient();
309  }
310  set_visible();
311}
312
313#if SMART_PLACEMENT
314// Helper functions for "smart" window placement.
315int overlap1(int p1, int l1, int p2, int l2) {
316  int ret = 0;
317  if(p1 <= p2 && p2 <= p1 + l1) {
318    ret = min(p1 + l1 - p2, l2);
319  } else if (p2 <= p1 && p1 <= p2 + l2) {
320    ret = min(p2 + l2 - p1, l1);
321  }
322  return ret;
323}
324
325int overlap(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2) {
326  return (overlap1(x1, w1, x2, w2) * overlap1(y1, h1, y2, h2));
327}
328
329// Compute the overlap with existing windows.
330// For normal windows the overlapping area is taken into account plus a
331// constant value for every overlapping window.
332// The active window counts twice.
333// For iconic windows half the overlapping area is taken into account.
334int getOverlap(int x, int y, int w, int h, Frame *first, Frame *self) {
335  int ret = 0;
336  short state;
337  for (Frame* f = first; f; f = f->next) {
338    if (f != self) {
339      state = f->state();
340      if (state == NORMAL || state == ICONIC) {
341	int o = overlap(x, y, w, h, f->x(), f->y(), f->w(), f->h());
342	if (state == NORMAL) {
343	  ret = ret + o + (o>0?40000:0) + (o * f->active());
344	} else if (state == ICONIC) {
345	  ret = ret + o/2;
346	}
347      }
348    }
349  }
350  return ret;
351}
352
353// autoplacement (brute force version for now)
354void Frame::place_window() {
355  int min_overlap = -1;
356  int tmp_x, tmp_y, tmp_o;
357  int best_x = 0;
358  int best_y = 0;
359  int _w = w();
360  int _h = h();
361  int max_x = Root->x() + Root->w();
362  int max_y = Root->y() + Root->h();
363
364  Frame *f1 = Frame::first;
365  for(int i=0;; i++) {
366    if (i==0) {
367      tmp_x = 0;
368    } else if (i==1) {
369      tmp_x = max_x - _w;
370    } else {
371      if (f1 == this) {
372	f1 = f1->next;
373      }
374      if (!f1) {
375	break;
376      }
377      tmp_x = f1->x() + f1->w();
378      f1 = f1->next;
379    }
380    Frame *f2 = Frame::first;
381    for(int j=0;; j++) {
382      if (j==0) {
383	tmp_y = 0;
384      } else if (j==1) {
385	tmp_y = max_y - _h;
386      } else {
387	if (f2 == this) {
388	  f2 = f2->next;
389	}
390	if (!f2) {
391	  break;
392	}
393	tmp_y = f2->y() + f2->h();
394	f2 = f2->next;
395      }
396
397      if ((tmp_x + _w <= max_x) && (tmp_y + _h <= max_y)) {
398	tmp_o = getOverlap(tmp_x, tmp_y, _w, _h, Frame::first, this);
399	if(tmp_o < min_overlap || min_overlap < 0) {
400	  best_x = tmp_x;
401	  best_y = tmp_y;
402	  min_overlap = tmp_o;
403	  if (min_overlap == 0) {
404	    break;
405	  }
406	}
407      }
408    }
409    if (min_overlap == 0) {
410      break;
411    }
412  }
413  x(best_x);
414  y(best_y);
415}
416
417#else
418
419// autoplacement (stupid version for now)
420void Frame::place_window() {
421  x(Root->x()+(Root->w()-w())/2);
422  y(Root->y()+(Root->h()-h())/2);
423  // move it until it does not hide any existing windows:
424  const int delta = TITLE_WIDTH+LEFT;
425    for (Frame* f = next; f; f = f->next) {
426      if (f->x()+delta > x() && f->y()+delta > y() &&
427	  f->x()+f->w()-delta < x()+w() && f->y()+f->h()-delta < y()+h()) {
428	x(max(x(),f->x()+delta));
429	y(max(y(),f->y()+delta));
430	f = this;
431      }
432    }
433}
434#endif
435
436// modify the passed X & W to a legal horizontal window position
437int Frame::force_x_onscreen(int X, int W) {
438  // force all except the black border on-screen:
439  X = min(X, Root->x()+Root->w()+1-W);
440  X = max(X, Root->x()-1);
441  // force the contents on-screen:
442  X = min(X, Root->x()+Root->w()-W+dwidth-left);
443  if (W-dwidth > Root->w() || h()-dheight > Root->h())
444    // windows bigger than the screen need title bar so they can move
445    X = max(X, Root->x()-LEFT);
446  else
447    X = max(X, Root->x()-left);
448  return X;
449}
450
451// modify the passed Y & H to a legal vertical window position:
452int Frame::force_y_onscreen(int Y, int H) {
453  // force border (except black edge) to be on-screen:
454  Y = min(Y, Root->y()+Root->h()+1-H);
455  Y = max(Y, Root->y()-1);
456  // force contents to be on-screen:
457  Y = min(Y, Root->y()+Root->h()-H+dheight-top);
458  Y = max(Y, Root->y()-top);
459  return Y;
460}
461
462////////////////////////////////////////////////////////////////
463// destructor
464// The destructor is called on DestroyNotify, so I don't have to do anything
465// to the contained window, which is already been destroyed.
466
467// fltk bug: it does not clear these pointers when window is deleted,
468// causing flwm to crash on window close sometimes:
469extern Fl_Window *fl_xfocus;
470extern Fl_Window *fl_xmousewin;
471
472Frame::~Frame() {
473
474  // It is possible for the frame to be destroyed while the menu is
475  // popped-up, and the menu will still contain a pointer to it.  To
476  // fix this the menu checks the state_ location for a legal and
477  // non-withdrawn state value before doing anything.  This should
478  // be reliable unless something reallocates the memory and writes
479  // a legal state value to this location:
480  state_ = UNMAPPED;
481
482#if FL_MAJOR_VERSION < 2
483  // fix fltk bug:
484  fl_xfocus = 0;
485  fl_xmousewin = 0;
486  Fl::focus_ = 0;
487#endif
488
489  // remove any pointers to this:
490  Frame** cp; for (cp = &first; *cp; cp = &((*cp)->next))
491    if (*cp == this) {*cp = next; break;}
492  for (Frame* f = first; f; f = f->next) {
493    if (f->transient_for_ == this) f->transient_for_ = transient_for_;
494    if (f->revert_to == this) f->revert_to = revert_to;
495  }
496  throw_focus(1);
497
498  if (colormapWinCount) {
499    XFree((char *)colormapWindows);
500    delete[] window_Colormaps;
501  }
502  //if (iconlabel()) XFree((char*)iconlabel());
503  if (label())     XFree((char*)label());
504}
505
506////////////////////////////////////////////////////////////////
507
508void Frame::getLabel(int del) {
509  char* old = (char*)label();
510  char* nu = del ? 0 : (char*)getProperty(XA_WM_NAME);
511  if (nu) {
512    // since many window managers print a default label when none is
513    // given, many programs send spaces to make a blank label.  Detect
514    // this and make it really be blank:
515    char* c = nu; while (*c == ' ') c++;
516    if (!*c) {XFree(nu); nu = 0;}
517  }
518  if (old) {
519    if (nu && !strcmp(old,nu)) {XFree(nu); return;}
520    XFree(old);
521  } else {
522    if (!nu) return;
523  }
524#ifdef TOPSIDE
525  Fl_Widget::label(nu);
526  if (nu) {
527    fl_font(TITLE_FONT_SLOT, TITLE_FONT_SIZE);
528    label_h = int(fl_size())+6;
529  } else
530    label_h = 0;
531  if (shown())// && label_w > 3 && top > 3)
532    XClearArea(fl_display, fl_xid(this), label_x, BUTTON_TOP, label_w, label_h, 1);
533#else
534  Fl_Widget::label(nu);
535  if (nu) {
536    fl_font(TITLE_FONT_SLOT, TITLE_FONT_SIZE);
537    label_w = int(fl_width(nu))+6;
538  } else
539    label_w = 0;
540  if (shown() && label_h > 3 && left > 3)
541    XClearArea(fl_display, fl_xid(this), 1, label_y+3, left-1, label_h-3, 1);
542#endif
543}
544
545////////////////////////////////////////////////////////////////
546
547int Frame::getGnomeState(int &) {
548// values for _WIN_STATE property are from Gnome WM compliance docs:
549#define WIN_STATE_STICKY          (1<<0) /*everyone knows sticky*/
550#define WIN_STATE_MINIMIZED       (1<<1) /*Reserved - definition is unclear*/
551#define WIN_STATE_MAXIMIZED_VERT  (1<<2) /*window in maximized V state*/
552#define WIN_STATE_MAXIMIZED_HORIZ (1<<3) /*window in maximized H state*/
553#define WIN_STATE_HIDDEN          (1<<4) /*not on taskbar but window visible*/
554#define WIN_STATE_SHADED          (1<<5) /*shaded (MacOS / Afterstep style)*/
555#define WIN_STATE_HID_WORKSPACE   (1<<6) /*not on current desktop*/
556#define WIN_STATE_HID_TRANSIENT   (1<<7) /*owner of transient is hidden*/
557#define WIN_STATE_FIXED_POSITION  (1<<8) /*window is fixed in position even*/
558#define WIN_STATE_ARRANGE_IGNORE  (1<<9) /*ignore for auto arranging*/
559  // nyi
560  return 0;
561}
562
563////////////////////////////////////////////////////////////////
564
565// Read the sizeHints, and try to remove the vast number of mistakes
566// that some applications seem to do writing them.
567// Returns true if autoplace should be done.
568
569int Frame::getSizes() {
570
571  XSizeHints sizeHints;
572  long junk;
573  if (!XGetWMNormalHints(fl_display, window_, &sizeHints, &junk))
574    sizeHints.flags = 0;
575
576  // get the increment, use 1 if none or illegal values:
577  if (sizeHints.flags & PResizeInc) {
578    inc_w = sizeHints.width_inc; if (inc_w < 1) inc_w = 1;
579    inc_h = sizeHints.height_inc; if (inc_h < 1) inc_h = 1;
580  } else {
581    inc_w = inc_h = 1;
582  }
583
584  // get the current size of the window:
585  int W = w()-dwidth;
586  int H = h()-dheight;
587  // I try a lot of places to get a good minimum size value.  Lots of
588  // programs set illegal or junk values, so getting this correct is
589  // difficult:
590  min_w = W;
591  min_h = H;
592
593  // guess a value for minimum size in case it is not set anywhere:
594  min_w = min(min_w, 4*BUTTON_H);
595  min_w = ((min_w+inc_w-1)/inc_w) * inc_w;
596  min_h = min(min_h, 4*BUTTON_H);
597  min_h = ((min_h+inc_h-1)/inc_h) * inc_h;
598  // some programs put the minimum size here:
599  if (sizeHints.flags & PBaseSize) {
600    junk = sizeHints.base_width; if (junk > 0) min_w = junk;
601    junk = sizeHints.base_height; if (junk > 0) min_h = junk;
602  }
603  // finally, try the actual place the minimum size should be:
604  if (sizeHints.flags & PMinSize) {
605    junk = sizeHints.min_width; if (junk > 0) min_w = junk;
606    junk = sizeHints.min_height; if (junk > 0) min_h = junk;
607  }
608
609  max_w = max_h = 0; // default maximum size is "infinity"
610  if (sizeHints.flags & PMaxSize) {
611    // Though not defined by ICCCM standard, I interpret any maximum
612    // size that is less than the minimum to mean "infinity".  This
613    // allows the maximum to be set in one direction only:
614    junk = sizeHints.max_width;
615    if (junk >= min_w && junk <= W) max_w = junk;
616    junk = sizeHints.max_height;
617    if (junk >= min_h && junk <= H) max_h = junk;
618  }
619
620  // set the maximize buttons according to current size:
621  max_w_button.value(W == maximize_width());
622  max_h_button.value(H == maximize_height());
623
624  // Currently only 1x1 aspect works:
625  if (sizeHints.flags & PAspect
626      && sizeHints.min_aspect.x == sizeHints.min_aspect.y)
627    set_flag(KEEP_ASPECT);
628
629  // another fix for gimp, which sets PPosition to 0,0:
630  if (x() <= 0 && y() <= 0) sizeHints.flags &= ~PPosition;
631
632  return !(sizeHints.flags & (USPosition|PPosition));
633}
634
635int max_w_switch;
636// return width of contents when maximize button pressed:
637int Frame::maximize_width() {
638  int W = max_w_switch; if (!W) W = Root->w();
639#ifdef TOPSIDE
640  return ((W-min_w)/inc_w) * inc_w + min_w;
641#else
642  return ((W-TITLE_WIDTH-min_w)/inc_w) * inc_w + min_w;
643#endif
644}
645
646int max_h_switch;
647int Frame::maximize_height() {
648  int H = max_h_switch; if (!H) H = Root->h();
649#ifdef TOPSIDE
650  return ((H-TITLE_HEIGHT-min_h)/inc_h) * inc_h + min_h;
651#else
652  return ((H-min_h)/inc_h) * inc_h + min_h;
653#endif
654}
655
656////////////////////////////////////////////////////////////////
657
658void Frame::getProtocols() {
659  int n; Atom* p = (Atom*)getProperty(wm_protocols, XA_ATOM, &n);
660  if (p) {
661    clear_flag(DELETE_WINDOW_PROTOCOL|TAKE_FOCUS_PROTOCOL|QUIT_PROTOCOL);
662    for (int i = 0; i < n; ++i) {
663      if (p[i] == wm_delete_window) {
664	set_flag(DELETE_WINDOW_PROTOCOL);
665      } else if (p[i] == wm_take_focus) {
666	set_flag(TAKE_FOCUS_PROTOCOL);
667      } else if (p[i] == wm_save_yourself) {
668	set_flag(SAVE_PROTOCOL);
669      } else if (p[i] == _wm_quit_app) {
670	set_flag(QUIT_PROTOCOL);
671      }
672    }
673  }
674  XFree((char*)p);
675}
676
677////////////////////////////////////////////////////////////////
678
679int Frame::getMotifHints() {
680  long* prop = (long*)getProperty(_motif_wm_hints, _motif_wm_hints);
681  if (!prop) return 0;
682
683  // see /usr/include/X11/Xm/MwmUtil.h for meaning of these bits...
684  // prop[0] = flags (what props are specified)
685  // prop[1] = functions (all, resize, move, minimize, maximize, close, quit)
686  // prop[2] = decorations (all, border, resize, title, menu, minimize,
687  //                        maximize)
688  // prop[3] = input_mode (modeless, primary application modal, system modal,
689  //                       full application modal)
690  // prop[4] = status (tear-off window)
691
692  // Fill in the default value for missing fields:
693  if (!(prop[0]&1)) prop[1] = 1;
694  if (!(prop[0]&2)) prop[2] = 1;
695
696  // The low bit means "turn the marked items off", invert this.
697  // Transient windows already have size & iconize buttons turned off:
698  if (prop[1]&1) prop[1] = ~prop[1] & (transient_for_xid ? ~0x58 : -1);
699  if (prop[2]&1) prop[2] = ~prop[2] & (transient_for_xid ? ~0x60 : -1);
700
701  int old_flags = flags();
702
703  // see if they are trying to turn off border:
704  if (!(prop[2])) set_flag(NO_BORDER); else clear_flag(NO_BORDER);
705
706  // see if they are trying to turn off title & close box:
707  if (!(prop[2]&0x18)) set_flag(THIN_BORDER); else clear_flag(THIN_BORDER);
708
709  // some Motif programs use this to disable resize :-(
710  // and some programs change this after the window is shown (*&%$#%)
711  if (!(prop[1]&2) || !(prop[2]&4))
712    set_flag(NO_RESIZE); else clear_flag(NO_RESIZE);
713
714  // and some use this to disable the Close function.  The commented
715  // out test is it trying to turn off the mwm menu button: it appears
716  // programs that do that still expect Alt+F4 to close them, so I
717  // leave the close on then:
718  if (!(prop[1]&0x20) /*|| !(prop[2]&0x10)*/)
719    set_flag(NO_CLOSE); else clear_flag(NO_CLOSE);
720
721  // see if they set "input hint" to non-zero:
722  // prop[3] should be nonzero but the only example of this I have
723  // found is Netscape 3.0 and it sets it to zero...
724  if (!shown() && (prop[0]&4) /*&& prop[3]*/) set_flag(::MODAL);
725
726  // see if it is forcing the iconize button back on.  This makes
727  // transient_for act like group instead...
728  if ((prop[1]&0x8) || (prop[2]&0x20)) set_flag(ICONIZE);
729
730  // Silly 'ol Amazon paint ignores WM_DELETE_WINDOW and expects to
731  // get the SGI-specific "_WM_QUIT_APP".  It indicates this by trying
732  // to turn off the close box. SIGH!!!
733  if (flag(QUIT_PROTOCOL) && !(prop[1]&0x20))
734    clear_flag(DELETE_WINDOW_PROTOCOL);
735
736  XFree((char*)prop);
737  return (flags() ^ old_flags);
738}
739
740////////////////////////////////////////////////////////////////
741
742void Frame::getColormaps(void) {
743  if (colormapWinCount) {
744    XFree((char *)colormapWindows);
745    delete[] window_Colormaps;
746  }
747  int n;
748  XWindow* cw = (XWindow*)getProperty(wm_colormap_windows, XA_WINDOW, &n);
749  if (cw) {
750    colormapWinCount = n;
751    colormapWindows = cw;
752    window_Colormaps = new Colormap[n];
753    for (int i = 0; i < n; ++i) {
754      if (cw[i] == window_) {
755	window_Colormaps[i] = colormap;
756      } else {
757	XWindowAttributes attr;
758	XSelectInput(fl_display, cw[i], ColormapChangeMask);
759	XGetWindowAttributes(fl_display, cw[i], &attr);
760	window_Colormaps[i] = attr.colormap;
761      }
762    }
763  } else {
764    colormapWinCount = 0;
765  }
766}
767
768void Frame::installColormap() const {
769  for (int i = colormapWinCount; i--;)
770    if (colormapWindows[i] != window_ && window_Colormaps[i])
771      XInstallColormap(fl_display, window_Colormaps[i]);
772  if (colormap)
773    XInstallColormap(fl_display, colormap);
774}
775
776////////////////////////////////////////////////////////////////
777
778// figure out transient_for(), based on the windows that exist, the
779// transient_for and group attributes, etc:
780void Frame::fix_transient_for() {
781  Frame* p = 0;
782  if (transient_for_xid && !flag(ICONIZE)) {
783    for (Frame* f = first; f; f = f->next) {
784      if (f != this && f->window_ == transient_for_xid) {p = f; break;}
785    }
786    // loops are illegal:
787    for (Frame* q = p; q; q = q->transient_for_) if (q == this) {p = 0; break;}
788  }
789  transient_for_ = p;
790}
791
792int Frame::is_transient_for(const Frame* f) const {
793  if (f)
794    for (Frame* p = transient_for(); p; p = p->transient_for())
795      if (p == f) return 1;
796  return 0;
797}
798
799// When a program maps or raises a window, this is called.  It guesses
800// if this window is in fact a modal window for the currently active
801// window and if so transfers the active state to this:
802// This also activates new main windows automatically
803int Frame::activate_if_transient() {
804  if (!Fl::pushed())
805    if (!transient_for() || is_transient_for(active_)) return activate(1);
806  return 0;
807}
808
809////////////////////////////////////////////////////////////////
810
811int Frame::activate(int warp) {
812  // see if a modal & newer window is up:
813  for (Frame* c = first; c && c != this; c = c->next)
814    if (c->flag(::MODAL) && c->transient_for() == this)
815      if (c->activate(warp)) return 1;
816  // ignore invisible windows:
817  if (state() != NORMAL || w() <= dwidth) return 0;
818  // always put in the colormap:
819  installColormap();
820  // move the pointer if desired:
821  // (note that moving the pointer is pretty much required for point-to-type
822  // unless you know the pointer is already in the window):
823  if (!warp || Fl::event_state() & (FL_BUTTON1|FL_BUTTON2|FL_BUTTON3)) {
824    ;
825  } else if (warp==2) {
826    // warp to point at title:
827#ifdef TOPSIDE
828    XWarpPointer(fl_display, None, fl_xid(this), 0,0,0,0, left/2+1,
829                 min(label_x+label_w/2+1, label_h/2));
830#else
831    XWarpPointer(fl_display, None, fl_xid(this), 0,0,0,0, left/2+1,
832                 min(label_y+label_w/2+1,h()/2));
833#endif
834  } else {
835    warp_pointer();
836  }
837  // skip windows that don't want focus:
838  if (flag(NO_FOCUS)) return 0;
839  // set this even if we think it already has it, this seems to fix
840  // bugs with Motif popups:
841  XSetInputFocus(fl_display, window_, RevertToPointerRoot, fl_event_time);
842  if (active_ != this) {
843    if (active_) active_->deactivate();
844    active_ = this;
845//#ifdef ACTIVE_COLOR
846//    XSetWindowAttributes a;
847//    a.background_pixel = fl_xpixel(FL_SELECTION_COLOR);
848//    XChangeWindowAttributes(fl_display, fl_xid(this), CWBackPixel, &a);
849//    labelcolor(fl_contrast(FL_FOREGROUND_COLOR, FL_BACKGROUND_COLOR)); // ML changed color comparison
850//    XClearArea(fl_display, fl_xid(this), 2, 2, w()-4, h()-4, 1);
851//#else
852    XSetWindowAttributes a;
853    a.background_pixel = fl_xpixel(FL_BACKGROUND2_COLOR);
854    XChangeWindowAttributes(fl_display, fl_xid(this), CWBackPixel, &a);
855    labelcolor(fl_contrast(FL_FOREGROUND_COLOR, FL_BACKGROUND2_COLOR));  // ML changed color comparison
856    XClearArea(fl_display, fl_xid(this), 2, 2, w()-4, h()-4, 1);
857#ifdef SHOW_CLOCK
858    redraw();
859#endif
860//#endif
861    if (flag(TAKE_FOCUS_PROTOCOL))
862      sendMessage(wm_protocols, wm_take_focus);
863  }
864  return 1;
865}
866
867// this private function should only be called by constructor and if
868// the window is active():
869void Frame::deactivate() {
870#ifdef ACTIVE_COLOR
871    XSetWindowAttributes a;
872    a.background_pixel = fl_xpixel(FL_GRAY);
873    XChangeWindowAttributes(fl_display, fl_xid(this), CWBackPixel, &a);
874    labelcolor(FL_FOREGROUND_COLOR);
875    XClearArea(fl_display, fl_xid(this), 2, 2, w()-4, h()-4, 1);
876#else
877#ifdef SHOW_CLOCK
878    redraw();
879#endif
880#endif
881}
882
883#if CLICK_RAISES || CLICK_TO_TYPE
884// After the XGrabButton, the main loop will get the mouse clicks, and
885// it will call here when it gets them:
886void click_raise(Frame* f) {
887  f->activate();
888#if CLICK_RAISES
889  if (fl_xevent->xbutton.button <= 1) f->raise();
890#endif
891  XAllowEvents(fl_display, ReplayPointer, CurrentTime);
892}
893#endif
894
895// get rid of the focus by giving it to somebody, if possible:
896void Frame::throw_focus(int destructor) {
897  if (!active()) return;
898  if (!destructor) deactivate();
899  active_ = 0;
900  if (revert_to && revert_to->activate()) return;
901  for (Frame* f = first; f; f = f->next)
902    if (f != this && f->activate()) return;
903}
904
905////////////////////////////////////////////////////////////////
906
907// change the state of the window (this is a private function and
908// it ignores the transient-for or desktop information):
909
910void Frame::state(short newstate) {
911  short oldstate = state();
912  if (newstate == oldstate) return;
913  state_ = newstate;
914  switch (newstate) {
915  case UNMAPPED:
916    throw_focus();
917    XUnmapWindow(fl_display, fl_xid(this));
918    //set_state_flag(IGNORE_UNMAP);
919    //XUnmapWindow(fl_display, window_);
920    XRemoveFromSaveSet(fl_display, window_);
921    break;
922  case NORMAL:
923    if (oldstate == UNMAPPED) XAddToSaveSet(fl_display, window_);
924    if (w() > dwidth) XMapWindow(fl_display, window_);
925    XMapWindow(fl_display, fl_xid(this));
926    clear_state_flag(IGNORE_UNMAP);
927    break;
928  default:
929    if (oldstate == UNMAPPED) {
930      XAddToSaveSet(fl_display, window_);
931    } else if (oldstate == NORMAL) {
932      throw_focus();
933      XUnmapWindow(fl_display, fl_xid(this));
934      //set_state_flag(IGNORE_UNMAP);
935      //XUnmapWindow(fl_display, window_);
936    } else {
937      return; // don't setStateProperty IconicState multiple times
938    }
939    break;
940  }
941  setStateProperty();
942}
943
944void Frame::setStateProperty() const {
945  long data[2];
946  switch (state()) {
947  case UNMAPPED :
948    data[0] = WithdrawnState; break;
949  case NORMAL :
950  case OTHER_DESKTOP :
951    data[0] = NormalState; break;
952  default :
953    data[0] = IconicState; break;
954  }
955  data[1] = (long)None;
956  XChangeProperty(fl_display, window_, wm_state, wm_state,
957		  32, PropModeReplace, (unsigned char *)data, 2);
958}
959
960////////////////////////////////////////////////////////////////
961// Public state modifiers that move all transient_for(this) children
962// with the frame and do the desktops right:
963
964void Frame::raise() {
965  Frame* newtop = 0;
966  Frame* previous = 0;
967  int previous_state = state_;
968  Frame** p;
969  // Find all the transient-for windows and this one, and raise them,
970  // preserving stacking order:
971  for (p = &first; *p;) {
972    Frame* f = *p;
973    if (f == this || f->is_transient_for(this) && f->state() != UNMAPPED) {
974      *p = f->next; // remove it from list
975      if (previous) {
976	XWindowChanges w;
977	w.sibling = fl_xid(previous);
978	w.stack_mode = Below;
979	XConfigureWindow(fl_display, fl_xid(f), CWSibling|CWStackMode, &w);
980	previous->next = f;
981      } else {
982	XRaiseWindow(fl_display, fl_xid(f));
983	newtop = f;
984      }
985#if DESKTOPS
986      if (f->desktop_ && f->desktop_ != Desktop::current())
987       f->state(OTHER_DESKTOP);
988      else
989#endif
990	f->state(NORMAL);
991      previous = f;
992    } else {
993      p = &((*p)->next);
994    }
995  }
996  previous->next = first;
997  first = newtop;
998#if DESKTOPS
999  if (!transient_for() && desktop_ && desktop_ != Desktop::current()) {
1000    // for main windows we also must move to the current desktop
1001    desktop(Desktop::current());
1002  }
1003#endif
1004  if (previous_state != NORMAL && newtop->state_==NORMAL)
1005    newtop->activate_if_transient();
1006}
1007
1008void Frame::lower() {
1009  Frame* t = transient_for(); if (t) t->lower();
1010  if (!next || next == t) return; // already on bottom
1011  // pull it out of the list:
1012  Frame** p = &first;
1013  for (; *p != this; p = &((*p)->next)) {}
1014  *p = next;
1015  // find end of list:
1016  Frame* f = next; while (f->next != t) f = f->next;
1017  // insert it after that:
1018  f->next = this; next = t;
1019  // and move the X window:
1020  XWindowChanges w;
1021  w.sibling = fl_xid(f);
1022  w.stack_mode = Below;
1023  XConfigureWindow(fl_display, fl_xid(this), CWSibling|CWStackMode, &w);
1024}
1025
1026void Frame::iconize() {
1027  for (Frame* c = first; c; c = c->next) {
1028    if (c == this || c->is_transient_for(this) && c->state() != UNMAPPED)
1029      c->state(ICONIC);
1030  }
1031}
1032
1033#if DESKTOPS
1034void Frame::desktop(Desktop* d) {
1035  if (d == desktop_) return;
1036  // Put all the relatives onto the desktop as well:
1037  for (Frame* c = first; c; c = c->next) {
1038    if (c == this || c->is_transient_for(this)) {
1039      c->desktop_ = d;
1040      c->setProperty(_win_state, XA_CARDINAL, !d);
1041      c->setProperty(kwm_win_sticky, kwm_win_sticky, !d);
1042      if (d) {
1043	c->setProperty(kwm_win_desktop, kwm_win_desktop, d->number());
1044	c->setProperty(_win_workspace, XA_CARDINAL, d->number()-1);
1045      }
1046      if (!d || d == Desktop::current()) {
1047	if (c->state() == OTHER_DESKTOP) c->state(NORMAL);
1048      } else {
1049	if (c->state() == NORMAL) c->state(OTHER_DESKTOP);
1050      }
1051    }
1052  }
1053}
1054#endif
1055
1056////////////////////////////////////////////////////////////////
1057
1058// Resize and/or move the window.  The size is given for the frame, not
1059// the contents.  This also sets the buttons on/off as needed:
1060
1061void Frame::set_size(int nx, int ny, int nw, int nh, int warp) {
1062  int dx = nx-x(); x(nx);
1063  int dy = ny-y(); y(ny);
1064  if (!dx && !dy && nw == w() && nh == h()) return;
1065  int unmap = 0;
1066  int remap = 0;
1067  // use XClearArea to cause correct damage events:
1068  if (nw != w()) {
1069    max_w_button.value(nw-dwidth == maximize_width());
1070    min_w_button.value(nw <= dwidth);
1071    if (nw <= dwidth) {
1072      unmap = 1;
1073    } else {
1074      if (w() <= dwidth) remap = 1;
1075    }
1076    int minw = (nw < w()) ? nw : w();
1077    XClearArea(fl_display, fl_xid(this), minw-RIGHT, 0, RIGHT, nh, 1);
1078    w(nw);
1079#ifdef TOPSIDE
1080	show_hide_buttons();
1081#endif
1082  }
1083  if (nh != h()) {
1084    max_h_button.value(nh-dheight == maximize_height());
1085    int minh = (nh < h()) ? nh : h();
1086    XClearArea(fl_display, fl_xid(this), 0, minh-BOTTOM, w(), BOTTOM, 1);
1087    // see if label or close box moved, erase the minimum area:
1088//     int old_label_y = label_y;
1089//     int old_label_h = label_h;
1090    h(nh);
1091#if 1 //def SHOW_CLOCK
1092#ifdef TOPSIDE
1093    //int t = label_x + 3; // we have to clear the entire label area
1094	XClearArea(fl_display,fl_xid(this), label_x, BUTTON_TOP, label_x + label_w,
1095				BUTTON_H, 1);  // ML
1096#else
1097	show_hide_buttons();
1098    //int t = label_y + 3; // we have to clear the entire label area
1099	XClearArea(fl_display,fl_xid(this), 1, label_y, left-1, nh-label_y, 1);  // ML
1100#endif
1101#else
1102    int t = nh;
1103    if (label_y != old_label_y) {
1104      t = label_y; if (old_label_y < t) t = old_label_y;
1105    } else if (label_y+label_h != old_label_y+old_label_h) {
1106      t = label_y+label_h;
1107      if (old_label_y+old_label_h < t) t = old_label_y+old_label_h;
1108    }
1109#endif
1110//ML    if (t < nh && left>LEFT)
1111//ML      XClearArea(fl_display,fl_xid(this), 1, t, left-1, nh-t, 1);
1112  }
1113  // for maximize button move the cursor first if window gets smaller
1114  if (warp == 1 && (dx || dy))
1115    XWarpPointer(fl_display, None,None,0,0,0,0, dx, dy);
1116  // for configure request, move the cursor first
1117  if (warp == 2 && active() && !Fl::pushed()) warp_pointer();
1118  XMoveResizeWindow(fl_display, fl_xid(this), nx, ny, nw, nh);
1119  if (nw <= dwidth) {
1120    if (unmap) {
1121      set_state_flag(IGNORE_UNMAP);
1122      XUnmapWindow(fl_display, window_);
1123    }
1124  } else {
1125    XResizeWindow(fl_display, window_, nw-dwidth, nh-dheight);
1126    if (remap) {
1127      XMapWindow(fl_display, window_);
1128#if CLICK_TO_TYPE
1129      if (active()) activate();
1130#else
1131      activate();
1132#endif
1133    }
1134  }
1135  // for maximize button move the cursor second if window gets bigger:
1136  if (warp == 3 && (dx || dy))
1137    XWarpPointer(fl_display, None,None,0,0,0,0, dx, dy);
1138  if (nw > dwidth) sendConfigureNotify();
1139  XSync(fl_display,0);
1140}
1141
1142void Frame::sendConfigureNotify() const {
1143  XConfigureEvent ce;
1144  ce.type   = ConfigureNotify;
1145  ce.event  = window_;
1146  ce.window = window_;
1147  ce.x = x()+left-app_border_width;
1148  ce.y = y()+top-app_border_width;
1149  ce.width  = w()-dwidth;
1150  ce.height = h()-dheight;
1151  ce.border_width = app_border_width;
1152  ce.above = None;
1153  ce.override_redirect = 0;
1154  XSendEvent(fl_display, window_, False, StructureNotifyMask, (XEvent*)&ce);
1155}
1156
1157// move the pointer inside the window:
1158void Frame::warp_pointer() {
1159  int X,Y; Fl::get_mouse(X,Y);
1160  X -= x();
1161  int Xi = X;
1162  if (X <= 0) X = left/2+1;
1163  if (X >= w()) X = w()-(RIGHT/2+1);
1164  Y -= y();
1165  int Yi = Y;
1166  if (Y < 0) Y = TOP/2+1;
1167  if (Y >= h()) Y = h()-(BOTTOM/2+1);
1168  if (X != Xi || Y != Yi)
1169    XWarpPointer(fl_display, None, fl_xid(this), 0,0,0,0, X, Y);
1170}
1171
1172// Resize the frame to match the current border type:
1173void Frame::updateBorder() {
1174  int nx = x()+left;
1175  int ny = y()+top;
1176  int nw = w()-dwidth;
1177  int nh = h()-dheight;
1178  if (flag(NO_BORDER)) {
1179    left = top = dwidth = dheight = 0;
1180  } else {
1181#ifdef TOPSIDE
1182    left = LEFT;
1183    dwidth = left+RIGHT;
1184    top = flag(THIN_BORDER) ? TOP : TOP+TITLE_HEIGHT;
1185    dheight = top+BOTTOM;
1186#else
1187    left = flag(THIN_BORDER) ? LEFT : LEFT+TITLE_WIDTH;
1188    dwidth = left+RIGHT;
1189    top = TOP;
1190    dheight = TOP+BOTTOM;
1191#endif
1192  }
1193  nx -= left;
1194  ny -= top;
1195  nw += dwidth;
1196  nh += dheight;
1197  if (x()==nx && y()==ny && w()==nw && h()==nh) return;
1198  x(nx); y(ny); w(nw); h(nh);
1199  if (!shown()) return; // this is so constructor can call this
1200  // try to make the contents not move while the border changes around it:
1201  XSetWindowAttributes a;
1202  a.win_gravity = StaticGravity;
1203  XChangeWindowAttributes(fl_display, window_, CWWinGravity, &a);
1204  XMoveResizeWindow(fl_display, fl_xid(this), nx, ny, nw, nh);
1205  a.win_gravity = NorthWestGravity;
1206  XChangeWindowAttributes(fl_display, window_, CWWinGravity, &a);
1207  // fix the window position if the X server didn't do the gravity:
1208  XMoveWindow(fl_display, window_, left, top);
1209}
1210
1211// position and show the buttons according to current border, size,
1212// and other state information:
1213#ifdef TOPSIDE
1214 // FIRST, the TOPSIDE VERSION of the function
1215void Frame::show_hide_buttons() {
1216  if (flag(THIN_BORDER|NO_BORDER)) {
1217    iconize_button.hide();
1218    max_w_button.hide();
1219    min_w_button.hide();
1220    max_h_button.hide();
1221    close_button.hide();
1222    return;
1223  }
1224  int bx = BUTTON_LEFT;
1225
1226// ML CLOSE BUTTON IS TOP LEFT --
1227#if CLOSE_BOX
1228  if (flag(NO_CLOSE)) {
1229#endif
1230    close_button.hide();
1231#if CLOSE_BOX
1232  } else {
1233    close_button.show();
1234    close_button.position(BUTTON_LEFT,BUTTON_TOP);
1235  }
1236#endif
1237//  ML: OTHER BUTTONS ARE PLACED IN UPPER RIGHT
1238  bx = w() - BUTTON_RIGHT - BUTTON_W;
1239
1240  // ML: FIRST PLACE ICONIZE/HIDE BUTTON FARTHEST INTO UPPER RIGHT
1241  if (transient_for()) {
1242	// don't show resize and iconize buttons for "transient" (e.g. dialog box) windows
1243    iconize_button.hide();
1244    max_w_button.hide();
1245    max_h_button.hide();
1246  } else {
1247    iconize_button.show();
1248    iconize_button.position(bx, BUTTON_TOP);
1249	bx -= BUTTON_W;
1250
1251    max_h_button.position(bx, BUTTON_TOP);
1252    max_h_button.show();
1253	bx -= BUTTON_W;
1254    max_w_button.position(bx, BUTTON_TOP);
1255    max_w_button.show();
1256	bx -= BUTTON_W;
1257  }
1258
1259#if MINIMIZE_BOX
1260  if (!transient_for()) {
1261    min_w_button.position(bx, BUTTON_TOP);
1262    min_w_button.show();
1263	bx -= BUTTON_W;
1264  }
1265  else {
1266#else
1267    min_w_button.hide();
1268#endif
1269
1270#if MINIMIZE_BOX
1271  }
1272#endif
1273  if (label_x != bx && shown())
1274//ML Buttons look garbled after expanding, so let's just clear the whole area
1275    XClearArea(fl_display,fl_xid(this), LEFT, TOP, w() - LEFT, TITLE_HEIGHT, 1);
1276  label_x = BUTTON_LEFT + BUTTON_W + left;
1277  label_w = bx - label_x;
1278
1279}
1280
1281#else // have a left-side titlebar version
1282
1283void Frame::show_hide_buttons() {
1284  if (flag(THIN_BORDER|NO_BORDER)) {
1285    iconize_button.hide();
1286    max_w_button.hide();
1287    min_w_button.hide();
1288    max_h_button.hide();
1289    close_button.hide();
1290    return;
1291  }
1292  int by = BUTTON_TOP;
1293//ML MOVED THESE BUTTONS TO BOTTOM --
1294//  if (transient_for()) {
1295//    iconize_button.hide();
1296//    min_w_button.hide();
1297//  } else {
1298//    iconize_button.position(BUTTON_LEFT,by);
1299//    iconize_button.show();
1300//    by += BUTTON_H;
1301//#if MINIMIZE_BOX
1302//    min_w_button.position(BUTTON_LEFT,by);
1303//    min_w_button.show();
1304//    by += BUTTON_H;
1305//#else
1306//    min_w_button.hide();
1307//#endif
1308//  }
1309// -- END ML
1310
1311// ML CLOSE BUTTON IS NOW TOP LEFT --
1312#if CLOSE_BOX
1313  if (flag(NO_CLOSE)) {
1314#endif
1315    close_button.hide();
1316#if CLOSE_BOX
1317  } else {
1318    close_button.show();
1319    close_button.position(BUTTON_LEFT,by);
1320    by += BUTTON_H;
1321  }
1322#endif
1323//  ML: And minimize button is next, if it is enabled (off in current config.h)
1324#if MINIMIZE_BOX
1325  if (!transient_for()) {
1326    min_w_button.position(BUTTON_LEFT,by);
1327    min_w_button.show();
1328    by += BUTTON_H;
1329  }
1330  else {
1331#else
1332    min_w_button.hide();
1333#endif
1334#if MINIMIZE_BOX
1335  }
1336#endif
1337// -- END ML
1338  if (min_h == max_h || flag(KEEP_ASPECT|NO_RESIZE) ||
1339      !max_h_button.value() && by+label_w+2*BUTTON_H > h()-BUTTON_BOTTOM) {
1340    max_h_button.hide();
1341  } else {
1342    max_h_button.position(BUTTON_LEFT,by);
1343    max_h_button.show();
1344    by += BUTTON_H;
1345  }
1346  if (min_w == max_w || flag(KEEP_ASPECT|NO_RESIZE) ||
1347      !max_w_button.value() && by+label_w+2*BUTTON_H > h()-BUTTON_BOTTOM) {
1348    max_w_button.hide();
1349  } else {
1350    max_w_button.position(BUTTON_LEFT,by);
1351    max_w_button.show();
1352    by += BUTTON_H;
1353  }
1354  if (label_y != by && shown())
1355//ML    XClearArea(fl_display,fl_xid(this), 1, by, left-1, label_h+label_y-by, 1);
1356//ML Buttons look garbled after expanding, so let's just clear the whole area
1357    XClearArea(fl_display,fl_xid(this), 1, 1, left-1, h()-1, 1);
1358  label_y = by;
1359
1360//ML MOVED CLOSE BUTTON TO TOP --
1361//#if CLOSE_BOX
1362//  if (by+BUTTON_H > h()-BUTTON_BOTTOM || flag(NO_CLOSE)) {
1363//#endif
1364//    label_h = h()-BOTTOM-by;
1365//    close_button.hide();
1366//#if CLOSE_BOX
1367//  } else {
1368//    close_button.show();
1369//    close_button.position(BUTTON_LEFT,h()-(BUTTON_BOTTOM+BUTTON_H));
1370//    label_h = close_button.y()-by;
1371//  }
1372//#endif
1373// -- END ML
1374
1375//ML MOVED ICONIZE BUTTON TO BOTTOM --
1376  if (by+BUTTON_H > h()-BUTTON_BOTTOM || transient_for()) {
1377	label_h = h()-BOTTOM-by;
1378    iconize_button.hide();
1379  } else {
1380    iconize_button.show();
1381    iconize_button.position(BUTTON_LEFT,h()-(BUTTON_BOTTOM+BUTTON_H));
1382	label_h = iconize_button.y()-by;
1383  }
1384// -- END ML
1385}
1386#endif
1387
1388// make sure fltk does not try to set the window size:
1389void Frame::resize(int, int, int, int) {}
1390// For fltk2.0:
1391void Frame::layout() {
1392#if FL_MAJOR_VERSION>1
1393  layout_damage(0); // actually this line is not needed in newest cvs fltk2.0
1394#endif
1395}
1396
1397////////////////////////////////////////////////////////////////
1398
1399void Frame::close() {
1400  if (flag(DELETE_WINDOW_PROTOCOL))
1401    sendMessage(wm_protocols, wm_delete_window);
1402  else if (flag(QUIT_PROTOCOL))
1403    sendMessage(wm_protocols, _wm_quit_app);
1404  else
1405    kill();
1406}
1407
1408void Frame::kill() {
1409  XKillClient(fl_display, window_);
1410}
1411
1412// this is called when window manager exits:
1413void Frame::save_protocol() {
1414  Frame* f;
1415  for (f = first; f; f = f->next) if (f->flag(SAVE_PROTOCOL)) {
1416    f->set_state_flag(SAVE_PROTOCOL_WAIT);
1417    f->sendMessage(wm_protocols, wm_save_yourself);
1418  }
1419  double t = 10.0; // number of seconds to wait before giving up
1420  while (t > 0.0) {
1421    for (f = first; ; f = f->next) {
1422      if (!f) return;
1423      if (f->flag(SAVE_PROTOCOL) && f->state_flags_&SAVE_PROTOCOL_WAIT) break;
1424    }
1425    t = Fl::wait(t);
1426  }
1427}
1428
1429////////////////////////////////////////////////////////////////
1430// Drawing code:
1431#if FL_MAJOR_VERSION>1
1432# include <fltk/Box.h>
1433#endif
1434
1435#ifdef TOPSIDE
1436void Frame::draw() {
1437  if (flag(NO_BORDER)) return;
1438  //ML--------------------- Paint opaque titlebar background
1439	  labelcolor(fl_contrast(FL_FOREGROUND_COLOR, FL_BACKGROUND2_COLOR));
1440	  if (active()) {
1441	 	 fl_rectf(2, 2, w() - 4, h()-4,
1442		   fl_color_average(FL_BACKGROUND2_COLOR, FL_WHITE, 0.6)
1443//        (ACTIVE_COLOR >> 16) & 0xFF, (ACTIVE_COLOR >> 8) & 0xFF, ACTIVE_COLOR & 0xFF
1444			);
1445      }
1446      else {
1447        fl_rectf(2, 2, w() - 4, h()-4,
1448		FL_BACKGROUND2_COLOR
1449		//FL_GRAY
1450					);
1451      }
1452	  // ------------------ML
1453
1454  if (!flag(THIN_BORDER)) Fl_Window::draw();
1455  if (damage() != FL_DAMAGE_CHILD) {
1456
1457#ifdef ACTIVE_COLOR
1458    fl_frame2(active() ? "AAAAJJWW" : "AAAAJJWWNNTT",0,0,w(),h());
1459    if (active()) {
1460      fl_color(FL_GRAY_RAMP+('N'-'A'));
1461      fl_xyline(2, h()-3, w()-3, 2);
1462    }
1463#else
1464    fl_frame("AAAAWWJJTTNN",0,0,w(),h());
1465#endif
1466    if (!flag(THIN_BORDER) && label_h > 3) {
1467      fl_color(labelcolor());
1468      fl_font(TITLE_FONT_SLOT, TITLE_FONT_SIZE);
1469      fl_draw(label(), label_x, BUTTON_TOP, label_w, BUTTON_H,
1470		      Fl_Align(FL_ALIGN_LEFT|FL_ALIGN_CLIP), 0, 0);
1471    }
1472  }
1473}
1474#else
1475void Frame::draw() {
1476  if (flag(NO_BORDER)) return;
1477  //ML--------------------- Paint opaque titlebar background
1478	  labelcolor(fl_contrast(FL_FOREGROUND_COLOR, FL_BACKGROUND2_COLOR));
1479	  if (active()) {
1480	 	 fl_rectf(2, 2, w() - 4, h()-4,
1481		 fl_color_average(FL_BACKGROUND2_COLOR, FL_WHITE, 0.6) ); // ML
1482        //(ACTIVE_COLOR >> 16) & 0xFF, (ACTIVE_COLOR >> 8) & 0xFF, ACTIVE_COLOR & 0xFF);
1483      }
1484      else {
1485        fl_rectf(2, 2, w() - 4, h()-4, FL_BACKGROUND2_COLOR); // ML
1486      }
1487	  // ------------------ML
1488
1489  if (!flag(THIN_BORDER)) Fl_Window::draw();
1490  if (damage() != FL_DAMAGE_CHILD) {
1491#ifdef ACTIVE_COLOR
1492    fl_frame2(active() ? "AAAAJJWW" : "AAAAJJWWNNTT",0,0,w(),h());
1493    if (active()) {
1494      fl_color(FL_GRAY_RAMP+('N'-'A'));
1495      fl_xyline(2, h()-3, w()-3, 2);
1496    }
1497#else
1498# if FL_MAJOR_VERSION>1
1499    static fltk::FrameBox framebox(0,"AAAAJJWWNNTT");
1500    drawstyle(style(),fltk::INVISIBLE); // INVISIBLE = draw edge only
1501    framebox.draw(Rectangle(w(),h()));
1502# else
1503    fl_frame("AAAAWWJJTTNN",0,0,w(),h());
1504# endif
1505#endif
1506    if (!flag(THIN_BORDER) && label_h > 3) {
1507#ifdef SHOW_CLOCK
1508      if (active()) {
1509	  int clkw = int(fl_width(clock_buf));
1510	  if (clock_alarm_on) {
1511	      fl_font(TITLE_FONT_SLOT, TITLE_FONT_SIZE);
1512	      fl_rectf(LEFT-1, label_y + label_h - 3 - clkw, TITLE_WIDTH, clkw,
1513		       (ALARM_BG_COLOR>>16)&0xff,
1514		       (ALARM_BG_COLOR>>8)&0xff,
1515		       ALARM_BG_COLOR&0xff);
1516	      fl_color((ALARM_FG_COLOR>>16)&0xff,
1517		       (ALARM_FG_COLOR>>8)&0xff,
1518		       ALARM_FG_COLOR&0xff);
1519	  } else
1520	      fl_font(MENU_FONT_SLOT, TITLE_FONT_SIZE);
1521	  // This might overlay the label if the label is long enough
1522	  // and the window height is short enough.  For now, we'll
1523	  // assume this is not enough of a problem to be concerned
1524	  // about.
1525	  draw_rotated90(clock_buf, 1, label_y+3, left-1, label_h-6,
1526			 Fl_Align(FL_ALIGN_BOTTOM|FL_ALIGN_CLIP));
1527      } else
1528	  // Only show the clock on the active frame.
1529	  XClearArea(fl_display, fl_xid(this), 1, label_y+3,
1530		     left-1, label_h-3, 0);
1531#endif
1532      fl_color(labelcolor());
1533      fl_font(TITLE_FONT_SLOT, TITLE_FONT_SIZE);
1534      draw_rotated90(label(), 1, label_y+3, left-1, label_h-3,
1535		     Fl_Align(FL_ALIGN_TOP|FL_ALIGN_CLIP));
1536    }
1537  }
1538}
1539#endif
1540
1541#ifdef SHOW_CLOCK
1542void Frame::redraw_clock() {
1543    double clkw = fl_width(clock_buf);
1544    XClearArea(fl_display, fl_xid(this),
1545	       1, label_y+label_h-3-(int)clkw,
1546	       left-1, (int)clkw, 1);
1547}
1548#endif
1549
1550void FrameButton::draw() {
1551#if FL_MAJOR_VERSION>1
1552  const int x = value()?1:0;
1553  const int y = x;
1554  drawstyle(style(),flags()|fltk::OUTPUT);
1555  FL_UP_BOX->draw(Rectangle(w(),h()));
1556#else
1557  const int x = this->x();
1558  const int y = this->y();
1559  Fl_Widget::draw_box(value() ? FL_DOWN_FRAME : FL_UP_FRAME,
1560					  value() ? fl_darker(FL_BACKGROUND2_COLOR)
1561							  : fl_color_average(FL_BACKGROUND2_COLOR, FL_WHITE, 0.6)); // ML
1562//  Fl_Widget::draw_box(value() ? FL_DOWN_FRAME : FL_UP_FRAME, FL_GRAY);
1563#endif
1564  fl_color(parent()->labelcolor());
1565  switch (label()[0]) {
1566  case 'W':
1567#if MINIMIZE_ARROW
1568    fl_line (x+2,y+(h())/2,x+w()-4,y+h()/2);
1569    fl_line (x+2,y+(h())/2,x+2+4,y+h()/2+4);
1570    fl_line (x+2,y+(h())/2,x+2+4,y+h()/2-4);
1571#else
1572    fl_rect(x+(h()-7)/2,y+3,2,h()-6);
1573#endif
1574    return;
1575  case 'w':
1576    fl_rect(x+2,y+(h()-7)/2,w()-4,7);
1577    return;
1578  case 'h':
1579    fl_rect(x+(h()-7)/2,y+2,7,h()-4);
1580    return;
1581  case 'X':
1582#if CLOSE_X
1583    fl_line(x+2,y+3,x+w()-5,y+h()-4);
1584    fl_line(x+3,y+3,x+w()-4,y+h()-4);
1585    fl_line(x+2,y+h()-4,x+w()-5,y+3);
1586    fl_line(x+3,y+h()-4,x+w()-4,y+3);
1587#endif
1588#if CLOSE_HITTITE_LIGHTNING
1589    fl_arc(x+3,y+3,w()-6,h()-6,0,360);
1590    fl_line(x+7,y+3, x+7,y+11);
1591#endif
1592    return;
1593  case 'i':
1594#if ICONIZE_BOX
1595    fl_rect(x+w()/2-1,y+h()/2-1,3,3);
1596#endif
1597    return;
1598  }
1599}
1600
1601////////////////////////////////////////////////////////////////
1602// User interface code:
1603
1604// this is called when user clicks the buttons:
1605void Frame::button_cb(Fl_Button* b) {
1606  switch (b->label()[0]) {
1607  case 'W':	// minimize button
1608    if (b->value()) {
1609      if (!max_w_button.value()) {
1610        restore_x = x()+left;
1611	restore_y = y()+top;
1612#if MINIMIZE_HEIGHT
1613	restore_w=w()-dwidth;
1614	restore_h = h()-dwidth;
1615#endif
1616      }
1617#if MINIMIZE_HEIGHT
1618#ifdef TOPSIDE
1619      set_size(x(), y(), dwidth-1, 350,1);// <-- crude hack for now
1620#else
1621      set_size(x(), y(), dwidth-1,
1622	       min(h(),min(350,label_w+3*BUTTON_H+BUTTON_TOP+BUTTON_BOTTOM)),
1623	       1);
1624#endif // not topside
1625#else
1626      set_size(x(), y(), dwidth-1, h(), 1);
1627#endif
1628    } else {
1629#if MINIMIZE_HEIGHT
1630      set_size(x(), y(), restore_w+dwidth, restore_h+dwidth, 1);
1631#else
1632      set_size(x(), y(), restore_w+dwidth, h(), 1);
1633#endif
1634    }
1635    show_hide_buttons();
1636    break;
1637  case 'w':	// max-width button
1638    if (b->value()) {
1639      if (!min_w_button.value()) {restore_x=x()+left; restore_w=w()-dwidth;}
1640      int W = maximize_width()+dwidth;
1641      int X = force_x_onscreen(x() + (w()-W)/2, W);
1642      set_size(X, y(), W, h(), 3);
1643    } else {
1644      set_size(restore_x-left, y(), restore_w+dwidth, h(), 1);
1645    }
1646    show_hide_buttons();
1647    break;
1648  case 'h':	// max-height button
1649    if (b->value()) {
1650      restore_y = y()+top;
1651      restore_h = h()-dwidth;
1652      int H = maximize_height()+dheight;
1653      int Y = force_y_onscreen(y() + (h()-H)/2, H);
1654      set_size(x(), Y, w(), H, 3);
1655    } else {
1656      set_size(x(), restore_y-top, w(), restore_h+dwidth, 1);
1657    }
1658    break;
1659  case 'X':
1660    close();
1661    break;
1662  default: // iconize button
1663    iconize();
1664    break;
1665  }
1666}
1667
1668// static callback for fltk:
1669void Frame::button_cb_static(Fl_Widget* w, void*) {
1670  ((Frame*)(w->parent()))->button_cb((Fl_Button*)w);
1671}
1672
1673// This method figures out what way the mouse will resize the window.
1674// It is used to set the cursor and to actually control what you grab.
1675// If the window cannot be resized in some direction this should not
1676// return that direction.
1677int Frame::mouse_location() {
1678  int x = Fl::event_x();
1679  int y = Fl::event_y();
1680  int r = 0;
1681  if (flag(NO_RESIZE)) return 0;
1682  if (min_h != max_h) {
1683    if (y < RESIZE_EDGE) r |= FL_ALIGN_TOP;
1684    else if (y >= h()-RESIZE_EDGE) r |= FL_ALIGN_BOTTOM;
1685  }
1686  if (min_w != max_w) {
1687#if RESIZE_LEFT
1688    if (x < RESIZE_EDGE) r |= FL_ALIGN_LEFT;
1689#else
1690    if (x < RESIZE_EDGE && r) r |= FL_ALIGN_LEFT;
1691#endif
1692    else if (x >= w()-RESIZE_EDGE) r |= FL_ALIGN_RIGHT;
1693  }
1694  return r;
1695}
1696
1697// set the cursor correctly for a return value from mouse_location():
1698void Frame::set_cursor(int r) {
1699//  Fl_Cursor c = r ? FL_CURSOR_ARROW : FL_CURSOR_MOVE;
1700  Fl_Cursor c = r ? FL_CURSOR_ARROW : FL_CURSOR_ARROW;
1701  switch (r) {
1702  case FL_ALIGN_TOP:
1703  case FL_ALIGN_BOTTOM:
1704    c = FL_CURSOR_NS;
1705    break;
1706  case FL_ALIGN_LEFT:
1707  case FL_ALIGN_RIGHT:
1708    c = FL_CURSOR_WE;
1709    break;
1710  case FL_ALIGN_LEFT|FL_ALIGN_TOP:
1711  case FL_ALIGN_RIGHT|FL_ALIGN_BOTTOM:
1712    c = FL_CURSOR_NWSE;
1713    break;
1714  case FL_ALIGN_LEFT|FL_ALIGN_BOTTOM:
1715  case FL_ALIGN_RIGHT|FL_ALIGN_TOP:
1716    c = FL_CURSOR_NESW;
1717    break;
1718  }
1719//#if FL_MAJOR_VERSION>1
1720  cursor(c);
1721//#else
1722//  static Frame* previous_frame;
1723//  static Fl_Cursor previous_cursor;
1724//  if (this != previous_frame || c != previous_cursor) {
1725//    previous_frame = this;
1726//    previous_cursor = c;
1727//    cursor(c, CURSOR_FG_SLOT, CURSOR_BG_SLOT);
1728//  }
1729//#endif
1730}
1731
1732#ifdef AUTO_RAISE
1733// timeout callback to cause autoraise:
1734void auto_raise(void*) {
1735  if (Frame::activeFrame() && !Fl::grab() && !Fl::pushed())
1736    Frame::activeFrame()->raise();
1737}
1738#endif
1739
1740extern void ShowMenu();
1741
1742// If cursor is in the contents of a window this is set to that window.
1743// This is only used to force the cursor to an arrow even though X keeps
1744// sending mysterious erroneous move events:
1745static Frame* cursor_inside = 0;
1746
1747// Handle an fltk event.
1748int Frame::handle(int e) {
1749  static int what, dx, dy, ix, iy, iw, ih;
1750  // see if child widget handles event:
1751#if FL_MAJOR_VERSION > 1
1752  if (fltk::Group::handle(e) && e != FL_ENTER && e != FL_MOVE) {
1753    if (e == FL_PUSH) set_cursor(-1);
1754    return 1;
1755  }
1756#else
1757  if (Fl_Group::handle(e) && e != FL_ENTER && e != FL_MOVE) {
1758    if (e == FL_PUSH) set_cursor(-1);
1759    return 1;
1760  }
1761#endif
1762  switch (e) {
1763
1764  case FL_SHOW:
1765  case FL_HIDE:
1766    return 0; // prevent fltk from messing things up
1767
1768  case FL_ENTER:
1769#if !CLICK_TO_TYPE
1770    if (Fl::pushed() || Fl::grab()) return 1;
1771    if (activate()) {
1772#ifdef AUTO_RAISE
1773      Fl::remove_timeout(auto_raise);
1774      Fl::add_timeout(AUTO_RAISE, auto_raise);
1775#endif
1776    }
1777#endif
1778    goto GET_CROSSINGS;
1779
1780  case FL_LEAVE:
1781#if !CLICK_TO_TYPE && !STICKY_FOCUS
1782    if (active()) {
1783      deactivate();
1784      XSetInputFocus(fl_display, PointerRoot, RevertToPointerRoot,
1785		     fl_event_time);
1786      active_ = 0;
1787    }
1788#endif
1789    goto GET_CROSSINGS;
1790
1791  case FL_MOVE:
1792  GET_CROSSINGS:
1793  // ML -- CLEAR ANY SPECIAL CURSOR IF JUST MOVING:
1794        set_cursor(-1);
1795/* ML --COMMENTED OUT SPECIAL CURSOR CODE... MAYBE fl_xevent->type NOT SETUP???
1796    // set cursor_inside to true when the mouse is inside a window
1797    // set it false when mouse is on a frame or outside a window.
1798    // fltk mangles the X enter/leave events, we need the original ones:
1799
1800    switch (fl_xevent->type) {
1801    case LeaveNotify:
1802      if (fl_xevent->xcrossing.detail == NotifyInferior) {
1803	// cursor moved from frame to interior
1804	cursor_inside = this;
1805	break;
1806      } else {
1807	// cursor moved to another window
1808	return 1;
1809      }
1810
1811    case EnterNotify:
1812      // see if cursor skipped over frame and directly to interior:
1813      if (fl_xevent->xcrossing.detail == NotifyVirtual ||
1814	  fl_xevent->xcrossing.detail == NotifyNonlinearVirtual)
1815	cursor_inside = this;
1816      else {
1817	// cursor is now pointing at frame:
1818	cursor_inside = 0;
1819      }
1820    }
1821    if (Fl::belowmouse() != this || cursor_inside == this)
1822      set_cursor(-1);
1823    else
1824      set_cursor(mouse_location());
1825--END ML */
1826    return 1;
1827
1828  case FL_PUSH:
1829    if (Fl::event_button() > 2) {
1830      set_cursor(-1);
1831      ShowMenu();
1832      return 1;
1833    }
1834    ix = x(); iy = y(); iw = w(); ih = h();
1835    if (!max_w_button.value() && !min_w_button.value()) {
1836      restore_x = ix+left; restore_w = iw-dwidth;
1837    }
1838#if MINIMIZE_HEIGHT
1839    if (!min_w_button.value())
1840#endif
1841    if (!max_h_button.value()) {
1842      restore_y = iy+top; restore_h = ih-dwidth;
1843    }
1844    what = mouse_location();
1845    if (Fl::event_button() > 1) what = 0; // middle button does drag
1846    dx = Fl::event_x_root()-ix;
1847    if (what & FL_ALIGN_RIGHT) dx -= iw;
1848    dy = Fl::event_y_root()-iy;
1849    if (what & FL_ALIGN_BOTTOM) dy -= ih;
1850    set_cursor(what);
1851    return 1;
1852  case FL_DRAG:
1853    if (Fl::event_is_click()) return 1; // don't drag yet
1854  case FL_RELEASE:
1855    if (Fl::event_is_click()) {
1856      if (Fl::grab()) return 1;
1857#if CLICK_TO_TYPE
1858      if (activate()) {
1859	if (Fl::event_button() <= 1) raise();
1860	return 1;
1861      }
1862#endif
1863      if (Fl::event_button() > 1) lower(); else raise();
1864    } else if (!what) {
1865      int nx = Fl::event_x_root()-dx;
1866      int W = Root->x()+Root->w();
1867      if (nx+iw > W && nx+iw < W+SCREEN_SNAP) {
1868	int t = W+1-iw;
1869	if (iw >= Root->w() || x() > t || nx+iw >= W+EDGE_SNAP)
1870	  t = W+(dwidth-left)-iw;
1871	if (t >= x() && t < nx) nx = t;
1872      }
1873      int X = Root->x();
1874      if (nx < X && nx > X-SCREEN_SNAP) {
1875	int t = X-1;
1876	if (iw >= Root->w() || x() < t || nx <= X-EDGE_SNAP) t = X-BUTTON_LEFT;
1877	if (t <= x() && t > nx) nx = t;
1878      }
1879      int ny = Fl::event_y_root()-dy;
1880      int H = Root->y()+Root->h();
1881      if (ny+ih > H && ny+ih < H+SCREEN_SNAP) {
1882	int t = H+1-ih;
1883	if (ih >= Root->h() || y() > t || ny+ih >= H+EDGE_SNAP)
1884	  t = H+(dheight-top)-ih;
1885	if (t >= y() && t < ny) ny = t;
1886      }
1887      int Y = Root->y();
1888      if (ny < Y && ny > Y-SCREEN_SNAP) {
1889	int t = Y-1;
1890	if (ih >= H || y() < t || ny <= Y-EDGE_SNAP) t = Y-top;
1891	if (t <= y() && t > ny) ny = t;
1892      }
1893      set_size(nx, ny, iw, ih);
1894    } else {
1895      int nx = ix;
1896      int ny = iy;
1897      int nw = iw;
1898      int nh = ih;
1899      if (what & FL_ALIGN_RIGHT)
1900	nw = Fl::event_x_root()-dx-nx;
1901      else if (what & FL_ALIGN_LEFT)
1902	nw = ix+iw-(Fl::event_x_root()-dx);
1903      else {nx = x(); nw = w();}
1904      if (what & FL_ALIGN_BOTTOM)
1905	nh = Fl::event_y_root()-dy-ny;
1906      else if (what & FL_ALIGN_TOP)
1907	nh = iy+ih-(Fl::event_y_root()-dy);
1908      else {ny = y(); nh = h();}
1909      if (flag(KEEP_ASPECT)) {
1910	if (nw-dwidth > nh-dwidth
1911	    && (what&(FL_ALIGN_LEFT|FL_ALIGN_RIGHT))
1912	    || !(what&(FL_ALIGN_TOP|FL_ALIGN_BOTTOM)))
1913	  nh = nw-dwidth+dheight;
1914	else
1915	  nw = nh-dheight+dwidth;
1916      }
1917      int MINW = min_w+dwidth;
1918      if (nw <= dwidth && dwidth > TITLE_WIDTH) {
1919	nw = dwidth-1;
1920#if MINIMIZE_HEIGHT
1921	restore_h = nh;
1922#endif
1923      } else {
1924	if (inc_w > 1) nw = ((nw-MINW+inc_w/2)/inc_w)*inc_w+MINW;
1925	if (nw < MINW) nw = MINW;
1926	else if (max_w && nw > max_w+dwidth) nw = max_w+dwidth;
1927      }
1928      int MINH = min_h+dheight;
1929      const int MINH_B = BUTTON_H+BUTTON_TOP+BOTTOM;
1930      if (MINH_B > MINH) MINH = MINH_B;
1931      if (inc_h > 1) nh = ((nh-MINH+inc_h/2)/inc_h)*inc_h+MINH;
1932      if (nh < MINH) nh = MINH;
1933      else if (max_h && nh > max_h+dheight) nh = max_h+dheight;
1934      if (what & FL_ALIGN_LEFT) nx = ix+iw-nw;
1935      if (what & FL_ALIGN_TOP) ny = iy+ih-nh;
1936      set_size(nx,ny,nw,nh);
1937    }
1938    return 1;
1939  }
1940  return 0;
1941}
1942
1943// Handle events that fltk did not recognize (mostly ones directed
1944// at the desktop):
1945
1946int Frame::handle(const XEvent* ei) {
1947
1948  switch (ei->type) {
1949
1950  case ConfigureRequest: {
1951    const XConfigureRequestEvent* e = &(ei->xconfigurerequest);
1952    unsigned long mask = e->value_mask;
1953    if (mask & CWBorderWidth) app_border_width = e->border_width;
1954    // Try to detect if the application is really trying to move the
1955    // window, or is simply echoing it's postion, possibly with some
1956    // variation (such as echoing the parent window position), and
1957    // dont' move it in that case:
1958    int X = (mask & CWX && e->x != x()) ? e->x+app_border_width-left : x();
1959    int Y = (mask & CWY && e->y != y()) ? e->y+app_border_width-top : y();
1960    int W = (mask & CWWidth) ? e->width+dwidth : w();
1961    int H = (mask & CWHeight) ? e->height+dheight : h();
1962    // Generally we want to obey any application positioning of the
1963    // window, except when it appears the app is trying to position
1964    // the window "at the edge".
1965    if (!(mask & CWX) || (X >= -2*left && X < 0)) X = force_x_onscreen(X,W);
1966    if (!(mask & CWY) || (Y >= -2*top && Y < 0)) Y = force_y_onscreen(Y,H);
1967    // Fix Rick Sayre's program that resizes it's windows bigger than the
1968    // maximum size:
1969    if (W > max_w+dwidth) max_w = 0;
1970    if (H > max_h+dheight) max_h = 0;
1971    set_size(X, Y, W, H, 2);
1972    if (e->value_mask & CWStackMode && e->detail == Above && state()==NORMAL)
1973      raise();
1974    return 1;}
1975
1976  case MapRequest: {
1977    //const XMapRequestEvent* e = &(ei->xmaprequest);
1978    raise();
1979    return 1;}
1980
1981  case UnmapNotify: {
1982    const XUnmapEvent* e = &(ei->xunmap);
1983    if (e->window == window_ && !e->from_configure) {
1984      if (state_flags_&IGNORE_UNMAP) clear_state_flag(IGNORE_UNMAP);
1985      else state(UNMAPPED);
1986    }
1987    return 1;}
1988
1989  case DestroyNotify: {
1990    //const XDestroyWindowEvent* e = &(ei->xdestroywindow);
1991    delete this;
1992    return 1;}
1993
1994  case ReparentNotify: {
1995    const XReparentEvent* e = &(ei->xreparent);
1996    if (e->parent==fl_xid(this)) return 1; // echo
1997    if (e->parent==fl_xid(Root)) return 1; // app is trying to tear-off again?
1998    delete this; // guess they are trying to paste tear-off thing back?
1999    return 1;}
2000
2001  case ClientMessage: {
2002    const XClientMessageEvent* e = &(ei->xclient);
2003    if (e->message_type == wm_change_state && e->format == 32) {
2004      if (e->data.l[0] == NormalState) raise();
2005      else if (e->data.l[0] == IconicState) iconize();
2006    } else
2007      // we may want to ignore _WIN_LAYER from xmms?
2008      Fl::warning("flwm: unexpected XClientMessageEvent, type 0x%lx, "
2009	      "window 0x%lx\n", e->message_type, e->window);
2010    return 1;}
2011
2012  case ColormapNotify: {
2013    const XColormapEvent* e = &(ei->xcolormap);
2014    if (e->c_new) {  // this field is called "new" in the old C++-unaware Xlib
2015      colormap = e->colormap;
2016      if (active()) installColormap();
2017    }
2018    return 1;}
2019
2020  case PropertyNotify: {
2021    const XPropertyEvent* e = &(ei->xproperty);
2022    Atom a = e->atom;
2023
2024    // case XA_WM_ICON_NAME: (do something similar to name)
2025    if (a == XA_WM_NAME) {
2026      getLabel(e->state == PropertyDelete);
2027
2028    } else if (a == wm_state) {
2029      // it's not clear if I really need to look at this.  Need to make
2030      // sure it is not seeing the state echoed by the application by
2031      // checking for it being different...
2032      switch (getIntProperty(wm_state, wm_state, state())) {
2033      case IconicState:
2034	if (state() == NORMAL || state() == OTHER_DESKTOP) iconize(); break;
2035      case NormalState:
2036	if (state() != NORMAL && state() != OTHER_DESKTOP) raise(); break;
2037      }
2038
2039    } else if (a == wm_colormap_windows) {
2040      getColormaps();
2041      if (active()) installColormap();
2042
2043    } else if (a == _motif_wm_hints) {
2044      // some #%&%$# SGI Motif programs change this after mapping the window!
2045      // :-( :=( :-( :=( :-( :=( :-( :=( :-( :=( :-( :=(
2046      if (getMotifHints()) { // returns true if any flags changed
2047	fix_transient_for();
2048	updateBorder();
2049	show_hide_buttons();
2050      }
2051
2052    } else if (a == wm_protocols) {
2053      getProtocols();
2054      // get Motif hints since they may do something with QUIT:
2055      getMotifHints();
2056
2057    } else if (a == XA_WM_NORMAL_HINTS || a == XA_WM_SIZE_HINTS) {
2058      getSizes();
2059      show_hide_buttons();
2060
2061    } else if (a == XA_WM_TRANSIENT_FOR) {
2062      XGetTransientForHint(fl_display, window_, &transient_for_xid);
2063      fix_transient_for();
2064      show_hide_buttons();
2065
2066    } else if (a == XA_WM_COMMAND) {
2067      clear_state_flag(SAVE_PROTOCOL_WAIT);
2068
2069    }
2070    return 1;}
2071
2072  }
2073  return 0;
2074}
2075
2076////////////////////////////////////////////////////////////////
2077// X utility routines:
2078
2079void* Frame::getProperty(Atom a, Atom type, int* np) const {
2080  return ::getProperty(window_, a, type, np);
2081}
2082
2083void* getProperty(XWindow w, Atom a, Atom type, int* np) {
2084  Atom realType;
2085  int format;
2086  unsigned long n, extra;
2087  int status;
2088  uchar* prop;
2089  status = XGetWindowProperty(fl_display, w,
2090			      a, 0L, 256L, False, type, &realType,
2091			      &format, &n, &extra, &prop);
2092  if (status != Success) return 0;
2093  if (!prop) return 0;
2094  if (!n) {XFree(prop); return 0;}
2095  if (np) *np = (int)n;
2096  return (void*)prop;
2097}
2098
2099int Frame::getIntProperty(Atom a, Atom type, int deflt) const {
2100  return ::getIntProperty(window_, a, type, deflt);
2101}
2102
2103int getIntProperty(XWindow w, Atom a, Atom type, int deflt) {
2104  void* prop = getProperty(w, a, type);
2105  if (!prop) return deflt;
2106  int r = int(*(long*)prop);
2107  XFree(prop);
2108  return r;
2109}
2110
2111void setProperty(XWindow w, Atom a, Atom type, int v) {
2112  long prop = v;
2113  XChangeProperty(fl_display, w, a, type, 32, PropModeReplace, (uchar*)&prop,1);
2114}
2115
2116void Frame::setProperty(Atom a, Atom type, int v) const {
2117  ::setProperty(window_, a, type, v);
2118}
2119
2120void Frame::sendMessage(Atom a, Atom l) const {
2121  XEvent ev;
2122  long mask;
2123  memset(&ev, 0, sizeof(ev));
2124  ev.xclient.type = ClientMessage;
2125  ev.xclient.window = window_;
2126  ev.xclient.message_type = a;
2127  ev.xclient.format = 32;
2128  ev.xclient.data.l[0] = long(l);
2129  ev.xclient.data.l[1] = long(fl_event_time);
2130  mask = 0L;
2131  XSendEvent(fl_display, window_, False, mask, &ev);
2132}
2133