From da31028b3c2dfdb4d203fd9481568fe01d16926e Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Wed, 16 Mar 2022 20:07:21 -0400 Subject: [PATCH] Implement the about dialog. --- Makefile.am | 5 ++- conf_post.h | 4 +++ configure.ac | 7 ++++ m4/.gitignore | 34 +++++++++++++++++++ m4/gnulib-cache.m4 | 10 +++++- src/motif.c | 31 ++++++++++++----- src/motif.h | 1 + src/motif_ui.c | 85 ++++++++++++++++++++++++++++++++++++++++++---- src/motifgui.dat | 4 +++ src/version.c | 51 ++++++++++++++++++++++++++++ src/version.h | 19 +++++++++++ 11 files changed, 233 insertions(+), 18 deletions(-) create mode 100644 conf_post.h create mode 100644 src/version.c create mode 100644 src/version.h diff --git a/Makefile.am b/Makefile.am index 15f3a5d..887411a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -22,9 +22,12 @@ bin_PROGRAMS = rrace-motif dist_man_MANS = doc/rrace-motif.1 endif +noinst_HEADERS = conf_post.h + rrace_motif_SOURCES = src/game.c src/x11.c src/game.h src/motif.h \ src/colour.h src/ewmhicon.c src/ewmhicon.h \ - src/xcounter.c src/xcounter.h + src/version.c src/version.h src/xcounter.c \ + src/xcounter.h rrace_motif_LDADD = $(libmotifmain_a_OBJECTS) $(libmotifui_a_OBJECTS) \ $(libglohelp_a_OBJECTS) libgnu.a $(MOTIF_LIBS) \ $(LIB_CLOCK_GETTIME) $(LIB_GETHRXTIME) diff --git a/conf_post.h b/conf_post.h new file mode 100644 index 0000000..6368d76 --- /dev/null +++ b/conf_post.h @@ -0,0 +1,4 @@ +/* Allow ENABLE_NLS to be used in C conditionals. */ +#ifndef ENABLE_NLS +# define ENABLE_NLS 0 +#endif diff --git a/configure.ac b/configure.ac index 2378c8e..755d911 100644 --- a/configure.ac +++ b/configure.ac @@ -22,6 +22,13 @@ gl_INIT AC_CACHE_SAVE m4_include([lib/gnulib.mk]) +dnl We will provide our own makefile rules for gettext. Disable tracing of +dnl AM_GNU_GETTEXT to prevent autoreconf from running autopoint, and to +dnl prevent automake from growing gratuitous error conditions. +m4_traceoff([AM_GNU_GETTEXT]) +AM_GNU_GETTEXT([external]) +AH_BOTTOM([#include ]) + AC_PATH_XTRA AS_IF([test x"$no_x" != x"yes"], [AC_CACHE_CHECK([for Motif], [dx_cv_have_motif], diff --git a/m4/.gitignore b/m4/.gitignore index 5ee6340..90eb929 100644 --- a/m4/.gitignore +++ b/m4/.gitignore @@ -1,9 +1,11 @@ /00gnulib.m4 /absolute-header.m4 /clock_time.m4 +/codeset.m4 /extensions.m4 /extern-inline.m4 /flexmember.m4 +/free.m4 /gethrxtime.m4 /getopt.m4 /gettime.m4 @@ -11,25 +13,57 @@ /gnulib-common.m4 /gnulib-comp.m4 /gnulib-tool.m4 +/host-cpu-c-abi.m4 +/iconv.m4 +/iconv_h.m4 +/iconv_open.m4 /include_next.m4 /inline.m4 +/inttypes.m4 +/lib-ld.m4 +/lib-link.m4 +/lib-prefix.m4 /libtool.m4 +/libunistring-base.m4 +/limits-h.m4 +/localcharset.m4 +/locale-fr.m4 +/locale-ja.m4 +/locale-zh.m4 +/locale_h.m4 /ltoptions.m4 /ltsugar.m4 /ltversion.m4 /lt~obsolete.m4 +/mbrtowc.m4 +/mbsinit.m4 +/mbstate_t.m4 +/mbswidth.m4 +/multiarch.m4 /nocrash.m4 /off_t.m4 /pid_t.m4 +/setlocale_null.m4 /ssize_t.m4 +/std-gnu11.m4 +/stdbool.m4 /stddef_h.m4 +/stdint.m4 +/stdlib_h.m4 +/string_h.m4 /sys_socket_h.m4 /sys_time_h.m4 /sys_types_h.m4 +/threadlib.m4 /time_h.m4 /timespec.m4 /unistd_h.m4 /vararrays.m4 +/visibility.m4 /warn-on-use.m4 +/wchar_h.m4 /wchar_t.m4 +/wctype_h.m4 +/wcwidth.m4 +/wint_t.m4 /zzgnulib.m4 diff --git a/m4/gnulib-cache.m4 b/m4/gnulib-cache.m4 index 6b6352e..e7a0fcf 100644 --- a/m4/gnulib-cache.m4 +++ b/m4/gnulib-cache.m4 @@ -42,7 +42,11 @@ # flexmember \ # gethrxtime \ # getopt-gnu \ -# inline +# gettext-h \ +# inline \ +# localcharset \ +# mbswidth \ +# striconv # Specification in the form of a few gnulib-tool.m4 macro invocations: gl_LOCAL_DIR([]) @@ -50,7 +54,11 @@ gl_MODULES([ flexmember gethrxtime getopt-gnu + gettext-h inline + localcharset + mbswidth + striconv ]) gl_AVOID([]) gl_SOURCE_BASE([lib]) diff --git a/src/motif.c b/src/motif.c index f9da292..ef06c52 100644 --- a/src/motif.c +++ b/src/motif.c @@ -28,6 +28,7 @@ #include "motif.h" #include "motifopt.h" #include "game.h" +#include "version.h" #define TIMER_UPDATE_MS 33 @@ -49,13 +50,14 @@ static char * const default_resources[] = { "*gameExit.accelerator: CtrlQ", "*gameExit.acceleratorText: Ctrl+Q", + "*aboutDialog*pixmapTextPadding: 10", + NULL }; static void print_version(void) { - printf("%s %s\n", PROGNAME, PACKAGE_VERSION); - printf("Copyright (C) 2022 Nick Bowler\n"); + version_print_head("rrace-motif", stdout); puts("License GPLv3+: GNU GPL version 3 or any later version"); puts("This is free software: you are free to change and redistribute it."); puts("There is NO WARRANTY, to the extent permitted by law."); @@ -211,6 +213,16 @@ static void game_input(Widget w, void *data, void *cb_data) static struct app_state state; +static Widget get_shell(Widget w) +{ + Widget shell; + + for (shell = w; !XtIsWMShell(shell);) + shell = XtParent(shell); + + return shell; +} + static void proc_exit(Widget w, XEvent *e, String *argv, Cardinal *argc) { XtAppSetExitFlag(XtWidgetToApplicationContext(w)); @@ -218,16 +230,11 @@ static void proc_exit(Widget w, XEvent *e, String *argv, Cardinal *argc) static void proc_new_game(Widget w, XEvent *e, String *argv, Cardinal *argc) { - Widget shell; - - for (shell = w; !XtIsWMShell(shell);) - shell = XtParent(shell); - game_reset(&state.board); x11_redraw_goal(&state, -1); x11_redraw_game(&state, -1); - x11_redraw_icon(&state, shell); + x11_redraw_icon(&state, get_shell(w)); if (!state.timer_tick) { XtAppContext app = XtWidgetToApplicationContext(w); @@ -238,9 +245,15 @@ static void proc_new_game(Widget w, XEvent *e, String *argv, Cardinal *argc) game_begin(&state.board); } +static void proc_about(Widget w, XEvent *e, String *argv, Cardinal *argc) +{ + ui_show_about(&state, get_shell(w)); +} + static const XtActionsRec menu_actions[] = { { "gameNew", proc_new_game }, - { "gameExit", proc_exit } + { "gameExit", proc_exit }, + { "helpAbout", proc_about } }; static XtAppContext app_initialize(int argc, char **argv) diff --git a/src/motif.h b/src/motif.h index cdbbe48..9064788 100644 --- a/src/motif.h +++ b/src/motif.h @@ -46,6 +46,7 @@ struct app_state { }; void ui_initialize(struct app_state *state, Widget shell); +void ui_show_about(struct app_state *state, Widget shell); void ui_timer_update(struct app_state *state, int_fast32_t elapsed); void x11_initialize(struct app_state *state, Widget shell); diff --git a/src/motif_ui.c b/src/motif_ui.c index a49dfec..05721e0 100644 --- a/src/motif_ui.c +++ b/src/motif_ui.c @@ -25,6 +25,7 @@ #include "motifstr.h" #include "motifgui.h" #include "xcounter.h" +#include "version.h" #define SPLIT_NUMERATOR 75 #define SPLIT_DENOMINATOR 100 @@ -42,7 +43,11 @@ enum { widgetMax, /* Pseudo-widgets */ - widgetMenuBar = widgetMax + widgetMenuBar = widgetMax, + widgetMessageDialog, + widgetScrolledText, + + widgetUnmanaged = 128 }; static WidgetClass * const widgets[widgetMax] = { @@ -171,14 +176,19 @@ static Widget create_widget(const struct ui_widget *item, Widget parent, ArgList args, Cardinal num_args) { String name = (void *)&tree_strtab[item->name]; - WidgetClass class; + unsigned type = item->widget_type & widgetUnmanaged-1; - if (item->widget_type == widgetMenuBar) + switch (type) { + case widgetMenuBar: return XmCreateMenuBar(parent, name, args, num_args); + case widgetMessageDialog: + return XmCreateMessageDialog(parent, name, args, num_args); + case widgetScrolledText: + return XmCreateScrolledText(parent, name, args, num_args); + } - assert(item->widget_type < widgetMax); - class = *widgets[item->widget_type]; - return XtCreateWidget(name, class, parent, args, num_args); + assert(type < widgetMax); + return XtCreateWidget(name, *widgets[type], parent, args, num_args); } static void @@ -192,7 +202,8 @@ construct_widgets(const struct ui_widget *root, Widget parent, unsigned i) if (item->subtree) construct_widgets(root, w, item->subtree); - XtManageChild(w); + if (!(item->widget_type & widgetUnmanaged)) + XtManageChild(w); } } @@ -365,3 +376,63 @@ void ui_initialize(struct app_state *state, Widget shell) XtAddCallback(state->goal, XmNresizeCallback, goal_resize, state); XtAddCallback(state->goal, XmNexposeCallback, goal_expose, state); } + +static void dialog_close(Widget w, void *data, void *cb_data) +{ + XtDestroyWidget(w); +} + +/* + * Expands to an XtVaSetValues argument list to set the given resource with + * a typed string value, which is internally converted to the appropriate + * resource type. + * + * Note that str is expanded twice and thus should not have side effects. + */ +#define STRING_ARG(resource, str) \ + XtVaTypedArg, (resource), XtRString, (str), strlen((str))+1 + +void ui_show_about(struct app_state *state, Widget shell) +{ + static const struct ui_widget dialog[] = { ABOUTDIALOG_INITIALIZER }; + Widget w, l; + char *msg; + + construct_widgets(dialog, shell, 0); + + w = XtNameToWidget(shell, "*aboutDialog"); + XtUnmanageChild(XmMessageBoxGetChild(w, XmDIALOG_CANCEL_BUTTON)); + + XtAddCallback(w, XmNunmapCallback, dialog_close, NULL); + + XtVaSetValues(w, STRING_ARG(XmNokLabelString, "Close"), (char *)NULL); + + msg = version_format_head("rrace-motif"); + l = XmMessageBoxGetChild(w, XmDIALOG_MESSAGE_LABEL); + XtVaSetValues(l, XmNlabelType, XmPIXMAP_AND_STRING, + XmNlabelPixmap, state->icon_pixmap, + STRING_ARG(XmNlabelString, msg), + (char *)NULL); + free(msg); + + l = XtNameToWidget(w, "*licenseBlurb"); + XmTextSetString(l, +"This program is free software: you can redistribute it and/or modify\n" +"it under the terms of the GNU General Public License as published by\n" +"the Free Software Foundation, either version 3 of the License, or\n" +"(at your option) any later version.\n\n" +"This program is distributed in the hope that it will be useful,\n" +"but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" +"GNU General Public License for more details.\n\n" +"You should have received a copy of the GNU General Public License\n" +"along with this program. If not, see ."); + + XtVaSetValues(l, XmNeditMode, XmMULTI_LINE_EDIT, + XmNeditable, FALSE, + XmNresizeWidth, TRUE, + XmNrows, 5, + (char *)NULL); + + XtManageChild(w); +} diff --git a/src/motifgui.dat b/src/motifgui.dat index 2e6a675..159a815 100644 --- a/src/motifgui.dat +++ b/src/motifgui.dat @@ -8,6 +8,10 @@ MAINWIN goalCanvas, 0, widgetDrawingArea timeDisplay, 0, widgetDrawingArea +ABOUTDIALOG + aboutDialog, aboutDialog_OFFSET, widgetUnmanaged|widgetMessageDialog + licenseBlurb, 0, widgetScrolledText + MAINMENU gameMenu, gameMenu_OFFSET, widgetCascadeButton, gameMenuLabel gameNew, 0, widgetPushButton, gameNewLabel diff --git a/src/version.c b/src/version.c new file mode 100644 index 0000000..6a5ce9a --- /dev/null +++ b/src/version.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include + +#include "version.h" + +const char *init_copysign(char **alloc) +{ + *alloc = NULL; + + if (ENABLE_NLS) { + char *buf = str_iconv("\xc2\xa9", "UTF-8", locale_charset()); + if (buf) + return *alloc = buf; + } + + return "(C)"; +} + +#define VERSION_HEAD_FMT "%s (RRace) %s\nCopyright %s 2022 Nick Bowler" +#define VERSION_HEAD_ARGS progname, PACKAGE_VERSION, copysign + +void version_print_head(const char *progname, FILE *f) +{ + const char *copysign; + char *copybuf; + + copysign = init_copysign(©buf); + + printf(VERSION_HEAD_FMT, VERSION_HEAD_ARGS); + putc('\n', f); + + free(copybuf); +} + +char *version_format_head(const char *progname) +{ + const char *copysign; + char *copybuf, *ret; + + copysign = init_copysign(©buf); + + ret = malloc(sizeof VERSION_HEAD_FMT + 100); + if (ret) + sprintf(ret, VERSION_HEAD_FMT, VERSION_HEAD_ARGS); + + free(copybuf); + return ret; +} diff --git a/src/version.h b/src/version.h new file mode 100644 index 0000000..5c3850f --- /dev/null +++ b/src/version.h @@ -0,0 +1,19 @@ +#ifndef RRACE_VERSION_H_ +#define RRACE_VERSION_H_ + +#include + +/* + * Print a line containing the name of the program and the version of the + * package, followed by a line for each copyright notice to the specified file. + */ +void version_print_head(const char *progname, FILE *f); + +/* + * As with the above, but the string is put in a newly-allocated string which + * must be freed by the caller. The resulting string has newline characters + * between each line but there is no newline after the final line. + */ +char *version_format_head(const char *progname); + +#endif -- 2.43.2