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_MENU(optional return value)
35  * will emit a warning and cause the function to return
36  * if a menu is not active. */
37 #ifndef NDEBUG
38 # define ENSURE_MENU(q) do {\
39 	if ((status.dialog_type & DIALOG_MENU) == 0) {\
40 		fprintf(stderr, "%s called with no menu\n", __FUNCTION__);\
41 		q;\
42 	}\
43 } while(0)
44 #else
45 # define ENSURE_MENU(q)
46 #endif
47 
48 /* --------------------------------------------------------------------- */
49 /* protomatypes */
50 
51 static void main_menu_selected_cb(void);
52 static void file_menu_selected_cb(void);
53 static void playback_menu_selected_cb(void);
54 static void sample_menu_selected_cb(void);
55 static void instrument_menu_selected_cb(void);
56 static void settings_menu_selected_cb(void);
57 
58 /* --------------------------------------------------------------------- */
59 
60 struct menu {
61 	unsigned int x, y, w;
62 	const char *title;
63 	int num_items;  /* meh... */
64 	const char *items[14];  /* writing **items doesn't work here :( */
65 	int selected_item;      /* the highlighted item */
66 	int active_item;        /* "pressed" menu item, for submenus */
67 	void (*selected_cb) (void);     /* triggered by return key */
68 };
69 
70 static struct menu main_menu = {
71 	.x = 6,
72 	.y = 11,
73 	.w = 25,
74 	.title = " Main Menu",
75 	.num_items = 10,
76 	.items = {
77 		"File Menu...",
78 		"Playback Menu...",
79 		"View Patterns        (F2)",
80 		"Sample Menu...",
81 		"Instrument Menu...",
82 		"View Orders/Panning (F11)",
83 		"View Variables      (F12)",
84 		"Message Editor (Shift-F9)",
85 		"Settings Menu...",
86 		"Help!                (F1)",
87 	},
88 	.selected_item = 0,
89 	.active_item = -1,
90 	.selected_cb = main_menu_selected_cb,
91 };
92 
93 static struct menu file_menu = {
94 	.x = 25,
95 	.y = 13,
96 	.w = 22,
97 	.title = "File Menu",
98 	.num_items = 7,
99 	.items = {
100 		"Load...           (F9)",
101 		"New...        (Ctrl-N)",
102 		"Save Current  (Ctrl-S)",
103 		"Save As...       (F10)",
104 		"Export...  (Shift-F10)",
105 		"Message Log (Ctrl-F11)",
106 		"Quit          (Ctrl-Q)",
107 	},
108 	.selected_item = 0,
109 	.active_item = -1,
110 	.selected_cb = file_menu_selected_cb,
111 };
112 
113 static struct menu playback_menu = {
114 	.x = 25,
115 	.y = 13,
116 	.w = 27,
117 	.title = " Playback Menu",
118 	.num_items = 9,
119 	.items = {
120 		"Show Infopage          (F5)",
121 		"Play Song         (Ctrl-F5)",
122 		"Play Pattern           (F6)",
123 		"Play from Order  (Shift-F6)",
124 		"Play from Mark/Cursor  (F7)",
125 		"Stop                   (F8)",
126 		"Reinit Soundcard   (Ctrl-I)",
127 		"Driver Screen    (Shift-F5)",
128 		"Calculate Length   (Ctrl-P)",
129 	},
130 	.selected_item = 0,
131 	.active_item = -1,
132 	.selected_cb = playback_menu_selected_cb,
133 };
134 
135 static struct menu sample_menu = {
136 	.x = 25,
137 	.y = 20,
138 	.w = 25,
139 	.title = "Sample Menu",
140 	.num_items = 2,
141 	.items = {
142 		"Sample List          (F3)",
143 		"Sample Library  (Ctrl-F3)",
144 	},
145 	.selected_item = 0,
146 	.active_item = -1,
147 	.selected_cb = sample_menu_selected_cb,
148 };
149 
150 static struct menu instrument_menu = {
151 	.x = 20,
152 	.y = 23,
153 	.w = 29,
154 	.title = "Instrument Menu",
155 	.num_items = 2,
156 	.items = {
157 		"Instrument List          (F4)",
158 		"Instrument Library  (Ctrl-F4)",
159 	},
160 	.selected_item = 0,
161 	.active_item = -1,
162 	.selected_cb = instrument_menu_selected_cb,
163 };
164 
165 static struct menu settings_menu = {
166 	.x = 22,
167 	.y = 25,
168 	.w = 34,
169 	.title = "Settings Menu",
170 	/* num_items is fiddled with when the menu is loaded (if there's no window manager,
171 	the toggle fullscreen item doesn't appear) */
172 	.num_items = 6,
173 	.items = {
174 		"Preferences             (Shift-F5)",
175 		"MIDI Configuration      (Shift-F1)",
176 		"System Configuration     (Ctrl-F1)",
177 		"Palette Editor          (Ctrl-F12)",
178 		"Font Editor            (Shift-F12)",
179 		"Toggle Fullscreen (Ctrl-Alt-Enter)",
180 	},
181 	.selected_item = 0,
182 	.active_item = -1,
183 	.selected_cb = settings_menu_selected_cb,
184 };
185 
186 /* *INDENT-ON* */
187 
188 /* updated to whatever menu is currently active.
189  * this generalises the key handling.
190  * if status.dialog_type == DIALOG_SUBMENU, use current_menu[1]
191  * else, use current_menu[0] */
192 static struct menu *current_menu[2] = { NULL, NULL };
193 
194 /* --------------------------------------------------------------------- */
195 
196 static void _draw_menu(struct menu *menu)
197 {
198 	int h = 6, n = menu->num_items;
199 
200 	while (n--) {
201 		draw_box(2 + menu->x, 4 + menu->y + 3 * n,
202 			 5 + menu->x + menu->w, 6 + menu->y + 3 * n,
203 			 BOX_THIN | BOX_CORNER | (n == menu->active_item ? BOX_INSET : BOX_OUTSET));
204 
205 		draw_text_len(menu->items[n], menu->w, 4 + menu->x, 5 + menu->y + 3 * n,
206 			      (n == menu->selected_item ? 3 : 0), 2);
207 
208 		draw_char(0, 3 + menu->x, 5 + menu->y + 3 * n, 0, 2);
209 		draw_char(0, 4 + menu->x + menu->w, 5 + menu->y + 3 * n, 0, 2);
210 
211 		h += 3;
212 	}
213 
214 	draw_box(menu->x, menu->y, menu->x + menu->w + 7, menu->y + h - 1,
215 		 BOX_THICK | BOX_OUTER | BOX_FLAT_LIGHT);
216 	draw_box(menu->x + 1, menu->y + 1, menu->x + menu->w + 6,
217 		 menu->y + h - 2, BOX_THIN | BOX_OUTER | BOX_FLAT_DARK);
218 	draw_fill_chars(menu->x + 2, menu->y + 2, menu->x + menu->w + 5, menu->y + 3, 2);
219 	draw_text(menu->title, menu->x + 6, menu->y + 2, 3, 2);
220 }
221 
222 void menu_draw(void)
223 {
224 	ENSURE_MENU(return);
225 
226 	_draw_menu(current_menu[0]);
227 	if (current_menu[1])
228 		_draw_menu(current_menu[1]);
229 }
230 
231 /* --------------------------------------------------------------------- */
232 
233 void menu_show(void)
234 {
235 	dialog_destroy_all();
236 	status.dialog_type = DIALOG_MAIN_MENU;
237 	current_menu[0] = &main_menu;
238 
239 	status.flags |= NEED_UPDATE;
240 }
241 
242 void menu_hide(void)
243 {
244 	ENSURE_MENU(return);
245 
246 	status.dialog_type = DIALOG_NONE;
247 
248 	/* "unpress" the menu items */
249 	current_menu[0]->active_item = -1;
250 	if (current_menu[1])
251 		current_menu[1]->active_item = -1;
252 
253 	current_menu[0] = current_menu[1] = NULL;
254 
255 	/* note! this does NOT redraw the screen; that's up to the caller.
256 	 * the reason for this is that so many of the menu items cause a
257 	 * page switch, and redrawing the current page followed by
258 	 * redrawing a new page is redundant. */
259 }
260 
261 /* --------------------------------------------------------------------- */
262 
263 static void set_submenu(struct menu *menu)
264 {
265 	ENSURE_MENU(return);
266 
267 	status.dialog_type = DIALOG_SUBMENU;
268 	main_menu.active_item = main_menu.selected_item;
269 	current_menu[1] = menu;
270 
271 	status.flags |= NEED_UPDATE;
272 }
273 
274 /* --------------------------------------------------------------------- */
275 /* callbacks */
276 
277 static void main_menu_selected_cb(void)
278 {
279 	switch (main_menu.selected_item) {
280 	case 0: /* file menu... */
281 		set_submenu(&file_menu);
282 		break;
283 	case 1: /* playback menu... */
284 		set_submenu(&playback_menu);
285 		break;
286 	case 2: /* view patterns */
287 		set_page(PAGE_PATTERN_EDITOR);
288 		break;
289 	case 3: /* sample menu... */
290 		set_submenu(&sample_menu);
291 		break;
292 	case 4: /* instrument menu... */
293 		set_submenu(&instrument_menu);
294 		break;
295 	case 5: /* view orders/panning */
296 		set_page(PAGE_ORDERLIST_PANNING);
297 		break;
298 	case 6: /* view variables */
299 		set_page(PAGE_SONG_VARIABLES);
300 		break;
301 	case 7: /* message editor */
302 		set_page(PAGE_MESSAGE);
303 		break;
304 	case 8: /* settings menu */
305 		/* fudge the menu to show/hide the fullscreen toggle as appropriate */
306 		if (status.flags & WM_AVAILABLE)
307 			settings_menu.num_items = 6;
308 		else
309 			settings_menu.num_items = 5;
310 		set_submenu(&settings_menu);
311 		break;
312 	case 9: /* help! */
313 		set_page(PAGE_HELP);
314 		break;
315 	}
316 }
317 
318 static void file_menu_selected_cb(void)
319 {
320 	switch (file_menu.selected_item) {
321 	case 0: /* load... */
322 		set_page(PAGE_LOAD_MODULE);
323 		break;
324 	case 1: /* new... */
325 		new_song_dialog();
326 		break;
327 	case 2: /* save current */
328 		save_song_or_save_as();
329 		break;
330 	case 3: /* save as... */
331 		set_page(PAGE_SAVE_MODULE);
332 		break;
333 	case 4:
334 		/* export ... */
335 		set_page(PAGE_EXPORT_MODULE);
336 		break;
337 	case 5: /* message log */
338 		set_page(PAGE_LOG);
339 		break;
340 	case 6: /* quit */
341 		show_exit_prompt();
342 		break;
343 	}
344 }
345 
346 static void playback_menu_selected_cb(void)
347 {
348 	switch (playback_menu.selected_item) {
349 	case 0: /* show infopage */
350 		if (song_get_mode() == MODE_STOPPED
351 		    || (song_get_mode() == MODE_SINGLE_STEP && status.current_page == PAGE_INFO))
352 			song_start();
353 		set_page(PAGE_INFO);
354 		return;
355 	case 1: /* play song */
356 		song_start();
357 		break;
358 	case 2: /* play pattern */
359 		song_loop_pattern(get_current_pattern(), 0);
360 		break;
361 	case 3: /* play from order */
362 		song_start_at_order(get_current_order(), 0);
363 		break;
364 	case 4: /* play from mark/cursor */
365 		play_song_from_mark();
366 		break;
367 	case 5: /* stop */
368 		song_stop();
369 		break;
370 	case 6: /* reinit soundcard */
371 		audio_reinit();
372 		break;
373 	case 7: /* driver screen */
374 		set_page(PAGE_PREFERENCES);
375 		return;
376 	case 8: /* calculate length */
377 		show_song_length();
378 		return;
379 	}
380 
381 	menu_hide();
382 	status.flags |= NEED_UPDATE;
383 }
384 
385 static void sample_menu_selected_cb(void)
386 {
387 	switch (sample_menu.selected_item) {
388 	case 0: /* sample list */
389 		set_page(PAGE_SAMPLE_LIST);
390 		break;
391 	case 1: /* sample library */
392 		set_page(PAGE_LIBRARY_SAMPLE);
393 		break;
394 	}
395 }
396 
397 static void instrument_menu_selected_cb(void)
398 {
399 	switch (instrument_menu.selected_item) {
400 	case 0: /* instrument list */
401 		set_page(PAGE_INSTRUMENT_LIST);
402 		break;
403 	case 1: /* instrument library */
404 		set_page(PAGE_LIBRARY_INSTRUMENT);
405 		break;
406 	}
407 }
408 
409 static void settings_menu_selected_cb(void)
410 {
411 	switch (settings_menu.selected_item) {
412 	case 0: /* preferences page */
413 		set_page(PAGE_PREFERENCES);
414 		return;
415 	case 1: /* midi configuration */
416 		set_page(PAGE_MIDI);
417 		return;
418 	case 2: /* config */
419 		set_page(PAGE_CONFIG);
420 		return;
421 	case 3: /* palette configuration */
422 		set_page(PAGE_PALETTE_EDITOR);
423 		return;
424 	case 4: /* font editor */
425 		set_page(PAGE_FONT_EDIT);
426 		return;
427 	case 5: /* toggle fullscreen */
428 		toggle_display_fullscreen();
429 		break;
430 	}
431 
432 	menu_hide();
433 	status.flags |= NEED_UPDATE;
434 }
435 
436 /* --------------------------------------------------------------------- */
437 
438 /* As long as there's a menu active, this function will return true. */
439 int menu_handle_key(struct key_event *k)
440 {
441 	struct menu *menu;
442 	int n, h;
443 
444 	if ((status.dialog_type & DIALOG_MENU) == 0)
445 		return 0;
446 
447 	menu = (status.dialog_type == DIALOG_SUBMENU
448 		? current_menu[1] : current_menu[0]);
449 
450 	if (k->mouse) {
451 		if (k->mouse == MOUSE_CLICK || k->mouse == MOUSE_DBLCLICK) {
452 			h = menu->num_items * 3;
453 			if (k->x >= menu->x + 2 && k->x <= menu->x + menu->w + 5
454 			    && k->y >= menu->y + 4 && k->y <= menu->y + h + 4) {
455 				n = ((k->y - 4) - menu->y) / 3;
456 				if (n >= 0 && n < menu->num_items) {
457 					menu->selected_item = n;
458 					if (k->state == KEY_RELEASE) {
459 						menu->active_item = -1;
460 						menu->selected_cb();
461 					} else {
462 						status.flags |= NEED_UPDATE;
463 						menu->active_item = n;
464 					}
465 				}
466 			} else if (k->state == KEY_RELEASE && (k->x < menu->x || k->x > 7+menu->x+menu->w
467 			|| k->y < menu->y || k->y >= 5+menu->y+h)) {
468 				/* get rid of the menu */
469 				current_menu[1] = NULL;
470 				if (status.dialog_type == DIALOG_SUBMENU) {
471 					status.dialog_type = DIALOG_MAIN_MENU;
472 					main_menu.active_item = -1;
473 				} else {
474 					menu_hide();
475 				}
476 				status.flags |= NEED_UPDATE;
477 			}
478 		}
479 		return 1;
480 	}
481 
482 	switch (k->sym) {
483 	case SDLK_ESCAPE:
484 		if (k->state == KEY_RELEASE)
485 			return 1;
486 		current_menu[1] = NULL;
487 		if (status.dialog_type == DIALOG_SUBMENU) {
488 			status.dialog_type = DIALOG_MAIN_MENU;
489 			main_menu.active_item = -1;
490 		} else {
491 			menu_hide();
492 		}
493 		break;
494 	case SDLK_UP:
495 		if (k->state == KEY_RELEASE)
496 			return 1;
497 		if (menu->selected_item > 0) {
498 			menu->selected_item--;
499 			break;
500 		}
501 		return 1;
502 	case SDLK_DOWN:
503 		if (k->state == KEY_RELEASE)
504 			return 1;
505 		if (menu->selected_item < menu->num_items - 1) {
506 			menu->selected_item++;
507 			break;
508 		}
509 		return 1;
510 		/* home/end are new here :) */
511 	case SDLK_HOME:
512 		if (k->state == KEY_RELEASE)
513 			return 1;
514 		menu->selected_item = 0;
515 		break;
516 	case SDLK_END:
517 		if (k->state == KEY_RELEASE)
518 			return 1;
519 		menu->selected_item = menu->num_items - 1;
520 		break;
521 	case SDLK_RETURN:
522 		if (k->state == KEY_PRESS) {
523 			menu->active_item = menu->selected_item;
524 			status.flags |= NEED_UPDATE;
525 			return 1;
526 		}
527 		menu->selected_cb();
528 		return 1;
529 	default:
530 		return 1;
531 	}
532 
533 	status.flags |= NEED_UPDATE;
534 
535 	return 1;
536 }
537