A Place Where No Dreams Come True...

Current Project: A very simple X11 tab implementation with no external dependencies. It currently supports hover effects and other cursor management.

What Does This Code Do...

Implements X11 Tabbed class window. When an enabled tab is selected (mouse-button-1 or mouse-scroll) a message is sent to the parent window with a message relayed from tab creation parameters. The message is intended for the parent window to select the current tab hiding and showing other tab objects to maintain proper context for the selected tab.

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.

//-----------------------------------------------------------------------------
// tab.c
//
//  X11 tab selecter 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 <X11/xpm.h>
#include <stdlib.h>
#include <string.h>

#include <report.h>

#include "xwinhlpr.h"
#include "xwinatom.h"
#include "tab.h"

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

#define MAXSIBLINGS 128

#define deftabBorder 0x00000000
#define deftabFace 0xFFB0B080
#define deftabText 0xFF000000
#define deftabAlert 0xFFC0C000
#define deftabHilite 0xFFE0E0E0
#define deftabShadow 0xFF505050
#define deftabHover 0xFF909090

#define MAXTABTITLE 128

struct _tab {
  int width;
  int length;
  char title[MAXTABTITLE + 1];
};

struct _tabbed {
  Display    *display;
  Window      parent;
  Window      window;
  GC          gc;
  Pixmap      bgnd;
  Font        font;
  XRectangle  map;
  Atom        message;
  int         border;
  int         itmborder;
  int         itemid;
  unsigned    clrBorder;
  unsigned    clrFace;
  unsigned    clrText;
  unsigned    clrAlert;
  unsigned    clrHilite;
  unsigned    clrShadow;
  unsigned    clrHover;
  int         btab;
  int         dx;
  int         dy;
  int         hovering;
  int         selected;
  int         origin;
  int         tabs;
  struct _tab tab[8];
};

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

