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