Files
flwm/Frame.C
Bill Spitzak 7dbb304393 Removed fltk bug fix that does not apply to newer fltk versions
It appears the underlying bug has been fixed.
2015-10-03 11:39:00 -07:00

1878 lines
56 KiB
C

// 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);
}