void tab_draw(pTab tab)
{
  int i;
  XArc arc[4];
  XSegment seg[5];
  XPoint pnt[9];
  XRectangle btnrect;

  if(tab)
  {
    XSetForeground(tab->display, tab->gc, tab->clrFace);
    XFillRectangle(tab->display, tab->bgnd, tab->gc,
		   0, 0, tab->map.width, tab->map.height);
    if(tab->font)
    {
      XSetFont(tab->display, tab->gc, tab->font);
    }
    btnrect.x = tab->origin;
    btnrect.y = tab->itmborder;
    btnrect.height = tab->map.height - (btnrect.y * 2);
    for(i = 0; i < tab->tabs; i++)
    {
      btnrect.width = tab->tab[i].width;
      if(i == tab->selected)
      {
	// FIXME: To primitive.
	arc[0].x = btnrect.x - (tab->dx / 2);
	arc[0].y = (btnrect.y + btnrect.height) - (tab->dy - 1);
	arc[0].width = tab->dx;
	arc[0].height = tab->dy;
	arc[0].angle1 = 270 * 64;
	arc[0].angle2 = 90 * 64;
	arc[1].x = btnrect.x + (tab->dx / 2);
	arc[1].y = btnrect.y;
	arc[1].width = tab->dx;
	arc[1].height = tab->dy;
	arc[1].angle1 = 90 * 64;
	arc[1].angle2 = 90 * 64;
	arc[2].x = (btnrect.x + btnrect.width) - (tab->dx / 2);
	arc[2].y = btnrect.y;
	arc[2].width = tab->dx;
	arc[2].height = tab->dy;
	arc[2].angle1 = 0;
	arc[2].angle2 = 90 * 64;
	arc[3].x = (btnrect.x + btnrect.width) + (tab->dx / 2);
	arc[3].y = (btnrect.y + btnrect.height) - (tab->dy - 1);
	arc[3].width = tab->dx;
	arc[3].height = tab->dy;
	arc[3].angle1 = 180 * 64;
	arc[3].angle2 = 90 * 64;

	seg[0].x1 = tab->border; // FIXME: Should be a border value.
	seg[0].y1 = (btnrect.y + btnrect.height) + 1;
	seg[0].x2 = (btnrect.x + seg[0].x1) - (tab->dx / 4);
	seg[0].y2 = seg[0].y1;
	seg[1].x1 = (seg[0].x1 + btnrect.x) + ((tab->dx / 4) + 1);
	seg[1].y1 = btnrect.y + (tab->dy / 2) + 1;
	seg[1].x2 = seg[1].x1;
	seg[1].y2 = (btnrect.y + btnrect.height) - (tab->dy / 2);
	seg[2].x1 = seg[1].x1 + (tab->dx / 2);
	seg[2].y1 = btnrect.y;
	seg[2].x2 = (seg[2].x1 + btnrect.width) - tab->dx;
	seg[2].y2 = btnrect.y;
	seg[3].x1 = seg[2].x2 + (tab->dx / 2);
	seg[3].y1 = seg[1].y1;
	seg[3].x2 = seg[3].x1;
	seg[3].y2 = seg[1].y2;
	seg[4].x1 = seg[3].x1 + (tab->dx / 2);
	seg[4].y1 = (btnrect.y + btnrect.height) + 1;
	seg[4].x2 = tab->map.width - (seg[0].x1 + 1);
	seg[4].y2 = seg[4].y1;
	XSetForeground(tab->display, tab->gc, tab->clrText);
	XDrawArcs(tab->display, tab->bgnd, tab->gc, arc, 4);
	XDrawSegments(tab->display, tab->bgnd, tab->gc, seg, 5);
      }
      else if(i == tab->hovering)
      {
	pnt[0].x = btnrect.x + 6;
	pnt[0].y = btnrect.y + 3;

	pnt[1].x = btnrect.x + 9;
	pnt[1].y = btnrect.y;

	pnt[2].x = btnrect.x + btnrect.width + tab->border;
	pnt[2].y = btnrect.y;

	pnt[3].x = btnrect.x + btnrect.width + tab->border + 3;
	pnt[3].y = btnrect.y + 3;

	pnt[4].x = btnrect.x + btnrect.width + tab->border + 3;
	pnt[4].y = (btnrect.y + btnrect.height) - 3;

	pnt[5].x = btnrect.x + btnrect.width + tab->border;
	pnt[5].y = btnrect.y + btnrect.height;

	pnt[6].x = btnrect.x + 9;
	pnt[6].y = btnrect.y + btnrect.height;

	pnt[7].x = btnrect.x + 6;
	pnt[7].y = (btnrect.y + btnrect.height) - 3;

	pnt[8].x = pnt[0].x;
	pnt[8].y = pnt[0].y;

	XSetForeground(tab->display, tab->gc, tab->clrHover);
	XFillPolygon(tab->display, tab->bgnd, tab->gc, pnt, 9, Convex, CoordModeOrigin);
      }
      XSetForeground(tab->display, tab->gc, tab->clrText);
      XDrawString(tab->display, tab->bgnd, tab->gc, btnrect.x + 2 + tab->dx, (btnrect.y + btnrect.height) - (tab->dy / 2),
		  tab->tab[i].title, tab->tab[i].length);
      btnrect.x += (tab->tab[i].width + tab->dx);
    }
    // Update window.
    XCopyArea(tab->display, tab->bgnd, tab->window, tab->gc,
    	      0, 0, tab->map.width, tab->map.height, 0, 0);
  }
}

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

Window tab_get_window(pTab tab)
{
  Window rtn = 0;
  if(tab)
  {
    rtn = tab->window;
  }
  return rtn;
}

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

void tab_show(pTab tab, XEvent *event)
{
  if(tab)
  {
    report(dbglevl_debug, " tab show.\n");
    XMapWindow(tab->display, tab->window);
  }
}

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

