]> git.draconx.ca Git - rrace.git/blob - src/motif.c
Make EWMH icon generation more abstract.
[rrace.git] / src / motif.c
1 /*
2  * X11 GUI for slide puzzle game
3  * Copyright © 2022 Nick Bowler
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  */
18
19 #include <config.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <locale.h>
23 #include <getopt.h>
24
25 #include <Xm/XmAll.h>
26
27 #include "help.h"
28 #include "motif.h"
29 #include "ewmhicon.h"
30 #include "motifopt.h"
31 #include "game.h"
32 #include "version.h"
33
34 #define TIMER_UPDATE_MS 33
35
36 #define PROGNAME "rrace"
37 static const char *progname = PROGNAME;
38 static const struct option lopts[] = { LOPTS_INITIALIZER, {0} };
39
40 static char * const default_resources[] = {
41         "*title: RRace",
42         "*game.height: 371", // 365 + 2*shadowThickness
43         "*game.width: 498",
44
45         "*game.XmFrame.shadowThickness: 3",
46         "*game.XmFrame.shadowType: shadow_in",
47         "*goalArea.leftOffset: 1",
48
49         "*gameNew.accelerator: Ctrl<Key>N",
50         "*gameNew.acceleratorText: Ctrl+N",
51         "*gameExit.accelerator: Ctrl<Key>Q",
52         "*gameExit.acceleratorText: Ctrl+Q",
53
54         "*aboutDialog*pixmapTextPadding: 10",
55
56         NULL
57 };
58
59 static void print_version(void)
60 {
61         version_print_head("rrace-motif", stdout);
62         puts("License GPLv3+: GNU GPL version 3 or any later version");
63         puts("This is free software: you are free to change and redistribute it.");
64         puts("There is NO WARRANTY, to the extent permitted by law.");
65 }
66
67 static void print_usage(FILE *f)
68 {
69         fprintf(f, "Usage: %s [options]\n", progname);
70         if (f != stdout)
71                 fprintf(f, "Try %s --help for more information.\n", progname);
72 }
73
74 static void print_help(void)
75 {
76         struct lopt_help help = {0};
77         const struct option *opt;
78
79         print_usage(stdout);
80
81         putchar('\n');
82         puts("Options:");
83         for (opt = lopts; opt->name; opt++) {
84                 if (!lopt_get_help(opt, &help))
85                         continue;
86                 help_print_option(opt, help.arg, help.desc, 20);
87         }
88         putchar('\n');
89
90         printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT);
91 }
92
93 static Widget early_setup(XtAppContext *app, int argc, char **argv)
94 {
95         Widget shell;
96         int opt;
97
98         /* Check for --help/--version early (before X connection) */
99         opterr = 0;
100         while ((opt = getopt_long_only(argc, argv, "", lopts, NULL)) != -1) {
101                 switch (opt) {
102                 case LOPT_VERSION:
103                         print_version();
104                         exit(EXIT_SUCCESS);
105                 case LOPT_HELP:
106                         print_help();
107                         exit(EXIT_SUCCESS);
108                 }
109         }
110
111         shell = XtOpenApplication(app, PACKAGE_TARNAME, NULL, 0,
112                                   &argc, argv, (String *)default_resources,
113                                   sessionShellWidgetClass, NULL, 0);
114
115         opterr = optind = 1;
116         while ((opt = getopt_long_only(argc, argv, "", lopts, NULL)) != -1) {
117                 switch (opt) {
118                 default:
119                         print_usage(stderr);
120                         exit(EXIT_FAILURE);
121                 }
122         }
123
124         if (argv[optind]) {
125                 fprintf(stderr, "%s: excess command-line arguments.\n",
126                                 progname);
127                 print_usage(stderr);
128                 exit(EXIT_FAILURE);
129         }
130
131         return shell;
132 }
133
134 static void timer_tick(void *data, XtIntervalId *id)
135 {
136         struct app_state *state = data;
137         XtAppContext app;
138
139         if (state->board.x > 4) {
140                 /* Game is over */
141                 state->timer_tick = 0;
142                 return;
143         }
144
145         app = XtWidgetToApplicationContext(state->timer);
146         ui_timer_update(state, game_elapsed(&state->board));
147         state->timer_tick = XtAppAddTimeOut(app, TIMER_UPDATE_MS,
148                                             timer_tick, state);
149 }
150
151 static void do_input_move(struct app_state *state, int x, int y)
152 {
153         uint_fast32_t mask;
154
155         if ((mask = game_do_move(&state->board, x, y)) != 0) {
156                 if (game_check_goal(&state->board) == 0) {
157                         int_fast32_t ms = game_finish(&state->board);
158                         unsigned min, sec;
159
160                         ui_timer_update(state, ms);
161
162                         sec = ms / 1000, ms %= 1000;
163                         min = sec / 60, sec %= 60;
164                         printf("You won!  Time was %u:%.2u.%.3u\n",
165                                min, sec, (unsigned)ms);
166
167                         mask |= ~GOAL_MASK;
168                 }
169
170                 x11_redraw_game(state, mask);
171         }
172 }
173
174 static void set_view_goal(struct app_state *state, int view_goal)
175 {
176         state->view_goal_on_game = view_goal;
177         x11_redraw_game(state, game_check_goal(&state->board));
178 }
179
180 static void game_input(Widget w, void *data, void *cb_data)
181 {
182         XmDrawingAreaCallbackStruct *cbs = cb_data;
183         XButtonEvent *b = &cbs->event->xbutton;
184         struct app_state *state = data;
185         Dimension width, height;
186
187         switch (cbs->event->type) {
188         case ButtonPress:
189                 switch (b->button) {
190                 case Button1:
191                         if (b->state & Button3Mask)
192                                 break;
193
194                         XtVaGetValues(w, XmNwidth, &width,
195                                          XmNheight, &height,
196                                          (char *)NULL);
197
198                         do_input_move(state, b->x / (width / 5),
199                                              b->y / (height / 5));
200                         break;
201                 case Button3:
202                         set_view_goal(state, 1);
203                         break;
204                 }
205                 break;
206         case ButtonRelease:
207                 switch (b->button) {
208                 case Button3:
209                         set_view_goal(state, 0);
210                         break;
211                 }
212         }
213 }
214
215 static struct app_state state;
216
217 static Widget get_shell(Widget w)
218 {
219         Widget shell;
220
221         for (shell = w; !XtIsWMShell(shell);)
222                 shell = XtParent(shell);
223
224         return shell;
225 }
226
227 static void proc_exit(Widget w, XEvent *e, String *argv, Cardinal *argc)
228 {
229         XtAppSetExitFlag(XtWidgetToApplicationContext(w));
230 }
231
232 static void proc_new_game(Widget w, XEvent *e, String *argv, Cardinal *argc)
233 {
234         game_reset(&state.board);
235
236         x11_redraw_goal(&state, -1);
237         x11_redraw_game(&state, -1);
238         x11_redraw_icon(&state, get_shell(w));
239
240         if (!state.timer_tick) {
241                 XtAppContext app = XtWidgetToApplicationContext(w);
242                 state.timer_tick = XtAppAddTimeOut(app, TIMER_UPDATE_MS,
243                                                    timer_tick, &state);
244         }
245
246         game_begin(&state.board);
247 }
248
249 static void proc_about(Widget w, XEvent *e, String *argv, Cardinal *argc)
250 {
251         ui_show_about(&state, get_shell(w));
252 }
253
254 static const XtActionsRec menu_actions[] = {
255         { "gameNew", proc_new_game },
256         { "gameExit", proc_exit },
257         { "helpAbout", proc_about }
258 };
259
260 static XtAppContext app_initialize(int argc, char **argv)
261 {
262         XtAppContext app;
263         Widget shell;
264         int i;
265
266         if (argc > 0)
267                 progname = argv[0];
268
269         shell = early_setup(&app, argc, argv);
270         XtAppAddActions(app, (void *)menu_actions, XtNumber(menu_actions));
271         ui_initialize(&state, shell);
272         x11_initialize(&state, shell);
273
274         XtAddCallback(state.game, XmNinputCallback,  game_input, &state);
275
276         /* Begin with the game in winning state */
277         game_reset(&state.board);
278         for (i = 0; i < 3; i++)
279                 state.board.game[i] = state.board.goal[i] << GOAL_SHIFT;
280         game_finish(&state.board);
281
282         state.use_ewmh_icons = ewmh_probe_wm_icon(shell);
283         XtRealizeWidget(shell);
284
285         x11_redraw_icon(&state, shell);
286
287         return app;
288 }
289
290 int main(int argc, char **argv)
291 {
292         setlocale(LC_ALL, "");
293         XtAppMainLoop(app_initialize(argc, argv));
294         return 0;
295 }