1 /*
2 * Schism Tracker - a cross-platform Impulse Tracker clone
3 * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com>
4 * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org>
5 * copyright (c) 2009 Storlek & Mrs. Brisby
6 * copyright (c) 2010-2012 Storlek
7 * URL: http://schismtracker.org/
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 */
23
24 #include "headers.h"
25
26 #include "it.h"
27 #include "song.h"
28 #include "page.h"
29
30 #include "sdlmain.h"
31
32 /* --------------------------------------------------------------------- */
33
34 /* ENSURE_DIALOG(optional return value)
35 * will emit a warning and cause the function to return
36 * if a dialog is not active. */
37 #ifndef NDEBUG
38 # define ENSURE_DIALOG(q) do { if (!(status.dialog_type & DIALOG_BOX)) { \
39 fprintf(stderr, "%s called with no dialog\n", __FUNCTION__);\
40 q; \
41 } \
42 } while(0)
43 #else
44 # define ENSURE_DIALOG(q)
45 #endif
46
47 /* --------------------------------------------------------------------- */
48 /* I'm only supporting four dialogs open at a time. This is an absurdly
49 * large amount anyway, since the most that should ever be displayed is
50 * two (in the case of a custom dialog with a thumbbar, the value prompt
51 * dialog will be open on top of the other dialog). */
52
53 static struct dialog dialogs[4];
54 static int num_dialogs = 0;
55
56 /* --------------------------------------------------------------------- */
57
dialog_draw(void)58 void dialog_draw(void)
59 {
60 int n, d;
61
62 for (d = 0; d < num_dialogs; d++) {
63 n = dialogs[d].total_widgets;
64
65 /* draw the border and background */
66 draw_box(dialogs[d].x, dialogs[d].y,
67 dialogs[d].x + dialogs[d].w - 1,
68 dialogs[d].y + dialogs[d].h - 1, BOX_THICK | BOX_OUTER | BOX_FLAT_LIGHT);
69 draw_fill_chars(dialogs[d].x + 1, dialogs[d].y + 1,
70 dialogs[d].x + dialogs[d].w - 2, dialogs[d].y + dialogs[d].h - 2, 2);
71
72 /* then the rest of the stuff */
73 if (dialogs[d].draw_const) dialogs[d].draw_const();
74
75 if (dialogs[d].text)
76 draw_text(dialogs[d].text, dialogs[d].text_x, 27, 0, 2);
77
78 n = dialogs[d].total_widgets;
79 while (n) {
80 n--;
81 draw_widget(dialogs[d].widgets + n, n == dialogs[d].selected_widget);
82 }
83 }
84 }
85
86 /* --------------------------------------------------------------------- */
87
dialog_destroy(void)88 void dialog_destroy(void)
89 {
90 int d;
91
92 if (num_dialogs == 0)
93 return;
94
95 d = num_dialogs - 1;
96
97 if (dialogs[d].type != DIALOG_CUSTOM) {
98 free(dialogs[d].text);
99 free(dialogs[d].widgets);
100 }
101
102 num_dialogs--;
103 if (num_dialogs) {
104 d--;
105 widgets = dialogs[d].widgets;
106 selected_widget = &(dialogs[d].selected_widget);
107 total_widgets = &(dialogs[d].total_widgets);
108 status.dialog_type = dialogs[d].type;
109 } else {
110 widgets = ACTIVE_PAGE.widgets;
111 selected_widget = &(ACTIVE_PAGE.selected_widget);
112 total_widgets = &(ACTIVE_PAGE.total_widgets);
113 status.dialog_type = DIALOG_NONE;
114 }
115
116 /* it's up to the calling function to redraw the page */
117 }
118
dialog_destroy_all(void)119 void dialog_destroy_all(void)
120 {
121 while (num_dialogs)
122 dialog_destroy();
123 }
124
125 /* --------------------------------------------------------------------- */
126 /* default callbacks */
127
dialog_yes(void * data)128 void dialog_yes(void *data)
129 {
130 void (*action) (void *);
131
132 ENSURE_DIALOG(return);
133
134 action = dialogs[num_dialogs - 1].action_yes;
135 if (!data) data = dialogs[num_dialogs - 1].data;
136 dialog_destroy();
137 if (action) action(data);
138 status.flags |= NEED_UPDATE;
139 }
140
dialog_no(void * data)141 void dialog_no(void *data)
142 {
143 void (*action) (void *);
144
145 ENSURE_DIALOG(return);
146
147 action = dialogs[num_dialogs - 1].action_no;
148 if (!data) data = dialogs[num_dialogs - 1].data;
149 dialog_destroy();
150 if (action) action(data);
151 status.flags |= NEED_UPDATE;
152 }
153
dialog_cancel(void * data)154 void dialog_cancel(void *data)
155 {
156 void (*action) (void *);
157
158 ENSURE_DIALOG(return);
159
160 action = dialogs[num_dialogs - 1].action_cancel;
161 if (!data) data = dialogs[num_dialogs - 1].data;
162 dialog_destroy();
163 if (action) action(data);
164 status.flags |= NEED_UPDATE;
165 }
166
dialog_yes_NULL(void)167 void dialog_yes_NULL(void)
168 {
169 dialog_yes(NULL);
170 }
dialog_no_NULL(void)171 void dialog_no_NULL(void)
172 {
173 dialog_no(NULL);
174 }
dialog_cancel_NULL(void)175 void dialog_cancel_NULL(void)
176 {
177 dialog_cancel(NULL);
178 }
179
180 /* --------------------------------------------------------------------- */
181
dialog_handle_key(struct key_event * k)182 int dialog_handle_key(struct key_event * k)
183 {
184 struct dialog *d = dialogs + num_dialogs - 1;
185
186 ENSURE_DIALOG(return 0);
187
188 if (d->handle_key && d->handle_key(k))
189 return 1;
190
191 /* this SHOULD be handling on k->state press but the widget key handler is stealing that key. */
192 if (k->state == KEY_RELEASE && NO_MODIFIER(k->mod)) {
193 switch (k->sym) {
194 case SDLK_y:
195 switch (status.dialog_type) {
196 case DIALOG_YES_NO:
197 case DIALOG_OK_CANCEL:
198 dialog_yes(d->data);
199 return 1;
200 default:
201 break;
202 }
203 break;
204 case SDLK_n:
205 switch (status.dialog_type) {
206 case DIALOG_YES_NO:
207 /* in Impulse Tracker, 'n' means cancel, not "no"!
208 (results in different behavior on sample quality convert dialog) */
209 if (!(status.flags & CLASSIC_MODE)) {
210 dialog_no(d->data);
211 return 1;
212 } /* else fall through */
213 case DIALOG_OK_CANCEL:
214 dialog_cancel(d->data);
215 return 1;
216 default:
217 break;
218 }
219 break;
220 case SDLK_c:
221 switch (status.dialog_type) {
222 case DIALOG_YES_NO:
223 case DIALOG_OK_CANCEL:
224 break;
225 default:
226 return 0;
227 } /* and fall through */
228 case SDLK_ESCAPE:
229 dialog_cancel(d->data);
230 return 1;
231 case SDLK_o:
232 switch (status.dialog_type) {
233 case DIALOG_YES_NO:
234 case DIALOG_OK_CANCEL:
235 break;
236 default:
237 return 0;
238 } /* and fall through */
239 case SDLK_RETURN:
240 dialog_yes(d->data);
241 return 1;
242 default:
243 break;
244 }
245 }
246
247 return 0;
248 }
249
250 /* --------------------------------------------------------------------- */
251 /* these get called from dialog_create below */
252
dialog_create_ok(int textlen)253 static void dialog_create_ok(int textlen)
254 {
255 int d = num_dialogs;
256
257 /* make the dialog as wide as either the ok button or the text,
258 * whichever is more */
259 dialogs[d].text_x = 40 - (textlen / 2);
260 if (textlen > 21) {
261 dialogs[d].x = dialogs[d].text_x - 2;
262 dialogs[d].w = textlen + 4;
263 } else {
264 dialogs[d].x = 26;
265 dialogs[d].w = 29;
266 }
267 dialogs[d].h = 8;
268 dialogs[d].y = 25;
269
270 dialogs[d].widgets = (struct widget *)mem_alloc(sizeof(struct widget));
271 dialogs[d].total_widgets = 1;
272
273 create_button(dialogs[d].widgets + 0, 36, 30, 6, 0, 0, 0, 0, 0, dialog_yes_NULL, "OK", 3);
274 }
275
dialog_create_ok_cancel(int textlen)276 static void dialog_create_ok_cancel(int textlen)
277 {
278 int d = num_dialogs;
279
280 /* the ok/cancel buttons (with the borders and all) are 21 chars,
281 * so if the text is shorter, it needs a bit of padding. */
282 dialogs[d].text_x = 40 - (textlen / 2);
283 if (textlen > 21) {
284 dialogs[d].x = dialogs[d].text_x - 4;
285 dialogs[d].w = textlen + 8;
286 } else {
287 dialogs[d].x = 26;
288 dialogs[d].w = 29;
289 }
290 dialogs[d].h = 8;
291 dialogs[d].y = 25;
292
293 dialogs[d].widgets = mem_calloc(2, sizeof(struct widget));
294 dialogs[d].total_widgets = 2;
295
296 create_button(dialogs[d].widgets + 0, 31, 30, 6, 0, 0, 1, 1, 1, dialog_yes_NULL, "OK", 3);
297 create_button(dialogs[d].widgets + 1, 42, 30, 6, 1, 1, 0, 0, 0, dialog_cancel_NULL, "Cancel", 1);
298 }
299
dialog_create_yes_no(int textlen)300 static void dialog_create_yes_no(int textlen)
301 {
302 int d = num_dialogs;
303
304 dialogs[d].text_x = 40 - (textlen / 2);
305 if (textlen > 21) {
306 dialogs[d].x = dialogs[d].text_x - 4;
307 dialogs[d].w = textlen + 8;
308 } else {
309 dialogs[d].x = 26;
310 dialogs[d].w = 29;
311 }
312 dialogs[d].h = 8;
313 dialogs[d].y = 25;
314
315 dialogs[d].widgets = mem_calloc(2, sizeof(struct widget));
316 dialogs[d].total_widgets = 2;
317
318 create_button(dialogs[d].widgets + 0, 30, 30, 7, 0, 0, 1, 1, 1, dialog_yes_NULL, "Yes", 3);
319 create_button(dialogs[d].widgets + 1, 42, 30, 6, 1, 1, 0, 0, 0, dialog_no_NULL, "No", 3);
320 }
321
322 /* --------------------------------------------------------------------- */
323 /* type can be DIALOG_OK, DIALOG_OK_CANCEL, or DIALOG_YES_NO
324 * default_widget: 0 = ok/yes, 1 = cancel/no */
325
dialog_create(int type,const char * text,void (* action_yes)(void * data),void (* action_no)(void * data),int default_widget,void * data)326 struct dialog *dialog_create(int type, const char *text, void (*action_yes) (void *data),
327 void (*action_no) (void *data), int default_widget, void *data)
328 {
329 int textlen = strlen(text);
330 int d = num_dialogs;
331
332 #ifndef NDEBUG
333 if ((type & DIALOG_BOX) == 0) {
334 fprintf(stderr, "dialog_create called with bogus dialog type %d\n", type);
335 return NULL;
336 }
337 #endif
338
339 /* FIXME | hmm... a menu should probably be hiding itself when a widget gets selected. */
340 if (status.dialog_type & DIALOG_MENU)
341 menu_hide();
342
343 dialogs[d].text = str_dup(text);
344 dialogs[d].data = data;
345 dialogs[d].action_yes = action_yes;
346 dialogs[d].action_no = action_no;
347 dialogs[d].action_cancel = NULL; /* ??? */
348 dialogs[d].selected_widget = default_widget;
349 dialogs[d].draw_const = NULL;
350 dialogs[d].handle_key = NULL;
351
352 switch (type) {
353 case DIALOG_OK:
354 dialog_create_ok(textlen);
355 break;
356 case DIALOG_OK_CANCEL:
357 dialog_create_ok_cancel(textlen);
358 break;
359 case DIALOG_YES_NO:
360 dialog_create_yes_no(textlen);
361 break;
362 default:
363 #ifndef NDEBUG
364 fprintf(stderr, "this man should not be seen\n");
365 #endif
366 type = DIALOG_OK_CANCEL;
367 dialog_create_ok_cancel(textlen);
368 break;
369 }
370
371 dialogs[d].type = type;
372 widgets = dialogs[d].widgets;
373 selected_widget = &(dialogs[d].selected_widget);
374 total_widgets = &(dialogs[d].total_widgets);
375
376 num_dialogs++;
377
378 status.dialog_type = type;
379 status.flags |= NEED_UPDATE;
380 return &dialogs[d];
381 }
382
383 /* --------------------------------------------------------------------- */
384 /* this will probably die painfully if two threads try to make custom dialogs at the same time */
385
dialog_create_custom(int x,int y,int w,int h,struct widget * dialog_widgets,int dialog_total_widgets,int dialog_selected_widget,void (* draw_const)(void),void * data)386 struct dialog *dialog_create_custom(int x, int y, int w, int h, struct widget *dialog_widgets,
387 int dialog_total_widgets, int dialog_selected_widget,
388 void (*draw_const) (void), void *data)
389 {
390 struct dialog *d = dialogs + num_dialogs;
391
392 /* FIXME | see dialog_create */
393 if (status.dialog_type & DIALOG_MENU)
394 menu_hide();
395
396 num_dialogs++;
397
398 d->type = DIALOG_CUSTOM;
399 d->x = x;
400 d->y = y;
401 d->w = w;
402 d->h = h;
403 d->widgets = dialog_widgets;
404 d->selected_widget = dialog_selected_widget;
405 d->total_widgets = dialog_total_widgets;
406 d->draw_const = draw_const;
407
408 d->text = NULL;
409 d->data = data;
410 d->action_yes = NULL;
411 d->action_no = NULL;
412 d->action_cancel = NULL;
413 d->handle_key = NULL;
414
415 status.dialog_type = DIALOG_CUSTOM;
416 widgets = d->widgets;
417 selected_widget = &(d->selected_widget);
418 total_widgets = &(d->total_widgets);
419
420 status.flags |= NEED_UPDATE;
421
422 return d;
423 }
424
425 /* --------------------------------------------------------------------- */
426 /* Other prompt stuff */
427
428 static const char *numprompt_title, *numprompt_secondary;
429 static int numprompt_smp_pos1, numprompt_smp_pos2; /* used by the sample prompt */
430 static int numprompt_titlelen; /* used by the number prompt */
431 static char numprompt_buf[4];
432 static struct widget numprompt_widgets[2];
433 static void (*numprompt_finish)(int n);
434
435 /* this is bound to the textentry's activate callback.
436 since this dialog might be called from another dialog as well as from a page, it can't use the
437 normal dialog_yes handler -- it needs to destroy the prompt dialog first so that ACTIVE_WIDGET
438 points to whatever thumbbar actually triggered the dialog box. */
numprompt_value(void)439 static void numprompt_value(void)
440 {
441 char *eptr;
442 long n = strtol(numprompt_buf, &eptr, 10);
443
444 dialog_destroy();
445 if (eptr > numprompt_buf && eptr[0] == '\0')
446 numprompt_finish(n);
447 }
448
numprompt_draw_const(void)449 static void numprompt_draw_const(void)
450 {
451 int wx = numprompt_widgets[0].x;
452 int wy = numprompt_widgets[0].y;
453 int ww = numprompt_widgets[0].width;
454
455 draw_text(numprompt_title, wx - numprompt_titlelen - 1, wy, 3, 2);
456 draw_box(wx - 1, wy - 1, wx + ww, wy + 1, BOX_THICK | BOX_INNER | BOX_INSET);
457 }
458
numprompt_create(const char * prompt,void (* finish)(int n),char initvalue)459 void numprompt_create(const char *prompt, void (*finish)(int n), char initvalue)
460 {
461 int y = 26; // an indisputable fact of life
462 int dlgwidth, dlgx, entryx;
463
464 numprompt_title = prompt;
465 numprompt_titlelen = strlen(prompt);
466 numprompt_buf[0] = initvalue;
467 numprompt_buf[1] = '\0';
468
469 /* Dialog is made up of border, padding (2 left, 1 right), frame around the text entry, the entry
470 itself, and the prompt; the text entry is offset from the left of the dialog by 4 chars (padding +
471 borders) plus the length of the prompt. */
472 dlgwidth = 2 + 3 + 2 + 4 + numprompt_titlelen;
473 dlgx = (80 - dlgwidth) / 2;
474 entryx = dlgx + 4 + numprompt_titlelen;
475
476 create_textentry(numprompt_widgets + 0, entryx, y, 4, 0, 0, 0, NULL, numprompt_buf, 3);
477 numprompt_widgets[0].activate = numprompt_value;
478 numprompt_widgets[0].d.textentry.cursor_pos = initvalue ? 1 : 0;
479 numprompt_finish = finish;
480 dialog_create_custom(dlgx, y - 2, dlgwidth, 5, numprompt_widgets, 1, 0, numprompt_draw_const, NULL);
481 }
482
483
strtonum99(const char * s)484 static int strtonum99(const char *s)
485 {
486 // aaarghhhh
487 int n = 0;
488 if (!s || !*s)
489 return -1;
490 if (s[1]) {
491 // two chars
492 int c = tolower(*s);
493 switch (c) {
494 case '0' ... '9': n = c - '0'; break;
495 case 'a' ... 'g': n = c - 'a' + 10; break;
496 case 'h' ... 'z': n = c - 'h' + 10; break;
497 default: return -1;
498 }
499 n *= 10;
500 s++;
501 }
502 return *s >= '0' && *s <= '9' ? n + *s - '0' : -1;
503 }
504
smpprompt_value(UNUSED void * data)505 static void smpprompt_value(UNUSED void *data)
506 {
507 int n = strtonum99(numprompt_buf);
508 numprompt_finish(n);
509 }
510
smpprompt_draw_const(void)511 static void smpprompt_draw_const(void)
512 {
513 int wx = numprompt_widgets[0].x;
514 int wy = numprompt_widgets[0].y;
515 int ww = numprompt_widgets[0].width;
516
517 draw_text(numprompt_title, numprompt_smp_pos1, 25, 0, 2);
518 draw_text(numprompt_secondary, numprompt_smp_pos2, 27, 0, 2);
519 draw_box(wx - 1, wy - 1, wx + ww, wy + 1, BOX_THICK | BOX_INNER | BOX_INSET);
520 }
521
smpprompt_create(const char * title,const char * prompt,void (* finish)(int n))522 void smpprompt_create(const char *title, const char *prompt, void (*finish)(int n))
523 {
524 struct dialog *dialog;
525
526 numprompt_title = title;
527 numprompt_secondary = prompt;
528 numprompt_smp_pos1 = (81 - strlen(title)) / 2;
529 numprompt_smp_pos2 = 41 - strlen(prompt);
530 numprompt_buf[0] = '\0';
531
532 create_textentry(numprompt_widgets + 0, 42, 27, 3, 1, 1, 1, NULL, numprompt_buf, 2);
533 create_button(numprompt_widgets + 1, 36, 30, 6, 0, 0, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1);
534 numprompt_finish = finish;
535 dialog = dialog_create_custom(26, 23, 29, 10, numprompt_widgets, 2, 0, smpprompt_draw_const, NULL);
536 dialog->action_yes = smpprompt_value;
537 }
538
539