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