/**
* Functions for dealing with window borders.
* Copyright (C) 2004 Joe Wingbermuehle
*/
#include "jwm.h"
#include "border.h"
#include "client.h"
#include "clientlist.h"
#include "color.h"
#include "main.h"
#include "icon.h"
#include "font.h"
#include "error.h"
typedef enum {
BP_CLOSE,
BP_MINIMIZE,
BP_MAXIMIZE,
BP_MAXIMIZE_ACTIVE,
BP_COUNT
} BorderPixmapType;
typedef unsigned char BorderPixmapDataType[32];
static BorderPixmapDataType bitmaps[BP_COUNT] = {
/* Close */
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x38, 0x38, 0x70, 0x1C,
0xE0, 0x0E, 0xC0, 0x07, 0x80, 0x03, 0xC0, 0x07, 0xE0, 0x0E, 0x70, 0x1C,
0x38, 0x38, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00 },
/* Minimize */
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x07,
0xF8, 0x07, 0xF8, 0x07, 0x00, 0x00, 0x00, 0x00 },
/* Maximize */
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x3F, 0xF8, 0x3F, 0xF8, 0x3F,
0x08, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0x20,
0x08, 0x20, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x00 },
/* Maximize Active */
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x1F, 0xC0, 0x1F,
0x00, 0x10, 0xF8, 0x13, 0xF8, 0x13, 0x08, 0x12, 0x08, 0x1A, 0x08, 0x02,
0x08, 0x02, 0xF8, 0x03, 0x00, 0x00, 0x00, 0x00 }
};
static Pixmap pixmaps[BP_COUNT];
static Region borderRegion = NULL;
static GC borderGC;
static void DrawBorderHelper(const ClientNode *np, int drawIcon);
static void DrawBorderButtons(const ClientNode *np, Pixmap canvas, GC gc);
static int GetButtonCount(const ClientNode *np);
/** Initialize non-server resources. */
void InitializeBorders() {
}
/** Initialize server resources. */
void StartupBorders() {
XGCValues gcValues;
unsigned long gcMask;
int x;
for(x = 0; x < BP_COUNT; x++) {
pixmaps[x] = JXCreateBitmapFromData(display, rootWindow,
(char*)bitmaps[x], 16, 16);
}
gcMask = GCGraphicsExposures;
gcValues.graphics_exposures = False;
borderGC = JXCreateGC(display, rootWindow, gcMask, &gcValues);
}
/** Release server resources. */
void ShutdownBorders() {
int x;
JXFreeGC(display, borderGC);
for(x = 0; x < BP_COUNT; x++) {
JXFreePixmap(display, pixmaps[x]);
}
}
/** Release non-server resources. */
void DestroyBorders() {
}
/** Get the size of the icon to display on a window. */
int GetBorderIconSize() {
return titleHeight - 6;
}
/** Determine the border action to take given coordinates. */
BorderActionType GetBorderActionType(const ClientNode *np, int x, int y) {
int north, south, east, west;
int offset;
Assert(np);
GetBorderSize(np, &north, &south, &east, &west);
/* Check title bar actions. */
if(np->state.border & BORDER_TITLE) {
/* Check buttons on the title bar. */
if(y > 0 && y <= titleHeight) {
/* Menu button. */
if(np->icon && np->width >= titleHeight) {
if(x > 0 && x <= titleHeight) {
return BA_MENU;
}
}
/* Close button. */
offset = np->width + west + east - titleHeight;
if((np->state.border & BORDER_CLOSE) && offset > titleHeight) {
if(x > offset && x < offset + titleHeight) {
return BA_CLOSE;
}
offset -= titleHeight;
}
/* Maximize button. */
if((np->state.border & BORDER_MAX) && offset > titleHeight) {
if(x > offset && x < offset + titleHeight) {
return BA_MAXIMIZE;
}
offset -= titleHeight;
}
/* Minimize button. */
if((np->state.border & BORDER_MIN) && offset > titleHeight) {
if(x > offset && x < offset + titleHeight) {
return BA_MINIMIZE;
}
}
}
/* Check for move. */
if(y > 0 && y <= titleHeight) {
if(x > 0 && x < np->width + east + west) {
if(np->state.border & BORDER_MOVE) {
return BA_MOVE;
} else {
return BA_NONE;
}
}
}
}
/* Now we check resize actions.
* There is no need to go further if resizing isn't allowed. */
if(!(np->state.border & BORDER_RESIZE)) {
return BA_NONE;
}
/* Check south east/west resizing. */
if(np->width >= titleHeight * 2 && np->height >= titleHeight * 2) {
if(y > np->height + north - titleHeight) {
if(x < titleHeight) {
return BA_RESIZE_S | BA_RESIZE_W | BA_RESIZE;
} else if(x > np->width + west - titleHeight) {
return BA_RESIZE_S | BA_RESIZE_E | BA_RESIZE;
}
}
}
/* Check east, west, and south resizing. */
if(x <= west) {
return BA_RESIZE_W | BA_RESIZE;
} else if(x >= np->width + west) {
return BA_RESIZE_E | BA_RESIZE;
} else if(y >= np->height + north) {
return BA_RESIZE_S | BA_RESIZE;
} else {
return BA_NONE;
}
}
/** Draw a client border. */
void DrawBorder(const ClientNode *np, const XExposeEvent *expose) {
XRectangle rect;
int drawIcon;
int temp;
Assert(np);
/* Don't draw any more if we are shutting down. */
if(shouldExit) {
return;
}
/* Must be either mapped or shaded to have a border. */
if(!(np->state.status & (STAT_MAPPED | STAT_SHADED))) {
return;
}
/* Hidden and fullscreen windows don't get borders. */
if(np->state.status & (STAT_HIDDEN | STAT_FULLSCREEN)) {
return;
}
/* Return if there is no border. */
if(!(np->state.border & (BORDER_TITLE | BORDER_OUTLINE))) {
return;
}
if(expose) {
/* An expose event caused this draw.
* Only draw what needs to be drawn to reduce flicker.
*/
/* Create the region to use if needed. */
if(!borderRegion) {
borderRegion = XCreateRegion();
}
/* Add the exposed area to the region. */
rect.x = (short)expose->x;
rect.y = (short)expose->y;
rect.width = (unsigned short)expose->width;
rect.height = (unsigned short)expose->height;
XUnionRectWithRegion(&rect, borderRegion, borderRegion);
/* We return now if there are more expose events coming. */
if(expose->count) {
return;
}
/* Determine if the icon should be redrawn. This is needed
* since icons need a separate GC for applying shape masks.
* Note that if the icon were naively redrawn, icons with
* alpha channels would acquire artifacts since the area under
* them would not be cleared. So if any part of the icon needs
* to be redrawn, we clear the area and redraw the whole icon.
*/
drawIcon = 0;
if(np->icon && (np->state.border & BORDER_TITLE)) {
temp = GetBorderIconSize();
rect.x = 3;
rect.y = (short)(titleHeight / 2 - temp / 2);
rect.width = (unsigned short)temp;
rect.height = (unsigned short)temp;
if(XRectInRegion(borderRegion,
rect.x, rect.y, rect.width, rect.height) != RectangleOut) {
drawIcon = 1;
XUnionRectWithRegion(&rect, borderRegion, borderRegion);
}
}
/* Time to redraw the border. Set the clip mask. */
XSetRegion(display, borderGC, borderRegion);
} else {
/* An expose event did not occur. Redraw everything. */
drawIcon = 1;
XSetClipMask(display, borderGC, None);
}
/* Do the actual drawing. */
DrawBorderHelper(np, drawIcon);
/* We no longer need the region, release it. */
if(expose) {
XDestroyRegion(borderRegion);
borderRegion = NULL;
}
}
/** Helper method for drawing borders. */
void DrawBorderHelper(const ClientNode *np, int drawIcon) {
ColorType borderTextColor;
long borderTextPixel;
long titleColor1, titleColor2;
long cornerColor;
long outlineColor;
int north, south, east, west;
unsigned int width, height;
int iconSize;
int buttonCount, titleWidth;
Pixmap canvas;
GC gc;
Assert(np);
iconSize = GetBorderIconSize();
GetBorderSize(np, &north, &south, &east, &west);
width = np->width + east + west;
height = np->height + north + south;
/* Determine the colors and gradients to use. */
if(np->state.status & STAT_ACTIVE) {
borderTextColor = COLOR_TITLE_ACTIVE_FG;
borderTextPixel = colors[COLOR_TITLE_ACTIVE_FG];
cornerColor = colors[COLOR_CORNER_ACTIVE_BG];
titleColor1 = colors[COLOR_TITLE_ACTIVE_BG1];
titleColor2 = colors[COLOR_TITLE_ACTIVE_BG2];
outlineColor = colors[COLOR_BORDER_ACTIVE_LINE];
} else {
borderTextColor = COLOR_TITLE_FG;
borderTextPixel = colors[COLOR_TITLE_FG];
cornerColor = colors[COLOR_CORNER_BG];
titleColor1 = colors[COLOR_TITLE_BG1];
titleColor2 = colors[COLOR_TITLE_BG2];
outlineColor = colors[COLOR_BORDER_LINE];
}
canvas = np->parent;
gc = borderGC;
/* Set the window background color (to reduce flickering). */
JXSetWindowBackground(display, canvas, titleColor2);
/* Draw the outside border (clear the window with the right color). */
JXSetForeground(display, gc, titleColor2);
JXFillRectangle(display, canvas, gc, 0, 0, width, height);
/* Determine how many pixels may be used for the title. */
buttonCount = GetButtonCount(np);
titleWidth = width;
titleWidth -= titleHeight * buttonCount;
titleWidth -= iconSize + 4 + 4;
/* Draw the top part (either a title or north border. */
if(np->state.border & BORDER_TITLE) {
/* Draw a title bar. */
DrawHorizontalGradient(canvas, gc, titleColor1, titleColor2,
1, 1, width - 2, titleHeight - 2);
/* Draw the icon. */
if(np->icon && np->width >= titleHeight && drawIcon) {
PutIcon(np->icon, canvas, 3, titleHeight / 2 - iconSize / 2,
iconSize, iconSize);
}
if(np->name && np->name[0] && titleWidth > 0) {
RenderString(canvas, FONT_BORDER, borderTextColor,
iconSize + 3 + 2,
titleHeight / 2 - GetStringHeight(FONT_BORDER) / 2,
titleWidth, borderRegion, np->name);
}
}
/* Draw the east, west, and south borders. */
if((np->state.border & BORDER_OUTLINE)
&& !(np->state.status & STAT_SHADED)) {
/* Draw corners if there's room. */
if((np->state.border & BORDER_RESIZE)
&& !(np->state.status & STAT_SHADED)
&& np->width >= titleHeight * 2
&& np->height >= titleHeight * 2) {
JXSetForeground(display, gc, cornerColor);
/* South west. */
JXFillRectangle(display, canvas, gc,
0, height - titleHeight, titleHeight, titleHeight);
/* South east. */
JXFillRectangle(display, canvas, gc,
width - titleHeight, height - titleHeight,
titleHeight, titleHeight);
/* Corner outline. */
JXSetForeground(display, gc, outlineColor);
JXDrawRectangle(display, canvas, gc,
0, height - titleHeight, titleHeight, titleHeight + 1);
JXDrawRectangle(display, canvas, gc,
width - titleHeight, height - titleHeight,
titleHeight - 1, titleHeight - 1);
}
}
/* Window outline. */
JXSetForeground(display, gc, outlineColor);
if(np->state.status & STAT_SHADED) {
XDrawRectangle(display, canvas, gc, 0, 0, width - 1, north - 1);
} else {
XDrawRectangle(display, canvas, gc, 0, 0, width - 1, height - 1);
XDrawRectangle(display, canvas, gc, west - 1, north - 1,
np->width + 1, np->height + 1);
}
DrawBorderButtons(np, canvas, gc);
}
/** Determine the number of buttons to be displayed for a client. */
int GetButtonCount(const ClientNode *np) {
int north, south, east, west;
int count;
int offset;
if(!(np->state.border & BORDER_TITLE)) {
return 0;
}
GetBorderSize(np, &north, &south, &east, &west);
offset = np->width + east + west - titleHeight;
if(offset <= titleHeight) {
return 0;
}
count = 0;
if(np->state.border & BORDER_CLOSE) {
offset -= titleHeight;
++count;
if(offset <= titleHeight) {
return count;
}
}
if(np->state.border & BORDER_MAX) {
offset -= titleHeight;
++count;
if(offset <= titleHeight) {
return count;
}
}
if(np->state.border & BORDER_MIN) {
++count;
}
return count;
}
/** Draw the buttons on a client frame. */
void DrawBorderButtons(const ClientNode *np, Pixmap canvas, GC gc) {
Pixmap pixmap;
long color;
long outlineColor;
int offset;
int yoffset;
int north, south, east, west;
Assert(np);
if(!(np->state.border & BORDER_TITLE)) {
return;
}
GetBorderSize(np, &north, &south, &east, &west);
offset = np->width + east + west - titleHeight;
if(offset <= titleHeight) {
return;
}
yoffset = titleHeight / 2 - 16 / 2;
/* Determine the colors to use. */
if(np->state.status & STAT_ACTIVE) {
color = colors[COLOR_TITLE_ACTIVE_FG];
outlineColor = colors[COLOR_BORDER_ACTIVE_LINE];
} else {
color = colors[COLOR_TITLE_FG];
outlineColor = colors[COLOR_BORDER_LINE];
}
/* Close button. */
if(np->state.border & BORDER_CLOSE) {
JXSetForeground(display, gc, outlineColor);
JXDrawLine(display, canvas, gc, offset, 0, offset, titleHeight);
pixmap = pixmaps[BP_CLOSE];
JXSetForeground(display, gc, color);
JXSetClipMask(display, gc, pixmap);
JXSetClipOrigin(display, gc, offset + yoffset, yoffset);
JXFillRectangle(display, canvas, gc, offset + yoffset, yoffset, 16, 16);
JXSetClipMask(display, gc, None);
offset -= titleHeight;
if(offset <= titleHeight) {
return;
}
}
/* Maximize button. */
if(np->state.border & BORDER_MAX) {
JXSetForeground(display, gc, outlineColor);
JXDrawLine(display, canvas, gc, offset, 0, offset, titleHeight);
if(np->state.status & (STAT_HMAX | STAT_VMAX)) {
pixmap = pixmaps[BP_MAXIMIZE_ACTIVE];
} else {
pixmap = pixmaps[BP_MAXIMIZE];
}
JXSetForeground(display, gc, color);
XSetClipMask(display, gc, pixmap);
XSetClipOrigin(display, gc, offset + yoffset, yoffset);
XFillRectangle(display, canvas, gc, offset + yoffset, yoffset, 16, 16);
XSetClipMask(display, gc, None);
offset -= titleHeight;
if(offset <= titleHeight) {
return;
}
}
/* Minimize button. */
if(np->state.border & BORDER_MIN) {
JXSetForeground(display, gc, outlineColor);
JXDrawLine(display, canvas, gc, offset, 0, offset, titleHeight);
pixmap = pixmaps[BP_MINIMIZE];
JXSetForeground(display, gc, color);
XSetClipMask(display, gc, pixmap);
XSetClipOrigin(display, gc, offset + yoffset, yoffset);
XFillRectangle(display, canvas, gc, offset + yoffset, yoffset, 16, 16);
XSetClipMask(display, gc, None);
}
}
/** Redraw the borders on the current desktop.
* This should be done after loading clients since the stacking order
* may cause borders on the current desktop to become visible after moving
* clients to their assigned desktops.
*/
void ExposeCurrentDesktop() {
ClientNode *np;
int layer;
for(layer = 0; layer < LAYER_COUNT; layer++) {
for(np = nodes[layer]; np; np = np->next) {
if(!(np->state.status & (STAT_HIDDEN | STAT_MINIMIZED))) {
DrawBorder(np, NULL);
}
}
}
}
/** Get the size of the borders for a client. */
void GetBorderSize(const ClientNode *np,
int *north, int *south, int *east, int *west) {
Assert(np);
Assert(north);
Assert(south);
Assert(east);
Assert(west);
/* Full screen is a special case. */
if(np->state.status & STAT_FULLSCREEN) {
*north = 0;
*south = 0;
*east = 0;
*west = 0;
return;
}
if(np->state.border & BORDER_OUTLINE) {
*north = borderWidth;
*south = borderWidth;
*east = borderWidth;
*west = borderWidth;
} else {
*north = 0;
*south = 0;
*east = 0;
*west = 0;
}
if(np->state.border & BORDER_TITLE) {
*north = titleHeight;
}
if(np->state.status & STAT_SHADED) {
*south = 0;
}
}
/** Set the size of window borders. */
void SetBorderWidth(const char *str) {
int width;
if(str) {
width = atoi(str);
if(width < MIN_BORDER_WIDTH || width > MAX_BORDER_WIDTH) {
borderWidth = DEFAULT_BORDER_WIDTH;
Warning("invalid border width specified: %d", width);
} else {
borderWidth = width;
}
}
}
/** Set the height of the title bar. */
void SetTitleHeight(const char *str) {
int height;
if(str) {
height = atoi(str);
if(height < MIN_TITLE_HEIGHT || height > MAX_TITLE_HEIGHT) {
titleHeight = DEFAULT_TITLE_HEIGHT;
Warning("invalid title height specified: %d", height);
} else {
titleHeight = height;
}
}
}
syntax highlighted by Code2HTML, v. 0.9.1