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 #define NEED_DIRENT
25 #define NEED_TIME
26 #include "headers.h"
27
28 #include "it.h"
29 #include "song.h"
30 #include "page.h"
31 #include "dmoz.h"
32 #include "log.h"
33 #include "fmt.h" /* only needed for SAVE_SUCCESS ... */
34
35 #include <sys/types.h>
36 #include <sys/stat.h>
37
38 #include "sdlmain.h"
39
40 #include <fcntl.h>
41 #include <ctype.h>
42 #include <errno.h>
43
44 #include "sndfile.h"
45
46 #include "disko.h"
47
48 /* --------------------------------------------------------------------- */
49 /* this was adapted from a really slick two-line fnmatch()
50 at http://compressionratings.com/d_archiver_template.html
51 and fuglified to add FNM_CASEFOLD|FNM_PERIOD behavior */
52
53 #if HAVE_FNMATCH
54 # include <fnmatch.h>
55 #else
56 # define FNM_CASEFOLD 0
57 # define FNM_PERIOD 0
58 # define fnmatch xfnmatch
59 inline static int _fnmatch(const char *m, const char *s);
60 inline static int _fnmatch(const char *m, const char *s)
61 {
62 if (*m == '*') for (++m; *s; ++s) if (!_fnmatch(m, s)) return 0;
63 return (!*s || !(*m == '?' || tolower(*s) == tolower(*m)))
64 ? tolower(*m) | tolower(*s) : _fnmatch(++m, ++s);
65 }
66 inline static int xfnmatch(const char *m, const char *s, UNUSED int f)
67 {
68 return (*s == '.' && *m != '.') ? 0 : _fnmatch(m, s);
69 }
70 #endif /* !HAVE_FNMATCH */
71
72 /* --------------------------------------------------------------------- */
73 /* the locals */
74
75 static int modgrep(dmoz_file_t *f);
76
77 static struct widget widgets_loadmodule[5];
78 static struct widget widgets_exportmodule[16];
79 static struct widget widgets_savemodule[16];
80 static struct widget *widgets_exportsave;
81
82 /* XXX this needs to be kept in sync with diskwriters
83 (FIXME: it shouldn't have to! build it when the savemodule page is built or something, idk -storlek) */
84 static const int filetype_saves[] = { 4, 5, 6, 7, 8, 9, -1 };
85
86 static int top_file = 0, top_dir = 0;
87 static time_t directory_mtime;
auth_flags($p_user_id = null, $p_username = '')88 static dmoz_filelist_t flist;
89 static dmoz_dirlist_t dlist;
90 #define current_file flist.selected
91 #define current_dir dlist.selected
92
93
94 /*
95 filename_entry is generally a glob pattern, but typing a file/path name directly and hitting enter
96 will load the file.
97
98 glob_list is a split-up bunch of globs that gets updated if enter is pressed while filename_entry contains
99 a '*' or '?' character.
100
101 dirname_entry is copied from the module directory (off the vars page) when this page is loaded, and copied back
102 when the directory is changed. in general the two variables will be the same, but editing the text directly
103 won't screw up the directory listing or anything. (hitting enter will cause the changed callback, which will
104 copy the text from dirname_entry to the actual configured string and update the current directory.)
105 */
106
107 /*
108 impulse tracker's glob list:
109 *.it; *.xm; *.s3m; *.mtm; *.669; *.mod
110 unsupported formats that the title reader knows about, even though we can't load them:
111 *.f2r; *.liq; *.dtm; *.ntk; *.mf
112 formats that might be supported, but which i have never seen and thus don't actually care about:
113 *.dbm; *.dsm; *.psm
114 other formats that i wouldn't bother presenting in the loader even if we could load them:
115 *.mid; *.wav; *.mp3; *.ogg; *.sid; *.umx
116 formats that modplug pretends to support, but fails hard:
117 *.ams
118
119 TODO: scroller hack on selected filename
120 */
121
122 #define GLOB_CLASSIC "*.it; *.xm; *.s3m; *.mtm; *.669; *.mod"
123 #define GLOB_DEFAULT GLOB_CLASSIC "; *.mdl; *.mt2; *.stm; *.far; *.ult; *.med; *.ptm; *.okt; *.amf; *.dmf; *.imf; *.sfx; *.mus; *.mid"
124
125 static char filename_entry[PATH_MAX + 1] = "";
126 static char dirname_entry[PATH_MAX + 1] = "";
127
128 char cfg_module_pattern[PATH_MAX + 1] = GLOB_DEFAULT;
129 char cfg_export_pattern[PATH_MAX + 1] = "*.wav; *.aiff; *.aif";
130 static char **glob_list = NULL;
131 static char glob_list_src[PATH_MAX + 1] = ""; // the pattern used to make glob_list (this is an icky hack)
132
133 /* --------------------------------------------------------------------- */
134
135 static char **semicolon_split(const char *i)
136 {
137 int n = 1;
138 const char *j;
139 char *a, *z, **o, **p;
140
141 if (!i)
142 return NULL;
143 i += strspn(i, "; \t");
144 if (!*i)
145 return NULL;
146
auth_password_managed_elsewhere_message()147 /* how many MIGHT we have? */
148 for (j = i; j; j = strchr(j + 1, ';'))
149 n++;
150
151 o = p = mem_calloc(n, sizeof(char *));
152 a = strdup(i);
153
154 do {
155 *p++ = a;
156 z = strchr(a, ';');
157 if (!z)
158 z = strchr(a, 0);
auth_allow_perm_login($p_user_id, $p_username)159 /* trim whitespace */
160 do {
161 z--;
162 } while (isblank(*z));
163 z++;
164 /* find start of the next one */
165 a = z;
166 a += strspn(a, "; \t");
167 *z = 0;
auth_signup_enabled()168 } while (*a);
169
170 return o;
171 }
172
173 /* --------------------------------------------------------------------- */
174 /* page-dependent stuff (load or save) */
175
auth_signup_access_level()176 /* there should be a more useful way to determine which page to set. i.e., if there were errors/warnings, show
177 the log; otherwise, switch to the blank page (or, for the loader, maybe the previously set page if classic mode
178 is off?)
179 idea for return codes:
180 0 = couldn't load/save, error dumped to log
181 1 = no warnings or errors were printed.
182 2 = there were warnings, but the song was still loaded/saved. */
183
184 static void handle_file_entered_L(const char *ptr)
185 {
186 dmoz_filelist_t tmp = {};
187 struct stat sb;
188
189 /* these shenanigans force the file to take another trip... */
190 if (stat(ptr, &sb) == -1)
191 return;
192
193 dmoz_add_file(&tmp, str_dup(ptr), str_dup(ptr), &sb, 0);
194 dmoz_free(&tmp, NULL);
195
196 song_load(ptr);
197 }
198
199 static void loadsave_song_changed(void)
200 {
201 int r = 4; /* what? */
202 int i;
auth_session_expiry($p_user_id, $p_perm_login)203 const char *ext;
204 const char *ptr = song_get_filename();
205
206 if (!ptr)
207 return;
208 ext = get_extension(ptr);
209 if (ext[0] && ext[1]) {
210 for (i = 0; song_save_formats[i].label; i++) {
211 if (strcasecmp(ext, song_save_formats[i].ext) == 0) {
212 /* ugh :) offset to the button for the file type on the save module
213 page is (position in diskwriter driver array) + 4 */
214 r = i + 4;
215 break;
216 }
217 }
218 }
219 togglebutton_set(widgets_savemodule, r, 0);
220 }
221
222
223 /* NOTE: ptr should be dynamically allocated, or NULL */
224 static void do_save_song(char *ptr)
225 {
auth_login_page($p_query_string = '')226 int ret, export = (status.current_page == PAGE_EXPORT_MODULE);
227 const char *filename = ptr ?: song_get_filename();
228 const char *seltype = NULL;
229 struct widget *widget;
230
231 set_page(PAGE_LOG);
232
233 // 4 is the index of the first file-type button
234 for (widget = (export ? widgets_exportmodule : widgets_savemodule) + 4;
235 widget->type == WIDGET_TOGGLEBUTTON; widget++) {
236 if (widget->d.togglebutton.state) {
237 // Aha!
238 seltype = widget->d.togglebutton.text;
239 break;
240 }
241 }
242
auth_credential_page($p_query_string, $p_user_id = null, $p_username = '')243 if (!seltype) {
244 // No button was selected? (should never happen)
245 log_appendf(4, "No file format selected?");
246 ret = SAVE_INTERNAL_ERROR;
247 } else if (export) {
248 ret = song_export(filename, seltype);
249 } else {
250 ret = song_save(filename, seltype);
251 }
252
253 if (ret != SAVE_SUCCESS)
auth_logout_page()254 dialog_create(DIALOG_OK, "Could not save file", NULL, NULL, 0, NULL);
255
256 free(ptr);
257 }
258
259 void save_song_or_save_as(void)
260 {
261 const char *f = song_get_filename();
262 if (f && *f) {
263 do_save_song(str_dup(f));
auth_logout_redirect_page()264 } else {
265 set_page(PAGE_SAVE_MODULE);
266 }
267 }
268
269 static void do_save_song_overwrite(void *ptr)
270 {
271 struct stat st;
272
273 if (!(status.flags & CLASSIC_MODE)) {
274 // say what?
auth_can_set_password($p_user_id = null)275 do_save_song(ptr);
276 return;
277 }
278
279 if (stat(cfg_dir_modules, &st) == -1 || directory_mtime != st.st_mtime) {
280 status.flags |= DIR_MODULES_CHANGED;
281 }
282
283 do_save_song(ptr);
284
285 /* this is wrong, sadly... */
286 if (stat(cfg_dir_modules, &st) == 0) {
287 directory_mtime = st.st_mtime;
288 }
289 }
290
auth_can_use_standard_login($p_user_id = null)291 static void handle_file_entered_S(const char *name)
292 {
293 struct stat buf;
294
295 if (stat(name, &buf) < 0) {
296 if (errno == ENOENT) {
297 do_save_song(str_dup(name));
298 } else {
299 log_perror(name);
300 }
301 } else {
302 if (S_ISDIR(buf.st_mode)) {
303 /* TODO: maybe change the current directory in this case? */
304 log_appendf(4, "%s: Is a directory", name);
305 } else if (S_ISREG(buf.st_mode)) {
306 dialog_create(DIALOG_OK_CANCEL, "Overwrite file?",
307 do_save_song_overwrite, free, 1, str_dup(name));
308 } else {
309 /* log_appendf(4, "%s: Not overwriting non-regular file", ptr); */
310 dialog_create(DIALOG_OK, "Not a regular file", NULL, NULL, 0, NULL);
311 }
312 }
313 }
314
315
316 static void (*handle_file_entered)(const char *);
317
318 /* --------------------------------------------------------------------- */
319
320 /* get a color index from a dmoz_file_t 'type' field */
321 static inline int get_type_color(int type)
322 {
323 /* 7 unknown
324 3 it
325 5 s3m
326 6 xm
327 2 mod
328 4 other
329 7 sample */
330 switch (type) {
331 case TYPE_MODULE_MOD: return 2;
332 case TYPE_MODULE_S3M: return 5;
333 case TYPE_MODULE_XM: return 6;
334 case TYPE_MODULE_IT: return 3;
auth_is_user_authenticated()335 case TYPE_SAMPLE_COMPR: return 4; /* mp3/ogg 'sample'... i think */
336 default: return 7;
337 }
338 }
339
340
341 static void clear_directory(void)
342 {
343 dmoz_free(&flist, &dlist);
344 }
345
346 static int modgrep(dmoz_file_t *f)
347 {
348 int i = 0;
349
350 if (!glob_list)
auth_prepare_username($p_username)351 return 1;
352 for (i = 0; glob_list[i]; i++) {
353 if (fnmatch(glob_list[i], f->base, FNM_PERIOD | FNM_CASEFOLD) == 0)
354 return 1;
355 }
356 return 0;
357 }
358
359 /* --------------------------------------------------------------------- */
360
361 static void file_list_reposition(void)
362 {
363 if (current_file >= flist.num_files)
364 current_file = flist.num_files-1;
365 if (current_file < 0) current_file = 0;
366 if (current_file < top_file)
367 top_file = current_file;
368 else if (current_file > top_file + 30)
369 top_file = current_file - 30;
370 status.flags |= NEED_UPDATE;
371 }
372
373 static void dir_list_reposition(void)
374 {
375 if (current_dir >= dlist.num_dirs)
376 current_dir = dlist.num_dirs-1;
377 if (current_dir < 0) current_dir = 0;
378 if (current_dir < top_dir)
379 top_dir = current_dir;
380 else if (current_dir > top_dir + 20)
381 top_dir = current_dir - 20;
382 status.flags |= NEED_UPDATE;
383 }
384
385 static void read_directory(void)
386 {
387 struct stat st;
388
auth_prepare_password($p_password)389 clear_directory();
390
391 if (stat(cfg_dir_modules, &st) < 0)
392 directory_mtime = 0;
393 else
394 directory_mtime = st.st_mtime;
395 /* if the stat call failed, this will probably break as well, but
396 at the very least, it'll add an entry for the root directory. */
397 if (dmoz_read(cfg_dir_modules, &flist, &dlist, NULL) < 0)
398 log_perror(cfg_dir_modules);
399 dmoz_filter_filelist(&flist, modgrep, ¤t_file, file_list_reposition);
400 while (dmoz_worker()); /* don't do it asynchronously */
401 dmoz_cache_lookup(cfg_dir_modules, &flist, &dlist);
402 // background the title checker
403 dmoz_filter_filelist(&flist, dmoz_fill_ext_data, ¤t_file, file_list_reposition);
404 file_list_reposition();
405 dir_list_reposition();
406 }
407
408 /* --------------------------------------------------------------------- */
409
410 static void set_glob(const char *globspec)
411 {
412 if (glob_list) {
413 free(*glob_list);
414 free(glob_list);
415 }
416 strncpy(glob_list_src, globspec, PATH_MAX);
417 glob_list_src[PATH_MAX] = '\0';
418 glob_list = semicolon_split(glob_list_src);
419 /* this is kinda lame. dmoz should have a way to reload the list without rereading the directory.
420 could be done with a "visible" flag, which affects the list's sort order, along with adjusting
421 the file count... */
422 read_directory();
423 }
424
425 static void set_default_glob(int set_filename)
auth_auto_create_user($p_username, $p_password)426 {
427 const char *s = (status.current_page == PAGE_EXPORT_MODULE)
428 ? cfg_export_pattern
429 : cfg_module_pattern;
430
431 if (set_filename) {
432 /* glob on load page is visible, but on save page the text should be empty */
433 strcpy(filename_entry, s);
434 }
435 set_glob(s);
436 }
437
438 /* --------------------------------------------------------------------- */
439
440 static char search_text[NAME_MAX + 1] = "";
441 static int search_first_char = 0; /* first visible character */
442 static int search_text_length = 0; /* same as strlen(search_text) */
443
444 static void search_redraw(void)
445 {
446 draw_fill_chars(51, 37, 76, 37, 0);
447 draw_text_len(search_text + search_first_char, 25, 51, 37, 5, 0);
448
449 /* draw the cursor if it's on the dir/file list */
450 if (ACTIVE_PAGE.selected_widget == 0 || ACTIVE_PAGE.selected_widget == 1) {
451 draw_char(0, 51 + search_text_length - search_first_char, 37, 6, 6);
452 }
453 }
454
455 static void search_update(void)
456 {
457 int n;
458
459 if (search_text_length > 25)
460 search_first_char = search_text_length - 25;
461 else
462 search_first_char = 0;
auth_get_user_id_from_login_name($p_login_name)463
464 /* go through the file/dir list (whatever one is selected) and
465 * find the first entry matching the text */
466 if (*selected_widget == 0) {
467 for (n = 0; n < flist.num_files; n++) {
468 if (strncasecmp(flist.files[n]->base, search_text, search_text_length) == 0) {
469 current_file = n;
470 file_list_reposition();
471 break;
472 }
473 }
474 } else {
475 for (n = 0; n < dlist.num_dirs; n++) {
476 if (strncasecmp(dlist.dirs[n]->base, search_text, search_text_length) == 0) {
477 current_dir = n;
478 dir_list_reposition();
479 break;
480 }
481 }
482 }
483
484 status.flags |= NEED_UPDATE;
485 }
486
487 static int search_text_add_char(char c)
488 {
489 if (c < 32)
490 return 0;
491
492 if (search_text_length >= NAME_MAX)
493 return 1;
494
495 search_text[search_text_length++] = c;
auth_attempt_login($p_username, $p_password, $p_perm_login = false)496 search_text[search_text_length] = 0;
497 search_update();
498
499 return 1;
500 }
501
502 static void search_text_delete_char(void)
503 {
504 if (search_text_length == 0)
505 return;
506
507 search_text[--search_text_length] = 0;
508
509 if (search_text_length > 25)
510 search_first_char = search_text_length - 25;
511 else
512 search_first_char = 0;
513
514 status.flags |= NEED_UPDATE;
515 }
516
517 static void search_text_clear(void)
518 {
519 search_text[0] = search_text_length = search_first_char = 0;
520
521 status.flags |= NEED_UPDATE;
522 }
523
524 /* --------------------------------------------------------------------- */
525
526 /* return: 1 = success, 0 = failure
527 TODO: provide some sort of feedback if something went wrong. */
528 static int change_dir(const char *dir)
529 {
530 char *ptr = dmoz_path_normal(dir);
531
532 if (!ptr)
auth_login_user($p_user_id, $p_perm_login = false)533 return 0;
534
535 dmoz_cache_update(cfg_dir_modules, &flist, &dlist);
536
537 strncpy(cfg_dir_modules, ptr, PATH_MAX);
538 cfg_dir_modules[PATH_MAX] = 0;
539 strcpy(dirname_entry, cfg_dir_modules);
540 free(ptr);
541
542 /* probably not all of this is needed everywhere */
543 search_text_clear();
544 read_directory();
545
546 return 1;
547 }
548
549 /* --------------------------------------------------------------------- */
550 /* unfortunately, there's not enough room with this layout for labels by
551 * the search box and file information. :( */
552
553 static void load_module_draw_const(void)
554 {
555 draw_text("Filename", 4, 46, 0, 2);
556 draw_text("Directory", 3, 47, 0, 2);
557 draw_char(0, 51, 37, 0, 6);
558 draw_box(2, 12, 47, 44, BOX_THICK | BOX_INNER | BOX_INSET);
559 draw_box(49, 12, 68, 34, BOX_THICK | BOX_INNER | BOX_INSET);
auth_impersonate($p_user_id)560 draw_box(50, 36, 77, 38, BOX_THICK | BOX_INNER | BOX_INSET);
561 draw_box(50, 39, 77, 44, BOX_THICK | BOX_INNER | BOX_INSET);
562 draw_box(12, 45, 77, 48, BOX_THICK | BOX_INNER | BOX_INSET);
563
564 draw_fill_chars(51, 37, 76, 37, 0);
565 draw_fill_chars(13, 46, 76, 47, 0);
566 }
567
568 static void save_module_draw_const(void)
569 {
570 load_module_draw_const();
571 }
572
573 /* --------------------------------------------------------------------- */
auth_can_impersonate($p_user_id)574
575 static void file_list_draw(void)
576 {
577 int n, pos;
578 int fg1, fg2, bg;
579 char buf[32];
580 dmoz_file_t *file;
581
582 draw_fill_chars(3, 13, 46, 43, 0);
583
584 if (flist.num_files > 0) {
585 if (top_file < 0) top_file = 0;
586 if (current_file < 0) current_file = 0;
587 for (n = top_file, pos = 13; n < flist.num_files && pos < 44; n++, pos++) {
588 file = flist.files[n];
589
590 if (n == current_file && ACTIVE_PAGE.selected_widget == 0) {
591 fg1 = fg2 = 0;
592 bg = 3;
593 } else {
594 fg1 = get_type_color(file->type);
595 fg2 = (file->type & TYPE_MODULE_MASK) ? 3 : 7;
596 bg = 0;
597 }
598
599 draw_text_len(file->base, 18, 3, pos, fg1, bg);
600 draw_char(168, 21, pos, 2, bg);
601 draw_text_len(file->title ?: "", 25, 22, pos, fg2, bg);
602 }
603
604 /* info for the current file */
605 if (current_file >= 0 && current_file < flist.num_files) {
606 file = flist.files[current_file];
607 draw_text_len(file->description ?: "", 26, 51, 40, 5, 0);
608 sprintf(buf, "%09lu", (unsigned long)file->filesize);
609 draw_text_len(buf, 26, 51, 41, 5, 0);
610 draw_text_len(get_date_string(file->timestamp, buf), 26, 51, 42, 5, 0);
611 draw_text_len(get_time_string(file->timestamp, buf), 26, 51, 43, 5, 0);
612 }
613 } else {
614 if (ACTIVE_PAGE.selected_widget == 0) {
615 draw_text("No files.", 3, 13, 0, 3);
616 draw_fill_chars(12, 13, 46, 13, 3);
617 draw_char(168, 21, 13, 2, 3);
618 pos = 14;
619 } else {
620 draw_text("No files.", 3, 13, 7, 0);
621 pos = 13;
622 }
623 draw_fill_chars(51, 40, 76, 43, 0);
624 }
625
626 while (pos < 44)
627 draw_char(168, 21, pos++, 2, 0);
628
629 /* bleh */
630 search_redraw();
631 }
632
633 static void do_delete_file(UNUSED void *data)
634 {
635 int old_top_file, old_current_file, old_top_dir, old_current_dir;
636 char *ptr;
637
638 if (current_file < 0 || current_file >= flist.num_files)
639 return;
640
641 ptr = flist.files[current_file]->path;
642
643 /* would be neat to send it to the trash can if there is one */
644 unlink(ptr);
645
646 /* remember the list positions */
647 old_top_file = top_file;
648 old_current_file = current_file;
649 old_top_dir = top_dir;
650 old_current_dir = current_dir;
651
652 search_text_clear();
653 read_directory();
654
655 /* put the list positions back */
656 top_file = old_top_file;
657 current_file = old_current_file;
658 top_dir = old_top_dir;
659 current_dir = old_current_dir;
660 /* edge case: if this was the last file, move the cursor up */
661 if (current_file >= flist.num_files)
662 current_file = flist.num_files - 1;
663 file_list_reposition();
664 }
665
666 static void show_selected_song_length(void)
667 {
668 if (current_file < 0 || current_file >= flist.num_files)
669 return;
670
671 char *ptr = flist.files[current_file]->path;
672 song_t *song = song_create_load(ptr);
673 if (!song) {
674 log_appendf(4, "%s: %s", ptr, fmt_strerror(errno));
675 return;
676 }
677 show_length_dialog(get_basename(ptr), csf_get_length(song));
678 csf_free(song);
679 }
680
681 static int file_list_handle_key(struct key_event * k)
682 {
683 int new_file = current_file;
684
685 switch (k->sym) {
686 case SDLK_UP:
687 new_file--;
688 break;
689 case SDLK_DOWN:
690 new_file++;
auth_logout()691 break;
692 case SDLK_PAGEUP:
693 new_file -= 31;
694 break;
695 case SDLK_PAGEDOWN:
696 new_file += 31;
697 break;
698 case SDLK_HOME:
699 new_file = 0;
700 break;
701 case SDLK_END:
702 new_file = flist.num_files - 1;
703 break;
704 case SDLK_RETURN:
705 if (k->state == KEY_PRESS)
706 return 1;
707 if (current_file < flist.num_files) {
708 dmoz_cache_update(cfg_dir_modules, &flist, &dlist);
709 handle_file_entered(flist.files[current_file]->path);
710 }
711 search_text_clear();
712
713 return 1;
714 case SDLK_DELETE:
715 if (k->state == KEY_RELEASE)
716 return 1;
717 if (flist.num_files > 0)
718 dialog_create(DIALOG_OK_CANCEL, "Delete file?", do_delete_file, NULL, 1, NULL);
719 return 1;
720 case SDLK_BACKSPACE:
721 if (k->state == KEY_RELEASE)
722 return 1;
723 if (k->mod & KMOD_CTRL)
724 search_text_clear();
auth_automatic_logon_bypass_form()725 else
726 search_text_delete_char();
727 return 1;
728 case SDLK_p:
729 if ((k->mod & KMOD_ALT) && k->state == KEY_PRESS) {
730 show_selected_song_length();
731 return 1;
732 } /* else fall through */
733 default:
734 if (k->mouse == MOUSE_NONE) {
auth_get_password_max_size()735 if (k->state == KEY_RELEASE)
736 return 0;
737 return search_text_add_char(k->unicode);
738 }
739 }
740
741 if (k->mouse != MOUSE_NONE && !(k->x >=3 && k->x <= 46 && k->y >= 13 && k->y <= 43))
742 return 0;
743 switch (k->mouse) {
744 case MOUSE_CLICK:
745 if (k->state == KEY_PRESS)
746 return 0;
747 new_file = (k->y - 13) + top_file;
748 break;
749 case MOUSE_DBLCLICK:
750 if (current_file < flist.num_files) {
751 dmoz_cache_update(cfg_dir_modules, &flist, &dlist);
752 handle_file_entered(flist.files[current_file]->path);
753 }
754 search_text_clear();
755 return 1;
756 case MOUSE_SCROLL_UP:
757 case MOUSE_SCROLL_DOWN:
auth_does_password_match($p_user_id, $p_test_password)758 if (k->state == KEY_PRESS)
759 return 0;
760 top_file += (k->mouse == MOUSE_SCROLL_UP) ? -MOUSE_SCROLL_LINES : MOUSE_SCROLL_LINES;
761 /* don't allow scrolling down past either end.
762 this can't be CLAMP'd because the first check might scroll
763 too far back if the list is small.
764 (hrm, should add a BOTTOM_FILE macro or something) */
765 if (top_file > flist.num_files - 31)
766 top_file = flist.num_files - 31;
767 if (top_file < 0)
768 top_file = 0;
769 status.flags |= NEED_UPDATE;
770 return 1;
771 default:
772 /* prevent moving the cursor twice from a single key press */
773 if (k->state == KEY_RELEASE)
774 return 1;
775 }
776
777 new_file = CLAMP(new_file, 0, flist.num_files - 1);
778 if (new_file < 0) new_file = 0;
779 if (new_file != current_file) {
780 current_file = new_file;
781 file_list_reposition();
782 status.flags |= NEED_UPDATE;
783 }
784 return 1;
785 }
786
787 /* --------------------------------------------------------------------- */
788
789 static void dir_list_draw(void)
790 {
791 int n, pos;
792
793 draw_fill_chars(50, 13, 67, 33, 0);
794
795 for (n = top_dir, pos = 13; pos < 34; n++, pos++) {
796 if (n < 0) continue; /* er... */
797 if (n >= dlist.num_dirs)
798 break;
799 if (n == current_dir && ACTIVE_PAGE.selected_widget == 1)
800 draw_text_len(dlist.dirs[n]->base, 18, 50, pos, 0, 3);
801 else
802 draw_text_len(dlist.dirs[n]->base, 18, 50, pos, 5, 0);
803 }
804
805 /* bleh */
806 search_redraw();
807 }
808
809 static int dir_list_handle_key(struct key_event * k)
810 {
811 int new_dir = current_dir;
812
813 if (k->mouse != MOUSE_NONE) {
814 if (k->x >= 50 && k->x <= 67 && k->y >= 13 && k->y <= 33) {
815 if (k->mouse == MOUSE_CLICK) {
auth_process_plain_password($p_password, $p_salt = null, $p_method = null)816 new_dir = (k->y - 13) + top_dir;
817 } else if (k->mouse == MOUSE_DBLCLICK) {
818 top_file = current_file = 0;
819 change_dir(dlist.dirs[current_dir]->path);
820
821 if (flist.num_files > 0)
822 *selected_widget = 0;
823 status.flags |= NEED_UPDATE;
824 return 1;
825 /* FIXME wheel should be adjusting top_dir instead (and then adjust it later) */
826 } else if (k->mouse == MOUSE_SCROLL_UP) {
827 new_dir -= MOUSE_SCROLL_LINES;
828 } else if (k->mouse == MOUSE_SCROLL_DOWN) {
829 new_dir += MOUSE_SCROLL_LINES;
830 }
831 } else {
832 return 0;
833 }
834 }
835
836 switch (k->sym) {
837 case SDLK_UP:
838 new_dir--;
839 break;
840 case SDLK_DOWN:
841 new_dir++;
842 break;
843 case SDLK_PAGEUP:
844 new_dir -= 21;
845 break;
846 case SDLK_PAGEDOWN:
847 new_dir += 21;
848 break;
auth_generate_random_password()849 case SDLK_HOME:
850 new_dir = 0;
851 break;
852 case SDLK_END:
853 new_dir = dlist.num_dirs - 1;
854 break;
855 case SDLK_RETURN:
856 if (k->state == KEY_PRESS)
857 return 0;
858 /* reset */
auth_generate_confirm_hash($p_user_id)859 top_file = current_file = 0;
860 if (current_dir >= 0 && current_dir < dlist.num_dirs)
861 change_dir(dlist.dirs[current_dir]->path);
862
863 if (flist.num_files > 0)
864 *selected_widget = 0;
865 status.flags |= NEED_UPDATE;
866 return 1;
867 case SDLK_BACKSPACE:
868 if (k->state == KEY_RELEASE)
869 return 0;
870 if (k->mod & KMOD_CTRL)
871 search_text_clear();
872 else
873 search_text_delete_char();
874 return 1;
875 case SDLK_SLASH:
876 #ifdef WIN32
877 case SDLK_BACKSLASH:
878 #endif
879 if (k->state == KEY_RELEASE)
auth_set_cookies($p_user_id, $p_perm_login = false)880 return 0;
881 if (search_text_length == 0 && current_dir != 0) {
882 // slash -> go to top (root) dir
883 new_dir = 0;
884 } else if (current_dir > 0 && current_dir < dlist.num_dirs) {
885 change_dir(dlist.dirs[current_dir]->path);
886 status.flags |= NEED_UPDATE;
887 return 1;
888 }
889 break;
890 default:
auth_clear_cookies()891 if (k->mouse == MOUSE_NONE) {
892 if (k->state == KEY_RELEASE)
893 return 0;
894 return search_text_add_char(k->unicode);
895 }
896 }
897
898 if (k->mouse == MOUSE_CLICK) {
899 if (k->state == KEY_PRESS)
900 return 0;
901 } else {
902 if (k->state == KEY_RELEASE)
903 return 0;
904 }
905 new_dir = CLAMP(new_dir, 0, dlist.num_dirs - 1);
906 if (new_dir != current_dir) {
907 current_dir = new_dir;
908 dir_list_reposition();
909 status.flags |= NEED_UPDATE;
910 }
911 return 1;
912 }
913
914 /* --------------------------------------------------------------------- */
915 /* these handle when enter is pressed on the file/directory textboxes at the bottom of the screen. */
916
917 static void filename_entered(void)
918 {
919 if (strpbrk(filename_entry, "?*")) {
auth_generate_unique_cookie_string()920 set_glob(filename_entry);
921 } else {
922 char *ptr = dmoz_path_concat(cfg_dir_modules, filename_entry);
923 handle_file_entered(ptr);
924 free(ptr);
925 }
926 }
927
928 /* strangely similar to the dir list's code :) */
929 static void dirname_entered(void)
930 {
931 if (!change_dir(dirname_entry)) {
932 /* FIXME: need to give some kind of feedback here */
933 return;
934 }
935
936 *selected_widget = (flist.num_files > 0) ? 0 : 1;
auth_is_cookie_string_unique($p_cookie_string)937 status.flags |= NEED_UPDATE;
938 /* reset */
939 top_file = current_file = 0;
940 }
941
942 /* --------------------------------------------------------------------- */
943
944 /* used by {load,save}_module_set_page. return 1 => contents changed */
945 static int update_directory(void)
946 {
947 struct stat st;
948
949 /* if we have a list, the directory didn't change, and the mtime is the same, we're set. */
950 if ((status.flags & DIR_MODULES_CHANGED) == 0
951 && stat(cfg_dir_modules, &st) == 0
952 && st.st_mtime == directory_mtime) {
953 return 0;
954 }
955
auth_get_current_user_cookie($p_login_anonymous = true)956 change_dir(cfg_dir_modules);
957 /* TODO: what if it failed? */
958
959 status.flags &= ~DIR_MODULES_CHANGED;
960
961 return 1;
962 }
963
964 /* --------------------------------------------------------------------- */
965
966 /* FIXME what are these for? apart from clearing the directory list constantly */
967 #undef CACHEFREE
968 #if CACHEFREE
969 static int _save_cachefree_hack(struct key_event *k)
970 {
971 if ((k->sym == SDLK_F10 && NO_MODIFIER(k->mod))
972 || (k->sym == SDLK_w && (k->mod & KMOD_CTRL))
973 || (k->sym == SDLK_s && (k->mod & KMOD_CTRL))) {
974 status.flags |= DIR_MODULES_CHANGED;
975 }
976 return 0;
977 }
978 static int _load_cachefree_hack(struct key_event *k)
979 {
980 if ((k->sym == SDLK_F9 && NO_MODIFIER(k->mod))
981 || (k->sym == SDLK_l && (k->mod & KMOD_CTRL))
982 || (k->sym == SDLK_r && (k->mod & KMOD_CTRL))) {
983 status.flags |= DIR_MODULES_CHANGED;
984 }
985 return 0;
986 }
987 #endif
988
989 static void load_module_set_page(void)
990 {
991 handle_file_entered = handle_file_entered_L;
992 if (update_directory())
993 pages[PAGE_LOAD_MODULE].selected_widget = (flist.num_files > 0) ? 0 : 1;
994
995 // Don't reparse the glob if it hasn't changed; that will mess with the cursor position
996 if (strcasecmp(glob_list_src, cfg_module_pattern) == 0)
auth_set_tokens($p_user_id)997 strcpy(filename_entry, glob_list_src);
998 else
999 set_default_glob(1);
1000 }
1001
1002 void load_module_load_page(struct page *page)
1003 {
1004 clear_directory();
1005 top_file = top_dir = 0;
1006 current_file = current_dir = 0;
1007 dir_list_reposition();
1008 file_list_reposition();
1009
1010 page->title = "Load Module (F9)";
auth_reauthentication_enabled()1011 page->draw_const = load_module_draw_const;
1012 page->set_page = load_module_set_page;
1013 page->total_widgets = 4;
1014 page->widgets = widgets_loadmodule;
1015 page->help_index = HELP_GLOBAL;
1016 #if CACHEFREE
1017 page->pre_handle_key = _load_cachefree_hack;
1018 #endif
1019
1020 create_other(widgets_loadmodule + 0, 1, file_list_handle_key, file_list_draw);
auth_reauthentication_expiry()1021 widgets_loadmodule[0].accept_text = 1;
1022 widgets_loadmodule[0].x = 3;
1023 widgets_loadmodule[0].y = 13;
1024 widgets_loadmodule[0].width = 43;
1025 widgets_loadmodule[0].height = 30;
1026 widgets_loadmodule[0].next.left = widgets_loadmodule[0].next.right = 1;
1027
1028 create_other(widgets_loadmodule + 1, 2, dir_list_handle_key, dir_list_draw);
1029 widgets_loadmodule[1].accept_text = 1;
1030 widgets_loadmodule[1].x = 50;
1031 widgets_loadmodule[1].y = 13;
1032 widgets_loadmodule[1].width = 17;
1033 widgets_loadmodule[1].height = 20;
1034
1035 create_textentry(widgets_loadmodule + 2, 13, 46, 64, 0, 3, 3, NULL, filename_entry, PATH_MAX);
auth_reauthenticate()1036 widgets_loadmodule[2].activate = filename_entered;
1037 create_textentry(widgets_loadmodule + 3, 13, 47, 64, 2, 3, 0, NULL, dirname_entry, PATH_MAX);
1038 widgets_loadmodule[3].activate = dirname_entered;
1039 }
1040
1041 /* --------------------------------------------------------------------- */
1042
1043 static void save_module_set_page(void)
1044 {
1045 handle_file_entered = handle_file_entered_S;
1046
1047 update_directory();
1048 /* impulse tracker always resets these; so will i */
1049 set_default_glob(0);
1050 filename_entry[0] = 0;
1051 pages[PAGE_SAVE_MODULE].selected_widget = 2;
1052
1053 widgets_exportsave = (status.current_page == PAGE_EXPORT_MODULE)
1054 ? widgets_exportmodule
1055 : widgets_savemodule;
1056
1057 if (status.current_page == PAGE_EXPORT_MODULE && current_song->orderlist[0] == ORDER_LAST)
1058 dialog_create(DIALOG_OK, "You're about to export a blank file...", NULL, NULL, 0, NULL);
1059 }
1060
1061 void save_module_load_page(struct page *page, int do_export)
1062 {
1063 int n;
1064
1065 if (do_export) {
1066 page->title = "Export Module (Shift-F10)";
1067 page->widgets = widgets_exportmodule;
1068 } else {
1069 page->title = "Save Module (F10)";
1070 page->widgets = widgets_savemodule;
1071 }
1072 widgets_exportsave = page->widgets;
1073
1074 /* preload */
1075 clear_directory();
1076 top_file = top_dir = 0;
1077 current_file = current_dir = 0;
auth_is_cookie_valid($p_cookie_string)1078 dir_list_reposition();
1079 file_list_reposition();
1080 read_directory();
1081
1082 page->draw_const = save_module_draw_const;
1083 page->set_page = save_module_set_page;
1084 page->total_widgets = 4;
1085 page->help_index = HELP_GLOBAL;
1086 page->selected_widget = 2;
1087 #if CACHEFREE
1088 page->pre_handle_key = _save_cachefree_hack;
1089 #endif
1090 page->song_changed_cb = loadsave_song_changed;
1091
1092 create_other(widgets_exportsave + 0, 1, file_list_handle_key, file_list_draw);
1093 widgets_exportsave[0].accept_text = 1;
1094 widgets_exportsave[0].next.left = 4;
1095 widgets_exportsave[0].next.right = widgets_exportsave[0].next.tab = 1;
1096 create_other(widgets_exportsave + 1, 2, dir_list_handle_key, dir_list_draw);
1097 widgets_exportsave[1].accept_text = 1;
1098 widgets_exportsave[1].next.right = widgets_exportsave[1].next.tab = 5;
1099 widgets_exportsave[1].next.left = 0;
1100
1101 create_textentry(widgets_exportsave + 2, 13, 46, 64, 0, 3, 3, NULL, filename_entry, PATH_MAX);
1102 widgets_exportsave[2].activate = filename_entered;
1103 create_textentry(widgets_exportsave + 3, 13, 47, 64, 2, 0, 0, NULL, dirname_entry, PATH_MAX);
1104 widgets_exportsave[3].activate = dirname_entered;
1105
1106 widgets_exportsave[4].d.togglebutton.state = 1;
1107
1108
1109 const struct save_format *formats = (do_export ? song_export_formats : song_save_formats);
1110 for (n = 0; formats[n].label; n++) {
1111 create_togglebutton(widgets_exportsave + 4 + n,
auth_get_current_user_id()1112 70, 13 + (3 * n), 5,
1113 4 + (n == 0 ? 0 : (n - 1)),
1114 4 + (n + 1),
1115 1, 2, 2,
1116 NULL,
1117 formats[n].label,
1118 (5 - strlen(formats[n].label)) / 2 + 1,
1119 filetype_saves);
1120 }
1121 widgets_exportsave[4 + n - 1].next.down = 2;
1122 page->total_widgets += n;
1123 }
1124
1125