// // "$Id: Fl.cxx,v 1.24.2.37 2001/01/15 23:51:37 spitzak Exp $" // // Main event handling code for the Fast Light Tool Kit (FLTK). // // Copyright 1998-2000 by Bill Spitzak and others. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Library General Public License for more details. // // You should have received a copy of the GNU Library General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 // USA. // // Please report all bugs and problems to "fltk-bugs@fltk.org". // #include #include #include #include #include #include // // Globals... // Fl_Widget *Fl::belowmouse_, *Fl::pushed_, *Fl::focus_, *Fl::selection_owner_; int Fl::damage_, Fl::e_x, Fl::e_y, Fl::e_x_root, Fl::e_y_root, Fl::e_state, Fl::e_clicks, Fl::e_is_click, Fl::e_keysym; char *Fl::e_text = (char *)""; int Fl::e_length; // // 'Fl:event_inside()' - Return whether or not the mouse event is inside // the given rectangle. // int Fl::event_inside(int x,int y,int w,int h) /*const*/ { int mx = event_x() - x; int my = event_y() - y; return (mx >= 0 && mx < w && my >= 0 && my < h); } int Fl::event_inside(const Fl_Widget *o) /*const*/ { return event_inside(o->x(),o->y(),o->w(),o->h()); } //////////////////////////////////////////////////////////////// // Timeouts are stored in a sorted list, so only the first one needs // to be checked to see if any should be called. struct Timeout { double time; void (*cb)(void*); void* arg; Timeout* next; }; static Timeout* first_timeout, *free_timeout; #ifndef WIN32 #include #endif // I avoid the overhead of getting the current time when we have no // timeouts by setting this flag instead of getting the time. // In this case calling elapse_timeouts() does nothing, but records // the current time, and the next call will actualy elapse time. static char reset_clock = 1; static void elapse_timeouts() { #ifdef WIN32 unsigned long newclock = GetTickCount(); static unsigned long prevclock; double elapsed = (newclock-prevclock)/1000.0; prevclock = newclock; #else static struct timeval prevclock; struct timeval newclock; gettimeofday(&newclock, NULL); double elapsed = newclock.tv_sec - prevclock.tv_sec + (newclock.tv_usec - prevclock.tv_usec)/1000000.0; prevclock.tv_sec = newclock.tv_sec; prevclock.tv_usec = newclock.tv_usec; #endif if (reset_clock) { reset_clock = 0; } else if (elapsed > 0) { for (Timeout* t = first_timeout; t; t = t->next) t->time -= elapsed; } } void Fl::add_timeout(double time, Fl_Timeout_Handler cb, void *arg) { elapse_timeouts(); repeat_timeout(time, cb, arg); } void Fl::repeat_timeout(double time, Fl_Timeout_Handler cb, void *arg) { elapse_timeouts(); Timeout* t = free_timeout; if (t) free_timeout = t->next; else t = new Timeout; t->time = time; t->cb = cb; t->arg = arg; // insert-sort the new timeout: Timeout** p = &first_timeout; while (*p && (*p)->time <= time) p = &((*p)->next); t->next = *p; *p = t; } int Fl::has_timeout(Fl_Timeout_Handler cb, void *arg) { for (Timeout* t = first_timeout; t; t = t->next) if (t->cb == cb && t->arg == arg) return 1; return 0; } void Fl::remove_timeout(Fl_Timeout_Handler cb, void *arg) { // This version removes all matching timeouts, not just the first one. // This may change in the future. for (Timeout** p = &first_timeout; *p;) { Timeout* t = *p; if (t->cb == cb && t->arg == arg) { *p = t->next; t->next = free_timeout; free_timeout = t; } else { p = &(t->next); } } } //////////////////////////////////////////////////////////////// // Checks are just stored in a list. They are called in the reverse // order that they were added (this may change in the future). // This is a bit messy because I want to allow checks to be added, // removed, and have wait() called from inside them, to do this // next_check points at the next unprocessed one for the outermost // call to Fl::wait(). struct Check { void (*cb)(void*); void* arg; Check* next; }; static Check* first_check, *next_check, *free_check; void Fl::add_check(Fl_Timeout_Handler cb, void *arg) { Check* t = free_check; if (t) free_check = t->next; else t = new Check; t->cb = cb; t->arg = arg; t->next = first_check; if (next_check == first_check) next_check = t; first_check = t; } void Fl::remove_check(Fl_Timeout_Handler cb, void *arg) { for (Check** p = &first_check; *p;) { Check* t = *p; if (t->cb == cb && t->arg == arg) { if (next_check == t) next_check = t->next; *p = t->next; t->next = free_check; free_check = t; } else { p = &(t->next); } } } //////////////////////////////////////////////////////////////// // wait/run/check/ready: void (*Fl::idle)(); // see Fl_add_idle.cxx for the add/remove functions extern int fl_wait(double time); // in Fl_x.cxx or Fl_win32.cxx static char in_idle; double Fl::wait(double time_to_wait) { if (first_timeout) { elapse_timeouts(); Timeout *t; while ((t = first_timeout)) { if (t->time > 0) break; // The first timeout in the array has expired. // We must remove timeout from array before doing the callback: void (*cb)(void*) = t->cb; void *arg = t->arg; first_timeout = t->next; t->next = free_timeout; free_timeout = t; // Now it is safe for the callback to do add_timeout: cb(arg); } } else { reset_clock = 1; // we are not going to check the clock } // checks are a bit messy so that add/remove and wait may be called // from inside them without causing an infinite loop: if (next_check == first_check) { while (next_check) { Check* check = next_check; next_check = check->next; (check->cb)(check->arg); } next_check = first_check; } if (idle) { if (!in_idle) {in_idle = 1; idle(); in_idle = 0;} // the idle function may turn off idle, we can then wait: if (idle) time_to_wait = 0.0; } if (first_timeout && first_timeout->time < time_to_wait) time_to_wait = first_timeout->time; if (time_to_wait <= 0.0) { // do flush second so that the results of events are visible: int ret = fl_wait(0.0); flush(); return ret; } else { // do flush first so that user sees the display: flush(); return fl_wait(time_to_wait); } } #define FOREVER 1e20 int Fl::run() { while (Fl_X::first) wait(FOREVER); return 0; } int Fl::wait() { wait(FOREVER); return Fl_X::first != 0; // return true if there is a window } int Fl::check() { wait(0.0); return Fl_X::first != 0; // return true if there is a window } extern int fl_ready(); int Fl::ready() { if (first_timeout) { elapse_timeouts(); if (first_timeout->time <= 0) return 1; } else { reset_clock = 1; } return fl_ready(); } //////////////////////////////////////////////////////////////// // Window list management: Fl_X* Fl_X::first; Fl_Window* fl_find(Window xid) { Fl_X *window; for (Fl_X **pp = &Fl_X::first; (window = *pp); pp = &window->next) if (window->xid == xid) { if (window != Fl_X::first && !Fl::modal()) { // make this window be first to speed up searches // this is not done if modal is true to avoid messing up modal stack *pp = window->next; window->next = Fl_X::first; Fl_X::first = window; } return window->w; } return 0; } Fl_Window* Fl::first_window() { Fl_X* x = Fl_X::first; return x ? x->w : 0; } Fl_Window* Fl::next_window(const Fl_Window* w) { Fl_X* x = Fl_X::i(w)->next; return x ? x->w : 0; } void Fl::first_window(Fl_Window* window) { if (!window || !window->shown()) return; fl_find(fl_xid(window)); } void Fl::redraw() { for (Fl_X* x = Fl_X::first; x; x = x->next) x->w->redraw(); } void Fl::flush() { if (damage()) { damage_ = 0; for (Fl_X* x = Fl_X::first; x; x = x->next) { if (x->w->damage() && x->w->visible_r()) { if (x->wait_for_expose) { // leave Fl::damage() set so programs can tell damage still exists damage_ = 1; } else { x->flush(); x->w->clear_damage(); } } } } #ifndef WIN32 if (fl_display) XFlush(fl_display); #endif } //////////////////////////////////////////////////////////////// // Event handlers: struct handler_link { int (*handle)(int); const handler_link *next; }; static const handler_link *handlers = 0; void Fl::add_handler(int (*h)(int)) { handler_link *l = new handler_link; l->handle = h; l->next = handlers; handlers = l; } static int send_handlers(int event) { for (const handler_link *h = handlers; h; h = h->next) if (h->handle(event)) return 1; return 0; } //////////////////////////////////////////////////////////////// Fl_Widget* fl_oldfocus; // kludge for Fl_Group... void Fl::focus(Fl_Widget *o) { if (grab()) return; // don't do anything while grab is on Fl_Widget *p = focus_; if (o != p) { Fl::compose_reset(); focus_ = o; fl_oldfocus = 0; for (; p && !p->contains(o); p = p->parent()) { p->handle(FL_UNFOCUS); fl_oldfocus = p; } } } void Fl::belowmouse(Fl_Widget *o) { if (grab()) return; // don't do anything while grab is on Fl_Widget *p = belowmouse_; if (o != p) { belowmouse_ = o; for (; p && !p->contains(o); p = p->parent()) p->handle(FL_LEAVE); } } void Fl::pushed(Fl_Widget *o) { pushed_ = o; } Fl_Window *fl_xfocus; // which window X thinks has focus Fl_Window *fl_xmousewin;// which window X thinks has FL_ENTER Fl_Window *Fl::grab_; // most recent Fl::grab() Fl_Window *Fl::modal_; // topmost modal() window // Update modal(), focus() and other state according to system state, // and send FL_ENTER, FL_LEAVE, FL_FOCUS, and/or FL_UNFOCUS events. // This is the only function that produces these events in response // to system activity. // This is called whenever a window is added or hidden, and whenever // X says the focus or mouse window have changed. void fl_fix_focus() { if (Fl::grab()) return; // don't do anything while grab is on. // set focus based on Fl::modal() and fl_xfocus Fl_Widget* w = fl_xfocus; if (w) { Fl::e_keysym = 0; // make sure widgets don't think a keystroke moved focus while (w->parent()) w = w->parent(); if (Fl::modal()) w = Fl::modal(); if (!w->contains(Fl::focus())) if (!w->take_focus()) Fl::focus(w); } else Fl::focus(0); if (!Fl::pushed()) { // set belowmouse based on Fl::modal() and fl_xmousewin: w = fl_xmousewin; if (w) { if (Fl::modal()) w = Fl::modal(); if (!w->contains(Fl::belowmouse())) { Fl::belowmouse(w); w->handle(FL_ENTER); } else { // send a FL_MOVE event so the enter/leave state is up to date Fl::e_x = Fl::e_x_root-fl_xmousewin->x(); Fl::e_y = Fl::e_y_root-fl_xmousewin->y(); w->handle(FL_MOVE); } } else { Fl::belowmouse(0); } } } #ifndef WIN32 Fl_Widget *fl_selection_requestor; // from Fl_cutpaste.C #endif // This function is called by ~Fl_Widget() and by Fl_Widget::deactivate // and by Fl_Widget::hide(). It indicates that the widget does not want // to receive any more events, and also removes all global variables that // point at the widget. // I changed this from the 1.0.1 behavior, the older version could send // FL_LEAVE or FL_UNFOCUS events to the widget. This appears to not be // desirable behavior and caused flwm to crash. void fl_throw_focus(Fl_Widget *o) { if (o->contains(Fl::pushed())) Fl::pushed_ = 0; if (o->contains(Fl::selection_owner())) Fl::selection_owner_ = 0; #ifndef WIN32 if (o->contains(fl_selection_requestor)) fl_selection_requestor = 0; #endif if (o->contains(Fl::belowmouse())) Fl::belowmouse_ = 0; if (o->contains(Fl::focus())) Fl::focus_ = 0; if (o == fl_xfocus) fl_xfocus = 0; if (o == fl_xmousewin) fl_xmousewin = 0; fl_fix_focus(); } //////////////////////////////////////////////////////////////// // Call to->handle but first replace the mouse x/y with the correct // values to account for nested X windows. 'window' is the outermost // window the event was posted to by X: static int send(int event, Fl_Widget* to, Fl_Window* window) { int dx = window->x(); int dy = window->y(); for (const Fl_Widget* w = to; w; w = w->parent()) if (w->type()>=FL_WINDOW) {dx -= w->x(); dy -= w->y();} int save_x = Fl::e_x; Fl::e_x += dx; int save_y = Fl::e_y; Fl::e_y += dy; int ret = to->handle(event); Fl::e_y = save_y; Fl::e_x = save_x; return ret; } int Fl::handle(int event, Fl_Window* window) { Fl_Widget* w = window; switch (event) { case FL_CLOSE: if (grab() || modal() && window != modal()) return 0; w->do_callback(); return 1; case FL_SHOW: ((Fl_Widget*)w)->show(); return 1; case FL_HIDE: ((Fl_Widget*)w)->hide(); return 1; case FL_PUSH: if (grab()) w = grab(); else if (modal() && w != modal()) return 0; pushed_ = w; if (send(event, w, window)) return 1; // raise windows that are clicked on: window->show(); return 1; case FL_MOVE: case FL_DRAG: fl_xmousewin = window; // this should already be set, but just in case. if (pushed()) { w = pushed(); event = FL_DRAG; } else if (modal() && w != modal()) { w = 0; } if (grab()) w = grab(); break; case FL_RELEASE: { if (pushed()) { w = pushed(); pushed_ = 0; // must be zero before callback is done! } if (grab()) w = grab(); int r = send(event, w, window); fl_fix_focus(); return r;} case FL_UNFOCUS: window = 0; case FL_FOCUS: fl_xfocus = window; fl_fix_focus(); return 1; case FL_KEYBOARD: fl_xfocus = window; // this should not happen! But maybe it does: // Try it as keystroke, sending it to focus and all parents: for (w = grab() ? grab() : focus(); w; w = w->parent()) if (send(FL_KEYBOARD, w, window)) return 1; // recursive call to try shortcut: if (handle(FL_SHORTCUT, window)) return 1; // and then try a shortcut with the case of the text swapped, by // changing the text and falling through to FL_SHORTCUT case: {char* c = (char*)event_text(); // cast away const if (!isalpha(*c)) return 0; *c = isupper(*c) ? tolower(*c) : toupper(*c);} event = FL_SHORTCUT; case FL_SHORTCUT: if (grab()) {w = grab(); break;} // send it to grab window // Try it as shortcut, sending to mouse widget and all parents: w = belowmouse(); if (!w) {w = modal(); if (!w) w = window;} for (; w; w = w->parent()) if (send(FL_SHORTCUT, w, window)) return 1; // try using add_handle() functions: if (send_handlers(FL_SHORTCUT)) return 1; // make Escape key close windows: if (event_key()==FL_Escape) { w = modal(); if (!w) w = window; w->do_callback(); return 1; } return 0; case FL_ENTER: fl_xmousewin = window; fl_fix_focus(); return 1; case FL_LEAVE: if (window == fl_xmousewin) {fl_xmousewin = 0; fl_fix_focus();} return 1; default: break; } if (w && send(event, w, window)) return 1; return send_handlers(event); } //////////////////////////////////////////////////////////////// // hide() destroys the X window, it does not do unmap! void Fl_Window::hide() { clear_visible(); if (!shown()) return; // remove from the list of windows: Fl_X* x = i; Fl_X** pp = &Fl_X::first; for (; *pp != x; pp = &(*pp)->next) if (!*pp) return; *pp = x->next; i = 0; // recursively remove any subwindows: for (Fl_X *w = Fl_X::first; w;) { Fl_Window* W = w->w; if (W->window() == this) { W->hide(); W->set_visible(); w = Fl_X::first; } else w = w->next; } if (this == Fl::modal_) { // we are closing the modal window, find next one: Fl_Window* w; for (w = Fl::first_window(); w; w = Fl::next_window(w)) if (w->modal()) break; Fl::modal_ = w; } // Make sure no events are sent to this window: fl_throw_focus(this); handle(FL_HIDE); #ifdef WIN32 if (x->private_dc) ReleaseDC(x->xid,x->private_dc); if (x->xid == fl_window && fl_gc) { ReleaseDC(fl_window, fl_gc); fl_window = (HWND)-1; fl_gc = 0; } #else if (x->region) XDestroyRegion(x->region); #endif XDestroyWindow(fl_display, x->xid); #ifdef WIN32 // Try to stop the annoying "raise another program" behavior if (non_modal() && Fl::first_window() && Fl::first_window()->shown()) Fl::first_window()->show(); #endif delete x; } Fl_Window::~Fl_Window() { hide(); } // FL_SHOW and FL_HIDE are called whenever the visibility of this widget // or any parent changes. We must correctly map/unmap the system's window. // For top-level windows it is assummed the window has already been // mapped or unmapped!!! This is because this should only happen when // Fl_Window::show() or Fl_Window::hide() is called, or in response to // iconize/deiconize events from the system. int Fl_Window::handle(int event) { if (parent()) switch (event) { case FL_SHOW: if (!shown()) show(); else XMapWindow(fl_display, fl_xid(this)); // extra map calls are harmless break; case FL_HIDE: if (shown()) { // Find what really turned invisible, if is was a parent window // we do nothing. We need to avoid unnecessary unmap calls // because they cause the display to blink when the parent is // remapped. However if this or any intermediate non-window // widget has really had hide() called directly on it, we must // unmap because when the parent window is remapped we don't // want to reappear. if (visible()) { Fl_Widget* p = parent(); for (;p->visible();p = p->parent()) {} if (p->type() >= FL_WINDOW) break; // don't do the unmap } XUnmapWindow(fl_display, fl_xid(this)); } break; } return Fl_Group::handle(event); } //////////////////////////////////////////////////////////////// // ~Fl_Widget() calls this: this function must get rid of any // global pointers to the widget. This is also called by hide() // and deactivate(). // call this to free a selection (or change the owner): void Fl::selection_owner(Fl_Widget *owner) { if (selection_owner_ && owner != selection_owner_) selection_owner_->handle(FL_SELECTIONCLEAR); if (focus_ && owner != focus_ && focus_ != selection_owner_) focus_->handle(FL_SELECTIONCLEAR); // clear non-X-selection highlight selection_owner_ = owner; } #include void Fl_Widget::redraw() {damage(FL_DAMAGE_ALL);} void Fl_Widget::damage(uchar flags) { if (type() < FL_WINDOW) { // damage only the rectangle covered by a child widget: damage(flags, x(), y(), w(), h()); } else { // damage entire window by deleting the region: Fl_X* i = Fl_X::i((Fl_Window*)this); if (!i) return; // window not mapped, so ignore it if (i->region) {XDestroyRegion(i->region); i->region = 0;} damage_ |= flags; Fl::damage(FL_DAMAGE_CHILD); } } void Fl_Widget::damage(uchar flags, int X, int Y, int W, int H) { Fl_Widget* window = this; // mark all parent widgets between this and window with FL_DAMAGE_CHILD: while (window->type() < FL_WINDOW) { window->damage_ |= flags; window = window->parent(); if (!window) return; flags = FL_DAMAGE_CHILD; } Fl_X* i = Fl_X::i((Fl_Window*)window); if (!i) return; // window not mapped, so ignore it if (X<=0 && Y<=0 && W>=window->w() && H>=window->h()) { // if damage covers entire window delete region: window->damage(flags); return; } // clip the damage to the window and quit if none: if (X < 0) {W += X; X = 0;} if (Y < 0) {H += Y; Y = 0;} if (W > window->w()-X) W = window->w()-X; if (H > window->h()-Y) H = window->h()-Y; if (W <= 0 || H <= 0) return; if (window->damage()) { // if we already have damage we must merge with existing region: if (i->region) { #ifndef WIN32 XRectangle R; R.x = X; R.y = Y; R.width = W; R.height = H; XUnionRectWithRegion(&R, i->region, i->region); #else Region R = XRectangleRegion(X, Y, W, H); CombineRgn(i->region, i->region, R, RGN_OR); XDestroyRegion(R); #endif } window->damage_ |= flags; } else { // create a new region: if (i->region) XDestroyRegion(i->region); i->region = XRectangleRegion(X,Y,W,H); window->damage_ = flags; } Fl::damage(FL_DAMAGE_CHILD); } void Fl_Window::flush() { make_current(); //if (damage() == FL_DAMAGE_EXPOSE && can_boxcheat(box())) fl_boxcheat = this; fl_clip_region(i->region); i->region = 0; draw(); } // // End of "$Id: Fl.cxx,v 1.24.2.37 2001/01/15 23:51:37 spitzak Exp $". //