ede/edewm/Frame.cpp

1675 lines
37 KiB
C++
Raw Normal View History

2009-02-20 16:05:43 +03:00
// Frame.cpp
//#define TITLE_H 20
#define TITLE_H title->h()
#include "config.h"
#include "Frame.h"
#include "Windowmanager.h"
#include "Desktop.h"
#include "Winhints.h"
#include "Netwm.h"
#include "Icon.h"
#include "Theme.h"
#include "debug.h"
#include <string.h>
#include <stdio.h>
#include <efltk/fl_draw.h>
#include <efltk/Fl_Tooltip.h>
static const int XEventMask =
ExposureMask
|StructureNotifyMask|VisibilityChangeMask
|KeyPressMask|KeyReleaseMask|KeymapStateMask|FocusChangeMask
|ButtonPressMask|ButtonReleaseMask
|EnterWindowMask|LeaveWindowMask
|PointerMotionMask|SubstructureRedirectMask|SubstructureNotifyMask;
extern bool grab();
extern void grab_cursor(bool grab);
extern Fl_Window* Root;
extern int title_active_color, title_active_color_text;
extern int title_normal_color, title_normal_color_text;
Frame* Frame::active_ = 0;
// Resizes opaque (draws everything), if this set to true
bool Frame::do_opaque = false;
bool Frame::focus_follows_mouse = false;
bool Frame::animate = true;
int Frame::animate_speed = 15;
static int px,py,pw,ph;
static GC invertGc=0;
static void draw_current_rect() { XDrawRectangle(fl_display, RootWindow(fl_display, fl_screen), invertGc, px, py, pw, ph); }
void clear_overlay() { if (pw > 0) { draw_current_rect(); pw = 0; } }
void draw_overlay(int x, int y, int w, int h) {
if(!invertGc) {
XGCValues v;
v.subwindow_mode = IncludeInferiors;
v.foreground = 0xffffffff;
v.function = GXxor;
v.line_width = 2;
v.graphics_exposures = False;
int mask = GCForeground|GCSubwindowMode|GCFunction|GCLineWidth|GCGraphicsExposures;
invertGc = XCreateGC(fl_display, RootWindow(fl_display, fl_screen), mask, &v);
}
if (w < 0) {x += w; w = -w;} else if (!w) w = 1;
if (h < 0) {y += h; h = -h;} else if (!h) h = 1;
if (pw > 0) {
if (x==px && y==py && w==pw && h==ph) return;
draw_current_rect();
}
px = x; py = y; pw = w; ph = h;
draw_current_rect();
}
static void animate(int fx, int fy, int fw, int fh, int tx, int ty, int tw, int th)
{
# undef max
# define max(a,b) (a) > (b) ? (a) : (b)
double max_steps = max( (tw-fw), (th-fh) );
double min_steps = max( (fw-tw), (fh-th) );
double steps = max(max_steps, min_steps);
steps/=Frame::animate_speed;
double sx = max( ((double)(fx-tx)/steps), ((double)(tx-fx)/steps) );
double sy = max( ((double)(fy-ty)/steps), ((double)(ty-fy)/steps) );
double sw = max( ((double)(fw-tw)/steps), ((double)(tw-fw)/steps) );
double sh = max( ((double)(fh-th)/steps), ((double)(th-fh)/steps) );
int xinc = fx < tx ? 1 : -1;
int yinc = fy < ty ? 1 : -1;
int winc = fw < tw ? 1 : -1;
int hinc = fh < th ? 1 : -1;
double rx=fx,ry=fy,rw=fw,rh=fh;
XGrabServer(fl_display);
while(steps-- > 0) {
rx+=(sx*xinc);
ry+=(sy*yinc);
rw+=(sw*winc);
rh+=(sh*hinc);
draw_overlay((int)rx, (int)ry, (int)rw, (int)rh);
Fl::sleep(10);
XFlush(fl_display);
}
clear_overlay();
XUngrabServer(fl_display);
}
// 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(Window window, XWindowAttributes* existing)
: Fl_Window(0,0),
window_(window),
frame_flags_(0), state_flags_(0), decor_flags_(0), func_flags_(0),
transient_for_xid(None),
transient_for_(0),
revert_to(active_),
colormapWinCount(0)
{
wintype = TYPE_NORMAL;
maximized = false;
offset_x = offset_y = offset_w = offset_h = 0;
strut_ = 0;
icon_ = 0;
mwm_hints = 0;
wm_hints = 0;
size_hints = XAllocSizeHints();
begin();
title = new Titlebar(0, 0, 100, Titlebar::default_height, _("Untitled"));
title->align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE);
title->label_color((Fl_Color)title_normal_color_text);
title->color((Fl_Color)title_normal_color);
title->label_font(label_font()->bold());
end();
bool autoplace = ICCCM::size_hints(this);
// do this asap so we don't miss any events...
if(!dont_set_event_mask) {
XSelectInput(fl_display, window_, VisibilityChangeMask | ColormapChangeMask | PropertyChangeMask | FocusChangeMask );
}
XGetTransientForHint(fl_display, window_, &transient_for_xid);
getColormaps();
get_protocols();
MWM::get_hints(this);
MWM::update_hints(this);
get_wm_hints();
update_wm_hints();
NETWM::get_strut(this);
if(NETWM::get_window_type(this)) {
// This call overwrites MWM hints!
get_funcs_from_type();
} else if(transient_for_xid) {// && wintype==TYPE_NORMAL && decor_flag(BORDER)) {
//title->h(15); title->label(0);
if(decor_flag(BORDER) || decor_flag(THIN_BORDER)) {
clear_decor_flag(BORDER);
set_decor_flag(THIN_BORDER);
wintype = TYPE_DIALOG;
get_funcs_from_type();
}
}
getLabel();
{
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);
}
int W=attr.width, H=attr.height;
ICCCM::get_size(this, W, H);
resize(attr.x, attr.y, W, H);
restore_x = attr.x;
restore_y = attr.y;
restore_w = attr.width;
restore_h = attr.height;
colormap = attr.colormap;
}
// Get icon & mask:
icon_ = new Icon(wm_hints);
switch(getIntProperty(_XA_WM_STATE, _XA_WM_STATE, 0))
{
case NormalState:
state_ = NORMAL; break;
case IconicState:
state_ = ICONIC; break;
// X also defines obsolete values ZoomState and InactiveState
default:
if(wm_hints && (wm_hints->flags&StateHint) && wm_hints->initial_state==IconicState)
state_ = ICONIC;
else
state_ = NORMAL;
}
fix_transient_for();
if(transient_for()) {
DBG("WIN %lx is transient for %lx", window_, transient_for()->window());
// Get state from parent
if(state_ == NORMAL)
state_ = transient_for()->state_;
// And put it on same desktop
desktop_ = transient_for()->desktop();
} else {
// get the desktop from either NET or GNOME (NET takes preference):
int p = getDesktop();
if (p > 0 && p <= 8)
desktop_ = Desktop::desktop(p);
DBG("WIN %lx belongs to %d", window_, desktop_?desktop_->number():-2);
}
send_desktop();
// some Motif programs assumme this will force the size to conform :-(
if(w() < size_hints->min_width || h() < size_hints->min_height) {
if(w() < size_hints->min_width) w(size_hints->min_width);
if(h() < size_hints->min_height) h(size_hints->min_height);
XResizeWindow(fl_display, window_, w(), h());
}
updateBorder();
if (autoplace && !existing && !(transient_for() && (x() || y()))) {
// autoplacement (stupid version for now)
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 = 20;//TITLE_WIDTH+LEFT;
for(uint n=0; n<map_order.size(); n++) {
Frame *f = map_order[n];
if(f==this) continue;
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;
}
}
}
// move window so contents and border are visible:
x(force_x_onscreen(x(), w()));
y(force_y_onscreen(y(), h()));
// FLTK thinks its override window, so it doesnt send any X junk to wm
set_override();
// Create Fl_X class and xid
create();
const int mask =
CWBitGravity|
CWBorderPixel|
CWColormap|
CWEventMask|
CWBackPixel|
CWOverrideRedirect;
XSetWindowAttributes sattr;
sattr.bit_gravity = NorthWestGravity;
sattr.event_mask = XEventMask;
sattr.colormap = fl_colormap;
sattr.border_pixel = fl_xpixel(FL_BLACK);
sattr.override_redirect = 0;
sattr.background_pixel = fl_xpixel(FL_GRAY);
XChangeWindowAttributes(fl_display, fl_xid(this), mask, &sattr);
send_state_property();
if(!dont_set_event_mask)
XAddToSaveSet(fl_display, window_);
if(existing)
set_state_flag(IGNORE_UNMAP);
// Reparent window to frames
XReparentWindow(fl_display, window_, fl_xid(this), offset_x, offset_y);
XSetWindowBorderWidth(fl_display, window_, 0);
XGrabButton(fl_display, AnyButton, AnyModifier, window_, False,
ButtonPressMask, GrabModeSync, GrabModeAsync, None, None);
if(state_ == NORMAL) {
XMapWindow(fl_display, window_);
if(decor_flag(TITLEBAR)) title->show();
show();
// many apps expect this even if window size unchanged
send_configurenotify();
if(!existing)
activate_if_transient();
}
#ifdef SHAPE
XShapeSelectInput(fl_display, window_, ShapeNotifyMask);
if(get_shape()) {
clear_decor_flag(BORDER|THIN_BORDER);
XShapeCombineShape(fl_display, fl_xid(this), ShapeBounding,
0, 0, window_,
ShapeBounding, ShapeSet);
}
#endif
map_order.append(this);
stack_order.append(this);
WindowManager::update_client_list();
if(strut()) root->update_workarea(true);
}
extern bool wm_shutdown;
// destructor
// The destructor is called on DestroyNotify, so I don't have to do anything
// to the contained window, which is already been destroyed.
Frame::~Frame()
{
}
void Frame::destroy_frame()
{
if(shown()) {
XUnmapWindow(fl_display, fl_xid(this));
XUnmapWindow(fl_display, window_);
if(title->shown()) XUnmapWindow(fl_display, fl_xid(title));
}
// 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;
// fix fltk bug:
Fl::focus_ = 0;
for(uint n=0; n<map_order.size(); n++) {
Frame *f = map_order[n];
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(mwm_hints) XFree((char *)mwm_hints);
if(wm_hints) XFree((char *)wm_hints);
if(size_hints) XFree((char *)size_hints);
if(icon_) {
delete icon_;
icon_ = 0;
}
// remove any pointers to this:
if(!wm_shutdown) {
stack_order.remove(this);
map_order.remove(this);
WindowManager::update_client_list();
}
if(strut_)
{
delete strut_;
strut_=0;
root->update_workarea(true);
}
if(grab()) grab_cursor(false);
remove_list.append(this);
Fl::awake();
}
bool Frame::get_shape()
{
#ifdef SHAPE
shaped = 0;
int xws, yws, xbs, ybs;
unsigned wws, hws, wbs, hbs;
Bool boundingShaped, clipShaped;
XShapeQueryExtents(fl_display, window_,
&boundingShaped, &xws, &yws, &wws, &hws,
&clipShaped, &xbs, &ybs, &wbs, &hbs);
return (shaped = boundingShaped);
#else
return false;
#endif
}
void Frame::get_funcs_from_type()
{
DBG("get_funcs_from_type() = %d", wintype);
switch(wintype) {
case TYPE_DESKTOP:
case TYPE_DOCK:
case TYPE_SPLASH:
desktop_ = 0;
clear_func_flags();
clear_decor_flags();
if(wintype == TYPE_DOCK)
set_frame_flag(SKIP_LIST|SKIP_FOCUS);
else
set_frame_flag(SKIP_LIST);
break;
case TYPE_TOOLBAR:
case TYPE_MENU:
clear_decor_flags();
set_frame_flag(SKIP_LIST|SKIP_FOCUS|NO_FOCUS);
break;
case TYPE_DIALOG:
case TYPE_UTIL:
title->h(Titlebar::default_height);
clear_decor_flag(BORDER);
set_decor_flag(THIN_BORDER);
break;
case TYPE_NORMAL:
default:
// Dont do anything for NORMAL and UNKNOWN types...
break;
};
}
void Frame::settings_changed_all()
{
for(uint n=0; n<map_order.size(); n++) {
Frame *f = map_order[n];
f->settings_changed();
}
XFlush(fl_display);
}
void Frame::settings_changed()
{
if(title && title->shown())
title->setting_changed();
if(!desktop() || desktop()==Desktop::current()) {
if(title && title->shown()) {
XClearWindow(fl_display, fl_xid(title)); //expose title
redraw(); //Set damage bits
draw(); //FORCE DRAW!
}
}
}
// modify the passed X & W to a legal horizontal window position
// Note (by V.): Previous code preserved the window dimensions so that the
// window middle was centered on screen
// I believe that window top left corner should remain visible, so that
// user can resize window
int Frame::force_x_onscreen(int X, int W)
{
if(X<root->x()) X=root->x();
// if(X+W > root->x()+root->w()) X=root->x()+root->w()-W;
return X;
}
// modify the passed Y & H to a legal vertical window position:
int Frame::force_y_onscreen(int Y, int H)
{
if(Y<root->y()) Y=root->y();
// if(Y+H > root->y()+root->h()) Y=root->y()+root->h()-H;
return Y;
}
int Frame::getDesktop()
{
if(wintype == TYPE_DESKTOP ||
wintype == TYPE_DOCK ||
wintype == TYPE_SPLASH ) {
set_frame_flag(STICKY);
return -2;
}
int ret=0;
unsigned long desk;
desk = getIntProperty(_XA_NET_WM_DESKTOP, XA_CARDINAL, 100000, &ret);
if(ret!=Success || desk==100000) {
clear_frame_flag(STICKY);
return Desktop::current_num();
}
if(desk==0xFFFFFFFF || desk==0xFFFFFFFE) {
desktop_ = 0;
set_frame_flag(STICKY);
DBG("getDesktop() returns 0xFFFFFFFF");
return -2;
}
if(desk>=0) {
clear_frame_flag(STICKY);
desk++;
}
DBG("getDesktop() returns %ld", desk);
return desk;
}
void Frame::getLabel(bool del, Atom from_atom)
{
char *newname=0;
if(!del)
{
if(from_atom==_XA_NET_WM_NAME || from_atom==0)
newname = NETWM::get_title(this);
if((from_atom==XA_WM_NAME||from_atom==0) && !newname)
newname = ICCCM::get_title(this);
} else {
label(0);
}
if(newname) {
// 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 = newname;
while(*c == ' ') c++;
if(!*c) {
XFree(newname);
newname = 0;
}
}
if(newname) {
label(newname);
XFree(newname);
}
title->layout();
title->redraw();
}
void Frame::getColormaps(void)
{
if (colormapWinCount) {
XFree((char *)colormapWindows);
delete[] window_Colormaps;
}
unsigned long n;
Window* cw = (Window*)getProperty(_XA_WM_COLORMAP_WINDOWS, XA_WINDOW, &n);
if (cw) {
colormapWinCount = n;
colormapWindows = cw;
window_Colormaps = new Colormap[n];
for(unsigned 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)
{
p = root->find_by_wid(transient_for_xid);
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!=0; 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
bool Frame::activate_if_transient()
{
if(!transient_for() || is_transient_for(active_)) {
return activate();
}
return false;
}
bool Frame::activate()
{
DBG("Frame::activate()");
// see if a modal & newer window is up:
//for(uint n=map_order.size(); n--;)
for(uint n=map_order.size(); n--;)
{
Frame *c = map_order[n];
if(c->state()!=NORMAL) continue;
if(c->frame_flag(MODAL) && c->transient_for()==this)
if(c->activate())
return true;
}
// ignore invisible windows:
if(state() != NORMAL) return 0;
// always put in the colormap:
installColormap();
// skip windows that don't want focus:
if(frame_flag(NO_FOCUS)) return false;
// 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);
// Change active window
if(active_ != this) {
if(active_) active_->deactivate();
active_ = this;
//Update titlebar
title->color((Fl_Color)title_active_color);
title->label_color((Fl_Color)title_active_color_text);
title->redraw();
XSetInputFocus(fl_display, window_, RevertToPointerRoot, fl_event_time);
NETWM::set_active_window(window_);
//And last thing is focus
if(frame_flag(TAKE_FOCUS_PRT))
sendMessage(_XA_WM_PROTOCOLS, _XA_WM_TAKE_FOCUS);
}
return true;
}
// this private function should only be called by constructor and if
// the window is active():
void Frame::deactivate()
{
title->color((Fl_Color)title_normal_color);
title->label_color((Fl_Color)title_normal_color_text);
title->redraw();
}
// After the XGrabButton, the main loop will get the mouse clicks, and
// it will call here when it gets them:
void Frame::content_click()
{
activate();
if(wintype==TYPE_DOCK || wintype==TYPE_DESKTOP) {
XAllowEvents(fl_display, ReplayPointer, CurrentTime);
return;
}
if(fl_xevent.xbutton.button == 1) raise();
if(fl_xevent.xbutton.button == 1 && Fl::get_key_state(FL_Alt_L)) {
int mx = Fl::event_x_root()-x();
int my = Fl::event_y_root()-y();
int ny,nx;
if(!Frame::do_opaque) {
XGrabServer(fl_display);
}
grab_cursor(true);
bool grabbing=true;
while(grabbing)
{
Fl::wait();
nx = Fl::event_x_root()-mx;
ny = Fl::event_y_root()-my;
handle_move(nx, ny);
if( !Fl::get_key_state(FL_Alt_L) || !Fl::event_state(FL_BUTTON1) )
grabbing=false;
}
if(!Frame::do_opaque) {
XUngrabServer(fl_display);
clear_overlay();
set_size(nx, ny, w(), h());
}
grab_cursor(false);
}
XAllowEvents(fl_display, ReplayPointer, CurrentTime);
}
// get rid of the focus by giving it to somebody, if possible,
// but not to workpanel
void Frame::throw_focus(int destructor)
{
if(!active()) return;
DBG("Frame::throw_focus(%d)", destructor);
if(!destructor) deactivate();
active_ = 0;
bool is_active;
for(uint i = stack_order.size(); --i;)
{
Frame *f = stack_order[i];
if(f != 0 && f != this && f->window_type() != TYPE_DOCK)
{
is_active = f->activate();
if(is_active)
f->raise();
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;
DBG("Frame(%s)::state(%d -> %d)", label().c_str(), oldstate, newstate);
switch (newstate) {
case UNMAPPED:
XRemoveFromSaveSet(fl_display, window_);
case OTHER_DESKTOP:
set_state_flag(IGNORE_UNMAP);
XUnmapWindow(fl_display, fl_xid(this));
XUnmapWindow(fl_display, window_);
title->hide();
break;
case ICONIC:
set_state_flag(ICONIC);
XUnmapWindow(fl_display, fl_xid(this));
XUnmapWindow(fl_display, window_);
title->hide();
break;
case NORMAL: {
int desktop = getDesktop();
if(desktop<0 || (desktop==Desktop::current_num()) ) {
if(oldstate == UNMAPPED) XAddToSaveSet(fl_display, window_);
XMapWindow(fl_display, window_);
if(decor_flag(TITLEBAR)) title->show();
show();
clear_state_flag(IGNORE_UNMAP);
} else {
state_ = OTHER_DESKTOP;
}
break;
}
default:
DBG("state() default:");
if(oldstate == UNMAPPED) {
XAddToSaveSet(fl_display, window_);
} else if(oldstate == NORMAL) {
throw_focus();
set_state_flag(IGNORE_UNMAP);
XUnmapWindow(fl_display, fl_xid(this));
XUnmapWindow(fl_display, window_);
title->hide();
} else { // don't setStateProperty IconicState multiple times
return;
}
break;
}
send_state_property();
}
void Frame::send_state_property()
{
switch(state())
{
case UNMAPPED:
ICCCM::state_withdrawn(this);
break;
case NORMAL:
case OTHER_DESKTOP:
ICCCM::state_normal(this);
break;
default :
ICCCM::state_iconic(this);
break;
}
}
void Frame::send_configurenotify()
{
ICCCM::configure(this);
}
void Frame::send_desktop()
{
// sends desktop where window belongs to server
if(desktop_) {
setProperty(_XA_NET_WM_DESKTOP, XA_CARDINAL, desktop_->number()-1);
if(desktop_!=Desktop::current() && state_==NORMAL)
state_=OTHER_DESKTOP;
} else {
// means sticky...
setProperty(_XA_NET_WM_DESKTOP, XA_CARDINAL, 0xFFFFFFFF);
//hmmm.. GNOME sticky should send also?!?
}
}
void Frame::raise(Window wid)
{
if(wid==None) {
Frame::active_=0;
for(uint n=0; n<map_order.size(); n++) {
Frame *f = map_order[n];
f->deactivate();
}
} else {
for(uint n=0; n<map_order.size(); n++) {
Frame *f = map_order[n];
if(wid==f->window()) {
f->raise();
f->activate_if_transient();
return;
}
}
}
}
// Public state modifiers that move all transient_for(this) children
// with the frame and do the desktops right:
void Frame::raise()
{
DBG("Frame(%s)::raise()", title->label().c_str());
Frame* newtop = 0;
int previous_state = state_;
Frame_List raise_list;
Frame *f;
uint n;
stack_order.remove(this);
raise_list.append(this);
if(desktop() && desktop()!=Desktop::current()) state(OTHER_DESKTOP);
else state(NORMAL);
for(n=0; n<stack_order.size(); n++) {
f = stack_order[n];
if(f==this || f->state()==UNMAPPED) continue;
if(f->is_transient_for(this)) {
stack_order.remove(f);
raise_list.append(f);
} else
continue;
if(f->desktop() && f->desktop()!=Desktop::current())
f->state(OTHER_DESKTOP);
else
f->state(NORMAL);
}
for(n=0; n<raise_list.size(); n++) {
f = raise_list[n];
stack_order.append(f);
newtop = f;
}
if(stack_order.count()!=map_order.count())
Fl::fatal(_("EDEWM: Internal bug, when restacking (%d != %d)! Exiting... "),
stack_order.count(), map_order.count());
if(!transient_for() && desktop_ && desktop_ != Desktop::current()) {
// for main windows we also must move to the current desktop
desktop(Desktop::current());
}
if(previous_state!=NORMAL && newtop->state_==NORMAL)
newtop->activate_if_transient();
DBG("Calling restack_windows()");
root->restack_windows();
DBG("Calling update_task_list()");
}
void Frame::lower()
{
DBG("Frame::lower()");
if(map_order.count()<2) return; // if we have just only one window
Frame* t = transient_for();
if(t) t->lower();
stack_order.remove(this);
stack_order.prepend(this);
root->restack_windows();
}
void Frame::iconize()
{
DBG("Frame::iconize()");
if(window_type()==TYPE_NORMAL || is_transient_for(this) && state() != UNMAPPED)
{
state(ICONIC);
throw_focus(1);
}
}
// maximize window
void Frame::maximize()
{
if(maximized)
return;
bool m = true;
int W=root->w();
int H=root->h();
W-=offset_w;
H-=offset_h;
if(ICCCM::get_size(this, W, H))
m=false;
restore_x = x();
restore_y = y();
restore_w = w();
restore_h = h();
W+=offset_w;
H+=offset_h;
if(Frame::animate)
::animate(x(), y(), w(), h(), root->x(), root->y(), root->w(), root->h());
set_size(root->x(), root->y(), W, H);
maximized = m;
redraw();
}
// restore window size
void Frame::restore()
{
if(!maximized)
return;
if(Frame::animate)
::animate(x(), y(), w(), h(), restore_x, restore_y, restore_w, restore_h);
set_size(restore_x, restore_y, restore_w, restore_h);
maximized = false;
}
void Frame::desktop(Window wid, int desktop)
{
for(uint n=0; n<map_order.size(); n++) {
Frame *f = map_order[n];
if(f->window()==wid) {
if(desktop==-2) f->desktop(0);
else f->desktop(Desktop::desktop(desktop));
return;
}
}
}
void Frame::desktop(Desktop* d)
{
DBG("Frame(%s)::desktop(%d)", label().c_str(), d->number());
if(d == desktop_) return;
if(d) clear_frame_flag(STICKY);
else set_frame_flag(STICKY);
// Put all the relatives onto the desktop as well:
for(uint n=0; n<map_order.size(); n++) {
Frame *c = map_order[n];
if(c==this || c->is_transient_for(this)) {
c->desktop_ = d;
if(d) {
c->setProperty(_XA_NET_WM_DESKTOP, XA_CARDINAL, d->number()-1);
} else {
// means sticky...
c->setProperty(_XA_NET_WM_DESKTOP, XA_CARDINAL, 0xFFFFFFFF);
}
if(!d || d == Desktop::current()) {
if(c->state() == OTHER_DESKTOP)
c->state(NORMAL);
} else {
if(c->state() == NORMAL)
c->state(OTHER_DESKTOP);
}
}
}
}
void Frame::handle_move(int &nx, int &ny)
{
int X = root->x();
if(nx < X && nx > X-SCREEN_SNAP) {
int t = X;
if(t <= x() && t > nx) nx = t;
}
int Y = root->y();
if(ny < Y && ny > Y-SCREEN_SNAP) {
int t = Y;
if(t <= y() && t > ny) ny = t;
}
int W = root->x()+root->w();
if (nx+w() > W && nx+w() < W+SCREEN_SNAP) {
int t = W-w();
if (t >= x() && t < nx) nx = t;
}
int H = root->y()+root->h();
if (ny+h() > H && ny+h() < H+SCREEN_SNAP) {
int t = H-h();
if(t >= y() && t < ny) ny = t;
}
if(!Frame::do_opaque) {
draw_overlay(nx-1, ny-1, w()+2, h()+2);
} else
set_size(nx, ny, w(), h());
}
// 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)
{
maximized = false;
int dx = nx-x();
int dy = ny-y();
if(!dx && !dy && nw == w() && nh == h())
return;
x(nx);
y(ny);
if(nw != w()) w(nw);
if(nh != h()) h(nh);
XMoveWindow(fl_display, fl_xid(this), nx, ny);
XResizeWindow(fl_display, fl_xid(this), nw, nh);
XResizeWindow(fl_display, window_, nw-offset_w, nh-offset_h);
send_configurenotify();
redraw();
//XSync(fl_display, False);
}
// Resize the frame to match the current border type:
void Frame::updateBorder()
{
DBG("updateBorder(): %s", label().c_str());
int nx = x()+offset_x;
int ny = y()+offset_y;
int nw = w()-offset_w;
int nh = h()-offset_h;
// Set boxes, according decorations flags
if(decor_flag(THIN_BORDER) && box()!=FL_UP_BOX)
box(FL_UP_BOX);
else if(decor_flag(BORDER) && box()!=FL_THICK_UP_BOX)
box(FL_THICK_UP_BOX);
else if(!decor_flag(THIN_BORDER) && !decor_flag(BORDER) && box()!=FL_NO_BOX)
box(FL_NO_BOX);
if(!decor_flag(BORDER) && !decor_flag(THIN_BORDER)) {
offset_x=offset_y=offset_w=offset_h=0;
if(decor_flag(TITLEBAR)) offset_y += TITLE_H;
} else {
offset_x = box()->dx();
offset_y = box()->dy();
offset_w = box()->dw();
offset_h = box()->dh();
offset_y+=title->h();
offset_h+=title->h();
}
nx -= offset_x;
ny -= offset_y;
nw += offset_w;
nh += offset_h;
if(x()==nx && y()==ny && w()==nw && h()==nh)
return;
x(nx); y(ny);
w(nw); h(nh);
redraw();
if(!shown())
return; // this is so constructor can call this
if(!decor_flag(TITLEBAR)) title->hide();
else title->show();
// 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_, offset_x, offset_y);
}
void Frame::close(Window wid)
{
for(uint n=0; n<map_order.size(); n++) {
Frame *f = map_order[n];
if(wid==f->window()) {
f->close();
return;
}
}
}
void Frame::close()
{
DBG("Frame::close()");
if(frame_flag(DELETE_WIN_PRT))
sendMessage(_XA_WM_PROTOCOLS, _XA_WM_DELETE_WINDOW);
else
kill();
}
void Frame::kill()
{
DBG("Frame::kill()");
XUnmapWindow(fl_display, fl_xid(this));
XUnmapWindow(fl_display, window_);
XUnmapWindow(fl_display, fl_xid(title));
XKillClient(fl_display, window_);
destroy_frame();
}
// Drawing code:
void Frame::draw()
{
if((!decor_flag(BORDER) || !decor_flag(THIN_BORDER)) && !decor_flag(TITLEBAR))
return;
if(!(damage()&FL_DAMAGE_ALL|FL_DAMAGE_EXPOSE) )
return;
DBG("Frame::draw(%x)", damage());
if(!Theme::use_theme() || Theme::frame_color()==FL_NO_COLOR)
{
if(!decor_flag(BORDER) || !decor_flag(THIN_BORDER))
draw_frame();
}
else
{
Fl_Color color = Theme::frame_color();
fl_color(fl_lighter(color));
fl_line(0,0,0,h());
fl_line(0,0,w(),0);
fl_color(fl_darker(color));
fl_line(w()-1,0,w()-1,h());
fl_line(0,h()-1,w(),h()-1);
fl_color(color);
fl_rect(1,1,w()-2,h()-2);
fl_rect(2,2,w()-4,h()-4);
}
if(decor_flag(TITLEBAR))
{
if(active()) { // this here to update colors of the titlebar when are changed
title->color((Fl_Color)title_active_color);
title->label_color((Fl_Color)title_active_color_text);
} else {
title->color((Fl_Color)title_normal_color);
title->label_color((Fl_Color)title_normal_color_text);
}
// Resize and draw titlebar
if(decor_flag(BORDER) || decor_flag(THIN_BORDER)) {
XMoveResizeWindow(fl_display, fl_xid(title), box()->dx(), box()->dy(), w()-box()->dw(), TITLE_H);
title->resize(box()->dx(), box()->dy(), w()-box()->dw(), TITLE_H);
} else {
//XMoveResizeWindow(fl_display, fl_xid(title), 0, 0, w(), TITLE_H);
title->resize(0, 0, w(), TITLE_H);
}
title->layout();
draw_child(*title);
}
}
// User interface code:
// 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(!decor_flag(RESIZE)) return 0;
if(size_hints->min_height != size_hints->max_height) {
if(y < RESIZE_EDGE)
r |= FL_ALIGN_TOP;
else if(y >= h()-RESIZE_EDGE)
r |= FL_ALIGN_BOTTOM;
}
if(size_hints->min_width != size_hints->max_width) {
if(x < RESIZE_EDGE)
r |= FL_ALIGN_LEFT;
else if (x >= w()-RESIZE_EDGE)
r |= FL_ALIGN_RIGHT;
}
return r;
}
// set the cursor correctly for a return value from mouse_location():
static Frame* previous_frame=0;
void Frame::set_cursor(int r) {
Fl_Cursor c = FL_CURSOR_ARROW;
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(this != previous_frame || c != root->get_cursor()) {
previous_frame = this;
if(r<=0)
root->set_default_cursor();
else
root->set_cursor(c, FL_WHITE ,FL_BLACK);
}
}
#ifdef AUTO_RAISE
// timeout callback to cause autoraise:
void auto_raise(void*) {
if (Frame::activeFrame() && !Fl::grab() && !Fl::pushed())
Frame::activeFrame()->raise();
}
#endif
// 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;
//If true, we send all push, move, drag events to titlebar
bool handle_title = false;
// Handle an fltk event.
int Frame::handle(int e)
{
static bool grabbed=false;
static int what, dx, dy, ix, iy, iw, ih;
switch (e) {
case FL_SHOW:
case FL_HIDE:
return 0; // prevent fltk from messing things up
case FL_ENTER:
set_cursor(mouse_location());
if (Frame::focus_follows_mouse == true) activate(); // AEW
return 1;
case FL_LEAVE:
set_cursor(-1);
return 1;
case FL_MOVE:
// Tooltips...
if(!handle_title && Fl::event_inside(title->x(), title->y(), title->w(), title->h())) {
if(!title->tooltip().empty()) {
tooltip(title->tooltip());
Fl_Tooltip::enter(this);
} else
tooltip("");
handle_title = true;
} else if(!Fl::event_inside(title->x(), title->y(), title->w(), title->h()) ) { //Fl::belowmouse()==title) {
tooltip(0);
Fl_Tooltip::exit();
handle_title = false;
}
if(Fl::belowmouse() != this || cursor_inside == this) {
set_cursor(-1);
} else {
set_cursor(mouse_location());
}
return 1;
case FL_PUSH:
if(grabbed) return 1;
activate();
raise();
ix = x(); iy = y(); iw = w(); ih = h();
what = mouse_location();
if(Fl::event_button() > 1) what = 0; // middle button does drag
// FL_MOVE sets this!
if(handle_title) {
return title->handle(e);
}
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);
if(!decor_flag(RESIZE)) return 0;
if(!Frame::do_opaque && !grabbed) {
XGrabServer(fl_display);
grabbed = true;
}
//We need to grab cursor, otherwise it keeps changing while resizing
grab_cursor(true);
return 1;
case FL_DRAG:
if(handle_title) {
return title->handle(e);
}
if(Fl::event_is_click()) return 1; // don't drag yet
if(!Fl::event_state(FL_BUTTON1)) return 0;
case FL_RELEASE:
if(e==FL_RELEASE) {
set_cursor(mouse_location());
if(Fl::event_state(FL_BUTTON1)) return 1;
}
// Check if titlebar handles event
if(handle_title) {
handle_title = false;
return title->handle(e);
}
//Otherwise it's resize event
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();
}
int W=nw, H=nh;
ICCCM::get_size(this, W, H);
if(nw!=w()) {
nw = W;
nw -= (nw-offset_w)%size_hints->width_inc;
}
if(nh!=h())
nh = H;
if(what & FL_ALIGN_LEFT) nx = ix+iw-nw;
if(what & FL_ALIGN_TOP) ny = iy+ih-nh;
if(Frame::do_opaque || e==FL_RELEASE) {
set_size(nx,ny,nw,nh);
redraw();
} else {
draw_overlay(nx,ny,nw,nh);
}
if(e==FL_RELEASE) {
if(grabbed && !Frame::do_opaque) {
XUngrabServer(fl_display);
clear_overlay();
grabbed = false;
}
grab_cursor(false);
}
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 VisibilityNotify: return visibility_event(&(ei->xvisibility));
case ConfigureRequest: return configure_event(&(ei->xconfigurerequest));
case MapRequest: return map_event(&(ei->xmaprequest));
case UnmapNotify: return unmap_event(&(ei->xunmap));
case DestroyNotify: return destroy_event(&(ei->xdestroywindow));
case ReparentNotify: return reparent_event(&(ei->xreparent));
case ClientMessage: return clientmsg_event(&(ei->xclient));
case ColormapNotify: return colormap_event(&(ei->xcolormap));
case PropertyNotify: return property_event(&(ei->xproperty));
case EnterNotify:
{
// see if cursor skipped over frame and directly to interior:
if (ei->xcrossing.detail == NotifyVirtual || ei->xcrossing.detail == NotifyNonlinearVirtual)
cursor_inside = this;
else
{
// cursor is now pointing at frame:
cursor_inside = 0;
set_cursor(0);
}
return 1;
}
case LeaveNotify:
{
if (ei->xcrossing.detail == NotifyInferior)
{
// cursor moved from frame to interior
cursor_inside = this;
set_cursor(0);
}
return 1;
}
default:
#ifdef SHAPE
if(ei->type == (root->XShapeEventBase + ShapeNotify))
return shape_event((XShapeEvent *)&ei);
#endif
break;
}
return 0;
}
// X utility routines:
void* getProperty(Window w, Atom a, Atom type, unsigned long *np, int *ret)
{
Atom realType;
int format;
unsigned long n, extra;
int status;
uchar *prop=0;
status = XGetWindowProperty(fl_display, w, a, 0L, 0x7fffffff,
False, type, &realType,
&format, &n, &extra, (uchar**)&prop);
if(ret) *ret = status;
if (status != Success) return 0;
if (!prop) { return 0; }
if (!n) { XFree(prop); return 0; }
if (np) *np = n;
return prop;
}
int getIntProperty(Window w, Atom a, Atom type, int deflt, int *ret)
{
void* prop = getProperty(w, a, type, 0, ret);
if(!prop) return deflt;
int r = int(*(long*)prop);
XFree(prop);
return r;
}
void setProperty(Window 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);
}
int Frame::getIntProperty(Atom a, Atom type, int deflt, int *ret) const
{
return ::getIntProperty(window_, a, type, deflt, ret);
}
void* Frame::getProperty(Atom a, Atom type, unsigned long *np, int *ret) const
{
return ::getProperty(window_, a, type, np, ret);
}
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);
}