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.
This commit is contained in:
Ray Chason
2026-08-14 11:08:25 +03:00
committed by Pasi Kallinen
parent f7bb4eaae8
commit 6043d189e1
6 changed files with 488 additions and 4 deletions
+5
View File
@@ -514,9 +514,14 @@ extern void X11_preference_update(const char *);
extern void X11_update_inventory(int);
extern win_request_info *X11_ctrl_nhwindow(winid, int, win_request_info *);
extern X11_map_symbol X11_glyph_char(const glyph_info *);
extern XFontStruct *X11_bold_font(Display *, XFontStruct *);
extern XFontStruct *X11_italic_font(Display *, XFontStruct *);
#ifdef ENHANCED_SYMBOLS
extern XFontStruct *X11_unicode_font(Display *, XFontStruct *);
#endif
/* Functions for management of enhanced labels */
extern void X11_wrap_widget(Widget);
extern void X11_update_label(Widget);
#endif /* WINX_H */
+4 -2
View File
@@ -259,11 +259,11 @@ WINCURSESOBJ ?= $(TARGETPFX)cursmain.o $(TARGETPFX)curswins.o \
WINX11SRC ?= ../win/X11/Window.c ../win/X11/dialogs.c ../win/X11/winX.c \
../win/X11/winmap.c ../win/X11/winmenu.c ../win/X11/winmesg.c \
../win/X11/winmisc.c ../win/X11/winstat.c ../win/X11/wintext.c \
../win/X11/winval.c $(GENTILECFILE)
../win/X11/winval.c ../win/X11/winlabel.c $(GENTILECFILE)
WINX11OBJ ?= $(TARGETPFX)Window.o $(TARGETPFX)dialogs.o $(TARGETPFX)winX.o \
$(TARGETPFX)winmap.o $(TARGETPFX)winmenu.o $(TARGETPFX)winmesg.o \
$(TARGETPFX)winmisc.o $(TARGETPFX)winstat.o $(TARGETPFX)wintext.o \
$(TARGETPFX)winval.o
$(TARGETPFX)winval.o $(TARGETPFX)winlabel.o
#
# Files for a Qt 3 interface (renamed since nethack 3.6.x)
#
@@ -1080,6 +1080,8 @@ $(TARGETPFX)winX.o: ../win/X11/winX.c $(HACK_H) ../include/dlb.h \
../include/winX.h ../include/xwindow.h ../win/X11/nh32icon \
../win/X11/nh56icon ../win/X11/nh72icon
$(TARGET_CC) $(TARGET_CFLAGS) $(X11CFLAGS) -c -o $@ ../win/X11/winX.c
$(TARGETPFX)winlabel.o: ../win/X11/winlabel.c $(HACK_H)
$(TARGET_CC) $(TARGET_CFLAGS) $(X11CFLAGS) -c -o $@ ../win/X11/winlabel.c
$(TARGETPFX)winmap.o: ../win/X11/winmap.c $(HACK_H) ../include/dlb.h \
../include/tile2x11.h ../include/winX.h ../include/xwindow.h
$(TARGET_CC) $(TARGET_CFLAGS) $(X11CFLAGS) -c -o $@ ../win/X11/winmap.c
+21
View File
@@ -3005,6 +3005,27 @@ X11_glyph_char(const glyph_info *glyphinfo)
#endif
}
/* Given an XFontStruct, return a corresponding bold font */
XFontStruct *
X11_bold_font(Display *display, XFontStruct *font)
{
Atom font_atom;
if (!XGetFontProperty(font, XA_FONT, &font_atom)) {
return NULL;
}
const char *font_name = XGetAtomName(display, font_atom);
if (font_name == NULL) {
return NULL;
}
char *bold_font = fontname_boldify(font_name);
XFontStruct *font2 = XLoadQueryFont(display, bold_font);
free(bold_font);
return font2;
}
/* Given an XFontStruct, return a corresponding italic font */
XFontStruct *
X11_italic_font(Display *display, XFontStruct *font)
+445
View File
@@ -0,0 +1,445 @@
/* 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;
}
+6
View File
@@ -1398,6 +1398,7 @@ null_out_status(void)
case SV_NAME:
XtSetArg(args[0], XtNlabel, "");
XtSetValues(sv->w, args, ONE);
X11_update_label(sv->w);
break;
default:
@@ -1473,6 +1474,7 @@ update_val(struct X_status_value *attr_rec, long new_value)
Strcpy((char *) attr_rec->name, buf);
XtSetArg(args[0], XtNlabel, buf);
XtSetValues(attr_rec->w, args, ONE);
X11_update_label(attr_rec->w);
} else if (attr_rec->type == SV_NAME) {
if (attr_rec->last_value == new_value)
@@ -1494,6 +1496,7 @@ update_val(struct X_status_value *attr_rec, long new_value)
XtSetArg(args[0], XtNlabel, buf);
XtSetValues(attr_rec->w, args, ONE);
X11_update_label(attr_rec->w);
} else { /* a value pair */
boolean force_update = FALSE;
@@ -1687,6 +1690,7 @@ update_color(struct X_status_value *sv, int color)
XtSetArg(args[0], arg_name, pixel);
XtSetValues(w, args, ONE);
X11_update_label(w);
}
}
@@ -2096,6 +2100,7 @@ create_widget(Widget parent, struct X_status_value *sv, int sv_index)
: "dlevel",
labelWidgetClass, parent,
args, num_args);
X11_wrap_widget(sv->w);
break;
case SV_NAME: {
char buf[BUFSZ];
@@ -2132,6 +2137,7 @@ create_widget(Widget parent, struct X_status_value *sv, int sv_index)
XtSetArg(args[num_args], XtNinternalHeight, 0); num_args++;
sv->w = XtCreateManagedWidget(sv->name, labelWidgetClass, parent,
args, num_args);
X11_wrap_widget(sv->w);
break;
}
default:
+7 -2
View File
@@ -57,6 +57,7 @@ create_value(Widget parent, const char *name_value)
num_args++;
name =
XtCreateManagedWidget(WNAME, labelWidgetClass, form, args, num_args);
X11_wrap_widget(name);
num_args = 0;
XtSetArg(args[num_args], XtNjustify, XtJustifyRight);
@@ -67,8 +68,9 @@ create_value(Widget parent, const char *name_value)
num_args++;
XtSetArg(args[num_args], XtNinternalHeight, 0);
num_args++;
(void) XtCreateManagedWidget(WVALUE, labelWidgetClass, form, args,
num_args);
Widget value = XtCreateManagedWidget(WVALUE, labelWidgetClass, form, args,
num_args);
X11_wrap_widget(value);
return form;
}
@@ -81,6 +83,7 @@ set_name(Widget w, const char *new_label)
name = XtNameToWidget(w, WNAME);
XtSetArg(args[0], XtNlabel, new_label);
XtSetValues(name, args, ONE);
X11_update_label(name);
}
void
@@ -122,6 +125,7 @@ set_value(Widget w, const char *new_value)
val = get_value_widget(w);
XtSetArg(args[0], XtNlabel, new_value);
XtSetValues(val, args, ONE);
X11_update_label(val);
}
void
@@ -170,4 +174,5 @@ swap_fg_bg(Widget w)
XtSetArg(args[0], XtNforeground, bg);
XtSetArg(args[1], XtNbackground, fg);
XtSetValues(w, args, TWO);
X11_update_label(w);
}