From 6043d189e1bc2f5a79d4eac70ec0f548dbe8caa0 Mon Sep 17 00:00:00 2001 From: Ray Chason Date: Tue, 11 Aug 2026 09:31:11 -0400 Subject: [PATCH] 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. --- include/winX.h | 5 + sys/unix/Makefile.src | 6 +- win/X11/winX.c | 21 ++ win/X11/winlabel.c | 445 ++++++++++++++++++++++++++++++++++++++++++ win/X11/winstat.c | 6 + win/X11/winval.c | 9 +- 6 files changed, 488 insertions(+), 4 deletions(-) create mode 100644 win/X11/winlabel.c diff --git a/include/winX.h b/include/winX.h index 935fd5f5c..2bd73d679 100644 --- a/include/winX.h +++ b/include/winX.h @@ -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 */ diff --git a/sys/unix/Makefile.src b/sys/unix/Makefile.src index 7347880e5..2bac600b8 100644 --- a/sys/unix/Makefile.src +++ b/sys/unix/Makefile.src @@ -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 diff --git a/win/X11/winX.c b/win/X11/winX.c index f641cfb56..e247a7c82 100644 --- a/win/X11/winX.c +++ b/win/X11/winX.c @@ -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) diff --git a/win/X11/winlabel.c b/win/X11/winlabel.c new file mode 100644 index 000000000..476bedcdd --- /dev/null +++ b/win/X11/winlabel.c @@ -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 +#include +#include + +#include + +#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; +} diff --git a/win/X11/winstat.c b/win/X11/winstat.c index 4075d4a4e..304be9579 100644 --- a/win/X11/winstat.c +++ b/win/X11/winstat.c @@ -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: diff --git a/win/X11/winval.c b/win/X11/winval.c index 9abd40f72..daea9812d 100644 --- a/win/X11/winval.c +++ b/win/X11/winval.c @@ -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); }