/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "page.h" /* --------------------------------------------------------------------- */ /* create_* functions (the constructors, if you will) */ void create_toggle(struct widget *w, int x, int y, int next_up, int next_down, int next_left, int next_right, int next_tab, void (*changed) (void)) { w->type = WIDGET_TOGGLE; w->accept_text = 0; w->x = x; w->y = y; w->width = 3; /* "Off" */ w->next.up = next_up; w->next.left = next_left; w->next.down = next_down; w->next.right = next_right; w->next.tab = next_tab; w->changed = changed; w->activate = NULL; w->depressed = 0; w->height = 1; } void create_menutoggle(struct widget *w, int x, int y, int next_up, int next_down, int next_left, int next_right, int next_tab, void (*changed) (void), const char *const *choices) { int n, width = 0, len; for (n = 0; choices[n]; n++) { len = strlen(choices[n]); if (width < len) width = len; } w->type = WIDGET_MENUTOGGLE; w->accept_text = 0; w->x = x; w->y = y; w->width = width; w->depressed = 0; w->height = 1; w->next.up = next_up; w->next.left = next_left; w->next.down = next_down; w->next.right = next_right; w->next.tab = next_tab; w->changed = changed; w->d.menutoggle.choices = choices; w->d.menutoggle.num_choices = n; w->activate = NULL; w->d.menutoggle.activation_keys = NULL; } void create_button(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_left, int next_right, int next_tab, void (*changed) (void), const char *text, int padding) { w->type = WIDGET_BUTTON; w->accept_text = 0; w->x = x; w->y = y; w->width = width; w->depressed = 0; w->height = 1; w->next.up = next_up; w->next.left = next_left; w->next.down = next_down; w->next.right = next_right; w->next.tab = next_tab; w->changed = changed; w->d.button.text = text; w->d.button.padding = padding; w->activate = NULL; } void create_togglebutton(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_left, int next_right, int next_tab, void (*changed) (void), const char *text, int padding, const int *group) { w->type = WIDGET_TOGGLEBUTTON; w->accept_text = 0; w->x = x; w->y = y; w->width = width; w->depressed = 0; w->height = 1; w->next.up = next_up; w->next.left = next_left; w->next.down = next_down; w->next.right = next_right; w->next.tab = next_tab; w->changed = changed; w->d.togglebutton.text = text; w->d.togglebutton.padding = padding; w->d.togglebutton.group = group; w->activate = NULL; } void create_textentry(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_tab, void (*changed) (void), char *text, int max_length) { w->type = WIDGET_TEXTENTRY; w->accept_text = 1; w->x = x; w->y = y; w->width = width; w->depressed = 0; w->height = 1; w->next.up = next_up; w->next.down = next_down; w->next.tab = next_tab; w->changed = changed; w->d.textentry.text = text; w->d.textentry.max_length = max_length; w->d.textentry.firstchar = 0; w->d.textentry.cursor_pos = 0; w->activate = NULL; } void create_numentry(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_tab, void (*changed) (void), int min, int max, int *cursor_pos) { w->type = WIDGET_NUMENTRY; w->accept_text = 1; w->x = x; w->y = y; w->width = width; w->depressed = 0; w->height = 1; w->next.up = next_up; w->next.down = next_down; w->next.tab = next_tab; w->changed = changed; w->d.numentry.min = min; w->d.numentry.max = max; w->d.numentry.cursor_pos = cursor_pos; w->d.numentry.handle_unknown_key = NULL; w->d.numentry.reverse = 0; w->activate = NULL; } void create_thumbbar(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_tab, void (*changed) (void), int min, int max) { w->type = WIDGET_THUMBBAR; w->accept_text = 0; w->x = x; w->y = y; w->width = width; w->depressed = 0; w->height = 1; w->next.up = next_up; w->next.down = next_down; w->next.tab = next_tab; w->changed = changed; w->d.thumbbar.min = min; w->d.thumbbar.max = max; w->d.thumbbar.text_at_min = NULL; w->d.thumbbar.text_at_max = NULL; w->activate = NULL; } void create_bitset(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_tab, void (*changed) (void), int nbits, const char* bits_on, const char* bits_off, int *cursor_pos) { w->type = WIDGET_BITSET; w->accept_text = 0; w->x = x; w->y = y; w->width = width; w->depressed = 0; w->height = 1; w->next.up = next_up; w->next.down = next_down; w->next.tab = next_tab; w->changed = changed; w->d.numentry.reverse = 0; w->d.bitset.nbits = nbits; w->d.bitset.bits_on = bits_on; w->d.bitset.bits_off = bits_off; w->d.bitset.cursor_pos = cursor_pos; w->activate = NULL; } void create_panbar(struct widget *w, int x, int y, int next_up, int next_down, int next_tab, void (*changed) (void), int channel) { w->type = WIDGET_PANBAR; w->accept_text = 0; w->x = x; w->y = y; w->width = 24; w->height = 1; w->next.up = next_up; w->next.down = next_down; w->next.tab = next_tab; w->changed = changed; w->d.numentry.reverse = 0; w->d.panbar.min = 0; w->d.panbar.max = 64; w->d.panbar.channel = channel; w->activate = NULL; } void create_other(struct widget *w, int next_tab, int (*i_handle_key) (struct key_event *k), void (*i_redraw) (void)) { w->type = WIDGET_OTHER; w->accept_text = 0; w->next.up = w->next.down = w->next.left = w->next.right = 0; w->next.tab = next_tab; /* w->changed = NULL; ??? */ w->depressed = 0; w->activate = NULL; /* unfocusable unless set */ w->x = -1; w->y = -1; w->width = -1; w->height = 1; w->d.other.handle_key = i_handle_key; w->d.other.redraw = i_redraw; } /* --------------------------------------------------------------------- */ /* generic text stuff */ void text_add_char(char *text, char c, int *cursor_pos, int max_length) { int len; text[max_length] = 0; len = strlen(text); if (*cursor_pos >= max_length) *cursor_pos = max_length - 1; /* FIXME: this causes some weirdness with the end key. maybe hitting end should trim spaces? */ while (len < *cursor_pos) text[len++] = ' '; memmove(text + *cursor_pos + 1, text + *cursor_pos, max_length - *cursor_pos - 1); text[*cursor_pos] = c; (*cursor_pos)++; } void text_delete_char(char *text, int *cursor_pos, int max_length) { if (*cursor_pos == 0) return; (*cursor_pos)--; memmove(text + *cursor_pos, text + *cursor_pos + 1, max_length - *cursor_pos); } void text_delete_next_char(char *text, int *cursor_pos, int max_length) { memmove(text + *cursor_pos, text + *cursor_pos + 1, max_length - *cursor_pos); } /* --------------------------------------------------------------------- */ /* text entries */ static void textentry_reposition(struct widget *w) { int len; w->d.textentry.text[w->d.textentry.max_length] = 0; len = strlen(w->d.textentry.text); if (w->d.textentry.cursor_pos < w->d.textentry.firstchar) { w->d.textentry.firstchar = w->d.textentry.cursor_pos; } else if (w->d.textentry.cursor_pos > len) { w->d.textentry.cursor_pos = len; } else if (w->d.textentry.cursor_pos > (w->d.textentry.firstchar + w->width - 1)) { w->d.textentry.firstchar = w->d.textentry.cursor_pos - w->width + 1; if (w->d.textentry.firstchar < 0) w->d.textentry.firstchar = 0; } } int textentry_add_char(struct widget *w, uint16_t unicode) { int c = unicode_to_ascii(unicode); if (c == 0) return 0; text_add_char(w->d.textentry.text, c, &(w->d.textentry.cursor_pos), w->d.textentry.max_length); if (w->changed) w->changed(); status.flags |= NEED_UPDATE; return 1; } int menutoggle_handle_key(struct widget *w, struct key_event *k) { if( ((k->mod & (KMOD_CTRL | KMOD_ALT | KMOD_META)) == 0) && w->d.menutoggle.activation_keys) { const char* m = w->d.menutoggle.activation_keys; const char* p = strchr(m, (char)k->unicode); if(p && *p) { w->d.menutoggle.state = p - m; if(w->changed) w->changed(); status.flags |= NEED_UPDATE; return 1; } } return 0; } int bitset_handle_key(struct widget *w, struct key_event *k) { if( ((k->mod & (KMOD_CTRL | KMOD_ALT | KMOD_META)) == 0) && w->d.bitset.activation_keys) { const char* m = w->d.bitset.activation_keys; const char* p = strchr(m, (char)k->unicode); if(p && *p) { int bit_index = p-m; w->d.bitset.value ^= (1 << bit_index); if(w->changed) w->changed(); status.flags |= NEED_UPDATE; return 1; } } return 0; } /* --------------------------------------------------------------------- */ /* numeric entries */ void numentry_change_value(struct widget *w, int new_value) { new_value = CLAMP(new_value, w->d.numentry.min, w->d.numentry.max); w->d.numentry.value = new_value; if (w->changed) w->changed(); status.flags |= NEED_UPDATE; } /* I'm sure there must be a simpler way to do this. */ int numentry_handle_digit(struct widget *w, struct key_event *k) { int width, value, n; static const int tens[7] = { 1, 10, 100, 1000, 10000, 100000, 1000000 }; int digits[7] = { 0 }; int c; c = numeric_key_event(k, 0); if (c == -1) { if (w->d.numentry.handle_unknown_key) { return w->d.numentry.handle_unknown_key(k); } return 0; } if (w->d.numentry.reverse) { w->d.numentry.value *= 10; w->d.numentry.value += c; if (w->changed) w->changed(); status.flags |= NEED_UPDATE; return 1; } width = w->width; value = w->d.numentry.value; for (n = width - 1; n >= 0; n--) digits[n] = value / tens[n] % 10; digits[width - *(w->d.numentry.cursor_pos) - 1] = c; value = 0; for (n = width - 1; n >= 0; n--) value += digits[n] * tens[n]; value = CLAMP(value, w->d.numentry.min, w->d.numentry.max); w->d.numentry.value = value; if (*(w->d.numentry.cursor_pos) < w->width - 1) (*(w->d.numentry.cursor_pos))++; if (w->changed) w->changed(); status.flags |= NEED_UPDATE; return 1; } /* --------------------------------------------------------------------- */ /* toggle buttons */ void togglebutton_set(struct widget *p_widgets, int widget, int do_callback) { const int *group = p_widgets[widget].d.togglebutton.group; int i; if (!group) return; /* assert */ for (i = 0; group[i] >= 0; i++) p_widgets[group[i]].d.togglebutton.state = 0; p_widgets[widget].d.togglebutton.state = 1; if (do_callback) { if (p_widgets[widget].changed) p_widgets[widget].changed(); } status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ /* /me takes a deep breath */ void draw_widget(struct widget *w, int selected) { char buf[16] = "Channel 42"; const char *ptr, *endptr; /* for the menutoggle */ char *str; int n; int tfg = selected ? 0 : 2; int tbg = selected ? 3 : 0; int drew_cursor = 0; int fg,bg; switch (w->type) { case WIDGET_TOGGLE: draw_fill_chars(w->x, w->y, w->x + w->width - 1, w->y, 0); draw_text((w->d.toggle.state ? "On" : "Off"), w->x, w->y, tfg, tbg); break; case WIDGET_MENUTOGGLE: draw_fill_chars(w->x, w->y, w->x + w->width - 1, w->y, 0); ptr = w->d.menutoggle.choices[w->d.menutoggle.state]; endptr = strchr(ptr, ' '); if (endptr) { n = endptr - ptr; draw_text_len(ptr, n, w->x, w->y, tfg, tbg); draw_text(endptr + 1, w->x + n + 1, w->y, 2, 0); } else { draw_text(ptr, w->x, w->y, tfg, tbg); } break; case WIDGET_BUTTON: draw_box(w->x - 1, w->y - 1, w->x + w->width + 2, w->y + 1, BOX_THIN | BOX_INNER | ( w->depressed ? BOX_INSET : BOX_OUTSET)); draw_text(w->d.button.text, w->x + w->d.button.padding, w->y, selected ? 3 : 0, 2); break; case WIDGET_TOGGLEBUTTON: draw_box(w->x - 1, w->y - 1, w->x + w->width + 2, w->y + 1, BOX_THIN | BOX_INNER |( (w->d.togglebutton.state || w->depressed) ? BOX_INSET : BOX_OUTSET)); draw_text(w->d.togglebutton.text, w->x + w->d.togglebutton.padding, w->y, selected ? 3 : 0, 2); break; case WIDGET_TEXTENTRY: textentry_reposition(w); draw_text_len(w->d.textentry.text + w->d.textentry.firstchar, w->width, w->x, w->y, 2, 0); if (selected && !drew_cursor) { n = w->d.textentry.cursor_pos - w->d.textentry.firstchar; draw_char(((n < (signed) strlen(w->d.textentry.text)) ? (w->d.textentry.text[w->d.textentry.cursor_pos]) : ' '), w->x + n, w->y, 0, 3); } break; case WIDGET_NUMENTRY: if (w->d.numentry.reverse) { str = numtostr(w->width, w->d.numentry.value, buf); while (*str == '0') str++; draw_text_len("", w->width, w->x, w->y, 2, 0); if (*str) { draw_text(str, (w->x+w->width) - strlen(str), w->y, 2, 0); } if (selected && !drew_cursor) { while (str[0] && str[1]) str++; if (!str[0]) str[0] = ' '; draw_char(str[0], w->x + (w->width-1), w->y, 0, 3); } } else { if (w->d.numentry.min < 0 || w->d.numentry.max < 0) { numtostr_signed(w->width, w->d.numentry.value, buf); } else { numtostr(w->width, w->d.numentry.value, buf); } draw_text_len(buf, w->width, w->x, w->y, 2, 0); if (selected && !drew_cursor) { n = *(w->d.numentry.cursor_pos); draw_char(buf[n], w->x + n, w->y, 0, 3); } } break; case WIDGET_BITSET: for(n = 0; n < w->d.bitset.nbits; ++n) { int set = !!(w->d.bitset.value & (1 << n)); char label_c1 = set ? w->d.bitset.bits_on[n*2+0] : w->d.bitset.bits_off[n*2+0]; char label_c2 = set ? w->d.bitset.bits_on[n*2+1] : w->d.bitset.bits_off[n*2+1]; int is_focused = selected && n == *w->d.bitset.cursor_pos; /* In textentries, cursor=0,3; normal=2,0 */ static const char fg_selection[4] = { 2, /* not cursor, not set */ 3, /* not cursor, is set */ 0, /* has cursor, not set */ 0 /* has cursor, is set */ }; static const char bg_selection[4] = { 0, /* not cursor, not set */ 0, /* not cursor, is set */ 2, /* has cursor, not set */ 3 /* has cursor, is set */ }; fg = fg_selection[set + is_focused*2]; bg = bg_selection[set + is_focused*2]; if(label_c2) draw_half_width_chars(label_c1, label_c2, w->x + n, w->y, fg, bg, fg, bg); else draw_char(label_c1, w->x + n, w->y, fg, bg); } break; case WIDGET_THUMBBAR: if (w->d.thumbbar.text_at_min && w->d.thumbbar.min == w->d.thumbbar.value) { draw_text_len(w->d.thumbbar.text_at_min, w->width, w->x, w->y, selected ? 3 : 2, 0); } else if (w->d.thumbbar.text_at_max && w->d.thumbbar.max == w->d.thumbbar.value) { /* this will probably do Bad Things if the text is too long */ int len = strlen(w->d.thumbbar.text_at_max); int pos = w->x + w->width - len; draw_fill_chars(w->x, w->y, pos - 1, w->y, 0); draw_text_len(w->d.thumbbar.text_at_max, len, pos, w->y, selected ? 3 : 2, 0); } else { draw_thumb_bar(w->x, w->y, w->width, w->d.thumbbar.min, w->d.thumbbar.max, w->d.thumbbar.value, selected); } if (w->d.thumbbar.min < 0 || w->d.thumbbar.max < 0) { numtostr_signed(3, w->d.thumbbar.value, buf); } else { numtostr(3, w->d.thumbbar.value, buf); } draw_text(buf, w->x + w->width + 1, w->y, 1, 2); break; case WIDGET_PANBAR: numtostr(2, w->d.panbar.channel, buf + 8); draw_text(buf, w->x, w->y, selected ? 3 : 0, 2); if (w->d.panbar.muted) { draw_text(" Muted ", w->x + 11, w->y, selected ? 3 : 5, 0); /* draw_fill_chars(w->x + 21, w->y, w->x + 23, w->y, 2); */ } else if (w->d.panbar.surround) { draw_text("Surround ", w->x + 11, w->y, selected ? 3 : 5, 0); /* draw_fill_chars(w->x + 21, w->y, w->x + 23, w->y, 2); */ } else { draw_thumb_bar(w->x + 11, w->y, 9, 0, 64, w->d.panbar.value, selected); draw_text(numtostr(3, w->d.thumbbar.value, buf), w->x + 21, w->y, 1, 2); } break; case WIDGET_OTHER: if (w->d.other.redraw) w->d.other.redraw(); break; default: /* shouldn't ever happen... */ break; } } /* --------------------------------------------------------------------- */ /* more crap */ void change_focus_to(int new_widget_index) { if (*selected_widget != new_widget_index) { if (ACTIVE_WIDGET.depressed) ACTIVE_WIDGET.depressed = 0; *selected_widget = new_widget_index; ACTIVE_WIDGET.depressed = 0; if (ACTIVE_WIDGET.type == WIDGET_TEXTENTRY) ACTIVE_WIDGET.d.textentry.cursor_pos = strlen(ACTIVE_WIDGET.d.textentry.text); status.flags |= NEED_UPDATE; } } static int _find_widget_xy(int x, int y) { struct widget *w; int i, pad; if (!total_widgets) return -1; for (i = 0; i < *total_widgets; i++) { w = widgets + i; switch (w->type) { case WIDGET_BUTTON: pad = w->d.button.padding + 1; break; case WIDGET_TOGGLEBUTTON: pad = w->d.togglebutton.padding + 1; break; default: pad = 0; } if (x >= w->x && x < w->x + w->width + pad && y >= w->y && y < w->y + w->height) { return i; } } return -1; } int change_focus_to_xy(int x, int y) { int n = _find_widget_xy(x, y); if (n >= 0) { change_focus_to(n); return 1; } return 0; }