commit fd0e9884cb88ed8e9ec58e30f6a92413bc0e7106 Author: Bill Spitzak Date: Tue Jan 18 01:05:49 2000 +0000 Initial checkin of the code to sourceforge. diff --git a/Desktop.C b/Desktop.C new file mode 100644 index 0000000..19ff4bd --- /dev/null +++ b/Desktop.C @@ -0,0 +1,149 @@ +// Desktop.C + +#include "config.h" + +#if DESKTOPS + +#include "Frame.H" +#include "Desktop.H" +#include +#include +#include + +Desktop* Desktop::current_ = 0; +Desktop* Desktop::first = 0; + +// return the highest desktop number: +int Desktop::max_number() { + int n = 0; + for (Desktop* d = first; d; d = d->next) + if (d->number_ > n) n = d->number_; + return n; +} + +// return an empty slot number: +int Desktop::available_number() { + int n = 1; + for (Desktop* d = first; d;) { + if (d->number_ == n) {n++; d = first;} + else d = d->next; + } + return n; +} + +// these are set by main.C: +Atom _win_workspace; +Atom _win_workspace_count; +Atom _win_workspace_names; +#ifndef __sgi +static Atom kwm_current_desktop; +#endif +extern Fl_Window* Root; + +static int dont_send; +static void send_desktops() { + if (dont_send) return; + int n = Desktop::max_number(); + setProperty(fl_xid(Root), _win_workspace_count, XA_CARDINAL, n); + char buffer[1025]; + char* p = buffer; + for (int i = 1; i <= n; i++) { + Desktop* d = Desktop::number(i); + const char* name = d ? d->name() : ""; + while (p < buffer+1024 && *name) *p++ = *name++; + *p++ = 0; + if (p >= buffer+1024) break; + } + XChangeProperty(fl_display, fl_xid(Root), _win_workspace_names, XA_STRING, + 8, PropModeReplace, (unsigned char *)buffer, p-buffer-1); +} + +Desktop::Desktop(const char* n, int num) { + next = first; + first = this; + name_ = strdup(n); + number_ = num; + send_desktops(); +} + +Desktop::~Desktop() { + // remove from list: + for (Desktop** p = &first; *p; p = &((*p)->next)) + if (*p == this) {*p = next; break;} + send_desktops(); + if (current_ == this || !first->next) current(first); + // put any clients onto another desktop: + for (Frame* c = Frame::first; c; c = c->next) + if (c->desktop() == this) c->desktop(first); + free((void*)name_); +} + +void Desktop::name(const char* l) { + free((void*)name_); + name_ = strdup(l); +} + +void Desktop::current(Desktop* n) { + if (n == current_) return; + current_ = n; + for (Frame* c = Frame::first; c; c = c->next) { + if (c->desktop() == n) { + if (c->state() == OTHER_DESKTOP) c->state(NORMAL); + } else if (c->desktop()) { + if (c->state() == NORMAL) c->state(OTHER_DESKTOP); + } + } + if (n && !dont_send) { +#ifndef __sgi + setProperty(fl_xid(Root), kwm_current_desktop, kwm_current_desktop, n->number()); +#endif + setProperty(fl_xid(Root), _win_workspace, XA_CARDINAL, n->number()-1); + } +} + +// return desktop with given number, create it if necessary: +Desktop* Desktop::number(int n, int create) { + if (!n) return 0; + Desktop* d; + for (d = first; d; d = d->next) if (d->number() == n) return d; + if (create) { + char buf[20]; sprintf(buf, "Desktop %d", n); + d = new Desktop(buf,n); + } + return d; +} + +// called at startup, read the list of desktops from the root +// window properties, or on failure make some default desktops. +void init_desktops() { + dont_send = 1; + int length; + char* buffer = + (char*)getProperty(fl_xid(Root), _win_workspace_names, XA_STRING, &length); + if (buffer) { + char* c = buffer; + for (int i = 1; c < buffer+length; i++) { + char* d = c; while (*d) d++; + if (*c != '<') new Desktop(c,i); + c = d+1; + } + XFree(buffer); + } + int current_num = 0; + int p = getIntProperty(fl_xid(Root), _win_workspace, XA_CARDINAL, -1); + if (p >= 0 && p < 25) current_num = p+1; +#ifndef __sgi + // SGI's Xlib barfs when you try to do this XInternAtom! + // Maybe somebody there does not like KDE? + kwm_current_desktop = XInternAtom(fl_display, "KWM_CURRENT_DESKTOP", 0); + if (!current_num) { + p = getIntProperty(fl_xid(Root), kwm_current_desktop, kwm_current_desktop); + if (p > 0 && p < 25) current_num = p; + } +#endif + if (!current_num) current_num = 1; + Desktop::current(Desktop::number(current_num, 1)); + dont_send = 0; +} + +#endif diff --git a/Desktop.H b/Desktop.H new file mode 100644 index 0000000..43fca25 --- /dev/null +++ b/Desktop.H @@ -0,0 +1,22 @@ +// Desktop.H + +class Desktop { + const char* name_; + int number_; + static Desktop* current_; +public: + static Desktop* first; + Desktop* next; + const char* name() const {return name_;} + void name(const char*); + int number() const {return number_;} + static Desktop* current() {return current_;} + static Desktop* number(int, int create = 0); + static void current(Desktop*); + static int available_number(); + static int max_number(); + Desktop(const char*, int); + ~Desktop(); + int junk; // for temporary storage by menu builder +}; + diff --git a/Frame.C b/Frame.C new file mode 100644 index 0000000..50feddc --- /dev/null +++ b/Frame.C @@ -0,0 +1,1726 @@ +// Frame.C + +#include "config.h" +#include "Frame.H" +#include "Desktop.H" +#include +#include +#include +#include "Rotated.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(Window 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") +{ + 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 + 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()))) { + // 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 = 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; + } + } + } + // 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, fl_xid(Root), + 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 + 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(); + } +} + +// 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. + +// 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; + +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; + + // fix fltk bug: + fl_xfocus = 0; + fl_xmousewin = 0; + Fl::focus_ = 0; + + // 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; + Window* cw = (Window*)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; +#if defined(ACTIVE_COLOR) + XSetWindowAttributes a; + a.background_pixel = fl_xpixel(FL_SELECTION_COLOR); + XChangeWindowAttributes(fl_display, fl_xid(this), CWBackPixel, &a); + labelcolor(contrast(FL_BLACK, FL_SELECTION_COLOR)); + XClearArea(fl_display, fl_xid(this), 2, 2, w()-4, h()-4, 1); +#else +#if defined(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() { +#if defined(ACTIVE_COLOR) + XSetWindowAttributes a; + a.background_pixel = fl_xpixel(FL_GRAY); + XChangeWindowAttributes(fl_display, fl_xid(this), CWBackPixel, &a); + labelcolor(FL_BLACK); + XClearArea(fl_display, fl_xid(this), 2, 2, w()-4, h()-4, 1); +#else +#if defined(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(); + set_state_flag(IGNORE_UNMAP); + XUnmapWindow(fl_display, fl_xid(this)); + 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(); + set_state_flag(IGNORE_UNMAP); + XUnmapWindow(fl_display, fl_xid(this)); + } 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(); +#ifdef 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) {} + +//////////////////////////////////////////////////////////////// + +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: + +void Frame::draw() { + if (flag(NO_BORDER)) return; + if (!flag(THIN_BORDER)) Fl_Window::draw(); + if (damage() != FL_DAMAGE_CHILD) { +#if 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) { +#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. + draw_rotated90(clock_buf, 1, label_y+3, left-1, label_h-6, + Fl_Align(FL_ALIGN_BOTTOM|FL_ALIGN_CLIP)); + } 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); + draw_rotated90(label(), 1, label_y+3, left-1, label_h-3, + Fl_Align(FL_ALIGN_TOP|FL_ALIGN_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() { + Fl_Widget::draw_box(value() ? FL_DOWN_FRAME : FL_UP_FRAME, FL_GRAY); + 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; + } + 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); + } +} + +#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_Window::handle(e) && e != FL_ENTER && e != FL_MOVE) { + if (e == FL_PUSH) set_cursor(-1); + return 1; + } + 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 0: + 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 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; + } + + // fall through to FL_MOVE: + break; + + case LeaveNotify: + if (fl_xevent->xcrossing.detail == NotifyInferior) { + // cursor moved from frame to interior + cursor_inside = this; + set_cursor(-1); + return 1; + } + return 1; + + default: + return 0; // other X event we don't understand + } + + case FL_MOVE: + 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) raise(); + return 1;} + + case MapRequest: { + //const XMapRequestEvent* e = &(ei->xmaprequest); + raise(); + return 1;} + + case UnmapNotify: { + const XUnmapEvent* e = &(ei->xunmap); + if (e->from_configure); + else 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(Window w, Atom a, Atom type, int* np) { + Atom realType; + int format; + unsigned long n, extra; + int status; + void* prop; + status = XGetWindowProperty(fl_display, w, + a, 0L, 256L, False, type, &realType, + &format, &n, &extra, (uchar**)&prop); + if (status != Success) return 0; + if (!prop) return 0; + if (!n) {XFree(prop); return 0;} + if (np) *np = (int)n; + return prop; +} + +int Frame::getIntProperty(Atom a, Atom type, int deflt) const { + return ::getIntProperty(window_, a, type, deflt); +} + +int getIntProperty(Window 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(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); +} + +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); +} diff --git a/Frame.H b/Frame.H new file mode 100644 index 0000000..6c680ab --- /dev/null +++ b/Frame.H @@ -0,0 +1,192 @@ +// Frame.H + +// Each X window being managed by fltk has one of these + +#ifndef Frame_H +#define Frame_H + +#include +#include +#include +#include + +// The state is an enumeration of reasons why the window may be invisible. +// Only if it is NORMAL is the window visible. +enum { + UNMAPPED = 0, // unmap command from app (X calls this WithdrawnState) + NORMAL = 1, // window is visible +//SHADED = 2, // acts like NORMAL + ICONIC = 3, // hidden/iconized + OTHER_DESKTOP = 4 // normal but on another desktop +}; + +// values for flags: +// The flags are constant and are turned on by information learned +// from the Gnome, KDE, and/or Motif window manager hints. Flwm will +// ignore attempts to change these hints after the window is mapped. +enum { + NO_FOCUS = 0x0001, // does not take focus + CLICK_TO_FOCUS = 0x0002, // must click on window to give it focus + NO_BORDER = 0x0004, // raw window with no border + THIN_BORDER = 0x0008, // just resize border + NO_RESIZE = 0x0010, // don't resize even if sizehints say its ok + NO_CLOSE = 0x0040, // don't put a close box on it + TAKE_FOCUS_PROTOCOL = 0x0080, // send junk when giving window focus + DELETE_WINDOW_PROTOCOL= 0x0100, // close box sends a message + KEEP_ASPECT = 0x0200, // aspect ratio from sizeHints + MODAL = 0x0400, // grabs focus from transient_for window + ICONIZE = 0x0800, // transient_for_ actually means group :-( + QUIT_PROTOCOL = 0x1000, // Irix 4DWM "quit" menu item + SAVE_PROTOCOL = 0x2000 // "WM_SAVE_YOURSELF" stuff +}; + +// values for state_flags: +// These change over time +enum { + IGNORE_UNMAP = 0x01, // we did something that echos an UnmapNotify + SAVE_PROTOCOL_WAIT = 0x02 +}; + +class FrameButton : public Fl_Button { + void draw(); +public: + FrameButton(int X, int Y, int W, int H, const char* L=0) + : Fl_Button(X,Y,W,H,L) {} +}; + +class Desktop; + +class Frame : public Fl_Window { + + Window window_; + + short state_; // X server state: iconic, withdrawn, normal + short state_flags_; // above state flags + void set_state_flag(short i) {state_flags_ |= i;} + void clear_state_flag(short i) {state_flags_&=~i;} + + int flags_; // above constant flags + void set_flag(int i) {flags_ |= i;} + void clear_flag(int i) {flags_&=~i;} + + int restore_x, restore_w; // saved size when min/max width is set + int restore_y, restore_h; // saved size when max height is set + int min_w, max_w, inc_w; // size range and increment + int min_h, max_h, inc_h; // size range and increment + int app_border_width; // value of border_width application tried to set + + int left, top, dwidth, dheight; // current thickness of border + int label_y, label_h; // location of label + int label_w; // measured width of printed label + + Window transient_for_xid; // value from X + Frame* transient_for_; // the frame for that xid, if found + + Frame* revert_to; // probably the xterm this was run from + + Colormap colormap; // this window's colormap + int colormapWinCount; // list of other windows to install colormaps for + Window *colormapWindows; + Colormap *window_Colormaps; // their colormaps + + Desktop* desktop_; + + FrameButton close_button; + FrameButton iconize_button; + FrameButton max_h_button; + FrameButton max_w_button; + FrameButton min_w_button; + + int maximize_width(); + int maximize_height(); + int force_x_onscreen(int X, int W); + int force_y_onscreen(int Y, int H); + + void sendMessage(Atom, Atom) const; + void sendConfigureNotify() const; + void setStateProperty() const; + + void* getProperty(Atom, Atom = AnyPropertyType, int* length = 0) const; + int getIntProperty(Atom, Atom = AnyPropertyType, int deflt = 0) const; + void setProperty(Atom, Atom, int) const; + void getLabel(int del = 0); + void getColormaps(); + int getSizes(); + int getGnomeState(int&); + void getProtocols(); + int getMotifHints(); + void updateBorder(); + void fix_transient_for(); // called when transient_for_xid changes + + void installColormap() const; + + void set_size(int,int,int,int, int warp=0); + void resize(int,int,int,int); + void show_hide_buttons(); + + int handle(int); // handle fltk events + void set_cursor(int); + int mouse_location(); + + void draw(); + + static Frame* active_; + static void button_cb_static(Fl_Widget*, void*); + void button_cb(Fl_Button*); + + void deactivate(); + int activate_if_transient(); + void _desktop(Desktop*); + + int border() const {return !(flags_&NO_BORDER);} + int flags() const {return flags_;} + int flag(int i) const {return flags_&i;} + void throw_focus(int destructor = 0); + void warp_pointer(); + +public: + + int handle(const XEvent*); + + static Frame* first; + Frame* next; // stacking order, top to bottom + + Frame(Window, XWindowAttributes* = 0); + ~Frame(); + + Window window() const {return window_;} + Frame* transient_for() const {return transient_for_;} + int is_transient_for(const Frame*) const; + + Desktop* desktop() const {return desktop_;} + void desktop(Desktop*); + + void raise(); // also does map + void lower(); + void iconize(); + void close(); + void kill(); + int activate(int warp = 0); // returns true if it actually sets active state + + short state() const {return state_;} + void state(short); // don't call this unless you know what you are doing! + + int active() const {return active_==this;} + static Frame* activeFrame() {return active_;} + + static void save_protocol(); // called when window manager exits + + // The following should be conditionally defined based on the + // SHOW_CLOCK definition in config.h but that definition is not + // available at the time we are evaluating this; it does no harm + // to be present even if not SHOW_CLOCK. + void redraw_clock(); + +}; + +// handy wrappers for those ugly X routines: +void* getProperty(Window, Atom, Atom = AnyPropertyType, int* length = 0); +int getIntProperty(Window, Atom, Atom = AnyPropertyType, int deflt = 0); +void setProperty(Window, Atom, Atom, int); + +#endif diff --git a/FrameWindow.C b/FrameWindow.C new file mode 100644 index 0000000..6ea8fd3 --- /dev/null +++ b/FrameWindow.C @@ -0,0 +1,42 @@ +// FrameWindow.C + +// X does not echo back the window-map events (it probably should when +// override_redirect is off). Unfortunately this means you have to use +// this subclass if you want a "normal" fltk window, it will force a +// Frame to be created and destroy it upon hide. + +// Warning: modal() does not work! Don't turn it on as it screws up the +// interface with the window borders. You can use set_non_modal() to +// disable the iconize box but the window manager must be written to +// not be modal. + +#include +#include "FrameWindow.H" +#include "Frame.H" + +extern int dont_set_event_mask; + +void FrameWindow::show() { + if (shown()) {Fl_Window::show(); return;} + Fl_Window::show(); + dont_set_event_mask = 1; + frame = new Frame(fl_xid(this)); + dont_set_event_mask = 0; +} + +void FrameWindow::hide() { + if (shown()) { + Fl_Window::hide(); + delete frame; + } +} + +int FrameWindow::handle(int e) { + if (Fl_Window::handle(e)) return 1; + // make Esc close the window: + if (e == FL_SHORTCUT && Fl::event_key()==FL_Escape) { + do_callback(); + return 1; + } + return 0; +} diff --git a/FrameWindow.H b/FrameWindow.H new file mode 100644 index 0000000..3b9e0ad --- /dev/null +++ b/FrameWindow.H @@ -0,0 +1,31 @@ +// FrameWindow.H + +// X does not echo back the window-map events (it probably should when +// override_redirect is off). Unfortunately this means you have to use +// this subclass if you want a "normal" fltk window, it will force a +// Frame to be created and destroy it upon hide. + +// Warning: modal() does not work! Don't turn it on as it screws up the +// interface with the window borders. You can use set_non_modal() to +// disable the iconize box but the window manager must be written to +// not be modal. + +#ifndef FrameWindow_H +#define FrameWindow_H + +#include +class Frame; + +class FrameWindow : public Fl_Window { + Frame* frame; +public: + void hide(); + void show(); + int handle(int); + FrameWindow(int X, int Y, int W, int H, const char* L = 0) : + Fl_Window(X,Y,W,H,L) {} + FrameWindow(int W, int H, const char* L = 0) : + Fl_Window(W,H,L) {} +}; + +#endif diff --git a/Hotkeys.C b/Hotkeys.C new file mode 100644 index 0000000..8b4197e --- /dev/null +++ b/Hotkeys.C @@ -0,0 +1,181 @@ +// Hotkeys.C + +// If you want to change what the hotkeys are, see the table at the bottom! + +#include "config.h" +#include "Frame.H" +#include "Desktop.H" +#include + +extern void ShowMenu(); +extern void ShowTabMenu(int tab); + +#if STANDARD_HOTKEYS + +static void NextWindow() { // Alt+Tab + ShowTabMenu(1); +} + +static void PreviousWindow() { // Alt+Shift+Tab + ShowTabMenu(-1); +} + +#endif + +#if DESKTOPS + +static void NextDesk() { + if (Desktop::current()) { + Desktop::current(Desktop::current()->next? + Desktop::current()->next:Desktop::first); + } else { + Desktop::current(Desktop::first); + } +} + +static void PreviousDesk() { + Desktop* search=Desktop::first; + while (search->next && search->next!=Desktop::current()){ + search=search->next; + } + Desktop::current(search); +} + +// warning: this assummes it is bound to Fn key: +static void DeskNumber() { + Desktop::current(Desktop::number(Fl::event_key()-FL_F, 1)); +} +#endif + +#if WMX_HOTKEYS || CDE_HOTKEYS + +static void Raise() { // Alt+Up + Frame* f = Frame::activeFrame(); + if (f) f->raise(); +} + +static void Lower() { // Alt+Down + Frame* f = Frame::activeFrame(); + if (f) f->lower(); +} + +static void Iconize() { // Alt+Enter + Frame* f = Frame::activeFrame(); + if (f) f->iconize(); + else ShowMenu(); // so they can deiconize stuff +} + +static void Close() { // Alt+Delete + Frame* f = Frame::activeFrame(); + if (f) f->close(); +} + +#endif + +//////////////////////////////////////////////////////////////// + +static struct {int key; void (*func)();} keybindings[] = { +#if STANDARD_HOTKEYS || MINIMUM_HOTKEYS + // these are very common and tend not to conflict, due to Windoze: + {FL_ALT+FL_Escape, ShowMenu}, + {FL_ALT+FL_Menu, ShowMenu}, +#endif +#if STANDARD_HOTKEYS + {FL_ALT+FL_Tab, NextWindow}, + {FL_ALT+FL_SHIFT+FL_Tab,PreviousWindow}, + {FL_ALT+FL_SHIFT+0xfe20,PreviousWindow}, // XK_ISO_Left_Tab +#endif +#if KWM_HOTKEYS && DESKTOPS // KWM uses these to switch desktops +// {FL_CTRL+FL_Tab, NextDesk}, +// {FL_CTRL+FL_SHIFT+FL_Tab,PreviousDesk}, +// {FL_CTRL+FL_SHIFT+0xfe20,PreviousDesk}, // XK_ISO_Left_Tab + {FL_CTRL+FL_F+1, DeskNumber}, + {FL_CTRL+FL_F+2, DeskNumber}, + {FL_CTRL+FL_F+3, DeskNumber}, + {FL_CTRL+FL_F+4, DeskNumber}, + {FL_CTRL+FL_F+5, DeskNumber}, + {FL_CTRL+FL_F+6, DeskNumber}, + {FL_CTRL+FL_F+7, DeskNumber}, + {FL_CTRL+FL_F+8, DeskNumber}, + {FL_CTRL+FL_F+9, DeskNumber}, + {FL_CTRL+FL_F+10, DeskNumber}, + {FL_CTRL+FL_F+11, DeskNumber}, + {FL_CTRL+FL_F+12, DeskNumber}, +#endif +#if WMX_HOTKEYS + // wmx also sets all these, they seem pretty useful: + {FL_ALT+FL_Up, Raise}, + {FL_ALT+FL_Down, Lower}, + {FL_ALT+FL_Enter, Iconize}, + {FL_ALT+FL_Delete, Close}, + //{FL_ALT+FL_Page_Up, ToggleMaxH}, + //{FL_ALT+FL_Page_Down,ToggleMaxW}, +#endif +#if WMX_DESK_HOTKEYS && DESKTOPS + // these wmx keys are not set by default as they break NetScape: + {FL_ALT+FL_Left, PreviousDesk}, + {FL_ALT+FL_Right, NextDesk}, +#endif +#if CDE_HOTKEYS + // CDE hotkeys (or at least what SGI's 4DWM uses): + {FL_ALT+FL_F+1, Raise}, +//{FL_ALT+FL_F+2, unknown}, // KWM uses this to run a typed-in command + {FL_ALT+FL_F+3, Lower}, + {FL_ALT+FL_F+4, Close}, // this matches KWM +//{FL_ALT+FL_F+5, Restore}, // useless because no icons visible +//{FL_ALT+FL_F+6, unknown}, // ? +//{FL_ALT+FL_F+7, Move}, // grabs the window for movement +//{FL_ALT+FL_F+8, Resize}, // grabs the window for resizing + {FL_ALT+FL_F+9, Iconize}, +//{FL_ALT+FL_F+10, Maximize}, +//{FL_ALT+FL_F+11, unknown}, // ? + {FL_ALT+FL_F+12, Close}, // actually does "quit" +#else +#if DESKTOPS && DESKTOP_HOTKEYS + // seem to be common to Linux window managers + {FL_ALT+FL_F+1, DeskNumber}, + {FL_ALT+FL_F+2, DeskNumber}, + {FL_ALT+FL_F+3, DeskNumber}, + {FL_ALT+FL_F+4, DeskNumber}, + {FL_ALT+FL_F+5, DeskNumber}, + {FL_ALT+FL_F+6, DeskNumber}, + {FL_ALT+FL_F+7, DeskNumber}, + {FL_ALT+FL_F+8, DeskNumber}, + {FL_ALT+FL_F+9, DeskNumber}, + {FL_ALT+FL_F+10, DeskNumber}, + {FL_ALT+FL_F+11, DeskNumber}, + {FL_ALT+FL_F+12, DeskNumber}, +#endif +#endif + {0}}; + +int Handle_Hotkey() { + for (int i = 0; keybindings[i].key; i++) { + if (Fl::test_shortcut(keybindings[i].key) || + (keybindings[i].key & 0xFFFF) == FL_Delete + && Fl::event_key() == FL_BackSpace// fltk bug? + ) { + keybindings[i].func(); + return 1; + } + } + return 0; +} + +extern Fl_Window* Root; + +void Grab_Hotkeys() { + Window root = fl_xid(Root); + for (int i = 0; keybindings[i].key; i++) { + int k = keybindings[i].key; + int keycode = XKeysymToKeycode(fl_display, k & 0xFFFF); + if (!keycode) continue; + // Silly X! we need to ignore caps lock & numlock keys by grabbing + // all the combinations: + XGrabKey(fl_display, keycode, k>>16, root, 0, 1, 1); + XGrabKey(fl_display, keycode, (k>>16)|2, root, 0, 1, 1); // CapsLock + XGrabKey(fl_display, keycode, (k>>16)|16, root, 0, 1, 1); // NumLock + XGrabKey(fl_display, keycode, (k>>16)|18, root, 0, 1, 1); // both + } +} + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..486c524 --- /dev/null +++ b/Makefile @@ -0,0 +1,84 @@ +SHELL=/bin/sh + +PROGRAM = flwm +VERSION = 0.25 + +CXXFILES = main.C Frame.C Rotated.C Menu.C FrameWindow.C Desktop.C Hotkeys.C + +LIBS = -lfltk + +MANPAGE = 1 + +################################################################ + +OBJECTS = $(CXXFILES:.C=.o) + +all: $(PROGRAM) + +$(PROGRAM) : $(OBJECTS) + $(CXX) $(LDFLAGS) -o $(PROGRAM) $(OBJECTS) $(LIBS) $(LDLIBS) + +makeinclude: configure + ./configure +include makeinclude + +.SUFFIXES : .fl .do .C .c .H + +.C.o : + $(CXX) $(CXXFLAGS) -c $< +.C : + $(CXX) $(CXXFLAGS) -c $< +.fl.C : + -fluid -c $< +.fl.H : + -fluid -c $< + +clean : + -@ rm -f *.o $(PROGRAM) $(CLEAN) core *~ makedepend + @touch makedepend + +depend: + $(MAKEDEPEND) -I.. $(CXXFLAGS) $(CXXFILES) $(CFILES) > makedepend +makedepend: + touch makedepend +include makedepend + +install: $(PROGRAM) + $(INSTALL) -s $(PROGRAM) $(bindir)/$(PROGRAM) + $(INSTALL) $(PROGRAM).$(MANPAGE) $(mandir)/man$(MANPAGE)/$(PROGRAM).$(MANPAGE) + +uninstall: + -@ rm -f $(bindir)/$(PROGRAM) + -@ rm -f $(mandir)/man$(MANPAGE)/$(PROGRAM).$(MANPAGE) + +dist: + cat /dev/null > makedepend + -@mkdir $(PROGRAM)-$(VERSION) + -@ln README Makefile configure install-sh makedepend *.C *.H *.h *.in *.fl $(PROGRAM).$(MANPAGE) flwm_wmconfig $(PROGRAM)-$(VERSION) + tar -cvzf $(PROGRAM)-$(VERSION).tgz $(PROGRAM)-$(VERSION) + -@rm -r $(PROGRAM)-$(VERSION) + +exedist: + -@mkdir $(PROGRAM)-$(VERSION)-x86 + -@ln README $(PROGRAM) $(PROGRAM).$(MANPAGE) flwm_wmconfig $(PROGRAM)-$(VERSION)-x86 + tar -cvzf $(PROGRAM)-$(VERSION)-x86.tgz $(PROGRAM)-$(VERSION)-x86 + -@rm -r $(PROGRAM)-$(VERSION)-x86 + +################################################################ + +PROGRAM_D = $(PROGRAM)_d + +debug: $(PROGRAM_D) + +OBJECTS_D = $(CXXFILES:.C=.do) $(CFILES:.c=.do) + +.C.do : + $(CXX) -I.. $(CXXFLAGS_D) -c -o $@ $< +.c.do : + $(CC) -I.. $(CFLAGS_D) -c -o $@ $< + +$(PROGRAM_D) : $(OBJECTS_D) + $(CXX) $(LDFLAGS) -o $(PROGRAM_D) $(OBJECTS_D) $(LIBS) $(LDLIBS) + +rotated_test: Rotated.o rotated_test.C + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o rotated_test rotated_test.C Rotated.o $(LIBS) $(LDLIBS) diff --git a/Menu.C b/Menu.C new file mode 100644 index 0000000..5e1883e --- /dev/null +++ b/Menu.C @@ -0,0 +1,627 @@ +// Menu.cxx + +#include "config.h" +#include "Frame.H" +#if DESKTOPS +#include "Desktop.H" +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "FrameWindow.H" + +#include +#include +#include + +// it is possible for the window to be deleted or withdrawn while +// the menu is up. This will detect that case (with reasonable +// reliability): +static int +window_deleted(Frame* c) +{ + return c->state() != NORMAL + && c->state() != ICONIC + && c->state() != OTHER_DESKTOP; +} + +static void +frame_callback(Fl_Widget*, void*d) +{ + Frame* c = (Frame*)d; + if (window_deleted(c)) return; + c->raise(); + c->activate(2); +} + +#if DESKTOPS +// raise it but also put it on the current desktop: +static void +move_frame_callback(Fl_Widget*, void*d) +{ + Frame* c = (Frame*)d; + if (window_deleted(c)) return; + c->desktop(Desktop::current()); + c->raise(); + c->activate(2); +} +#endif + +#define SCREEN_DX 1 // offset to corner of contents area +#define SCREEN_W (MENU_ICON_W-2) // size of area to draw contents in +#define SCREEN_H (MENU_ICON_H-2) // size of area to draw contents in + +#define MAX_NESTING_DEPTH 32 + +extern Fl_Window* Root; + +static void +frame_label_draw(const Fl_Label* o, int X, int Y, int W, int H, Fl_Align align) +{ + Frame* f = (Frame*)(o->value); + if (window_deleted(f)) return; + fl_draw_box(FL_THIN_DOWN_BOX, X, Y, MENU_ICON_W, MENU_ICON_H, FL_GRAY); + for (Frame* c = Frame::first; c; c = c->next) { + if (c->state() != UNMAPPED && (c==f || c->is_transient_for(f))) { + int x = ((c->x()-Root->x())*SCREEN_W+Root->w()/2)/Root->w(); + int w = (c->w()*SCREEN_W+Root->w()-1)/Root->w(); + if (w > SCREEN_W) w = SCREEN_W; + if (w < 3) w = 3; + if (x+w > SCREEN_W) x = SCREEN_W-w; + if (x < 0) x = 0; + int y = ((c->y()-Root->y())*SCREEN_H+Root->h()/2)/Root->h(); + int h = (c->h()*SCREEN_H+Root->h()-1)/Root->h(); + if (h > SCREEN_H) h = SCREEN_H; + if (h < 3) h = 3; + if (y+h > SCREEN_H) y = SCREEN_H-h; + if (y < 0) y = 0; + fl_color(FL_BLACK); + if (c->state() == ICONIC) + fl_rect(X+x+SCREEN_DX, Y+y+SCREEN_DX, w, h); + else + fl_rectf(X+x+SCREEN_DX, Y+y+SCREEN_DX, w, h); + } + } + fl_font(o->font, o->size); + fl_color((Fl_Color)o->color); + const char* l = f->label(); if (!l) l = "unnamed"; + fl_draw(l, X+MENU_ICON_W+3, Y, W-MENU_ICON_W-3, H, align); +} + +static void +frame_label_measure(const Fl_Label* o, int& W, int& H) +{ + Frame* f = (Frame*)(o->value); + if (window_deleted(f)) {W = MENU_ICON_W+3; H = MENU_ICON_H; return;} + const char* l = f->label(); if (!l) l = "unnamed"; + fl_font(o->font, o->size); + fl_measure(l, W, H); + W += MENU_ICON_W+3; + if (W > MAX_MENU_WIDTH) W = MAX_MENU_WIDTH; + if (H < MENU_ICON_H) H = MENU_ICON_H; +} + +// This labeltype is used for non-frame items so the text can line +// up with the icons: + +static void +label_draw(const Fl_Label* o, int X, int Y, int W, int H, Fl_Align align) +{ + fl_font(o->font, o->size); + fl_color((Fl_Color)o->color); + fl_draw(o->value, X+MENU_ICON_W+3, Y, W-MENU_ICON_W-3, H, align); +} + +static void +label_measure(const Fl_Label* o, int& W, int& H) +{ + fl_font(o->font, o->size); + fl_measure(o->value, W, H); + W += MENU_ICON_W+3; + if (W > MAX_MENU_WIDTH) W = MAX_MENU_WIDTH; + if (H < MENU_ICON_H) H = MENU_ICON_H; +} + +#define FRAME_LABEL FL_FREE_LABELTYPE +#define TEXT_LABEL Fl_Labeltype(FL_FREE_LABELTYPE+1) + +//////////////////////////////////////////////////////////////// + +static void +cancel_cb(Fl_Widget* w, void*) +{ + w->window()->hide(); +} + +#if DESKTOPS + +static void +desktop_cb(Fl_Widget*, void* v) +{ + Desktop::current((Desktop*)v); +} + +static void +delete_desktop_cb(Fl_Widget*, void* v) +{ + delete (Desktop*)v; +} + +#if ASK_FOR_NEW_DESKTOP_NAME + +static Fl_Input* new_desktop_input; + +static void +new_desktop_ok_cb(Fl_Widget* w, void*) +{ + w->window()->hide(); + Desktop::current(new Desktop(new_desktop_input->value(), Desktop::available_number())); +} + +static void +new_desktop_cb(Fl_Widget*, void*) +{ + if (!new_desktop_input) { + FrameWindow* w = new FrameWindow(190,90); + new_desktop_input = new Fl_Input(10,30,170,25,"New desktop name:"); + new_desktop_input->align(FL_ALIGN_TOP_LEFT); + new_desktop_input->labelfont(FL_BOLD); + Fl_Return_Button* b = new Fl_Return_Button(100,60,80,20,"OK"); + b->callback(new_desktop_ok_cb); + Fl_Button* b2 = new Fl_Button(10,60,80,20,"Cancel"); + b2->callback(cancel_cb); + w->set_non_modal(); + w->end(); + } + char buf[120]; + sprintf(buf, "Desktop %d", Desktop::available_number()); + new_desktop_input->value(buf); + new_desktop_input->window()->hotspot(new_desktop_input); + new_desktop_input->window()->show(); +} + +#else // !ASK_FOR_NEW_DESKTOP_NAME + +static void +new_desktop_cb(Fl_Widget*, void*) +{ + char buf[120]; + int i = Desktop::available_number(); + sprintf(buf, "Desktop %d", i); + Desktop::current(new Desktop(buf, i)); +} + +#endif + +#endif +//////////////////////////////////////////////////////////////// + +static void +exit_cb(Fl_Widget*, void*) +{ + Frame::save_protocol(); + exit(0); +} + +static void +logout_cb(Fl_Widget*, void*) +{ + static FrameWindow* w; + if (!w) { + w = new FrameWindow(190,90); + Fl_Box* l = new Fl_Box(0, 0, 190, 60, "Really log out?"); + l->labelfont(FL_BOLD); + Fl_Return_Button* b = new Fl_Return_Button(100,60,80,20,"OK"); + b->callback(exit_cb); + Fl_Button* b2 = new Fl_Button(10,60,80,20,"Cancel"); + b2->callback(cancel_cb); + w->set_non_modal(); + w->end(); + } + w->hotspot(w); + w->show(); +} + +//////////////////////////////////////////////////////////////// + +#include +#include +#include + +#if XTERM_MENU_ITEM || WMX_MENU_ITEMS + +static const char* xtermname = "xterm"; + +static void +spawn_cb(Fl_Widget*, void*n) +{ + char* name = (char*)n; + // strange code thieved from 9wm to avoid leaving zombies + if (fork() == 0) { + if (fork() == 0) { + close(ConnectionNumber(fl_display)); + if (name == xtermname) execlp(name, name, "-ut", 0); + else execl(name, name, 0); + fprintf(stderr, "flwm: can't run %s, %s\n", name, strerror(errno)); + XBell(fl_display, 70); + exit(1); + } + exit(0); + } + wait((int *) 0); +} + +#endif + +static Fl_Menu_Item other_menu_items[] = { +#if XTERM_MENU_ITEM + {"New xterm", 0, spawn_cb, (void*)xtermname, 0, 0, 0, 12}, +#endif +#if DESKTOPS + {"New desktop", 0, new_desktop_cb, 0, 0, 0, 0, 12}, +#endif + {"Logout", 0, logout_cb, 0, 0, 0, 0, 12}, + {0}}; +#define num_other_items (sizeof(other_menu_items)/sizeof(Fl_Menu_Item)) + +// use this to fill in a menu location: +static void +init(Fl_Menu_Item& m, const char* data) +{ +#ifdef HAVE_STYLES + m.style = 0; +#endif + m.label(data); + m.flags = 0; + m.labeltype(FL_NORMAL_LABEL); + m.shortcut(0); + m.labelfont(MENU_FONT_SLOT); + m.labelsize(MENU_FONT_SIZE); + m.labelcolor(FL_BLACK); +} + +#if WMX_MENU_ITEMS + +// wmxlist is an array of char* pointers (for efficient sorting purposes), +// which are stored in wmxbuffer (for memory efficiency and to avoid +// freeing and fragmentation) +static char** wmxlist = NULL; +static int wmxlistsize = 0; +// wmx commands are read from ~/.wmx, +// they are stored null-separated here: +static char* wmxbuffer = NULL; +static int wmxbufsize = 0; +static int num_wmx = 0; +static time_t wmx_time = 0; +static int wmx_pathlen = 0; + +static int +scan_wmx_dir (char *path, int bufindex, int nest) +{ + DIR* dir = opendir(path); + struct stat st; + int pathlen = strlen (path); + if (dir) { + struct dirent* ent; + while ((ent=readdir(dir))) { + if (ent->d_name[0] == '.') + continue; + strcpy(path+pathlen, ent->d_name); + if (stat(path, &st) < 0) continue; + int len = pathlen+strlen(ent->d_name); + // worst-case alloc needs + if (bufindex+len+nest+1 > wmxbufsize) + wmxbuffer = (char*)realloc(wmxbuffer, (wmxbufsize+=1024)); + for (int i=0; i toupper(*pB)) + return(1); + if (toupper(*pA) < toupper(*pB)) + return(-1); + pA++; + pB++; + } + if (*pA) + return(1); + if (*pB) + return(-1); + return(0); +} + +static void +load_wmx() +{ + const char* home=getenv("HOME"); if (!home) home = "."; + char path[1024]; + strcpy(path, home); + if (path[strlen(path)-1] != '/') strcat(path, "/"); + strcat(path, ".wmx/"); + struct stat st; if (stat(path, &st) < 0) return; + if (st.st_mtime == wmx_time) return; + wmx_time = st.st_mtime; + num_wmx = 0; + wmx_pathlen = strlen(path); + scan_wmx_dir(path, 0, 0); + + // Build wmxlist + if (num_wmx > wmxlistsize) { + if (wmxlist) + delete [] wmxlist; + wmxlist = new char *[num_wmx]; + wmxlistsize = num_wmx; + } + for (int i=0; itransient_for()) + if (a == c) return 1; + return 0; +} + +void +ShowTabMenu(int tab) +{ + + static char beenhere; + if (!beenhere) { + beenhere = 1; + Fl::set_labeltype(FRAME_LABEL, frame_label_draw, frame_label_measure); + Fl::set_labeltype(TEXT_LABEL, label_draw, label_measure); + if (exit_flag) { + Fl_Menu_Item* m = other_menu_items+num_other_items-2; + m->label("Exit"); + m->callback(exit_cb); + } + } + + static Fl_Menu_Item* menu = 0; + static int arraysize = 0; + +#if DESKTOPS + int one_desktop = !Desktop::first->next; +#endif + + // count up how many items are on the menu: + + int n = num_other_items; +#if WMX_MENU_ITEMS + load_wmx(); + if (num_wmx) { + n -= 1; // delete "new xterm" + // add wmx items + int level = 0; + for (int i=0; i level) + level++; + n++; + } + } +#endif + +#if DESKTOPS + // count number of items per desktop in these variables: + int numsticky = 0; + Desktop* d; + for (d = Desktop::first; d; d = d->next) d->junk = 0; +#endif + + // every frame contributes 1 item: + Frame* c; + for (c = Frame::first; c; c = c->next) { + if (c->state() == UNMAPPED || c->transient_for()) continue; +#if DESKTOPS + if (!c->desktop()) { + numsticky++; + } else { + c->desktop()->junk++; + } +#endif + n++; + } + +#if DESKTOPS + if (!one_desktop) { + // add the sticky "desktop": + n += 2; if (!numsticky) n++; + if (Desktop::current()) { + n += numsticky; + Desktop::current()->junk += numsticky; + } + // every desktop contributes menu title, null terminator, + // and possible delete: + for (d = Desktop::first; d; d = d->next) { + n += 2; if (!d->junk) n++; + } + } +#endif + + if (n > arraysize) { + delete[] menu; + menu = new Fl_Menu_Item[arraysize = n]; + } + + // build the menu: + n = 0; + const Fl_Menu_Item* preset = 0; + const Fl_Menu_Item* first_on_desk = 0; +#if DESKTOPS + if (one_desktop) { +#endif + for (c = Frame::first; c; c = c->next) { + if (c->state() == UNMAPPED || c->transient_for()) continue; + init(menu[n],(char*)c); + menu[n].labeltype(FRAME_LABEL); + menu[n].callback(frame_callback, c); + if (is_active_frame(c)) preset = menu+n; + n++; + } + if (n > 0) first_on_desk = menu; +#if DESKTOPS + } else for (d = Desktop::first; ; d = d->next) { + // this loop adds the "sticky" desktop last, when d==0 + if (d == Desktop::current()) preset = menu+n; + init(menu[n], d ? d->name() : "Sticky"); + menu[n].callback(desktop_cb, d); + menu[n].flags = FL_SUBMENU; + n++; + if (d && !d->junk) { + init(menu[n],"delete this desktop"); + menu[n].callback(delete_desktop_cb, d); + n++; + } else if (!d && !numsticky) { + init(menu[n],"(empty)"); + menu[n].callback_ = 0; + menu[n].deactivate(); + n++; + } else { + if (d == Desktop::current()) first_on_desk = menu+n; + for (c = Frame::first; c; c = c->next) { + if (c->state() == UNMAPPED || c->transient_for()) continue; + if (c->desktop() == d || !c->desktop() && d == Desktop::current()) { + init(menu[n],(char*)c); + menu[n].labeltype(FRAME_LABEL); + menu[n].callback(d == Desktop::current() ? + frame_callback : move_frame_callback, c); + if (d == Desktop::current() && is_active_frame(c)) preset = menu+n; + n++; + } + } + } + menu[n].label(0); n++; // terminator for submenu + if (!d) break; + } +#endif + + // For ALT+Tab, move the selection forward or backward: + if (tab > 0 && first_on_desk) { + if (!preset) + preset = first_on_desk; + else { + preset++; + if (!preset->label() || preset->callback_ != frame_callback) + preset = first_on_desk; + } + } else if (tab < 0 && first_on_desk) { + if (preset && preset != first_on_desk) + preset--; + else { + // go to end of menu + preset = first_on_desk; + while (preset[1].label() && preset[1].callback_ == frame_callback) + preset++; + } + } + +#if WMX_MENU_ITEMS + // put wmx-style commands above that: + if (num_wmx > 0) { + char* cmd; + int pathlen[MAX_NESTING_DEPTH]; + int level = 0; + pathlen[0] = wmx_pathlen; + for (int i = 0; i < num_wmx; i++) { + cmd = wmxlist[i]; + cmd += strspn(cmd, "/")-1; + init(menu[n], cmd+pathlen[level]); +#if DESKTOPS + if (one_desktop) +#endif + if (!level) + menu[n].labeltype(TEXT_LABEL); + + int nextlev = (i==num_wmx-1)?0:strspn(wmxlist[i+1], "/")-1; + if (nextlev < level) { + menu[n].callback(spawn_cb, cmd); + // Close 'em off + for (; level>nextlev; level--) + init(menu[++n], 0); + } else if (nextlev > level) { + // This should be made a submenu + pathlen[++level] = strlen(cmd)+1; // extra for next trailing / + menu[n].flags = FL_SUBMENU; + menu[n].callback((Fl_Callback*)0); + } else { + menu[n].callback(spawn_cb, cmd); + } + n++; + } + } + + // put the fixed menu items at the bottom: +#if XTERM_MENU_ITEM + if (num_wmx) // if wmx commands, delete the built-in xterm item: + memcpy(menu+n, other_menu_items+1, sizeof(other_menu_items)-sizeof(Fl_Menu_Item)); + else +#endif +#endif + memcpy(menu+n, other_menu_items, sizeof(other_menu_items)); +#if DESKTOPS + if (one_desktop) +#endif + // fix the menus items so they are indented to align with window names: + while (menu[n].label()) menu[n++].labeltype(TEXT_LABEL); + + const Fl_Menu_Item* picked = + menu->popup(Fl::event_x(), Fl::event_y(), 0, preset); + if (picked && picked->callback()) picked->do_callback(0); +} + +void ShowMenu() {ShowTabMenu(0);} diff --git a/README b/README new file mode 100644 index 0000000..5214641 --- /dev/null +++ b/README @@ -0,0 +1,121 @@ +flwm Version 0.25 + +---------------------------------------------------------------- +How to compile flwm: +---------------------------------------------------------------- + +You need fltk. If you do not have it yet, download it from +http://fltk.easysw.com, and compile and install it. + +To customize flwm (for instance to turn on click-to-type), edit the +config.h file. + +Type "./configure" (not necessary if you have gmake) + +Type "make" + +Become superuser and type "make install" + +If you wish to edit the code, type "make depend" + +---------------------------------------------------------------- +How to run flwm: +---------------------------------------------------------------- + +Flwm should be run by X when it logs you in. This is done by putting +a call to flwm into the file ~/.xinitrc. With any luck you already +have this file. If not try copying /usr/X11/lib/X11/xinit/xinitrc. +Edit the file and try to remove any call to another window manager +(these are usually near the end). + +Recommended contents of ~/.xinitrc: + +#!/bin/sh +xsetroot -solid \#006060 +xrdb .Xresources +# +flwm & +WindowManager=$! +# +wait $WindowManager + +ALLOWING THE WINDOW MANAGER TO EXIT W/O LOGOUT: + +That is the most user-friendly but it logs you out when flwm exits, +which means it logs out if flwm crashes (:-)) and you cannot switch +window managers. Another possibility is to run another program last +so flwm can exit, by putting lines like this at the end: + +/usr/local/bin/flwm -x & +exec rxvt -geometry 80x11+8-8 -C -T "Ctrl-D_to_logout" + +The -x tells flwm to put "exit" on the menu rather than "logout". + +REDHAT USERS: + +You may want to run the program "./flwm_wmconfig". This will read +RedHat's window manager menu configuration files and build an initial +.wmx directory so you have a large set of menu items that run +programs. + +SGI IRIX: + +You need to edit the file ~/.xsession instead of ~/.xinitrc. + +SGI's version of XDM has a nice feature so that the window manager can +still have a logout command, but you are not logged out if it +crashes. This is done by running the programs "reaper" and +"endsession", as in this sample .xsession file: + +#! /bin/sh +xsetroot -solid \#004040 +xrdb .Xresources +reaper +flwm -x & +xwsh -console -t console -iconic & + +Also create the file "~/.wmx/Logout" with these contents: + +#! /bin/sh +endsession + +The result will be that flwm has a menu itme "Logout" that logs you +out. + +---------------------------------------------------------------- +Usage: +---------------------------------------------------------------- + +Type "man flwm" for the manual page. + +---------------------------------------------------------------- +Acknoledgements +---------------------------------------------------------------- + +This program was inspired by and much code copied from the "wm2" +window manager by Chris Cannam + +Code contributions by Steve );Hara-Smith + +---------------------------------------------------------------- +Copyright (C) 1998-1999 Bill Spitzak +---------------------------------------------------------------- +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or (at +your option) any later version. + +This program 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 +General Public License for more details. + +You should have received a copy of the GNU 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. + +Written by Bill Spitzak spitzak@d2.com +---------------------------------------------------------------- +END +---------------------------------------------------------------- diff --git a/Rotated.C b/Rotated.C new file mode 100644 index 0000000..daa4d43 --- /dev/null +++ b/Rotated.C @@ -0,0 +1,427 @@ +// Rotated text drawing with X. + +// Original code: +// Copyright (c) 1992 Alan Richardson (mppa3@uk.ac.sussex.syma) */ +// +// Modifications for fltk: +// Copyright (c) 1997 Bill Spitzak (spitzak@d2.com) +// Modifications are to draw using the current fl_font. All fonts +// used are cached in local structures. This can get real expensive, +// use " + +/* xvertext, Copyright (c) 1992 Alan Richardson (mppa3@uk.ac.sussex.syma) + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose and without fee is hereby granted, provided + * that the above copyright notice appear in all copies and that both the + * copyright notice and this permission notice appear in supporting + * documentation. All work developed as a consequence of the use of + * this program should duly acknowledge such use. No representations are + * made about the suitability of this software for any purpose. It is + * provided "as is" without express or implied warranty. + */ + +// if not defined then portions not used by flwm are included: +#define FLWM 1 + +/* ********************************************************************** */ + +#include +#include +#include "Rotated.H" +#include +#include +#include + +struct BitmapStruct { + int bit_w; + int bit_h; + Pixmap bm; +}; + +struct XRotCharStruct { + int ascent; + int descent; + int lbearing; + int rbearing; + int width; + BitmapStruct glyph; +}; + +struct XRotFontStruct { + int dir; + int height; + int max_ascent; + int max_descent; + int max_char; + int min_char; + XFontStruct* xfontstruct; + XRotCharStruct per_char[256]; +}; + +/* *** Load the rotated version of a given font *** */ + +static XRotFontStruct* +XRotLoadFont(Display *dpy, XFontStruct* fontstruct, int dir) +{ + char val; + XImage *I1, *I2; + Pixmap canvas; + Window root; + int screen; + GC font_gc; + char text[3];/*, errstr[300];*/ + + XRotFontStruct *rotfont; + int ichar, i, j, index, boxlen = 60; + int vert_w, vert_h, vert_len, bit_w, bit_h, bit_len; + int min_char, max_char; + unsigned char *vertdata, *bitdata; + int ascent, descent, lbearing, rbearing; + int on = 1, off = 0; + + /* useful macros ... */ + screen = DefaultScreen(dpy); + root = DefaultRootWindow(dpy); + + /* create the depth 1 canvas bitmap ... */ + canvas = XCreatePixmap(dpy, root, boxlen, boxlen, 1); + + /* create a GC ... */ + font_gc = XCreateGC(dpy, canvas, 0, 0); + XSetBackground(dpy, font_gc, off); + + XSetFont(dpy, font_gc, fontstruct->fid); + + /* allocate space for rotated font ... */ + rotfont = (XRotFontStruct *)malloc((unsigned)sizeof(XRotFontStruct)); + + /* determine which characters are defined in font ... */ + min_char = fontstruct->min_char_or_byte2; + if (min_char<0) min_char = 0; + rotfont->min_char = min_char; + max_char = fontstruct->max_char_or_byte2; + if (max_char>255) max_char = 255; + rotfont->max_char = max_char; + + /* some overall font data ... */ + rotfont->dir = dir; + rotfont->max_ascent = fontstruct->max_bounds.ascent; + rotfont->max_descent = fontstruct->max_bounds.descent; + rotfont->height = rotfont->max_ascent+rotfont->max_descent; + + rotfont->xfontstruct = fontstruct; + /* remember xfontstruct for `normal' text ... */ + if (dir != 0) { + /* font needs rotation ... */ + /* loop through each character ... */ + for (ichar = min_char; ichar <= max_char; ichar++) { + + index = ichar-fontstruct->min_char_or_byte2; + + /* per char dimensions ... */ + ascent = rotfont->per_char[ichar].ascent = + fontstruct->per_char[index].ascent; + descent = rotfont->per_char[ichar].descent = + fontstruct->per_char[index].descent; + lbearing = rotfont->per_char[ichar].lbearing = + fontstruct->per_char[index].lbearing; + rbearing = rotfont->per_char[ichar].rbearing = + fontstruct->per_char[index].rbearing; + rotfont->per_char[ichar].width = + fontstruct->per_char[index].width; + + /* some space chars have zero body, but a bitmap can't have ... */ + if (!ascent && !descent) + ascent = rotfont->per_char[ichar].ascent = 1; + if (!lbearing && !rbearing) + rbearing = rotfont->per_char[ichar].rbearing = 1; + + /* glyph width and height when vertical ... */ + vert_w = rbearing-lbearing; + vert_h = ascent+descent; + + /* width in bytes ... */ + vert_len = (vert_w-1)/8+1; + + XSetForeground(dpy, font_gc, off); + XFillRectangle(dpy, canvas, font_gc, 0, 0, boxlen, boxlen); + + /* draw the character centre top right on canvas ... */ + sprintf(text, "%c", ichar); + XSetForeground(dpy, font_gc, on); + XDrawImageString(dpy, canvas, font_gc, boxlen/2 - lbearing, + boxlen/2 - descent, text, 1); + + /* reserve memory for first XImage ... */ + vertdata = (unsigned char *) malloc((unsigned)(vert_len*vert_h)); + + /* create the XImage ... */ + I1 = XCreateImage(dpy, DefaultVisual(dpy, screen), 1, XYBitmap, + 0, (char *)vertdata, vert_w, vert_h, 8, 0); + +// if (I1 == NULL) ... do something here + + I1->byte_order = I1->bitmap_bit_order = MSBFirst; + + /* extract character from canvas ... */ + XGetSubImage(dpy, canvas, boxlen/2, boxlen/2-vert_h, + vert_w, vert_h, 1, XYPixmap, I1, 0, 0); + I1->format = XYBitmap; + + /* width, height of rotated character ... */ + if (dir == 2) { + bit_w = vert_w; + bit_h = vert_h; + } else { + bit_w = vert_h; + bit_h = vert_w; + } + + /* width in bytes ... */ + bit_len = (bit_w-1)/8 + 1; + + rotfont->per_char[ichar].glyph.bit_w = bit_w; + rotfont->per_char[ichar].glyph.bit_h = bit_h; + + /* reserve memory for the rotated image ... */ + bitdata = (unsigned char *)calloc((unsigned)(bit_h*bit_len), 1); + + /* create the image ... */ + I2 = XCreateImage(dpy, DefaultVisual(dpy, screen), 1, XYBitmap, 0, + (char *)bitdata, bit_w, bit_h, 8, 0); + +// if (I2 == NULL) ... error + + I2->byte_order = I2->bitmap_bit_order = MSBFirst; + + /* map vertical data to rotated character ... */ + for (j = 0; j < bit_h; j++) { + for (i = 0; i < bit_w; i++) { + /* map bits ... */ + if (dir == 1) + val = vertdata[i*vert_len + (vert_w-j-1)/8] & + (128>>((vert_w-j-1)%8)); + + else if (dir == 2) + val = vertdata[(vert_h-j-1)*vert_len + (vert_w-i-1)/8] & + (128>>((vert_w-i-1)%8)); + + else + val = vertdata[(vert_h-i-1)*vert_len + j/8] & + (128>>(j%8)); + + if (val) + bitdata[j*bit_len + i/8] = bitdata[j*bit_len + i/8] | + (128>>(i%8)); + } + } + + /* create this character's bitmap ... */ + rotfont->per_char[ichar].glyph.bm = + XCreatePixmap(dpy, root, bit_w, bit_h, 1); + + /* put the image into the bitmap ... */ + XPutImage(dpy, rotfont->per_char[ichar].glyph.bm, + font_gc, I2, 0, 0, 0, 0, bit_w, bit_h); + + /* free the image and data ... */ + XDestroyImage(I1); + XDestroyImage(I2); + /* free((char *)bitdata); -- XDestroyImage does this + free((char *)vertdata);*/ + } + + } + + for (ichar = 0; ichar < min_char; ichar++) + rotfont->per_char[ichar] = rotfont->per_char['?']; + for (ichar = max_char+1; ichar < 256; ichar++) + rotfont->per_char[ichar] = rotfont->per_char['?']; + + /* free pixmap and GC ... */ + XFreePixmap(dpy, canvas); + XFreeGC(dpy, font_gc); + + return rotfont; +} + +/* *** Free the resources associated with a rotated font *** */ + +static void XRotUnloadFont(Display *dpy, XRotFontStruct *rotfont) +{ + int ichar; + + if (rotfont->dir != 0) { + /* loop through each character, freeing its pixmap ... */ + for (ichar = rotfont->min_char; ichar <= rotfont->max_char; ichar++) + XFreePixmap(dpy, rotfont->per_char[ichar].glyph.bm); + } + /* rotfont should never be referenced again ... */ + free((char *)rotfont); +} + +/* ---------------------------------------------------------------------- */ + +/* *** A front end to XRotPaintString : mimics XDrawString *** */ + +static void +XRotDrawString(Display *dpy, XRotFontStruct *rotfont, Drawable drawable, + GC gc, int x, int y, const char *str, int len) +{ + int i, xp, yp, dir, ichar; + + if (str == NULL || len<1) return; + + dir = rotfont->dir; + + /* a horizontal string is easy ... */ + if (dir == 0) { + XSetFont(dpy, gc, rotfont->xfontstruct->fid); + XDrawString(dpy, drawable, gc, x, y, str, len); + return; + } + + /* vertical or upside down ... */ + + XSetFillStyle(dpy, gc, FillStippled); + + /* loop through each character in string ... */ + for (i = 0; iper_char[ichar].ascent; + yp = y-rotfont->per_char[ichar].rbearing; + } + else if (dir == 2) { + xp = x-rotfont->per_char[ichar].rbearing; + yp = y-rotfont->per_char[ichar].descent+1; + } + else { + xp = x-rotfont->per_char[ichar].descent+1; + yp = y+rotfont->per_char[ichar].lbearing; + } + + /* draw the glyph ... */ + XSetStipple(dpy, gc, rotfont->per_char[ichar].glyph.bm); + + XSetTSOrigin(dpy, gc, xp, yp); + + XFillRectangle(dpy, drawable, gc, xp, yp, + rotfont->per_char[ichar].glyph.bit_w, + rotfont->per_char[ichar].glyph.bit_h); + + /* advance position ... */ + if (dir == 1) + y -= rotfont->per_char[ichar].width; + else if (dir == 2) + x -= rotfont->per_char[ichar].width; + else + y += rotfont->per_char[ichar].width; + } + XSetFillStyle(dpy, gc, FillSolid); +} + +#ifndef FLWM +/* *** Return the width of a string *** */ + +static int XRotTextWidth(XRotFontStruct *rotfont, const char *str, int len) +{ + int i, width = 0, ichar; + + if (str == NULL) return 0; + + if (rotfont->dir == 0) + width = XTextWidth(rotfont->xfontstruct, str, strlen(str)); + + else + for (i = 0; iper_char[((unsigned char*)str)[i]].width; + } + + return width; +} +#endif + +/* ---------------------------------------------------------------------- */ + +// the public functions use the fltk global variables for font & gc: + +static XRotFontStruct* font; + +void draw_rotated(const char* text, int n, int x, int y, int angle) { + if (!text || !*text) return; + /* make angle positive ... */ + if (angle < 0) do angle += 360; while (angle < 0); + /* get nearest vertical or horizontal direction ... */ + int dir = ((angle+45)/90)%4; + + if (font && font->xfontstruct == fl_xfont && font->dir == dir) { + ; + } else { + if (font) XRotUnloadFont(fl_display, font); + font = XRotLoadFont(fl_display, fl_xfont, dir); + } + XRotDrawString(fl_display, font, fl_window, fl_gc, x, y, text, n); +} + +#ifndef FLWM +void draw_rotated(const char* text, int x, int y, int angle) { + if (!text || !*text) return; + draw_rotated(text, strlen(text), x, y, angle); +} +#endif + +static void draw_rot90(const char* str, int n, int x, int y) { + draw_rotated(str, n, y, -x, 90); +} +void draw_rotated90( + const char* str, // the (multi-line) string + int x, int y, int w, int h, // bounding box + Fl_Align align) { + if (!str || !*str) return; + if (w && h && !fl_not_clipped(x, y, w, h)) return; + if (align & FL_ALIGN_CLIP) fl_clip(x, y, w, h); + int a1 = align&(-16); + if (align & FL_ALIGN_LEFT) a1 |= FL_ALIGN_TOP; + if (align & FL_ALIGN_RIGHT) a1 |= FL_ALIGN_BOTTOM; + if (align & FL_ALIGN_TOP) a1 |= FL_ALIGN_RIGHT; + if (align & FL_ALIGN_BOTTOM) a1 |= FL_ALIGN_LEFT; + fl_draw(str, -(y+h), x, h, w, (Fl_Align)a1, draw_rot90); + if (align & FL_ALIGN_CLIP) fl_pop_clip(); +} + +#ifndef FLWM +static void draw_rot180(const char* str, int n, int x, int y) { + draw_rotated(str, n, -x, -y, 180); +} +void draw_rotated180( + const char* str, // the (multi-line) string + int x, int y, int w, int h, // bounding box + Fl_Align align) { + int a1 = align&(-16); + if (align & FL_ALIGN_LEFT) a1 |= FL_ALIGN_RIGHT; + if (align & FL_ALIGN_RIGHT) a1 |= FL_ALIGN_LEFT; + if (align & FL_ALIGN_TOP) a1 |= FL_ALIGN_BOTTOM; + if (align & FL_ALIGN_BOTTOM) a1 |= FL_ALIGN_TOP; + fl_draw(str, -(x+w), -(y+h), w, h, (Fl_Align)a1, draw_rot180); +} + +static void draw_rot270(const char* str, int n, int x, int y) { + draw_rotated(str, n, -y, x, 270); +} +void draw_rotated270( + const char* str, // the (multi-line) string + int x, int y, int w, int h, // bounding box + Fl_Align align) { + int a1 = align&(-16); + if (align & FL_ALIGN_LEFT) a1 |= FL_ALIGN_BOTTOM; + if (align & FL_ALIGN_RIGHT) a1 |= FL_ALIGN_TOP; + if (align & FL_ALIGN_TOP) a1 |= FL_ALIGN_LEFT; + if (align & FL_ALIGN_BOTTOM) a1 |= FL_ALIGN_RIGHT; + fl_draw(str, y, -(x+w), h, w, (Fl_Align)a1, draw_rot270); +} +#endif + diff --git a/Rotated.H b/Rotated.H new file mode 100644 index 0000000..025333e --- /dev/null +++ b/Rotated.H @@ -0,0 +1,18 @@ +// Rotated text drawing with X. + +// Original code: +// Copyright (c) 1992 Alan Richardson (mppa3@uk.ac.sussex.syma) */ +// +// Modifications for fltk: +// Copyright (c) 1997 Bill Spitzak (spitzak@d2.com) + +#ifndef Rotated_H +#define Rotated_H + +void draw_rotated(const char* text, int n, int x, int y, int angle); +void draw_rotated(const char* text, int x, int y, int angle); +void draw_rotated90(const char*, int x, int y, int w, int h, Fl_Align); +void draw_rotated270(const char*, int x, int y, int w, int h, Fl_Align); +void draw_rotated180(const char*, int x, int y, int w, int h, Fl_Align); + +#endif diff --git a/configure.in b/configure.in new file mode 100644 index 0000000..90c5298 --- /dev/null +++ b/configure.in @@ -0,0 +1,60 @@ +dnl# -*- sh -*- +dnl# the "configure" script is made from this by running GNU "autoconf" + +AC_INIT(Frame.C) +AC_PROG_CC +AC_PROG_CXX +AC_PROG_INSTALL + +AC_PATH_XTRA +echo "Ignoring libraries \"$X_PRE_LIBS\" requested by configure." +dnl# LIBS="$LIBS$X_LIBS$X_PRE_LIBS" +LIBS="$LIBS$X_LIBS" + +MAKEDEPEND="\$(CXX) -M" + +dnl# add warnings and optimization to compiler switches: +dnl# do this last so messing with switches does not break tests +if test -n "$GXX"; then + # GNU C compiler + # -Wno-return-type is necessary for Xlib header files on many systems: + CFLAGS="$CFLAGS -Wall -Wno-return-type -O2 $X_CFLAGS" + CFLAGS_D="$CFLAGS -Wall -Wno-return-type -g -DDEBUG $X_CFLAGS" + CXXFLAGS="$CXXFLAGS -Wall -Wno-return-type -O2 $X_CFLAGS" + CXXFLAGS_D="$CXXFLAGS -Wall -Wno-return-type -g -DDEBUG $X_CFLAGS" +else +if test "`(uname) 2>/dev/null`" = IRIX; then + if expr "`(uname -r)`" \>= 6.2; then + # turn on new "n32" Irix compiler: + CXX="CC -n32" + CC="cc -n32" + LD="ld -n32" + # but -M is broken so use old compiler: + MAKEDEPEND="CC -M" + # -woff 3322 is necessary due to errors in Xlib headers on IRIX + CFLAGS="$CFLAGS -fullwarn -O2 $X_CFLAGS" + CFLAGS_D="$CFLAGS -fullwarn -gslim -DDEBUG $X_CFLAGS" + CXXFLAGS="$CXXFLAGS -fullwarn -woff 3322 -O2 $X_CFLAGS" + CXXFLAGS_D="$CXXFLAGS -fullwarn -woff 3322 -gslim -DDEBUG $X_CFLAGS" + else + # old Irix compiler: + CFLAGS="$CFLAGS -O2 $X_CFLAGS" + CFLAGS_D="$CFLAGS -g -DDEBUG $X_CFLAGS" + CXXFLAGS="$CXXFLAGS +w +pp -O2 $X_CFLAGS" + CXXFLAGS_D="$CXXFLAGS +w +pp -g -DDEBUG $X_CFLAGS" + fi +else + # generic C compiler: + CFLAGS="$CFLAGS -O $X_CFLAGS" + CFLAGS_D="$CFLAGS -g -DDEBUG $X_CFLAGS" + CXXFLAGS="$CXXFLAGS -O $X_CFLAGS" + CXXFLAGS_D="$CXXFLAGS -g -DDEBUG $X_CFLAGS" +fi +fi +AC_SUBST(MAKEDEPEND) +AC_SUBST(CFLAGS_D) +AC_SUBST(CXXFLAGS_D) +dnl# AC_CONFIG_HEADER(config.h:configh.in) +AC_OUTPUT(makeinclude) + +dnl# end of configure.in diff --git a/flwm.1 b/flwm.1 new file mode 100644 index 0000000..f41cdf8 --- /dev/null +++ b/flwm.1 @@ -0,0 +1,274 @@ +.\"Man page for flwm, by Bill Spitzak. +.TH flwm 1 "15 May 1999" +.SH NAME +\fIflwm\fR - The Fast Light Window Manager +.SH SYNOPSIS +.B flwm +[-d[isplay] host:n.n] [-g[eometry] WxH+X+Y] +[-fg color] [-bg color] [-bg2 color] +.SH DESCRIPTION +.I flwm +is a very small and fast X window manager, featuring +.I no +icons and "sideways" title bars. + +.SH .xinitrc + +Recommened contents of your ~/.xinitrc file: + +.nf +#!/bin/sh +xsetroot -solid \#006060 +xrdb .Xresources +# +flwm & +WindowManager=$! +# +wait $WindowManager +.fi + +You may instead use the standard of running the window manager last with +an "exec flwm" statement at the end of .xinitrc. + +.SH SWITCHES + +.B -d[isplay] host:#.# +Sets the display and screen for flwm to manage + +.B -v[isual] # +Visual number to use (probably only works for non-color-mapped ones) + +.B -g[eometry] WxH+X+Y +Flwm will act as though the screen is only the specified area. It +will constrain initial window positions to this area and stop them at +the edges when dragging them around. This can be used to surround the +screen with fixed "toolbars" that are never covered by windows. These +toolbars must be created by a program using override-redirect so that +flwm does not try to move them. + +.B -m[aximum] WxH +Set the size of windows when the maximize buttons are pushed. +Normally this is the size of the screen. This is useful for +XFree86 servers that are run with a smaller screen than display +memory. + +.B -x +The menu will say "Exit" instead of "Logout" and will not ask for +confirmation. This is a good idea if you are running flwm in some +other way than with exec at the end of .xinitrc, since it won't log +you out then. + +.B -fg color, -bg color +Set the label color and the color of the window frames and the +menu. + +.B -c[ursor] # +What cursor to use on the desktop (you will have to experiment to find +out what each number means) + +.B -cfg color, -cbg color +Colors for the desktop and window resizing cursors + +In addition to these switches there is much customization that can be +done by editing the config.h file in the source code and recompiling. +GCC is your friend. + +.SH MENU ITEMS + +Flwm can launch programs from it's menu. This is controlled by files +in the directory +.B ~/.wmx +(this was chosen to be compatible with wmx and wm2). + +Each executable file in ~/.wmx is a program to run. Usually these are +symbolic links to the real program or very short shell scripts. + +Each subdirectory creates a child menu so you can build a hierarchy +(up to 10 deep). + +Cut and paste the following lines you your shell to create some +example files: + +.nf +mkdir ~/.wmx +ln -s /usr/bin/gimp ~/.wmx/"The Gimp" +cat << EOF > ~/.wmx/"Terminal" +#! /bin/sh +/usr/local/bin/rxvt -ut +EOF +chmod +x !* +.fi + +RedHat users can run the program +.B flwm_wmconfig +to read the /etc/X11/wmconfig directory and produce an initial set of +menu items. + +.SH MOUSE USAGE + +.B Left-click +on a window border raises window. + +.B Left-drag +will move the window when in the title bar, and will resize it in the +edges. If the window cannot be resized then it will always move the +window. What it will do is indicated by the cursor shape. + +.B Middle-click +on a window border lowers it to bottom. + +.B Middle-drag +anywhere on window border will move the window. + +When you move a window it will stop at the edges of the screen. +Dragging about 150 pixels further will unstick it and let you drag it +off the screen. + +.B Right-click +on a window border pops up the menu. + +.B Any button +on the desktop will pop up the menu. + +.SH BUTTONS + +The empty button "iconizes" the window: it will completely vanish. To +get it back use the menu. + +The vertical-bar button "shades" (or "Venetian blinds"?) the window. +Click it again to restore the window. You can also resize the shaded +window to a new height or "open" it by resizing horizontally. + +The two buttons below it toggle maximum height and/or maximum width. + +The X button at the bottom closes the window. + +.SH MENU + +.B Right-click +on window border, or +.B any-click +on the desktop, or typing +.B Alt+Esc +or +.B Alt+Tab +or +.B Alt+Shift+Tab +will pop up the menu. + +Releasing Alt will pick the current menu item. This makes flwm work +very much (exactly?) like the Windows 95 shortcuts. + +Each main window is a menu item. If the window is "iconized" the +little picture shows an open rectangle, otherwise it shows a filled +rectangle. Picking a menu item deiconizes and raises that window and +warps the pointer so it is current. + +.B New desktop +asks for a name of a new desktop and makes it current. The desktop +will initially be empty (except for sticky items). + +To move windows to the current desktop, pop up the menu and pick +windows off of other desktops (if using the keyboard, use left +arrow to go to the desktop names, move up and down to the other +desktop, and use right arrow to enter that desktop). The window will +be moved from the other desktop to the current one. + +To switch to another desktop, pick the title of the desktop (if using +the keyboard, use left arrow to go to the desktop names, move up and +down to the other desktop). + +If a desktop is empty you can delete it. It's sub menu will show +.B delete this desktop. +Pick that and the desktop is gone. + +.B Sticky +is a special "desktop": windows on it appear on all desktops. To make +a window "sticky" switch to the Sticky desktop and pick the window off +it's current desktop (thus "moving" it to the Sticky desktop). To +"unstick" a window go to another desktop and pick the window off the +sticky desktop menu. + +.B New xterm +will run a new xterm on the current desktop. Useful if +you accidentally close everything. This item does not appear if a +~/.wmx directory exists. + +.B Logout +will ask for confirmation and if so flwm will exit. + +.B Exit +will exit flwm without confirmation. This item will appear if flwm +was run with the -x switch. + +.SH HOT KEYS + +These are the defaults, the hot keys may be different depending on how +flwm was compiled: + +.B Alt+Escape +Pops up the menu with the current window preselected + +.B Alt+Tab +Pops up the menu with the next window preselected + +.B Alt+Shift+Tab +Pops up the menu with the previous window preselected + +.B Ctrl+Tab +Switch to the next desktop. + +.B Ctrl+Shift+Tab +Switch to the previous desktop. + +.B Ctrl+Function key +Switch to desktop N. + +.B Alt+Up +Raise the current window. + +.B Alt+Down +Lower the current window. + +.B Alt+Delete +Close the current window (same as clicking close box). + +.B Alt+Enter +"Iconizes" (hides) the current window. + +.SH BUGS + +It is impossible to move windows smaller than 100 pixels off +the screen. + +Only obeys "keep aspect" if the aspect ratio is 1x1. + +.SH ACKNOWLEDGEMENTS + +This program was inspired by and much code copied from the "wm2" +window manager by Chris Cannam + +Thanks to Ron Koerner for the recursive .wmx directory reading code. + +.SH COPYRIGHT + +Copyright (C) 1999 Bill Spitzak + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or (at +your option) any later version. + +This program 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 +General Public License for more details. + +You should have received a copy of the GNU 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. + +.SH AUTHORS + +Written by Bill Spitzak spitzak@d2.com diff --git a/flwm_wmconfig b/flwm_wmconfig new file mode 100755 index 0000000..e8af6c6 --- /dev/null +++ b/flwm_wmconfig @@ -0,0 +1,65 @@ +#! /usr/bin/tcl + +# flwm_wmconfig reads the RedHat "/etc/X11/wmconfig" directory (and +# the ~/.wmconfig directory) and builds a ~/.wmx directory from it so +# that you have a big complex menu in flwm! + +set count 0 + +proc read_wmfile {fname} { + global count + global env + if [catch {set f [open $fname]} message] { + puts $message + } else { + set group "" + set name "" + set exec "" + while {[gets $f list]>=0} { + if [llength $list]<3 continue + set tag [lindex $list 1] + set value [lrange $list 2 1000] + if [llength $value]==1 {set value [lindex $value 0]} + if {$tag=="group"} {set group $value} + if {$tag=="name"} {set name $value} + if {$tag=="exec"} {set exec $value} + } + close $f + if {$group=="" || $name == "" || $exec == ""} { + puts "$fname is missing necessary data" + return + } + set dir $env(HOME)/.wmx/$group + set exec [string trimright $exec "& "] + catch {mkdir [list $dir]} + if [llength $exec]==1 { + if [catch {set command [exec which $exec]}] { + puts "$fname : can't find the program $exec" + return + } else { + catch {unlink [list $dir/$name]} + link -sym $command $dir/$name + } + } else { + set f [open $dir/$name "w"] + puts $f "#! /bin/sh" + puts $f "exec $exec" + close $f + chmod +x [list $dir/$name] + } + incr count + } +} + +if ![catch {set l [glob /etc/X11/wmconfig/*]}] { + foreach f $l {read_wmfile $f} +} + +if ![catch {set l [glob $env(HOME)/.wmconfig/*]}] { + foreach f $l {read_wmfile $f} +} + +if !$count { + error "No files found in /etc/X11/wmconfig or ~/.wmconfig" +} + diff --git a/main.C b/main.C new file mode 100644 index 0000000..91c7fd9 --- /dev/null +++ b/main.C @@ -0,0 +1,374 @@ +// Define "TEST" and it will compile to make a single fake window so +// you can test the window controls. +//#define TEST 1 + +#include "Frame.H" +#include +#include +#include +#include +#include +#include "config.h" +#ifdef SHOW_CLOCK +#include +#include +#endif + +//////////////////////////////////////////////////////////////// + +static const char* program_name; +static int initializing; + +static int xerror_handler(Display* d, XErrorEvent* e) { + if (initializing && (e->request_code == X_ChangeWindowAttributes) && + e->error_code == BadAccess) + Fl::fatal("Another window manager is running. You must exit it before running %s.", program_name); +#ifndef DEBUG + if (e->error_code == BadWindow) return 0; + if (e->error_code == BadColor) return 0; +#endif + char buf1[128], buf2[128]; + sprintf(buf1, "XRequest.%d", e->request_code); + XGetErrorDatabaseText(d,"",buf1,buf1,buf2,128); + XGetErrorText(d, e->error_code, buf1, 128); + Fl::warning("%s: %s: %s 0x%lx", program_name, buf2, buf1, e->resourceid); + return 0; +} + +//////////////////////////////////////////////////////////////// +// The Fl_Root class looks like a window to fltk but is actually the +// screen's root window. This is done by using set_xid to "show" it +// rather than have fltk create the window. + +class Fl_Root : public Fl_Window { + int handle(int); +public: + Fl_Root() : Fl_Window(0,0,Fl::w(),Fl::h()) {} + void show() { + if (!shown()) Fl_X::set_xid(this, RootWindow(fl_display, fl_screen)); + } +}; +Fl_Window *Root; + +extern void ShowMenu(); +extern int Handle_Hotkey(); +extern void Grab_Hotkeys(); + +int Fl_Root::handle(int e) { + if (e == FL_PUSH) { + ShowMenu(); + return 1; + } + return 0; +} + +#if CLICK_RAISES || CLICK_TO_TYPE +extern void click_raise(Frame*); +#endif + +// fltk calls this for any events it does not understand: +static int flwm_event_handler(int e) { + if (!e) { // XEvent that fltk did not understand. + Window window = fl_xevent->xany.window; + // unfortunately most of the redirect events put the interesting + // window id in a different place: + switch (fl_xevent->type) { + case CirculateNotify: + case CirculateRequest: + case ConfigureNotify: + case ConfigureRequest: + case CreateNotify: + case DestroyNotify: + case GravityNotify: + case MapNotify: + case MapRequest: + case ReparentNotify: + case UnmapNotify: + window = fl_xevent->xmaprequest.window; + } + for (Frame* c = Frame::first; c; c = c->next) + if (c->window() == window || fl_xid(c) == window) +#if CLICK_RAISES || CLICK_TO_TYPE + if (fl_xevent->type == ButtonPress) {click_raise(c); return 1;} + else +#endif + return c->handle(fl_xevent); + switch (fl_xevent->type) { + case ButtonPress: + printf("got a button press in main\n"); + return 0; + case ConfigureRequest: { + const XConfigureRequestEvent *e = &(fl_xevent->xconfigurerequest); + XConfigureWindow(fl_display, e->window, + e->value_mask&~(CWSibling|CWStackMode), + (XWindowChanges*)&(e->x)); + return 1;} + case MapRequest: { + const XMapRequestEvent* e = &(fl_xevent->xmaprequest); + (void)new Frame(e->window); + return 1;} + case KeyRelease: { + if (!Fl::grab()) return 0; + // see if they released the alt key: + unsigned long keysym = + XKeycodeToKeysym(fl_display, fl_xevent->xkey.keycode, 0); + if (keysym == FL_Alt_L || keysym == FL_Alt_R) { + Fl::e_keysym = FL_Enter; + return Fl::grab()->handle(FL_KEYBOARD); + } + return 0;} + } + } else if (e == FL_SHORTCUT) { +#if FL_MAJOR_VERSION == 1 && FL_MINOR_VERSION == 0 && FL_PATCH_VERSION < 3 + // make the tab keys work in the menus in older fltk's: + // (they do not cycle around however, so a new fltk is a good idea) + if (Fl::grab()) { + // make fltk's menus resond to tab + shift+tab: + if (Fl::event_key() == FL_Tab) { + if (Fl::event_state() & FL_SHIFT) goto J1; + Fl::e_keysym = FL_Down; + } else if (Fl::event_key() == 0xFE20) { + J1: Fl::e_keysym = FL_Up; + } else return 0; + return Fl::grab()->handle(FL_KEYBOARD); + } +#endif + return Handle_Hotkey(); + } + return 0; +} + +#if DESKTOPS +extern void init_desktops(); +extern Atom _win_workspace; +extern Atom _win_workspace_count; +extern Atom _win_workspace_names; +#endif + +extern Atom _win_state; +extern Atom _win_hints; + +#ifdef SHOW_CLOCK +int clock_period = 1; +int clock_oldmin = 61; +int clock_alarm_on = 0; +char clock_buf[80]; + +struct sigaction flwm_clock_alarm_start = {0,}, flwm_clock_alarm_stop = {0,}; + +void flwm_update_clock(void*) { + time_t newtime; + struct tm *tm_p; + + // get current time + time(&newtime); + tm_p = localtime(&newtime); + + // Update a window frame if necessary + if (Frame::activeFrame() && tm_p->tm_min != clock_oldmin) { + if (clock_oldmin != 61) + clock_period = 60; // now that we're in sync, only update 1/minute + clock_oldmin = tm_p->tm_min; + strftime(clock_buf, 80, SHOW_CLOCK, tm_p); + Frame::activeFrame()->redraw_clock(); + } + // Now reschedule the timeout + Fl::remove_timeout(flwm_update_clock); + Fl::add_timeout(clock_period, flwm_update_clock); +} + +void flwm_clock_alarm_on(int) { + clock_alarm_on = 1; + Frame::activeFrame()->redraw_clock(); +} + +void flwm_clock_alarm_off(int) { + clock_alarm_on = 0; + Frame::activeFrame()->redraw_clock(); +} +#endif + +static const char* cfg, *cbg; +static int cursor = FL_CURSOR_ARROW; + +static void color_setup(Fl_Color slot, const char* arg, ulong value) { + if (arg) { + XColor x; + if (XParseColor(fl_display, fl_colormap, arg, &x)) + value = ((x.red>>8)<<24)|((x.green>>8)<<16)|((x.blue)); + } + Fl::set_color(slot, value); +} + +static void initialize() { + + Display* d = fl_display; + +#ifdef TEST + Window w = XCreateSimpleWindow(d, root, + 100, 100, 200, 300, 10, + BlackPixel(fl_display, 0), +// WhitePixel(fl_display, 0)); + 0x1234); + Frame* frame = new Frame(w); + XSelectInput(d, w, + ExposureMask | StructureNotifyMask | + KeyPressMask | KeyReleaseMask | FocusChangeMask | + KeymapStateMask | + ButtonPressMask | ButtonReleaseMask | + EnterWindowMask | LeaveWindowMask /*|PointerMotionMask*/ + ); +#else + + Fl::add_handler(flwm_event_handler); + + // setting attributes on root window makes me the window manager: + initializing = 1; + XSelectInput(d, fl_xid(Root), + SubstructureRedirectMask | SubstructureNotifyMask | + ColormapChangeMask | PropertyChangeMask | + ButtonPressMask | ButtonReleaseMask | + EnterWindowMask | LeaveWindowMask | + KeyPressMask | KeyReleaseMask | KeymapStateMask); + color_setup(CURSOR_FG_SLOT, cfg, CURSOR_FG_COLOR<<8); + color_setup(CURSOR_BG_SLOT, cbg, CURSOR_BG_COLOR<<8); + Root->cursor((Fl_Cursor)cursor, CURSOR_FG_SLOT, CURSOR_BG_SLOT); + +#ifdef TITLE_FONT + Fl::set_font(TITLE_FONT_SLOT, TITLE_FONT); +#endif +#ifdef MENU_FONT + Fl::set_font(MENU_FONT_SLOT, MENU_FONT); +#endif +#ifdef ACTIVE_COLOR + Fl::set_color(FL_SELECTION_COLOR, ACTIVE_COLOR<<8); +#endif + + // Gnome crap: + // First create a window that can be watched to see if wm dies: + Atom a = XInternAtom(d, "_WIN_SUPPORTING_WM_CHECK", False); + Window win = XCreateSimpleWindow(d, fl_xid(Root), -200, -200, 5, 5, 0, 0, 0); + CARD32 val = win; + XChangeProperty(d, fl_xid(Root), a, XA_CARDINAL, 32, PropModeReplace, (uchar*)&val, 1); + XChangeProperty(d, win, a, XA_CARDINAL, 32, PropModeReplace, (uchar*)&val, 1); + // Next send a list of Gnome stuff we understand: + a = XInternAtom(d, "_WIN_PROTOCOLS", 0); + Atom list[10]; unsigned int i = 0; +//list[i++] = XInternAtom(d, "_WIN_LAYER", 0); + list[i++] = _win_state = XInternAtom(d, "_WIN_STATE", 0); + list[i++] = _win_hints = XInternAtom(d, "_WIN_HINTS", 0); +//list[i++] = XInternAtom(d, "_WIN_APP_STATE", 0); +//list[i++] = XInternAtom(d, "_WIN_EXPANDED_SIZE", 0); +//list[i++] = XInternAtom(d, "_WIN_ICONS", 0); +#if DESKTOPS + list[i++] = _win_workspace = XInternAtom(d, "_WIN_WORKSPACE", 0); + list[i++] = _win_workspace_count = XInternAtom(d, "_WIN_WORKSPACE_COUNT", 0); + list[i++] = _win_workspace_names = XInternAtom(d, "_WIN_WORKSPACE_NAMES", 0); +#endif +//list[i++] = XInternAtom(d, "_WIN_FRAME_LIST", 0); + XChangeProperty(d, fl_xid(Root), a, XA_ATOM, 32, PropModeReplace, (uchar*)list, i); + + Grab_Hotkeys(); + +#ifdef SHOW_CLOCK + Fl::add_timeout(clock_period, flwm_update_clock); + flwm_clock_alarm_start.sa_handler = &flwm_clock_alarm_on; + flwm_clock_alarm_stop.sa_handler = &flwm_clock_alarm_off; + sigaction(SIGALRM, &flwm_clock_alarm_start, NULL); + sigaction(SIGCONT, &flwm_clock_alarm_stop, NULL); +#endif + + XSync(d, 0); + initializing = 0; + +#if DESKTOPS + init_desktops(); +#endif + + // find all the windows and create a Frame for each: + unsigned int n; + Window w1, w2, *wins; + XWindowAttributes attr; + XQueryTree(d, fl_xid(Root), &w1, &w2, &wins, &n); + for (i = 0; i < n; ++i) { + XGetWindowAttributes(d, wins[i], &attr); + if (attr.override_redirect || !attr.map_state) continue; + (void)new Frame(wins[i],&attr); + } + XFree((void *)wins); + +#endif +} + +//////////////////////////////////////////////////////////////// + +extern int exit_flag; +extern int max_w_switch; +extern int max_h_switch; + +// consume a switch from argv. Returns number of words eaten, 0 on error: +int arg(int argc, char **argv, int &i) { + const char *s = argv[i]; + if (s[0] != '-') return 0; + s++; + + // do single-word switches: + if (!strcmp(s,"x")) { + exit_flag = 1; + i++; + return 1; + } + + // do switches with a value: + const char *v = argv[i+1]; + if (i >= argc-1 || !v) + return 0; // all the rest need an argument, so if missing it is an error + + if (!strcmp(s, "cfg")) { + cfg = v; + } else if (!strcmp(s, "cbg")) { + cbg = v; + } else if (*s == 'c') { + cursor = atoi(v); + } else if (*s == 'v') { + int visid = atoi(v); + fl_open_display(); + XVisualInfo templt; int num; + templt.visualid = visid; + fl_visual = XGetVisualInfo(fl_display, VisualIDMask, &templt, &num); + if (!fl_visual) Fl::fatal("No visual with id %d",visid); + fl_colormap = XCreateColormap(fl_display, RootWindow(fl_display,fl_screen), + fl_visual->visual, AllocNone); + } else if (*s == 'm') { + max_w_switch = atoi(v); + while (*v && *v++ != 'x'); + max_h_switch = atoi(v); + } else + return 0; // unrecognized + // return the fact that we consumed 2 switches: + i += 2; + return 2; +} + +int main(int argc, char** argv) { + program_name = filename_name(argv[0]); + int i; if (Fl::args(argc, argv, i, arg) < argc) Fl::error( +"options are:\n" +" -d[isplay] host:#.#\tX display & screen to use\n" +" -v[isual] #\t\tvisual to use\n" +" -g[eometry] WxH+X+Y\tlimits windows to this area\n" +" -m[aximum] WxH\t\tsize of maximized windows\n" +" -x\t\t\tmenu says Exit instead of logout\n" +" -bg color\t\tFrame color\n" +" -fg color\t\tLabel color\n" +" -bg2 color\t\tText field color\n" +" -c[ursor] #\t\tCursor number for root\n" +" -cfg color\t\tCursor color\n" +" -cbg color\t\tCursor outline color" +); + Root = new Fl_Root(); + Root->show(argc,argv); // fools fltk into using -geometry to set the size + XSetErrorHandler(xerror_handler); + initialize(); + return Fl::run(); +} diff --git a/makeinclude.in b/makeinclude.in new file mode 100644 index 0000000..28ae844 --- /dev/null +++ b/makeinclude.in @@ -0,0 +1,26 @@ +# @configure_input@ + +prefix =@prefix@ +exec_prefix =@exec_prefix@ +bindir =@bindir@ +mandir =@mandir@ +includedir =@includedir@ +libdir =@libdir@ +srcdir =@srcdir@ +VPATH =@srcdir@ + +# compiler names: +CXX =@CXX@ +CC =@CC@ +MAKEDEPEND =@MAKEDEPEND@ + +# flags for C++ compiler: +CFLAGS =@CFLAGS@ +CFLAGS_D =@CFLAGS_D@ +CXXFLAGS =@CXXFLAGS@ +CXXFLAGS_D =@CXXFLAGS_D@ + +# libraries to link with: +LDLIBS =@LIBS@ -lX11 -lXext @X_EXTRA_LIBS@ -lm + +INSTALL =@INSTALL@ diff --git a/rotated_test.C b/rotated_test.C new file mode 100644 index 0000000..2714376 --- /dev/null +++ b/rotated_test.C @@ -0,0 +1,118 @@ +// Test the xvertext routines for rotated text + +#include +#include +#include +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////// + +#include "Rotated.H" + +class RotText : public Fl_Box { + void draw(); +public: + RotText(int X, int Y, int W, int H, const char* L = 0) : + Fl_Box(X,Y,W,H,L) {} +}; + +void RotText::draw() { + draw_box(); + fl_color(FL_BLACK); + fl_font(labelfont(), labelsize()); + draw_rotated90(label(), x(), y(), w(), h(), align()); +} + +//////////////////////////////////////////////////////////////// + +Fl_Toggle_Button *leftb,*rightb,*topb,*bottomb,*insideb,*clipb,*wrapb; +RotText *text; +Fl_Input *input; +Fl_Hor_Value_Slider *fonts; +Fl_Hor_Value_Slider *sizes; +Fl_Double_Window *window; + +void button_cb(Fl_Widget *,void *) { + int i = 0; + if (leftb->value()) i |= FL_ALIGN_LEFT; + if (rightb->value()) i |= FL_ALIGN_RIGHT; + if (topb->value()) i |= FL_ALIGN_TOP; + if (bottomb->value()) i |= FL_ALIGN_BOTTOM; + if (insideb->value()) i |= FL_ALIGN_INSIDE; + if (clipb->value()) i |= FL_ALIGN_CLIP; + if (wrapb->value()) i |= FL_ALIGN_WRAP; + text->align(i); + window->redraw(); +} + +void font_cb(Fl_Widget *,void *) { + text->labelfont(int(fonts->value())); + window->redraw(); +} + +void size_cb(Fl_Widget *,void *) { + text->labelsize(int(sizes->value())); + window->redraw(); +} + +void input_cb(Fl_Widget *,void *) { + text->label(input->value()); + window->redraw(); +} + +int main(int argc, char **argv) { + window = new Fl_Double_Window(400,400); + + input = new Fl_Input(50,0,350,25); + input->static_value("The quick brown fox jumped over the lazy dog."); + input->when(FL_WHEN_CHANGED); + input->callback(input_cb); + + sizes= new Fl_Hor_Value_Slider(50,25,350,25,"Size:"); + sizes->align(FL_ALIGN_LEFT); + sizes->bounds(1,64); + sizes->step(1); + sizes->value(14); + sizes->callback(size_cb); + + fonts=new Fl_Hor_Value_Slider(50,50,350,25,"Font:"); + fonts->align(FL_ALIGN_LEFT); + fonts->bounds(0,15); + fonts->step(1); + fonts->value(0); + fonts->callback(font_cb); + + Fl_Group *g = new Fl_Group(0,0,0,0); + leftb = new Fl_Toggle_Button(50,75,50,25,"left"); + leftb->callback(button_cb); + rightb = new Fl_Toggle_Button(100,75,50,25,"right"); + rightb->callback(button_cb); + topb = new Fl_Toggle_Button(150,75,50,25,"top"); + topb->callback(button_cb); + bottomb = new Fl_Toggle_Button(200,75,50,25,"bottom"); + bottomb->callback(button_cb); + insideb = new Fl_Toggle_Button(250,75,50,25,"inside"); + insideb->callback(button_cb); + wrapb = new Fl_Toggle_Button(300,75,50,25,"wrap"); + wrapb->callback(button_cb); + clipb = new Fl_Toggle_Button(350,75,50,25,"clip"); + clipb->callback(button_cb); + g->resizable(insideb); + g->forms_end(); + + text= new RotText(100,225,200,100,input->value()); + text->box(FL_FRAME_BOX); + text->align(FL_ALIGN_CENTER); + window->resizable(text); + window->forms_end(); + window->show(argc,argv); + return Fl::run(); +} + +// +// End of "$Id$". +//