// 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 #include #include #include 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; nx()+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; ntransient_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; nsettings_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(Xx()) 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(Yy()) 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; ndeactivate(); } } else { for(uint n=0; nwindow()) { 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; nstate()==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; nstate_==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; nwindow()==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; nis_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; nwindow()) { 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); }