A Place Where No Dreams Come True...

Current Project: A very simple X11 scrollbar implementation with no external dependencies. It also demonstrates simple cursor hover effects.

What Does This Code Do...

Implements X11 scrollbar class window. When an enabled element of the scrollbar is selected (mouse-button-1) a message is sent to the owner window with a message identifier relayed from scrollbar creation parameters along with state information. the owner window is responsible for processing the event and possibly updating the state of the scrollbar. Possible events are line up/down, page up/down, scroll top, scroll bottom and thumb position changes. Typical actions include owner; updates display; updates scrollbar state to reflect new display position.

What Doesn't This Code Do...

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.

//-----------------------------------------------------------------------------
// scrollbar.c
//
//  X11 scrollbar 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 <X11/xpm.h>

#include "xwinatom.h"
#include "xwinhlpr.h"
#include "scrollbar.h"
#include "report.h"

#define defsbSizeBorder 1
#define defsbBorder 0xFFB0B080
#define defsbFace 0xFFB0B0A8
#define defsbAlert 0xFFC0C000
#define defsbHilite 0xFFE0E0E0
#define defsbShadow 0xFFA0A098
#define defsbThumbFace 0xFFD8D8D0
#define defsbThumbAlert 0xFFC0C000
#define defsbThumbHilite 0xFFF8F8F0
#define defsbThumbShadow 0xFF808078
#define defsbBackground 0x00000000

#define SB_BUTTON_HEIGHT 16

struct _scrollbar {
  Display      *display;
  Window        parent;
  Window        window;
  int           screen;
  GC            gc;
  Pixmap        bgnd;
  Font          font;
  XRectangle    map;
  Atom          message;
  unsigned      clrBorder;
  unsigned      clrFace;
  unsigned      clrAlert;
  unsigned      clrHilite;
  unsigned      clrShadow;
  unsigned      clrThumbFace;
  unsigned      clrThumbAlert;
  unsigned      clrThumbHilite;
  unsigned      clrThumbShadow;
  unsigned      clrBackground;
  int           itemid;
  unsigned      state;
  unsigned      flags;
  int           bscrollbar;
  int           selected;
  int           sbborder;
  int           btracking;
  int           min;
  int           max;
  int           pos;
};

//-----------------------------------------------------------------------------

static void scrollbar_notify_parent(pScrollbar sb)
{
  XEvent event;

  memset(&event, 0, sizeof(event));

  event.xclient.type         = ClientMessage;
  event.xclient.display      = sb->display;
  event.xclient.window       = sb->parent;
  event.xclient.message_type = sb->message;
  event.xclient.format       = 32;
  event.xclient.data.l[0]    = sb->window;
  event.xclient.data.l[1]    = sb->itemid;
  event.xclient.data.l[2]    = sb->pos;
  event.xclient.data.l[3]    = sb->state;
  event.xclient.data.l[4]    = sb->flags;
  XSendEvent(sb->display, sb->parent, 0, 0, &event);
}

//-----------------------------------------------------------------------------
/*
static void scrollbar_set_gc_mask(pScrollbar sb, Pixmap mask, int x , int y)
{
  XGCValues vals;
  vals.clip_x_origin = x;
  vals.clip_y_origin = y;
  vals.clip_mask = mask;
  XChangeGC(sb->display, sb->gc, GCClipMask | GCClipXOrigin | GCClipYOrigin, &vals);
}
*/
//-----------------------------------------------------------------------------

Window scrollbar_get_window(pScrollbar sb)
{
  Window rtn = 0;
  if(sb)
  {
    rtn = sb->window;
  }
  return rtn;
}

//-----------------------------------------------------------------------------

static void scrollbar_get_thumb_position(pScrollbar sb, XRectangle *map)
{
  map->x      = 4;
  map->width  = sb->map.width - 8;
  map->height = ((float)((sb->map.height - (SB_BUTTON_HEIGHT * 2)) - 2) / (sb->max));
  map->y      = (map->height * sb->pos) + (SB_BUTTON_HEIGHT) + 1;
}

//-----------------------------------------------------------------------------

