/**
* @file traybutton.h
* @author Joe Wingbermuehle
* @date 2004-2006
*
* @brief Button tray component.
*
*/
#include "jwm.h"
#include "traybutton.h"
#include "tray.h"
#include "icon.h"
#include "image.h"
#include "error.h"
#include "root.h"
#include "main.h"
#include "color.h"
#include "font.h"
#include "button.h"
#include "misc.h"
#include "screen.h"
#include "desktop.h"
#include "popup.h"
#include "timing.h"
#include "command.h"
#include "cursor.h"
#define BUTTON_SIZE 4
typedef struct TrayButtonType {
TrayComponentType *cp;
char *label;
char *popup;
char *iconName;
IconNode *icon;
char *action;
int mousex;
int mousey;
TimeType mouseTime;
struct TrayButtonType *next;
} TrayButtonType;
static TrayButtonType *buttons;
static void CheckedCreate(TrayComponentType *cp);
static void Create(TrayComponentType *cp);
static void Destroy(TrayComponentType *cp);
static void SetSize(TrayComponentType *cp, int width, int height);
static void Resize(TrayComponentType *cp);
static void Draw(TrayComponentType *cp, int active);
static void ProcessButtonPress(TrayComponentType *cp,
int x, int y, int mask);
static void ProcessButtonRelease(TrayComponentType *cp,
int x, int y, int mask);
static void ProcessMotionEvent(TrayComponentType *cp,
int x, int y, int mask);
/** Initialize tray button data. */
void InitializeTrayButtons() {
buttons = NULL;
}
/** Startup tray buttons. */
void StartupTrayButtons() {
TrayButtonType *bp;
for(bp = buttons; bp; bp = bp->next) {
if(bp->label) {
bp->cp->requestedWidth
= GetStringWidth(FONT_TRAYBUTTON, bp->label) + 4;
bp->cp->requestedHeight
= GetStringHeight(FONT_TRAYBUTTON);
} else {
bp->cp->requestedWidth = 0;
bp->cp->requestedHeight = 0;
}
if(bp->iconName) {
bp->icon = LoadNamedIcon(bp->iconName);
if(bp->icon) {
bp->cp->requestedWidth += bp->icon->image->width;
bp->cp->requestedHeight += bp->icon->image->height;
} else {
Warning("could not load tray icon: \"%s\"", bp->iconName);
}
}
bp->cp->requestedWidth += 2 * BUTTON_SIZE;
bp->cp->requestedHeight += 2 * BUTTON_SIZE;
}
}
/** Shutdown tray buttons. */
void ShutdownTrayButtons() {
}
/** Release tray button data. */
void DestroyTrayButtons() {
TrayButtonType *bp;
while(buttons) {
bp = buttons->next;
if(buttons->label) {
Release(buttons->label);
}
if(buttons->iconName) {
Release(buttons->iconName);
}
if(buttons->action) {
Release(buttons->action);
}
if(buttons->popup) {
Release(buttons->popup);
}
Release(buttons);
buttons = bp;
}
}
/** Create a button tray component. */
TrayComponentType *CreateTrayButton(const char *iconName,
const char *label, const char *action,
const char *popup, int width, int height) {
TrayButtonType *bp;
TrayComponentType *cp;
if((label == NULL || strlen(label) == 0)
&& (iconName == NULL || strlen(iconName) == 0)) {
Warning("no icon or label for TrayButton");
return NULL;
}
if(width < 0) {
Warning("invalid TrayButton width: %d", width);
width = 0;
}
if(height < 0) {
Warning("invalid TrayButton height: %d", height);
height = 0;
}
bp = Allocate(sizeof(TrayButtonType));
bp->next = buttons;
buttons = bp;
bp->icon = NULL;
bp->iconName = CopyString(iconName);
bp->label = CopyString(label);
bp->action = CopyString(action);
bp->popup = CopyString(popup);
cp = CreateTrayComponent();
cp->object = bp;
bp->cp = cp;
cp->requestedWidth = width;
cp->requestedHeight = height;
cp->Create = CheckedCreate;
cp->Destroy = Destroy;
cp->SetSize = SetSize;
cp->Resize = Resize;
cp->ProcessButtonPress = ProcessButtonPress;
cp->ProcessButtonRelease = ProcessButtonRelease;
if(popup || label) {
cp->ProcessMotionEvent = ProcessMotionEvent;
}
return cp;
}
/** Set the size of a button tray component. */
void SetSize(TrayComponentType *cp, int width, int height) {
TrayButtonType *bp;
int labelWidth, labelHeight;
int iconWidth, iconHeight;
double ratio;
bp = (TrayButtonType*)cp->object;
if(bp->icon) {
if(bp->label) {
labelWidth = GetStringWidth(FONT_TRAYBUTTON, bp->label) + 4;
labelHeight = GetStringHeight(FONT_TRAYBUTTON);
} else {
labelWidth = 0;
labelHeight = 0;
}
iconWidth = bp->icon->image->width;
iconHeight = bp->icon->image->height;
ratio = (double)iconWidth / iconHeight;
if(width > 0) {
/* Compute height from width. */
iconWidth = width - labelWidth - 2 * BUTTON_SIZE;
iconHeight = iconWidth / ratio;
height = Max(iconHeight, labelHeight) + 2 * BUTTON_SIZE;
} else if(height > 0) {
/* Compute width from height. */
iconHeight = height - 2 * BUTTON_SIZE;
iconWidth = iconHeight * ratio;
width = iconWidth + labelWidth + 2 * BUTTON_SIZE;
}
cp->width = width;
cp->height = height;
}
}
/** Initialize a button tray component (display errors). */
void CheckedCreate(TrayComponentType *cp) {
TrayButtonType *bp;
bp = (TrayButtonType*)cp->object;
/* Validate the action for this tray button. */
if(bp->action && strlen(bp->action) > 0) {
if(!strncmp(bp->action, "exec:", 5)) {
/* Valid. */
} else if(!strncmp(bp->action, "root:", 5)) {
/* Valid. However, the specified root menu may not exist.
* This case is handled in ValidateTrayButtons.
*/
} else if(!strcmp(bp->action, "showdesktop")) {
/* Valid. */
} else {
Warning("invalid TrayButton action: \"%s\"", bp->action);
}
} else {
/* Valid. However, root menu 1 may not exist.
* This case is handled in ValidateTrayButtons.
*/
}
Create(cp);
}
/** Initialize a button tray component. */
void Create(TrayComponentType *cp) {
cp->pixmap = JXCreatePixmap(display, rootWindow,
cp->width, cp->height, rootDepth);
Draw(cp, 0);
}
/** Resize a button tray component. */
void Resize(TrayComponentType *cp) {
Destroy(cp);
Create(cp);
}
/** Destroy a button tray component. */
void Destroy(TrayComponentType *cp) {
if(cp->pixmap != None) {
JXFreePixmap(display, cp->pixmap);
}
}
/** Draw a tray button. */
void Draw(TrayComponentType *cp, int active) {
ButtonNode button;
TrayButtonType *bp;
int labelx;
bp = (TrayButtonType*)cp->object;
JXSetForeground(display, rootGC, colors[COLOR_TRAYBUTTON_BG]);
JXFillRectangle(display, cp->pixmap, rootGC, 0, 0, cp->width, cp->height);
ResetButton(&button, cp->pixmap, rootGC);
if(active) {
button.type = BUTTON_TASK_ACTIVE;
} else {
button.type = BUTTON_TASK;
}
button.width = cp->width - 3;
button.height = cp->height - 3;
button.x = 1;
button.y = 1;
DrawButton(&button);
/* Compute the offset of the text. */
if(bp->label) {
if(!bp->icon) {
labelx = 2 + cp->width / 2;
labelx -= GetStringWidth(FONT_TRAYBUTTON, bp->label) / 2;
} else {
labelx = cp->width;
labelx -= GetStringWidth(FONT_TRAYBUTTON, bp->label) + 4;
}
} else {
labelx = cp->width;
}
labelx -= BUTTON_SIZE;
if(bp->icon) {
PutIcon(bp->icon, cp->pixmap, BUTTON_SIZE, BUTTON_SIZE,
labelx - BUTTON_SIZE, cp->height - BUTTON_SIZE * 2);
}
if(bp->label) {
RenderString(cp->pixmap, FONT_TRAYBUTTON, COLOR_TRAYBUTTON_FG,
labelx + 2, cp->height / 2 - GetStringHeight(FONT_TRAYBUTTON) / 2,
cp->width - labelx, NULL, bp->label);
}
}
/** Process a button press. */
void ProcessButtonPress(TrayComponentType *cp, int x, int y, int mask) {
const ScreenType *sp;
int mwidth, mheight;
int button;
TrayButtonType *bp = (TrayButtonType*)cp->object;
Assert(bp);
if(bp->action && strlen(bp->action) > 0) {
if(strncmp(bp->action, "root:", 5)) {
GrabMouse(cp->tray->window);
cp->grabbed = 1;
Draw(cp, 1);
UpdateSpecificTray(cp->tray, cp);
return;
} else {
button = atoi(bp->action + 5);
}
} else {
button = 1;
}
GetRootMenuSize(button, &mwidth, &mheight);
sp = GetCurrentScreen(cp->screenx, cp->screeny);
if(cp->tray->layout == LAYOUT_HORIZONTAL) {
x = cp->screenx;
if(cp->screeny + cp->height / 2 < sp->y + sp->height / 2) {
y = cp->screeny + cp->height;
} else {
y = cp->screeny - mheight;
}
} else {
y = cp->screeny;
if(cp->screenx + cp->width / 2 < sp->x + sp->width / 2) {
x = cp->screenx + cp->width;
} else {
x = cp->screenx - mwidth;
}
}
Draw(cp, 1);
UpdateSpecificTray(cp->tray, cp);
ShowRootMenu(button, x, y);
Draw(cp, 0);
UpdateSpecificTray(cp->tray, cp);
}
/** Process a button release. */
void ProcessButtonRelease(TrayComponentType *cp, int x, int y, int mask) {
TrayButtonType *bp = (TrayButtonType*)cp->object;
Assert(bp);
Draw(cp, 0);
UpdateSpecificTray(cp->tray, cp);
// Since we grab the mouse, make sure the mouse is actually
// over the button.
if(x < 0 || x >= cp->width) {
return;
}
if(y < 0 || y >= cp->height) {
return;
}
// Run the tray button action (if any).
if(bp->action && strlen(bp->action) > 0) {
if(!strncmp(bp->action, "exec:", 5)) {
RunCommand(bp->action + 5);
return;
} else if(!strcmp(bp->action, "showdesktop")) {
ShowDesktop();
return;
}
}
}
/** Process a motion event. */
void ProcessMotionEvent(TrayComponentType *cp, int x, int y, int mask) {
TrayButtonType *bp = (TrayButtonType*)cp->object;
bp->mousex = cp->screenx + x;
bp->mousey = cp->screeny + y;
GetCurrentTime(&bp->mouseTime);
}
/** Signal (needed for popups). */
void SignalTrayButton(const TimeType *now, int x, int y) {
TrayButtonType *bp;
const char *popup;
for(bp = buttons; bp; bp = bp->next) {
if(bp->popup) {
popup = bp->popup;
} else if(bp->label) {
popup = bp->label;
} else {
continue;
}
if(abs(bp->mousex - x) < POPUP_DELTA
&& abs(bp->mousey - y) < POPUP_DELTA) {
if(GetTimeDifference(now, &bp->mouseTime) >= popupDelay) {
ShowPopup(x, y, popup);
}
}
}
}
/** Validate tray buttons. */
void ValidateTrayButtons() {
TrayButtonType *bp;
int bindex;
for(bp = buttons; bp; bp = bp->next) {
if(bp->action && !strncmp(bp->action, "root:", 5)) {
bindex = atoi(bp->action + 5);
if(!IsRootMenuDefined(bindex)) {
Warning("tray button: root menu %d not defined", bindex);
}
}
}
}
syntax highlighted by Code2HTML, v. 0.9.1