Files
nethack/win/X11/winlabel.c
T
Ray Chason 6043d189e1 Define wrapper for Label widgets
Renders to a Pixmap and then sets the Pixmap. This in itself does not
change the appearance, but provides a means to do percentage bars,
italics and more.
2026-08-14 11:08:25 +03:00

446 lines
13 KiB
C

/* NetHack 5.0 winlabel.c $NHDT-Date: 1781973110 2026/06/20 16:31:50 $ $NHDT-Branch: NetHack-5.0 $:$NHDT-Revision: 1.50 $ */
/* Copyright (c) Ray Chason, 2026 */
/* NetHack may be freely redistributed. See license for details. */
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Label.h>
#include <inttypes.h>
#ifdef PRESERVE_NO_SYSV
#ifdef SYSV
#undef SYSV
#endif
#undef PRESERVE_NO_SYSV
#endif
#include "hack.h"
#include "winX.h"
/* Data attached to a wrapped widget */
typedef struct WidgetData {
/* Displayed text */
Pixmap pixmap;
/* Attributes */
unsigned attrs;
/* Fonts for italic, bold and bold-italic */
XFontStruct *font[4]; /* To use the fonts */
XFontStruct *font_ptr[4]; /* To free the fonts */
/* Percentage bar */
unsigned percent;
Pixel bar_color;
} WidgetData;
/* Bits for WidgetData::font */
enum { font_bold = 1, font_italic = 2 };
static boolean check_label(Widget);
static void delete_callback(Widget, XtPointer, XtPointer);
static void update_label(Widget, WidgetData *);
static void allocate_font(Widget, WidgetData *, unsigned);
static void free_fonts(Widget, WidgetData *);
static WidgetData *add_widget(Widget w);
static void delete_widget(Widget w);
static WidgetData *get_widget_data(Widget w);
/*
* Create a wrapper for labelWidgetClass and its descendant classes.
*
* A table is maintained to associate each widget with a data block.
* The block contains additional fonts for italic, bold and bold-italic,
* attribute flags and a percentage bar.
*/
void
X11_wrap_widget(Widget w)
{
/* We shouldn't use this for anything other than labels and subclasses
of labels */
if (!check_label(w)) {
impossible("Widget is not a Label or of a class derived from Label");
return;
}
/* Only wrap once */
if (get_widget_data(w)) {
impossible("Tried to wrap a widget twice");
return;
}
/* Attach a destroy callback to reclaim resources attached to the widget */
XtAddCallback(w, XtNdestroyCallback, delete_callback, NULL);
/* Create the structure with its initial settings */
WidgetData *data = add_widget(w);
/* Copy resources from the created widget */
Cardinal num_args2 = 0;
Arg args2[1];
XtSetArg(args2[num_args2], XtNfont, &data->font[0]); num_args2++;
XtGetValues(w, args2, num_args2);
/* Create the pixmap for the first time */
update_label(w, data);
return w;
}
/* Callback when the widget is deleted */
static void
delete_callback(Widget w, XtPointer client_data, XtPointer call_data)
{
nhUse(client_data);
nhUse(call_data);
/*
* We shouldn't get here with any widget for which check_label returns
* false, because such widgets will not have this callback set
*/
WidgetData *data = get_widget_data(w);
if (data != NULL) {
Display *display = XtDisplay(w);
if (data->pixmap != 0) {
XFreePixmap(display, data->pixmap);
}
free_fonts(w, data);
free(data);
}
delete_widget(w);
}
/* Return true if the widget is a Label or of a class derived from Label */
static boolean
check_label(Widget w)
{
return XtIsSubclass(w, labelWidgetClass);
}
/* Update the label's pixmap */
void
X11_update_label(Widget w)
{
if (check_label(w)) {
WidgetData *data = get_widget_data(w);
update_label(w, data);
}
}
/* Update the label's pixmap */
/* This is called from functions that have altered the data block, and already
have a pointer to it */
static void
update_label(Widget w, WidgetData *data)
{
Display *display = XtDisplay(w);
Screen *screen = DefaultScreenOfDisplay(display);
int depth = DefaultDepthOfScreen(screen);
Pixmap new_pixmap = 0;
Cardinal num_args;
Arg args[10];
/* Select the font */
unsigned font_idx = 0;
if (data->attrs & HL_BOLD) {
font_idx |= font_bold;
allocate_font(w, data, font_idx);
}
if (data->attrs & HL_ITALIC) {
font_idx |= font_italic;
allocate_font(w, data, font_idx);
}
XFontStruct *font = data->font[font_idx];
String label;
Boolean sens; /* Make dim if not sensitive */
XtJustify justify; /* Left, center, right */
Pixel fgpixel; /* Colors for the first pass */
Pixel bgpixel;
Boolean resize; /* Resizable? */
num_args = 0;
XtSetArg(args[num_args], XtNlabel, &label); num_args++;
XtSetArg(args[num_args], XtNsensitive, &sens); num_args++;
XtSetArg(args[num_args], XtNjustify, &justify); num_args++;
XtSetArg(args[num_args], XtNforeground, &fgpixel); num_args++;
XtSetArg(args[num_args], XtNbackground, &bgpixel); num_args++;
XtSetArg(args[num_args], XtNresize, &resize); num_args++;
XtGetValues(w, args, num_args);
unsigned attrs = data->attrs;
if (!sens) {
attrs |= HL_DIM;
}
/* Dimensions of pixmap */
Dimension width;
Dimension height;
if (resize) {
/* Render the widest width and the total height of the lines */
size_t i = 0;
width = 0;
height = 0;
while (label[i] != '\0') {
size_t line1 = strcspn(label + i, "\n");
size_t line2 = line1;
/* Exclude \r from the rendering */
if (line2 != 0 && label[i + line2 - 1] == '\r') {
--line2;
}
/* Get the width of the line */
int lwidth = XTextWidth(font, label + i, line2);
/* Update pixmap dimensions */
width = max(lwidth, width);
height += font->ascent + font->descent;
/* Advance to next line */
i += line1;
if (label[i] == '\n') {
++i;
}
}
/* Always size for at least one line */
height = max(height, font->ascent + font->descent);
} else {
num_args = 0;
XtSetArg(args[num_args], XtNwidth, &width); num_args++;
XtSetArg(args[num_args], XtNheight, &height); num_args++;
XtGetValues(w, args, num_args);
}
width = max(width, 1);
height = max(height, 1);
/* Create the pixmap */
new_pixmap = XCreatePixmap(display, RootWindowOfScreen(screen),
width, height, depth);
/* If a percent bar is specified, make two passes over the text and render
the percent bar in the second pass */
for (unsigned pass = 0; pass < 2; ++pass) {
XGCValues values;
if (attrs & HL_INVERSE) {
values.foreground = bgpixel;
values.background = fgpixel;
} else {
values.foreground = fgpixel;
values.background = bgpixel;
}
if (attrs & HL_DIM) {
values.foreground = (values.foreground & 0xFEFEFE) >> 1;
values.background = (values.background & 0xFEFEFE) >> 1;
}
values.font = font->fid;
values.function = GXcopy;
GC ggc = XtGetGC(w,
GCFunction | GCForeground | GCBackground | GCFont,
&values);
if (pass == 1) {
/* Percent bar will occupy this area */
XRectangle clip = {
.x = 0,
.y = 0,
.width = width * data->percent / 100,
.height = height
};
XSetClipRectangles(display, ggc, 0, 0, &clip, 1, Unsorted);
}
XSetForeground(display, ggc, values.background);
XFillRectangle(display, new_pixmap, ggc, 0, 0, width, height);
XSetForeground(display, ggc, values.foreground);
int y = font->max_bounds.ascent;
size_t i = 0;
while (label[i] != '\0') {
size_t line1 = strcspn(label + i, "\n");
size_t line2 = line1;
/* Exclude \r from the rendering */
if (line2 != 0 && label[i + line2 - 1] == '\r') {
--line2;
}
/* Get the width of the line */
int lwidth = XTextWidth(font, label + i, line2);
/* Place the line horizontally */
int x = 0;
switch (justify) {
case XtJustifyLeft:
x = 0;
break;
case XtJustifyCenter:
x = (width - lwidth) / 2;
break;
case XtJustifyRight:
x = width - lwidth;
break;
}
/* Render the line */
XDrawString(display, new_pixmap, ggc,
x, y,
label + i, line2);
y += font->ascent + font->descent;
/* Advance to next line */
i += line1;
if (label[i] == '\n') {
++i;
}
}
XtReleaseGC(w, ggc);
/* Set up to display the percent bar on the second pass */
if (data->percent == 0) {
break;
}
fgpixel = bgpixel;
bgpixel = data->bar_color;
}
/* Update the pixmap */
num_args = 0;
XtSetArg(args[num_args], XtNbitmap, new_pixmap); num_args++;
//XtSetArg(args[num_args], XtNwidth, width); num_args++;
//XtSetArg(args[num_args], XtNheight, height); num_args++;
XtSetValues(w, args, num_args);
if (data->pixmap != 0) {
XFreePixmap(display, data->pixmap);
}
data->pixmap = new_pixmap;
}
/* Allocate a bold or italic font */
static void
allocate_font(Widget w, WidgetData *data, unsigned font_idx)
{
Display *display = XtDisplay(w);
XFontStruct *font1 = (font_idx == (font_bold | font_italic))
? data->font[font_bold]
: data->font[0];
data->font_ptr[font_idx] = (font_idx == font_bold)
? X11_bold_font(display, font1)
: X11_italic_font(display, font1);
data->font[font_idx] = data->font_ptr[font_idx]
? data->font_ptr[font_idx]
: font1;
}
/* Free any allocated fonts */
static void
free_fonts(Widget w, WidgetData *data)
{
Display *display = XtDisplay(w);
for (unsigned i = 0; i < 4; ++i) {
XFreeFont(display, data->font_ptr[i]);
data->font[i] = NULL;
data->font_ptr[i] = NULL;
}
}
//////////////////////////////////////////////////////////////////////////////
// A table of widgets and their data blocks //
//////////////////////////////////////////////////////////////////////////////
typedef struct WidgetBucket {
Widget w;
WidgetData *data;
} WidgetBucket;
#define MAX_WIDGETS 1024
static WidgetBucket widget_table[MAX_WIDGETS];
static unsigned num_widgets;
/* bsearch compare function to search widget-table */
static int
widget_compare(const void *key_, const void *value_)
{
const Widget *key = key_;
const WidgetBucket *value = value_;
if ((uintptr_t)key < (uintptr_t)value->w) {
return -1;
}
if ((uintptr_t)key > (uintptr_t)value->w) {
return +1;
}
return 0;
}
/* Given the widget, return the entry in widget_table, or NULL if not found */
static WidgetBucket *
find_widget_bucket(Widget w)
{
WidgetBucket *bucket =
bsearch(w, widget_table, num_widgets, sizeof(WidgetBucket),
widget_compare);
return bucket;
}
/* Given the widget, return the WidgetData structure, or NULL if not found */
static WidgetData *
get_widget_data(Widget w)
{
WidgetBucket *bucket = find_widget_bucket(w);
return (bucket != NULL) ? bucket->data : NULL;
}
/* Add a widget to the table, set its normal and bold fonts and provide for
its removal from the table */
static WidgetData *
add_widget(Widget w)
{
/* Panic rather than overflow the array */
if (num_widgets >= MAX_WIDGETS) {
panic("Widget table is full\n");
}
/* Insert the widget into the table, maintaining its order */
unsigned i;
for (i = num_widgets;
i != 0 && (uintptr_t)widget_table[i-1].w > (uintptr_t)w;
--i) {
widget_table[i] = widget_table[i-1];
}
++num_widgets;
widget_table[i].w = w;
/* Create the data block */
WidgetData *data = (WidgetData *)alloc(sizeof(*data));
memset(data, 0, sizeof(*data));
widget_table[i].data = data;
return data;
}
/* Remove the widget from the table */
static void
delete_widget(Widget w)
{
/* Find its location in the table */
WidgetBucket *bucket = find_widget_bucket(w);
if (bucket == NULL) {
return;
}
/* Remove the widget from the table */
for (unsigned i = (unsigned)(bucket - widget_table);
i + 1 < MAX_WIDGETS;
++i) {
widget_table[i] = widget_table[i+1];
}
--num_widgets;
}