static void scrollbar_get_page_position(pScrollbar sb, XRectangle *map)
{
  map->x      = 3;
  map->y      = SB_BUTTON_HEIGHT;
  map->width  = sb->map.width - 6;
  map->height = sb->map.height - (SB_BUTTON_HEIGHT * 2);
}

//-----------------------------------------------------------------------------

static void scrollbar_draw_prepare(pScrollbar sb)
{
  XSetForeground(sb->display, sb->gc, sb->clrBorder);
  XFillRectangle(sb->display, sb->bgnd, sb->gc,
		 0,
		 0,
		 sb->map.width,
		 sb->map.height);
}

//-----------------------------------------------------------------------------

static void scrollbar_draw_buttons(pScrollbar sb)
{
}

//-----------------------------------------------------------------------------

static void scrollbar_draw_page(pScrollbar sb)
{
  XRectangle map;

  scrollbar_get_page_position(sb, &map);

  XSetForeground(sb->display, sb->gc, sb->clrFace);
  XFillRectangle(sb->display, sb->bgnd, sb->gc,
		 map.x + 1,
		 map.y + 1,
		 map.width - 1,
		 map.height - 1);
  XSetForeground(sb->display, sb->gc, sb->clrThumbShadow);
  XDrawRectangle(sb->display, sb->bgnd, sb->gc,
		 map.x,
		 map.y,
		 map.width,
		 map.height);
}

//-----------------------------------------------------------------------------

static void scrollbar_draw_thumb(pScrollbar sb)
{
  XRectangle map;
  XSegment segment[4];

  scrollbar_get_thumb_position(sb, &map);
  XSetForeground(sb->display, sb->gc, sb->clrThumbFace);
  XFillRectangle(sb->display, sb->bgnd, sb->gc,
		 map.x + 2,
		 map.y + 2,
		 map.width - 3,
		 map.height - 3);
  // Shadow.
  XSetForeground(sb->display, sb->gc, sb->clrThumbShadow);
  XDrawRectangle(sb->display, sb->bgnd, sb->gc,
		 map.x,
		 map.y + 1,
		 map.width,
		 map.height - 2);

  segment[0].x1 = map.x + 1;
  segment[0].y1 = map.y;
  segment[0].x2 = (map.x + map.width) - 1;
  segment[0].y2 = map.y;
  segment[1].x1 = map.x + 1;
  segment[1].y1 = map.y + map.height;
  segment[1].x2 = (map.x + map.width) - 1;
  segment[1].y2 = map.y + map.height;
  XDrawSegments(sb->display, sb->bgnd, sb->gc, segment, 2);
  // Highlight.
  XSetForeground(sb->display, sb->gc, sb->clrThumbHilite);
  segment[0].x1 = map.x + 2;
  segment[0].y1 = map.y + 1;
  segment[0].x2 = (map.x + map.width) - 2;
  segment[0].y2 = map.y + 1;
  segment[1].x1 = map.x + 1;
  segment[1].y1 = map.y + 2;
  segment[1].x2 = map.x + 1;
  segment[1].y2 = (map.y + map.height) - 2;
  segment[2].x1 = (map.x + map.width) - 1;
  segment[2].y1 = map.y + 2;
  segment[2].x2 = (map.x + map.width) - 1;
  segment[2].y2 = (map.y + map.height) - 2;
  segment[3].x1 = map.x + 2;
  segment[3].y1 = (map.y + map.height) - 1;
  segment[3].x2 = (map.x + map.width) - 2;
  segment[3].y2 = (map.y + map.height) - 1;
  XDrawSegments(sb->display, sb->bgnd, sb->gc, segment, 4);
}

//-----------------------------------------------------------------------------

void scrollbar_draw(pScrollbar sb)
{
  if(sb)
  {
    // Start with blank canvas.
    scrollbar_draw_prepare(sb);
    // Line-Up/Down buttons.
    scrollbar_draw_buttons(sb);
    // Page-up/Down area.
    scrollbar_draw_page(sb);
    // Thumb.
    scrollbar_draw_thumb(sb);
    // Damage screen - Force expose.
    XCopyArea(sb->display, sb->bgnd, sb->window, sb->gc,
    	      0, 0, sb->map.width, sb->map.height, 0, 0);
  }
}

