Current Project: A very simple X11 button implementation with no external dependencies. It also demonstrates simple cursor hover effects.
What Does This Code Do...
Implements X11 button class window. Additionally there is support for checked and unchecked states, enable, disable and hover effects. When an enabled button is selected (mouse-button-1) a message is sent to the initiating window with a message identifier relayed from button creation parameters.
What Doesn't This Code Do...
I started but have not yet completed work on including a 'spin' style button. Although dynamically resettable, the initial theme is hardcoded. Eventually I Hope to create a set of Window-Manager Property definitions to handle themeing. Finally some of the constants in the drawing routines need to be made runtime-selectable and probably some additional features should be added.
//----------------------------------------------------------------------------- // hbutton.c // // X11 hover-button implementation. // // Copyright (c) 2013 - No Fun Farms A.K.A. www.smegware.com // // All Smegware software is free; 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 software 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. // //----------------------------------------------------------------------------- // // History... // // $Source$ // $Author$ // $Revision$ // // $Log$ // //----------------------------------------------------------------------------- #include <X11/Xlib.h> #include <X11/Xutil.h> #include <stdlib.h> #include <string.h> #include "hbutton.h" #include "report.h" #include "xwinatom.h" #include "xwinhlpr.h" #define defBtnPts 3 #define defClrFace 0xFFB0B080 #define defClrHighlight 0xFFFFFFC0 #define defClrShadow 0xFF808060 #define defClrText 0xFF000000 #define defClrTextShadow 0xFFFFFFFF #define defClrBorder 0xFF606060 #define defClrBackground 0x00000000 typedef enum _hquadrant { hqNone = 0, hq1 = 0b0001, hq2 = 0b0010, hq12 = 0b0011, hq3 = 0b0100, hq4 = 0b1000 } hquadrant; struct _hbutton { Display *display; Window window; Window parent; GC gc; XRectangle map; Pixmap bgnd; Font font; XCharStruct xchar; Atom message; BtnType type; BtnState state; hquadrant substate; BtnState previous; int border; unsigned clrtext; unsigned clrtextshdw; unsigned clrface; unsigned clrhilight; unsigned clrshadow; unsigned clrborder; unsigned clrbkgnd; int bhbutton; int id; int bdeleteGC; char name[64]; }; //----------------------------------------------------------------------------- Window hbutton_get_window(phbutton btn) { Window rtn = 0; if(btn) { rtn = btn->window; } return rtn; } //----------------------------------------------------------------------------- int hbutton_get_width(phbutton btn) { int rtn = 0; if(btn) { rtn = btn->map.width; } return rtn; } //----------------------------------------------------------------------------- void hbutton_set_width(phbutton btn, int width) { if(btn) { btn->map.width = width; } } //----------------------------------------------------------------------------- int hbutton_get_height(phbutton btn) { int rtn = 0; if(btn) { rtn = btn->map.height; } return rtn; } //----------------------------------------------------------------------------- void hbutton_set_height(phbutton btn, int height) { if(btn) { btn->map.height = height; } } //----------------------------------------------------------------------------- int hbutton_get_position_x(phbutton btn) { int rtn = 0; if(btn) { rtn = btn->map.x; } return rtn; } //----------------------------------------------------------------------------- void hbutton_set_position_x(phbutton btn, int x) { if(btn) { btn->map.x = x; } } //----------------------------------------------------------------------------- int hbutton_get_position_y(phbutton btn) { int rtn = 0; if(btn) { rtn = btn->map.y; } return rtn; } //----------------------------------------------------------------------------- void hbutton_set_position_y(phbutton btn, int y) { if(btn) { btn->map.y = y; } } //----------------------------------------------------------------------------- void hbutton_set_font(phbutton btn, char *sfont) { int direction; int ascent; int descent; if(btn) { report(dbglevl_debug, " hbutton_set_font(%s).\n", sfont); if(btn->font) { XUnloadFont(btn->display, btn->font); } btn->font = XLoadFont(btn->display, sfont); if(btn->font) { XQueryTextExtents(btn->display, btn->font, btn->name, strlen(btn->name), &direction, &ascent, &descent, &btn->xchar); } } } //----------------------------------------------------------------------------- static int hbutton_sub_hit(phbutton btn, int x, int y, hquadrant q) { int x1 = 0; int x2 = 0; int y1 = 0; int y2 = 0; int rtn = 0; switch(q) { case hq1: x2 = (btn->map.width / 2); y2 = (btn->map.height / 2); break; case hq12: x2 = (btn->map.width / 2); y2 = btn->map.height; break; default: x2 = btn->map.width; y2 = btn->map.height; break; } printf("cx=%i cy=%i x=%i y=%i\n", x, y, x1, y2); if(((x >= x1) && (x < x2)) && ((y >= y1) && (y < y2))) { rtn = 1; } return rtn; } //----------------------------------------------------------------------------- static int hbutton_hit(phbutton btn, int x, int y) { int rtn = 0; if(((x >= btn->map.x) && (x < (btn->map.x + btn->map.width))) && ((y >= btn->map.y) && (y < (btn->map.y + btn->map.height)))) { rtn = 1; } return rtn; } //----------------------------------------------------------------------------- static void hbutton_draw_spin(phbutton btn) { XPoint points[4]; unsigned clrhilt = btn->clrface; unsigned clrshdw = btn->clrface; XRectangle zone = { 0, 0, 0, 0 }; switch(btn->substate) { case hq12: zone.width = btn->map.width / 2; zone.height = btn->map.height; break; default: break; } XSetForeground(btn->display, btn->gc, btn->clrface); XFillRectangle(btn->display, btn->window, btn->gc, 0, 0, btn->map.width, btn->map.height); XSetForeground(btn->display, btn->gc, btn->clrborder); XDrawLine(btn->display, btn->window, btn->gc, 0, 0, btn->map.width, 0); XDrawLine(btn->display, btn->window, btn->gc, 0, btn->map.height - 1, btn->map.width, btn->map.height - 1); points[0].x = zone.width - 2; points[0].y = 1; points[1].x = 1; points[1].y = 1; points[2].x = 1; points[2].y = zone.height - 2; XSetForeground(btn->display, btn->gc, clrhilt); XDrawLines(btn->display, btn->window, btn->gc, points, 3/*btn->points*/, CoordModeOrigin); points[0].x = 2; points[0].y = zone.height - 2; points[1].x = zone.width - 2; points[1].y = zone.height - 2; points[2].x = zone.width - 2; points[2].y = 1; XSetForeground(btn->display, btn->gc, clrshdw); XDrawLines(btn->display, btn->window, btn->gc, points, 3/*btn->points*/, CoordModeOrigin); } //----------------------------------------------------------------------------- void hbutton_draw(phbutton btn) { if(btn && btn->bhbutton) { XSetForeground(btn->display, btn->gc, btn->clrface); XFillRectangle(btn->display, btn->window, btn->gc, 0, 0, btn->map.width, btn->map.height); //if(btn->substate == BtnTypeSpin) if(btn->type == BtnTypeSpin) { hbutton_draw_spin(btn); } else { XPoint points[4]; unsigned clrhilt = btn->clrface; unsigned clrshdw = btn->clrface; report(dbglevl_verbose, "hbutton_draw(% 2s) : x=%i y=%i w=%i h=%i.\n", btn->name, btn->map.x, btn->map.y, btn->map.width, btn->map.height); switch(btn->state) { case BtnNone: break; case BtnHovering: clrhilt = btn->clrhilight; clrshdw = btn->clrshadow; break; case BtnPressed: case BtnChecked: clrhilt = btn->clrshadow; clrshdw = btn->clrhilight; break; default: break; } /* XSetForeground(btn->display, btn->gc, btn->clrface); XFillRectangle(btn->display, btn->window, btn->gc, 0, 0, btn->map.width, btn->map.height); */ //XSetForeground(btn->display, btn->gc, btn->clrborder); //XDrawLine(btn->display, btn->window, btn->gc, 0, 0, btn->width - 1, 0); //XDrawLine(btn->display, btn->window, btn->gc, 0, btn->height - 1, btn->width - 1, btn->height - 1); /* points[0].x = btn->width - 3; points[0].y = 2; points[1].x = 1; points[1].y = 2; points[2].x = 1; points[2].y = btn->height - 4; XSetForeground(btn->display, btn->gc, clrhilt); XDrawLines(btn->display, btn->window, btn->gc, points, 3, CoordModeOrigin); points[0].x = 2; points[0].y = btn->height - 3; points[1].x = btn->width - 2; points[1].y = btn->height - 3; points[2].x = btn->width - 2; points[2].y = 3; XSetForeground(btn->display, btn->gc, clrshdw); XDrawLines(btn->display, btn->window, btn->gc, points, 3, CoordModeOrigin); */ points[0].x = btn->map.width - ((btn->border * 2) + 1); points[0].y = btn->border; points[1].x = btn->border; points[1].y = btn->border; points[2].x = btn->border; points[2].y = btn->map.height - ((btn->border * 2) + 1); XSetForeground(btn->display, btn->gc, clrhilt); XDrawLines(btn->display, btn->window, btn->gc, points, 3, CoordModeOrigin); points[0].x = btn->border; points[0].y = btn->map.height - ((btn->border * 2) + 1); points[1].x = btn->map.width - ((btn->border * 2) + 1); points[1].y = btn->map.height - ((btn->border * 2) + 1); points[2].x = btn->map.width - ((btn->border * 2) + 1); points[2].y = btn->border; XSetForeground(btn->display, btn->gc, clrshdw); XDrawLines(btn->display, btn->window, btn->gc, points, 3, CoordModeOrigin); if(btn->name[0]) { //int tx = ((btn->map.width / 2) - ((btn->nx * strlen(btn->name)) / 4)); //int tx = ((btn->map.width / 2) - (btn->xchar.width / 2)) / 2; int tx = (btn->map.width - btn->xchar.width) / 2; //int ty = ((btn->map.height / 2) + (btn->ny / 2)) - 2; int ty = ((btn->map.height / 2) + ((btn->xchar.ascent + btn->xchar.descent) / 2)) - 1; switch(btn->state) { case BtnHovering: tx -= 1; ty -= 1; break; case BtnPressed: case BtnChecked: break; default: break; } if(btn->font) { XSetFont(btn->display, btn->gc, btn->font); } if(btn->state == BtnDisabled) { // Chisled effect. XSetForeground(btn->display, btn->gc, btn->clrhilight); XDrawString(btn->display, btn->window, btn->gc, tx + 2, ty + 2, btn->name, strlen(btn->name)); XSetForeground(btn->display, btn->gc, 0xFF606048); XDrawString(btn->display, btn->window, btn->gc, tx + 1, ty + 1, btn->name, strlen(btn->name)); } else { XSetForeground(btn->display, btn->gc, btn->clrtext); XDrawString(btn->display, btn->window, btn->gc, tx + 1, ty + 1, btn->name, strlen(btn->name)); } } } } } //----------------------------------------------------------------------------- // Increase reference count. int hbutton_enable_hbutton(phbutton btn) { int ref = -1; if(btn) { ref = btn->bhbutton; btn->bhbutton += 1; report(dbglevl_debug, "hbutton_enable_hbutton(%i).\n", btn->bhbutton); // Show and redraw. hbutton_draw(btn); } return ref; } //----------------------------------------------------------------------------- // Decrease reference count. int hbutton_disable_hbutton(phbutton btn) { int ref = -1; if(btn) { ref = btn->bhbutton; // FIXME: should catch -1 but not yet. btn->bhbutton -= 1; report(dbglevl_debug, "hbutton_disable_hbutton(%i).\n", btn->bhbutton); } return ref; } //----------------------------------------------------------------------------- static void hbutton_notify_parent(phbutton btn) { XEvent event; memset(&event, 0, sizeof(event)); event.xclient.type = ClientMessage; event.xclient.display = btn->display; event.xclient.window = btn->window; event.xclient.message_type = btn->message; event.xclient.format = 32; event.xclient.data.l[0] = btn->window; event.xclient.data.l[1] = btn->id; event.xclient.data.l[2] = btn->type; event.xclient.data.l[3] = btn->state; XSendEvent(btn->display, btn->parent, 0, 0, &event); } //----------------------------------------------------------------------------- static void hbutton_change(phbutton btn) { if(btn->state != BtnDisabled) { if(btn->previous != btn->state) { hbutton_draw(btn); btn->previous = btn->state; } } } //----------------------------------------------------------------------------- void hbutton_press(phbutton btn, XButtonEvent *bevent) { if(btn && (btn->state != BtnDisabled)) { if((bevent->button == Button1)) { switch(btn->type) { case BtnTypeCheck: if(btn->state == BtnChecked) { btn->state = BtnPressed;; } else { btn->state = BtnChecked; } break; default: // Catched BtnTypeNone. btn->state = BtnPressed; break; } hbutton_change(btn); } } } //----------------------------------------------------------------------------- void hbutton_release(phbutton btn, XButtonEvent *bevent) { if(btn && (btn->state != BtnDisabled)) { if((bevent->state & Button1Mask) && (bevent->button == Button1)) { switch(btn->type) { case BtnTypeCheck: if(btn->state != BtnChecked) { btn->state = BtnHovering; } // Toggle notifies both ways. hbutton_notify_parent(btn); break; default: // Catches BtnTypeNone. if(btn->state == BtnPressed) { hbutton_notify_parent(btn); } btn->state = BtnHovering; break; } hbutton_change(btn); } } } //----------------------------------------------------------------------------- void hbutton_motion(phbutton btn, XMotionEvent *mevent) { } //----------------------------------------------------------------------------- void hbutton_motion_cross(phbutton btn, XCrossingEvent *cevent) { if(btn && (btn->state != BtnDisabled)) { if(hbutton_hit(btn, cevent->x, cevent->y)) { btn->state = BtnHovering; } else { btn->state = BtnNone; } hbutton_change(btn); } } //----------------------------------------------------------------------------- void hbutton_motion_enter(phbutton btn, XCrossingEvent *cevent) { if(btn && (btn->state != BtnDisabled)) { switch(btn->type) { case BtnTypeCheck: if(btn->state != BtnChecked) { if(cevent->state & Button1Mask) { btn->state = BtnPressed; } else { btn->state = BtnHovering; } } break; case BtnTypeSpin: if(hbutton_sub_hit(btn, cevent->x, cevent->y, hq12)) { btn->state = BtnHovering; btn->substate = hq12; } break; default: // Catches BtnTypeNone. if(cevent->state & Button1Mask) { btn->state = BtnPressed; } else { btn->state = BtnHovering; } break; } hbutton_change(btn); } } //----------------------------------------------------------------------------- void hbutton_motion_leave(phbutton btn, XCrossingEvent *mevent) { if(btn && (btn->state != BtnDisabled)) { switch(btn->type) { case BtnTypeCheck: // 'Checked' state persists. if(btn->state != BtnChecked) { btn->state = BtnNone; } break; default: // Catches BtnTypeNone. btn->state = BtnNone; break; } hbutton_change(btn); } } //----------------------------------------------------------------------------- void hbutton_set_state(phbutton btn, BtnState state) { if(btn) { btn->state = state; hbutton_draw(btn); } } //----------------------------------------------------------------------------- BtnState hbutton_get_state(phbutton btn) { BtnState rtn = BtnNone; if(btn) { rtn = btn->state; } return rtn; } //----------------------------------------------------------------------------- void hbutton_set_type(phbutton btn, BtnType type) { if(btn) { btn->type = type; hbutton_draw(btn); } } //----------------------------------------------------------------------------- BtnType hbutton_get_type(phbutton btn) { BtnType rtn = BtnTypeNone; if(btn) { rtn = btn->type; } return rtn; } //----------------------------------------------------------------------------- phbutton hbutton_create(Display *display, Window wparent, GC gc, int x, int y, int width, int height, Atom message, int id, char *name) { phbutton btn = malloc(sizeof(hbutton)); if(btn) { memset(btn, 0, sizeof(hbutton)); btn->display = display; btn->parent = wparent; btn->gc = gc; btn->type = BtnTypeNone; btn->state = BtnNone; btn->previous = BtnNone; btn->border = 0; btn->clrtext = defClrText; btn->clrtextshdw = defClrTextShadow; btn->clrface = defClrFace; btn->clrhilight = defClrHighlight; btn->clrshadow = defClrShadow; btn->clrborder = defClrBorder; btn->clrbkgnd = defClrBackground; btn->map.x = 0; btn->map.y = 0; btn->map.width = width; btn->map.height = height; btn->message = message; btn->id = id; btn->name[0] = 0; btn->bhbutton = 1; // Enabled by default. if(name) { strncpy(btn->name, name, sizeof(btn->name) - 1); btn->name[sizeof(btn->name) - 1] = 0; } btn->window = XCreateSimpleWindow(btn->display, wparent, btn->map.x, btn->map.y, btn->map.width, btn->map.height, 0, btn->clrtext, btn->clrbkgnd); XSelectInput(btn->display, btn->window, ExposureMask | ButtonPressMask | ButtonReleaseMask | //PointerMotionMask | EnterWindowMask | LeaveWindowMask ); hbutton_set_font(btn, "fixed"); //btn->font = XLoadFont(btn->display, "fixed"); if(btn->gc == NULL) { btn->gc = XCreateGC(btn->display, btn->window, 0, NULL); //btn->font = XLoadFont(btn->display, "fixed"); XSetFont(btn->display, btn->gc, btn->font); btn->bdeleteGC = 1; } /* int direction; int ascent; int descent; XCharStruct xchar; XQueryTextExtents(display, btn->font, //XGContextFromGC(btn->gc), name, strlen(name), &direction, &ascent, &descent, &xchar); btn->nx = xchar.width; btn->ny = ascent + descent; */ xwin_set_window_property_atoms(btn->display, btn->window, _NET_WM_WINDOW_TYPE, (unsigned char*)&_NET_WM_WINDOW_TYPE_HBUTTON, 1); } report(dbglevl_debug, " Created hover-button : %08X window=%08X.\n", btn, btn->window); return btn; } //----------------------------------------------------------------------------- void hbutton_destroy(phbutton *pbtn) { if(pbtn) { phbutton btn = *pbtn; if(btn) { report(dbglevl_debug, " Destroying hover-button : %08X window=%08x.\n", btn, btn->window); free(btn); } *pbtn = NULL; } } //----------------------------------------------------------------------------- // end: hbutton.c //
And The Companion Header...
//----------------------------------------------------------------------------- // hbutton.h // // X11 hover-button definition. // // Copyright (c) 2013 - No Fun Farms A.K.A. www.smegware.com // // All Smegware software is free; 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 software 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. // //----------------------------------------------------------------------------- // // History... // // $Source$ // $Author$ // $Revision$ // // $Log$ // //----------------------------------------------------------------------------- typedef enum _btnstate { BtnNone = 0, BtnHovering, BtnPressed, BtnChecked, BtnDisabled } BtnState; typedef enum _btntype { BtnTypeNone = 0, BtnTypeCheck, BtnTypeRadio, BtnTypeSpin, BtnTypeDial, BtnTypeJoy } BtnType; typedef struct _hbutton hbutton, *phbutton; //----------------------------------------------------------------------------- extern Window hbutton_get_window(phbutton); extern int hbutton_get_width(phbutton); extern void hbutton_set_width(phbutton, int); extern int hbutton_get_height(phbutton); extern void hbutton_set_height(phbutton, int); extern int hbutton_get_position_x(phbutton); extern void hbutton_set_position_x(phbutton, int); extern int hbutton_get_position_y(phbutton); extern void hbutton_set_position_y(phbutton, int); extern int hbutton_enable_hbutton(phbutton); extern int hbutton_disable_hbutton(phbutton); extern void hbutton_set_font(phbutton, char*); extern void hbutton_draw(phbutton); extern void hbutton_motion(phbutton, XMotionEvent*); extern void hbutton_motion_cross(phbutton, XCrossingEvent*); extern void hbutton_motion_enter(phbutton, XCrossingEvent*); extern void hbutton_motion_leave(phbutton, XCrossingEvent*); extern void hbutton_press(phbutton, XButtonEvent*); extern void hbutton_release(phbutton, XButtonEvent*); extern void hbutton_set_state(phbutton, BtnState); extern BtnState hbutton_get_state(phbutton); extern void hbutton_set_type(phbutton, BtnType); extern BtnType hbutton_get_type(phbutton); extern phbutton hbutton_create(Display*, Window, GC, int, int, int, int, Atom, int, char*); extern void hbutton_destroy(phbutton*); //----------------------------------------------------------------------------- // end: hbutton.h //
The Code Demonstrates The Following Programming Techniques...
Conclusion...
It is known that this code has many 'Freshman' mistakes in it. It is not claimed to be good code. Hopefully though; it demonstrates some of the less obvious interface mechanisms when communicating with both a Window-Manager and a Client program. A definition of how to use the code is also missing. Not purposely, an interface spec just can't be fit into my schedule. For now. Besides... This code is not really meant to be used. Although I use this code it is presented here purely for educational purposes.
6995