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 #include "it.h"
26 #include "page.h"
27
28 /* --------------------------------------------------------------------- */
29 /* create_* functions (the constructors, if you will) */
30
31 void create_toggle(struct widget *w, int x, int y, int next_up, int next_down,
32 int next_left, int next_right, int next_tab, void (*changed) (void))
33 {
34 w->type = WIDGET_TOGGLE;
35 w->accept_text = 0;
36 w->x = x;
37 w->y = y;
38 w->width = 3; /* "Off" */
39 w->next.up = next_up;
40 w->next.left = next_left;
41 w->next.down = next_down;
42 w->next.right = next_right;
43 w->next.tab = next_tab;
44 w->changed = changed;
45 w->activate = NULL;
46 w->depressed = 0;
47 w->height = 1;
48 }
49
50 void create_menutoggle(struct widget *w, int x, int y, int next_up, int next_down, int next_left,
51 int next_right, int next_tab, void (*changed) (void), const char *const *choices)
52 {
53 int n, width = 0, len;
54
55 for (n = 0; choices[n]; n++) {
56 len = strlen(choices[n]);
57 if (width < len)
58 width = len;
59 }
60
61 w->type = WIDGET_MENUTOGGLE;
62 w->accept_text = 0;
63 w->x = x;
64 w->y = y;
65 w->width = width;
66 w->depressed = 0;
67 w->height = 1;
68 w->next.up = next_up;
69 w->next.left = next_left;
70 w->next.down = next_down;
71 w->next.right = next_right;
72 w->next.tab = next_tab;
73 w->changed = changed;
74 w->d.menutoggle.choices = choices;
75 w->d.menutoggle.num_choices = n;
76 w->activate = NULL;
77 w->d.menutoggle.activation_keys = NULL;
78 }
79
80 void create_button(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_left,
81 int next_right, int next_tab, void (*changed) (void), const char *text, int padding)
82 {
83 w->type = WIDGET_BUTTON;
84 w->accept_text = 0;
85 w->x = x;
86 w->y = y;
87 w->width = width;
88 w->depressed = 0;
89 w->height = 1;
90 w->next.up = next_up;
91 w->next.left = next_left;
92 w->next.down = next_down;
93 w->next.right = next_right;
94 w->next.tab = next_tab;
95 w->changed = changed;
96 w->d.button.text = text;
97 w->d.button.padding = padding;
98 w->activate = NULL;
99 }
100
101 void create_togglebutton(struct widget *w, int x, int y, int width, int next_up, int next_down,
102 int next_left, int next_right, int next_tab, void (*changed) (void),
103 const char *text, int padding, const int *group)
104 {
105 w->type = WIDGET_TOGGLEBUTTON;
106 w->accept_text = 0;
107 w->x = x;
108 w->y = y;
109 w->width = width;
110 w->depressed = 0;
audio_callback(UNUSED void * qq,uint8_t * stream,int len)111 w->height = 1;
112 w->next.up = next_up;
113 w->next.left = next_left;
114 w->next.down = next_down;
115 w->next.right = next_right;
116 w->next.tab = next_tab;
117 w->changed = changed;
118 w->d.togglebutton.text = text;
119 w->d.togglebutton.padding = padding;
120 w->d.togglebutton.group = group;
121 w->activate = NULL;
122 }
123
124 void create_textentry(struct widget *w, int x, int y, int width, int next_up, int next_down,
125 int next_tab, void (*changed) (void), char *text, int max_length)
126 {
127 w->type = WIDGET_TEXTENTRY;
128 w->accept_text = 1;
129 w->x = x;
130 w->y = y;
131 w->width = width;
132 w->depressed = 0;
133 w->height = 1;
134 w->next.up = next_up;
135 w->next.down = next_down;
136 w->next.tab = next_tab;
137 w->changed = changed;
138 w->d.textentry.text = text;
139 w->d.textentry.max_length = max_length;
140 w->d.textentry.firstchar = 0;
141 w->d.textentry.cursor_pos = 0;
142 w->activate = NULL;
143 }
144
145 void create_numentry(struct widget *w, int x, int y, int width, int next_up, int next_down,
146 int next_tab, void (*changed) (void), int min, int max, int *cursor_pos)
147 {
148 w->type = WIDGET_NUMENTRY;
149 w->accept_text = 1;
150 w->x = x;
151 w->y = y;
152 w->width = width;
153 w->depressed = 0;
154 w->height = 1;
155 w->next.up = next_up;
156 w->next.down = next_down;
157 w->next.tab = next_tab;
158 w->changed = changed;
159 w->d.numentry.min = min;
160 w->d.numentry.max = max;
161 w->d.numentry.cursor_pos = cursor_pos;
162 w->d.numentry.handle_unknown_key = NULL;
163 w->d.numentry.reverse = 0;
164 w->activate = NULL;
165 }
166
167 void create_thumbbar(struct widget *w, int x, int y, int width, int next_up, int next_down,
168 int next_tab, void (*changed) (void), int min, int max)
169 {
170 w->type = WIDGET_THUMBBAR;
171 w->accept_text = 0;
172 w->x = x;
173 w->y = y;
174 w->width = width;
175 w->depressed = 0;
176 w->height = 1;
177 w->next.up = next_up;
178 w->next.down = next_down;
179 w->next.tab = next_tab;
180 w->changed = changed;
181 w->d.thumbbar.min = min;
182 w->d.thumbbar.max = max;
183 w->d.thumbbar.text_at_min = NULL;
184 w->d.thumbbar.text_at_max = NULL;
185 w->activate = NULL;
186 }
187
188 void create_bitset(struct widget *w, int x, int y, int width, int next_up, int next_down,
189 int next_tab, void (*changed) (void),
190 int nbits, const char* bits_on, const char* bits_off,
191 int *cursor_pos)
192 {
193 w->type = WIDGET_BITSET;
194 w->accept_text = 0;
195 w->x = x;
196 w->y = y;
197 w->width = width;
198 w->depressed = 0;
main_song_mode_changed_cb(void)199 w->height = 1;
200 w->next.up = next_up;
201 w->next.down = next_down;
202 w->next.tab = next_tab;
203 w->changed = changed;
204 w->d.numentry.reverse = 0;
205 w->d.bitset.nbits = nbits;
206 w->d.bitset.bits_on = bits_on;
207 w->d.bitset.bits_off = bits_off;
208 w->d.bitset.cursor_pos = cursor_pos;
209 w->activate = NULL;
210 }
211
song_get_current_play_channel(void)212 void create_panbar(struct widget *w, int x, int y, int next_up, int next_down, int next_tab,
213 void (*changed) (void), int channel)
214 {
215 w->type = WIDGET_PANBAR;
216 w->accept_text = 0;
217 w->x = x;
218 w->y = y;
219 w->width = 24;
220 w->height = 1;
221 w->next.up = next_up;
222 w->next.down = next_down;
223 w->next.tab = next_tab;
224 w->changed = changed;
225 w->d.numentry.reverse = 0;
226 w->d.panbar.min = 0;
227 w->d.panbar.max = 64;
228 w->d.panbar.channel = channel;
229 w->activate = NULL;
230 }
song_toggle_multichannel_mode(void)231
232 void create_other(struct widget *w, int next_tab, int (*i_handle_key) (struct key_event *k),
233 void (*i_redraw) (void))
234 {
235 w->type = WIDGET_OTHER;
236 w->accept_text = 0;
237 w->next.up = w->next.down = w->next.left = w->next.right = 0;
238 w->next.tab = next_tab;
239 /* w->changed = NULL; ??? */
240 w->depressed = 0;
241 w->activate = NULL;
242
243 /* unfocusable unless set */
244 w->x = -1;
245 w->y = -1;
246 w->width = -1;
247 w->height = 1;
248
249 w->d.other.handle_key = i_handle_key;
250 w->d.other.redraw = i_redraw;
251 }
252
253 /* --------------------------------------------------------------------- */
254 /* generic text stuff */
255
256 void text_add_char(char *text, char c, int *cursor_pos, int max_length)
257 {
258 int len;
259
260 text[max_length] = 0;
261 len = strlen(text);
262 if (*cursor_pos >= max_length)
263 *cursor_pos = max_length - 1;
264 /* FIXME: this causes some weirdness with the end key. maybe hitting end should trim spaces? */
265 while (len < *cursor_pos)
266 text[len++] = ' ';
267 memmove(text + *cursor_pos + 1, text + *cursor_pos, max_length - *cursor_pos - 1);
268 text[*cursor_pos] = c;
269 (*cursor_pos)++;
270 }
271
272 void text_delete_char(char *text, int *cursor_pos, int max_length)
273 {
274 if (*cursor_pos == 0)
275 return;
276 (*cursor_pos)--;
277 memmove(text + *cursor_pos, text + *cursor_pos + 1, max_length - *cursor_pos);
278 }
279
280 void text_delete_next_char(char *text, int *cursor_pos, int max_length)
281 {
282 memmove(text + *cursor_pos, text + *cursor_pos + 1, max_length - *cursor_pos);
283 }
284
285 /* --------------------------------------------------------------------- */
286 /* text entries */
287
288 static void textentry_reposition(struct widget *w)
289 {
290 int len;
291
292 w->d.textentry.text[w->d.textentry.max_length] = 0;
293
294 len = strlen(w->d.textentry.text);
295 if (w->d.textentry.cursor_pos < w->d.textentry.firstchar) {
296 w->d.textentry.firstchar = w->d.textentry.cursor_pos;
297 } else if (w->d.textentry.cursor_pos > len) {
298 w->d.textentry.cursor_pos = len;
299 } else if (w->d.textentry.cursor_pos > (w->d.textentry.firstchar + w->width - 1)) {
300 w->d.textentry.firstchar = w->d.textentry.cursor_pos - w->width + 1;
301 if (w->d.textentry.firstchar < 0)
302 w->d.textentry.firstchar = 0;
303 }
304 }
305
306 int textentry_add_char(struct widget *w, uint16_t unicode)
307 {
308 int c = unicode_to_ascii(unicode);
309
310 if (c == 0)
311 return 0;
312 text_add_char(w->d.textentry.text, c, &(w->d.textentry.cursor_pos), w->d.textentry.max_length);
313
314 if (w->changed) w->changed();
315 status.flags |= NEED_UPDATE;
316
317 return 1;
318 }
319
320 int menutoggle_handle_key(struct widget *w, struct key_event *k)
321 {
322 if( ((k->mod & (KMOD_CTRL | KMOD_ALT | KMOD_META)) == 0)
323 && w->d.menutoggle.activation_keys)
324 {
325 const char* m = w->d.menutoggle.activation_keys;
326 const char* p = strchr(m, (char)k->unicode);
327 if(p && *p)
328 {
329 w->d.menutoggle.state = p - m;
330 if(w->changed) w->changed();
331 status.flags |= NEED_UPDATE;
332 return 1;
333 }
334 }
335 return 0;
336 }
337
338 int bitset_handle_key(struct widget *w, struct key_event *k)
339 {
340 if( ((k->mod & (KMOD_CTRL | KMOD_ALT | KMOD_META)) == 0)
341 && w->d.bitset.activation_keys)
342 {
343 const char* m = w->d.bitset.activation_keys;
344 const char* p = strchr(m, (char)k->unicode);
345 if(p && *p)
346 {
347 int bit_index = p-m;
348 w->d.bitset.value ^= (1 << bit_index);
349 if(w->changed) w->changed();
350 status.flags |= NEED_UPDATE;
351 return 1;
352 }
353 }
354 return 0;
355 }
356
357 /* --------------------------------------------------------------------- */
358 /* numeric entries */
359
360 void numentry_change_value(struct widget *w, int new_value)
361 {
362 new_value = CLAMP(new_value, w->d.numentry.min, w->d.numentry.max);
363 w->d.numentry.value = new_value;
364 if (w->changed) w->changed();
365 status.flags |= NEED_UPDATE;
366 }
367
368 /* I'm sure there must be a simpler way to do this. */
369 int numentry_handle_digit(struct widget *w, struct key_event *k)
370 {
371 int width, value, n;
372 static const int tens[7] = { 1, 10, 100, 1000, 10000, 100000, 1000000 };
373 int digits[7] = { 0 };
374 int c;
375
376 c = numeric_key_event(k, 0);
377 if (c == -1) {
378 if (w->d.numentry.handle_unknown_key) {
379 return w->d.numentry.handle_unknown_key(k);
380 }
381 return 0;
382 }
383 if (w->d.numentry.reverse) {
384 w->d.numentry.value *= 10;
385 w->d.numentry.value += c;
386 if (w->changed) w->changed();
387 status.flags |= NEED_UPDATE;
388 return 1;
389 }
390
391 width = w->width;
392 value = w->d.numentry.value;
393 for (n = width - 1; n >= 0; n--)
394 digits[n] = value / tens[n] % 10;
395 digits[width - *(w->d.numentry.cursor_pos) - 1] = c;
396 value = 0;
397 for (n = width - 1; n >= 0; n--)
398 value += digits[n] * tens[n];
399 value = CLAMP(value, w->d.numentry.min, w->d.numentry.max);
400 w->d.numentry.value = value;
401 if (*(w->d.numentry.cursor_pos) < w->width - 1)
402 (*(w->d.numentry.cursor_pos))++;
403
404 if (w->changed) w->changed();
405 status.flags |= NEED_UPDATE;
406
407 return 1;
408 }
409
410 /* --------------------------------------------------------------------- */
411 /* toggle buttons */
412
413 void togglebutton_set(struct widget *p_widgets, int widget, int do_callback)
song_keydown(int samp,int ins,int note,int vol,int chan)414 {
415 const int *group = p_widgets[widget].d.togglebutton.group;
416 int i;
417
418 if (!group) return; /* assert */
419
420 for (i = 0; group[i] >= 0; i++)
421 p_widgets[group[i]].d.togglebutton.state = 0;
422 p_widgets[widget].d.togglebutton.state = 1;
423
424 if (do_callback) {
425 if (p_widgets[widget].changed)
426 p_widgets[widget].changed();
427 }
428
429 status.flags |= NEED_UPDATE;
430 }
431
432 /* --------------------------------------------------------------------- */
433 /* /me takes a deep breath */
434
435 void draw_widget(struct widget *w, int selected)
436 {
437 char buf[16] = "Channel 42";
438 const char *ptr, *endptr; /* for the menutoggle */
439 char *str;
440 int n;
441 int tfg = selected ? 0 : 2;
442 int tbg = selected ? 3 : 0;
443 int drew_cursor = 0;
444 int fg,bg;
445
446 switch (w->type) {
447 case WIDGET_TOGGLE:
448 draw_fill_chars(w->x, w->y, w->x + w->width - 1, w->y, 0);
449 draw_text((w->d.toggle.state ? "On" : "Off"), w->x, w->y, tfg, tbg);
450 break;
451 case WIDGET_MENUTOGGLE:
452 draw_fill_chars(w->x, w->y, w->x + w->width - 1, w->y, 0);
453 ptr = w->d.menutoggle.choices[w->d.menutoggle.state];
454 endptr = strchr(ptr, ' ');
455 if (endptr) {
456 n = endptr - ptr;
457 draw_text_len(ptr, n, w->x, w->y, tfg, tbg);
458 draw_text(endptr + 1, w->x + n + 1, w->y, 2, 0);
459 } else {
460 draw_text(ptr, w->x, w->y, tfg, tbg);
461 }
462 break;
463 case WIDGET_BUTTON:
464 draw_box(w->x - 1, w->y - 1, w->x + w->width + 2, w->y + 1,
465 BOX_THIN | BOX_INNER | (
466 w->depressed ? BOX_INSET : BOX_OUTSET));
467 draw_text(w->d.button.text, w->x + w->d.button.padding, w->y, selected ? 3 : 0, 2);
468 break;
469 case WIDGET_TOGGLEBUTTON:
song_reset_play_state(void)470 draw_box(w->x - 1, w->y - 1, w->x + w->width + 2, w->y + 1,
471 BOX_THIN | BOX_INNER |(
472 (w->d.togglebutton.state || w->depressed) ? BOX_INSET : BOX_OUTSET));
473 draw_text(w->d.togglebutton.text, w->x + w->d.togglebutton.padding, w->y, selected ? 3 : 0, 2);
474 break;
475 case WIDGET_TEXTENTRY:
476 textentry_reposition(w);
477 draw_text_len(w->d.textentry.text + w->d.textentry.firstchar, w->width, w->x, w->y, 2, 0);
478 if (selected && !drew_cursor) {
479 n = w->d.textentry.cursor_pos - w->d.textentry.firstchar;
480 draw_char(((n < (signed) strlen(w->d.textentry.text))
481 ? (w->d.textentry.text[w->d.textentry.cursor_pos]) : ' '),
482 w->x + n, w->y, 0, 3);
483 }
484 break;
485 case WIDGET_NUMENTRY:
486 if (w->d.numentry.reverse) {
487 str = numtostr(w->width, w->d.numentry.value, buf);
488 while (*str == '0') str++;
489 draw_text_len("", w->width, w->x, w->y, 2, 0);
490 if (*str) {
491 draw_text(str, (w->x+w->width) - strlen(str),
song_start_once(void)492 w->y, 2, 0);
493 }
494 if (selected && !drew_cursor) {
495 while (str[0] && str[1]) str++;
496 if (!str[0]) str[0] = ' ';
497 draw_char(str[0], w->x + (w->width-1),
498 w->y, 0, 3);
499 }
500 } else {
501 if (w->d.numentry.min < 0 || w->d.numentry.max < 0) {
502 numtostr_signed(w->width, w->d.numentry.value,
503 buf);
504 } else {
505 numtostr(w->width, w->d.numentry.value,
506 buf);
507 }
song_start(void)508 draw_text_len(buf,
509 w->width, w->x, w->y, 2, 0);
510 if (selected && !drew_cursor) {
511 n = *(w->d.numentry.cursor_pos);
512 draw_char(buf[n], w->x + n, w->y, 0, 3);
513 }
514 }
515 break;
516 case WIDGET_BITSET:
517 for(n = 0; n < w->d.bitset.nbits; ++n)
518 {
519 int set = !!(w->d.bitset.value & (1 << n));
520 char label_c1 = set ? w->d.bitset.bits_on[n*2+0]
521 : w->d.bitset.bits_off[n*2+0];
song_pause(void)522 char label_c2 = set ? w->d.bitset.bits_on[n*2+1]
523 : w->d.bitset.bits_off[n*2+1];
524 int is_focused = selected && n == *w->d.bitset.cursor_pos;
525 /* In textentries, cursor=0,3; normal=2,0 */
526 static const char fg_selection[4] =
527 {
528 2, /* not cursor, not set */
529 3, /* not cursor, is set */
530 0, /* has cursor, not set */
531 0 /* has cursor, is set */
song_stop(void)532 };
533 static const char bg_selection[4] =
534 {
535 0, /* not cursor, not set */
536 0, /* not cursor, is set */
537 2, /* has cursor, not set */
538 3 /* has cursor, is set */
539 };
540 fg = fg_selection[set + is_focused*2];
541 bg = bg_selection[set + is_focused*2];
542 if(label_c2)
543 draw_half_width_chars(label_c1, label_c2, w->x + n, w->y, fg, bg, fg, bg);
544 else
545 draw_char(label_c1, w->x + n, w->y, fg, bg);
546 }
547 break;
548 case WIDGET_THUMBBAR:
549 if (w->d.thumbbar.text_at_min && w->d.thumbbar.min == w->d.thumbbar.value) {
550 draw_text_len(w->d.thumbbar.text_at_min, w->width, w->x, w->y, selected ? 3 : 2, 0);
song_stop_unlocked(int quitting)551 } else if (w->d.thumbbar.text_at_max && w->d.thumbbar.max == w->d.thumbbar.value) {
552 /* this will probably do Bad Things if the text is too long */
553 int len = strlen(w->d.thumbbar.text_at_max);
554 int pos = w->x + w->width - len;
555
556 draw_fill_chars(w->x, w->y, pos - 1, w->y, 0);
557 draw_text_len(w->d.thumbbar.text_at_max, len, pos, w->y, selected ? 3 : 2, 0);
558 } else {
559 draw_thumb_bar(w->x, w->y, w->width, w->d.thumbbar.min,
560 w->d.thumbbar.max, w->d.thumbbar.value, selected);
561 }
562 if (w->d.thumbbar.min < 0 || w->d.thumbbar.max < 0) {
563 numtostr_signed(3, w->d.thumbbar.value, buf);
564 } else {
565 numtostr(3, w->d.thumbbar.value, buf);
566 }
567 draw_text(buf,
568 w->x + w->width + 1, w->y, 1, 2);
569 break;
570 case WIDGET_PANBAR:
571 numtostr(2, w->d.panbar.channel, buf + 8);
572 draw_text(buf, w->x, w->y, selected ? 3 : 0, 2);
573 if (w->d.panbar.muted) {
574 draw_text(" Muted ", w->x + 11, w->y, selected ? 3 : 5, 0);
575 /* draw_fill_chars(w->x + 21, w->y, w->x + 23, w->y, 2); */
576 } else if (w->d.panbar.surround) {
577 draw_text("Surround ", w->x + 11, w->y, selected ? 3 : 5, 0);
578 /* draw_fill_chars(w->x + 21, w->y, w->x + 23, w->y, 2); */
579 } else {
580 draw_thumb_bar(w->x + 11, w->y, 9, 0, 64, w->d.panbar.value, selected);
581 draw_text(numtostr(3, w->d.thumbbar.value, buf), w->x + 21, w->y, 1, 2);
582 }
583 break;
584 case WIDGET_OTHER:
585 if (w->d.other.redraw) w->d.other.redraw();
586 break;
587 default:
588 /* shouldn't ever happen... */
589 break;
590 }
591 }
592
593 /* --------------------------------------------------------------------- */
594 /* more crap */
595
596 void change_focus_to(int new_widget_index)
597 {
598 if (*selected_widget != new_widget_index) {
599 if (ACTIVE_WIDGET.depressed) ACTIVE_WIDGET.depressed = 0;
600
601 *selected_widget = new_widget_index;
602
603 ACTIVE_WIDGET.depressed = 0;
604
605 if (ACTIVE_WIDGET.type == WIDGET_TEXTENTRY)
606 ACTIVE_WIDGET.d.textentry.cursor_pos
607 = strlen(ACTIVE_WIDGET.d.textentry.text);
608
609 status.flags |= NEED_UPDATE;
610 }
611 }
612
613 static int _find_widget_xy(int x, int y)
614 {
615 struct widget *w;
616 int i, pad;
617
618 if (!total_widgets)
619 return -1;
620 for (i = 0; i < *total_widgets; i++) {
621 w = widgets + i;
song_loop_pattern(int pattern,int row)622 switch (w->type) {
623 case WIDGET_BUTTON:
624 pad = w->d.button.padding + 1;
625 break;
626 case WIDGET_TOGGLEBUTTON:
627 pad = w->d.togglebutton.padding + 1;
628 break;
629 default:
630 pad = 0;
631 }
632 if (x >= w->x && x < w->x + w->width + pad && y >= w->y && y < w->y + w->height) {
633 return i;
634 }
635 }
636 return -1;
637 }
638
song_start_at_order(int order,int row)639 int change_focus_to_xy(int x, int y)
640 {
641 int n = _find_widget_xy(x, y);
642 if (n >= 0) {
643 change_focus_to(n);
644 return 1;
645 }
646 return 0;
647 }
648