//-----------------------------------------------------------------------------

static void scrollbar_damage(pScrollbar sb)
{
  XClearArea(sb->display, sb->window, 0, 0, 0, 0, True);
}

//-----------------------------------------------------------------------------
// Increase reference count.

int scrollbar_enable_scrollbar(pScrollbar sb)
{
  int ref = -1;
  if(sb)
  {
    ref = sb->bscrollbar;
    sb->bscrollbar += 1;
    report(dbglevl_debug, "menu_enable_scrollbar(%i).\n", sb->bscrollbar);
  }
  return ref;
}

//-----------------------------------------------------------------------------
// Decrease reference count.

int scrollbar_disable_scrollbar(pScrollbar sb)
{
  int ref = -1;
  if(sb)
  {
    ref = sb->bscrollbar;
    // FIXME: should catch -1 but not yet.
    sb->bscrollbar -= 1;
    report(dbglevl_debug, "menu_disable_scrollbar(%i).\n", sb->bscrollbar);
  }
  return ref;
}

//-----------------------------------------------------------------------------

void scrollbar_show(pScrollbar sb, XEvent *event)
{
  if(sb)
  {
    report(dbglevl_debug, " scrollbar w=%i h=%i.\n", sb->map.width, sb->map.height);
    XMapWindow(sb->display, sb->window);
  }
}

//-----------------------------------------------------------------------------

void scrollbar_hide(pScrollbar sb)
{
  if(sb)
  {
    XUnmapWindow(sb->display, sb->window);
    report(dbglevl_debug, " scrollbar hide.\n");
  }
}

//-----------------------------------------------------------------------------

void scrollbar_set_position(pScrollbar sb, int pos)
{
  if(sb)
  {
    if((sb->min >= 0) && (sb->max > 0) && (pos < sb->max))
    {
      sb->pos = pos;
    }
    else if(sb->max > 0)
    {
      sb->pos = sb->max;
    }
    else
    {
      sb->pos = 0;
    }
    scrollbar_damage(sb);
  }
}

//-----------------------------------------------------------------------------

int scrollbar_get_min(pScrollbar sb)
{
  int rtn = 0;
  if(sb)
  {
    rtn = sb->min;
  }
  return rtn;
}

//-----------------------------------------------------------------------------

int scrollbar_get_max(pScrollbar sb)
{
  int rtn = 0;
  if(sb)
  {
    rtn = sb->max - 1;
  }
  return rtn;
}

//-----------------------------------------------------------------------------

int scrollbar_get_position(pScrollbar sb)
{
  int rtn = 0;
  if(sb)
  {
    rtn = sb->pos;
  }
  return rtn;
}

//-----------------------------------------------------------------------------

void scrollbar_set_range(pScrollbar sb, int min, int max, int pos)
{
  if(sb)
  {
    sb->min = min;
    sb->max = max + 1;
    scrollbar_set_position(sb, pos);
  }
}

//-----------------------------------------------------------------------------

int scrollbar_get_range(pScrollbar sb, int *min, int *max)
{
  if(sb && min && max)
  {
    *min = sb->min;
    *max = sb->max - 1;
  }
  return scrollbar_get_position(sb);
}

//-----------------------------------------------------------------------------