void tab_hide(pTab tab)
{
  if(tab)
  {
    XUnmapWindow(tab->display, tab->window);
    report(dbglevl_debug, " tab hide.\n");
  }
}

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

void tab_track(pTab tab, XEvent *event)
{
  int i;
  int x = 10;
  int y = 2;
  int width;
  int height = 20;

  if(tab)
  {
    x = 10 + (tab->dx / 2);
    for(i = 0; i < tab->tabs; i++)
    {
      width = tab->tab[i].width;
      if((event->xmotion.x > x) &&
	 (event->xmotion.x < (x + width)) &&
	 (event->xmotion.y > y) &&
	 (event->xmotion.y < (y + height)))
      {
	if(tab->hovering != i)
	{
	  tab->hovering = i;
	  report(dbglevl_debug, " tab_track() - point.x=%i point.y=%i highlight=%i.\n",
		 event->xmotion.x, event->xmotion.y, i);
	  tab_draw(tab);
	}
	break;
      }
      x += (tab->tab[i].width + tab->dx);
    }
    if(i >= tab->tabs)
    {
      if(tab->hovering != -1)
      {
	tab->hovering = -1;
	report(dbglevl_debug, " tab_track() - resetting : point.x=%i point.y=%i highlight=-1.\n",
	       event->xmotion.x, event->xmotion.y);
	tab_draw(tab);
      }
    }
  }
}

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

void tab_track_reset(pTab tab, XEvent *event)
{
  if(tab)
  {
    if(tab->hovering != -1)
    {
      if((event->xcrossing.x < 1) || (event->xcrossing.x >= tab->map.width) ||
	 (event->xcrossing.y < 1) || (event->xcrossing.y >= tab->map.height))
      {
	tab->hovering = -1;
	report(dbglevl_debug, " tab_track_reset() - point.x=%i point.y=%i highlight=-1.\n",
	       event->xcrossing.x, event->xcrossing.y);
	tab_draw(tab);
      }
    }
  }
}

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

static void tab_notify_parent(pTab tab)
{
  XEvent event;

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

  event.xclient.type         = ClientMessage;
  event.xclient.display      = tab->display;
  event.xclient.window       = tab->window;
  event.xclient.message_type = tab->message;
  event.xclient.format       = 32;
  event.xclient.data.l[0]    = tab->window;
  event.xclient.data.l[1]    = tab->itemid;
  event.xclient.data.l[2]    = tab->selected;
  XSendEvent(tab->display, tab->parent, 0, 0, &event);
}

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

void tab_select(pTab tab, XEvent *event)
{
  if(tab)
  {
    switch(event->xbutton.button)
    {
      case Button1:
	if((tab->hovering > -1) && (tab->hovering < tab->tabs))
	{
	  tab->selected = tab->hovering;
	  tab_draw(tab);
	  tab_notify_parent(tab);
	}
	break;

      case Button4:
	if((tab->selected + 1) == tab->tabs)
	{
	  tab->selected = 0;
	}
	else
	{
	  tab->selected++;
	}
	tab_draw(tab);
	tab_notify_parent(tab);
	break;

      case Button5:
	if(tab->selected == 0)
	{
	  tab->selected = tab->tabs - 1;
	}
	else
	{
	  tab->selected--;
	}
	tab_draw(tab);
	tab_notify_parent(tab);
	break;

      default:
	break;
    }
  }
}

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

void tab_select_tab(pTab tab, int select)
{
  if(tab)
  {
    if(tab->selected != select)
    {
      if((select > -1) && select < (tab->tabs))
      {
	report(dbglevl_debug, " changing tab select from %i to %i.\n", tab->selected, select);
	tab->selected = select;
      }
    }
  }
}

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

void tab_set_font(pTab tab, char *sfont)
{
  if(tab)
  {
    report(dbglevl_debug, " tab_set_font(%s).\n", sfont);
    if(tab->font)
    {
      XUnloadFont(tab->display, tab->font);
    }
    tab->font = XLoadFont(tab->display, sfont);
  }
}

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

