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