static unsigned int scrollbar_hittest(pScrollbar sb, XEvent *event)
{
  XRectangle map_t;
  XRectangle map_p;
  unsigned int rtn = 0;

  if((event->xmotion.x > sb->map.x) && (event->xmotion.x < (sb->map.width + sb->map.x)) &&
     (event->xmotion.y > sb->map.y) && (event->xmotion.y < (sb->map.height + sb->map.y)))
  {
    rtn = SB_HIT_ITEM;
    // Narrow in [button-thumb-page-wasteland].
    scrollbar_get_thumb_position(sb, &map_t);
    if((event->xmotion.x > map_t.x) && (event->xmotion.x < (map_t.x + map_t.width)) &&
       (event->xmotion.y > map_t.y) && (event->xmotion.y < (map_t.y + map_t.height)))
    {
      rtn |= SB_HIT_THUMB;
    }
    else
    {
      scrollbar_get_page_position(sb, &map_p);
      if((event->xmotion.x > map_p.x) && (event->xmotion.x < (map_p.x + map_p.width)) &&
	 (event->xmotion.y > map_p.y) && (event->xmotion.y < (map_p.y + map_p.height)))
      {
	if(event->xmotion.y < map_t.y)
	{
	  rtn |= SB_HIT_PAGE_DOWN;
	}
	else
	{
	  rtn |= SB_HIT_PAGE_UP;
	}
      }
      else if(event->xmotion.y < map_p.y)
      {
	rtn |= SB_HIT_SCROLL_DOWN;
      }
      else if(event->xmotion.y > (map_p.y + map_p.height))
      {
	rtn |= SB_HIT_SCROLL_UP;
      }
    }
  }
  return rtn;
}

//-----------------------------------------------------------------------------