void tab_add_tab(pTab tab, char *title, int width)
{
  if(tab)
  {
    snprintf(tab->tab[tab->tabs].title, MAXTABTITLE, "%s", title);
    tab->tab[tab->tabs].title[MAXTABTITLE - 1] = '\0';
    tab->tab[tab->tabs].length = strlen(tab->tab[tab->tabs].title);
    tab->tab[tab->tabs].width = width;
    tab->tabs++;
  }
}

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

void tab_destroy(pTab *tab)
{
  if(tab)
  {
    pTab tb = *tab;
    if(tb)
    {
      report(dbglevl_debug, " Destroying tab : %08X window=%08X.\n", tb, tb->window);
      if(tb->font)
      {
	XUnloadFont(tb->display, tb->font);
      }
      if(tb->bgnd)
      {
        report(dbglevl_debug, " Freeing tab background pixmap.\n");
	XFreePixmap(tb->display, tb->bgnd);
      }
      free(tb);
    }
    *tab = NULL;
  }
}

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

pTab tab_create(Display *display, Window parent, GC gc, int width, int height, Atom message, int itemid)
{
  pTab tab = malloc(sizeof(Tab));
  if(tab)
  {
    memset(tab, 0, sizeof(Tab));
    tab->display    = display;
    tab->parent     = parent;
    tab->gc         = gc;
    tab->map.x      = 0;
    tab->map.y      = 0;
    tab->map.width  = width;
    tab->map.height = height;
    tab->message    = message;
    tab->itemid     = itemid;
    tab->clrBorder  = deftabBorder;
    tab->clrFace    = deftabFace;
    tab->clrText    = deftabText;
    tab->clrAlert   = deftabAlert;
    tab->clrHilite  = deftabHilite;
    tab->clrShadow  = deftabShadow;
    tab->clrHover   = deftabHover;
    tab->border     = 2;
    tab->itmborder  = 2;
    tab->origin     = 10;
    tab->dx         = 12;
    tab->dy         = 10;
    tab->hovering   = -1;
    tab->window  = XCreateSimpleWindow(display,
				       parent,
				       tab->map.x,
				       tab->map.y,
				       tab->map.width,
				       tab->map.height,
				       0,
				       WhitePixel(display, 0),
				       BlackPixel(display, 0));
    XSelectInput(tab->display, tab->window,
		 ExposureMask | ButtonPressMask | PointerMotionMask | LeaveWindowMask);
    tab->bgnd = XCreatePixmap(tab->display, tab->window, width, height, 24);
    XSetWindowBackgroundPixmap(tab->display, tab->window, tab->bgnd);
    xwin_set_window_property_atoms(tab->display, tab->window, _NET_WM_WINDOW_TYPE,
				   (unsigned char*)&_NET_WM_WINDOW_TYPE_TAB, 1);
    report(dbglevl_debug, " Created tab : %08X window=%08X.\n", tab, tab->window);
  }
  return tab;
}

//-----------------------------------------------------------------------------
// end: tab.c
//

  

And The Companion Header...

//-----------------------------------------------------------------------------
// tab.h
//
//  X11 container parent-window 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 struct _tabbed Tab, *pTab;

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

extern void tab_draw(pTab);
extern Window tab_get_window(pTab);
extern void tab_show(pTab, XEvent*);
extern void tab_hide(pTab);
extern int tab_add(pTab, char*, int, Atom);
extern void tab_set_font(pTab, char*);
extern void tab_track(pTab, XEvent*);
extern void tab_track_reset(pTab, XEvent*);
extern void tab_select(pTab, XEvent*);
extern void tab_select_tab(pTab, int);
extern void tab_set_font(pTab, char*);
extern void tab_add_tab(pTab, char*, int);
extern void tab_destroy(pTab*);
extern pTab tab_create(Display*, Window, GC, int, int, Atom, int);

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

6990