void scrollbar_select(pScrollbar sb, XEvent *event)
{
  sb->flags &= (SB_HIT_MASK ^ 0XFFFFFFFF);
  sb->flags |= scrollbar_hittest(sb, event);
  switch (event->xbutton.button)
  {
    case Button1:
      if(sb->flags & (SB_HIT_THUMB | SB_HIT_PAGE_UP | SB_HIT_PAGE_DOWN))
      {
	if(sb->flags & SB_HIT_THUMB)
	{
	  XGrabPointer(sb->display, sb->window, False,
		       ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
		       GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
	  sb->btracking = 1;
	}
	scrollbar_notify_parent(sb);
      }
      break;

    default:
      XUngrabPointer(sb->display, CurrentTime);
      sb->btracking = 0;
      break;
  }
}

//-----------------------------------------------------------------------------

void scrollbar_release(pScrollbar sb, XEvent *event)
{
  switch (event->xbutton.button)
  {
    case Button1:
      XUngrabPointer(sb->display, CurrentTime);
      sb->btracking = 0;
      break;

    default:
      break;
  }
}

//-----------------------------------------------------------------------------

void scrollbar_track_enter(pScrollbar sb, XEvent *event)
{
}

//-----------------------------------------------------------------------------

void scrollbar_track(pScrollbar sb, XEvent *event)
{
  XRectangle map;
  scrollbar_get_thumb_position(sb, &map);
  if(sb->btracking)
  {
    if(event->xmotion.y < map.y)
    {
      if(scrollbar_get_position(sb) > 0)
      {
	scrollbar_set_position(sb, sb->pos - 1);
	sb->flags &= (SB_HIT_SCROLL_UP ^ 0XFFFFFFFF);
	sb->flags |= SB_HIT_SCROLL_DOWN;
	scrollbar_notify_parent(sb);
      }
    }
    else if (event->xmotion.y > (map.y + map.height))
    {
      if(scrollbar_get_position(sb) < scrollbar_get_max(sb))
      {
	scrollbar_set_position(sb, sb->pos + 1);
	sb->flags &= (SB_HIT_SCROLL_DOWN ^ 0XFFFFFFFF);
	sb->flags |= SB_HIT_SCROLL_UP;
	scrollbar_notify_parent(sb);
      }
    }
  }
}

//-----------------------------------------------------------------------------

void scrollbar_track_reset(pScrollbar sb, XEvent *event)
{
}

//-----------------------------------------------------------------------------

void scrollbar_client_event(pScrollbar sb, XEvent *event)
{
  if(sb)
  {
  }
}

//-----------------------------------------------------------------------------

void scrollbar_destroy(pScrollbar *sb)
{
  if(sb)
  {
    pScrollbar _sb = *sb;
    if(_sb)
    {
      report(dbglevl_debug, " Destroying scrollbar : %08X window=%08X.\n", _sb, _sb->window);
      free(_sb);
    }
    *sb = NULL;
  }
}

//-----------------------------------------------------------------------------

pScrollbar scrollbar_create(Display *display, Window parent, GC gc, int width, int height, Atom message, int rcid)
{
  pScrollbar sb = malloc(sizeof(Scrollbar));
  if(sb)
  {
    memset(sb, 0, sizeof(Scrollbar));
    sb->display        = display;
    sb->parent         = parent;
    sb->screen         = DefaultScreen(display);
    sb->gc             = gc;
    sb->map.x          = 0;
    sb->map.y          = 0;
    sb->map.width      = width;
    sb->map.height     = height;
    sb->message        = message;
    sb->clrBorder      = defsbBorder;
    sb->clrFace        = defsbFace;
    sb->clrAlert       = defsbAlert;
    sb->clrHilite      = defsbHilite;
    sb->clrShadow      = defsbShadow;
    sb->clrThumbFace   = defsbThumbFace;
    sb->clrThumbAlert  = defsbThumbAlert;
    sb->clrThumbHilite = defsbThumbHilite;
    sb->clrThumbShadow = defsbThumbShadow;
    sb->clrBackground  = defsbBackground;
    sb->itemid         = rcid;
    sb->window  = XCreateSimpleWindow(sb->display,
				      sb->parent,
				      sb->map.x,
				      sb->map.y,
				      sb->map.width,
				      sb->map.height,
				      0,
				      sb->clrFace,
				      sb->clrBackground);
    XSelectInput(sb->display, sb->window,
		 ExposureMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask);
    sb->bgnd = XCreatePixmap(sb->display, sb->window, sb->map.width, sb->map.height, 24);
    XSetWindowBackgroundPixmap(sb->display, sb->window, sb->bgnd);

    //menu_set_font(menu, "9x15bold");//"7x14");
    xwin_set_window_property_atoms(sb->display, sb->window, _NET_WM_WINDOW_TYPE,
				   (unsigned char*)&_NET_WM_WINDOW_TYPE_SCROLLBAR, 1);
    sb->font = XLoadFont(sb->display, "fixed");
    report(dbglevl_debug, " Created scrollbar : %08X window=%08X.\n", sb, sb->window);
  }
  return sb;
}

//-----------------------------------------------------------------------------
// end: scrollbar.c
//

  

And The Companion Header...

//-----------------------------------------------------------------------------
// scrollbar.h
//
//  X11 scrollbar 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$
//
//-----------------------------------------------------------------------------

#define SB_HIT_MASK        0X0000FF00
#define SB_HIT_NOWHERE     0X00000000
#define SB_HIT_ITEM        0X00000100
#define SB_HIT_SCROLL_UP   0X00000200
#define SB_HIT_SCROLL_DOWN 0X00000400
#define SB_HIT_THUMB       0X00000800
#define SB_HIT_PAGE_UP     0X00001000
#define SB_HIT_PAGE_DOWN   0X00002000

typedef struct _scrollbar Scrollbar, *pScrollbar;

//-----------------------------------------------------------------------------

extern Window scrollbar_get_window(pScrollbar);
extern void scrollbar_draw(pScrollbar);
extern int scrollbar_enable_scrollbar(pScrollbar);
extern int scrollbar_disable_scrollbar(pScrollbar);
extern void scrollbar_show(pScrollbar, XEvent*);
extern void scrollbar_hide(pScrollbar);
extern void scrollbar_select(pScrollbar, XEvent*);
extern void scrollbar_release(pScrollbar, XEvent*);
extern void scrollbar_track_enter(pScrollbar, XEvent*);
extern void scrollbar_track(pScrollbar, XEvent*);
extern void scrollbar_track_reset(pScrollbar, XEvent*);
extern void scrollbar_client_event(pScrollbar, XEvent*);
extern void scrollbar_set_position(pScrollbar, int);
extern int scrollbar_get_range(pScrollbar, int*, int*);
extern void scrollbar_set_range(pScrollbar, int, int, int);
extern int scrollbar_get_range(pScrollbar, int*, int*);
extern void scrollbar_destroy(pScrollbar*);
extern pScrollbar scrollbar_create(Display*, Window, GC, int, int, Atom, int);

//-----------------------------------------------------------------------------
// end: menu.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.

6993