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
34 #include <sys/types.h>
35 #include <sys/stat.h>
36
37 #include "sdlmain.h"
38
39 #include <fcntl.h>
40 #include <ctype.h>
41 #include <errno.h>
42
43 /* --------------------------------------------------------------------------------------------------------- */
44 /* the locals */
45
46 static struct widget widgets_loadinst[1];
47 static char inst_cwd[PATH_MAX+1] = "";
48
49 /* --------------------------------------------------------------------------------------------------------- */
50
51 /* files:
52 file type color displayed title notes
53 --------- ----- --------------- -----
54 unchecked 4 <the filename> IT uses color 6 for these
55 directory 5 "........Directory........" dots are char 154 (same for libraries)
56 sample 3 <the sample name>
57 libraries 6 ".........Library........." IT uses color 3. maybe use module name here?
58 unknown 2 <the filename> any regular file that's not recognized
59 */
60
61 static int top_file = 0;
62 static time_t directory_mtime;
63 static int _library_mode = 0;
64 static dmoz_filelist_t flist;
65 #define current_file flist.selected
66
67 static int slash_search_mode = -1;
68 static char slash_search_str[PATH_MAX];
69
70 /* get a color index from a dmoz_file_t 'type' field */
get_type_color(int type)71 static inline int get_type_color(int type)
72 {
73 if (type == TYPE_DIRECTORY)
74 return 5;
75 if (!(type & TYPE_EXT_DATA_MASK))
76 return 4; /* unchecked */
77 if (type & TYPE_BROWSABLE_MASK)
78 return 6; /* library */
79 if (type == TYPE_UNKNOWN)
80 return 2;
81 return 3; /* sample */
82 }
83
84
clear_directory(void)85 static void clear_directory(void)
86 {
87 dmoz_free(&flist, NULL);
88 }
89
instgrep(dmoz_file_t * f)90 static int instgrep(dmoz_file_t *f)
91 {
92 dmoz_fill_ext_data(f);
93 return f->type & (TYPE_INST_MASK | TYPE_BROWSABLE_MASK);
94 }
95
96 /* --------------------------------------------------------------------------------------------------------- */
97
file_list_reposition(void)98 static void file_list_reposition(void)
99 {
100 if (current_file >= flist.num_files)
101 current_file = flist.num_files-1;
102 if (current_file < 0) current_file = 0;
103 if (current_file < top_file)
104 top_file = current_file;
105 else if (current_file > top_file + 34)
106 top_file = current_file - 34;
107 }
108
read_directory(void)109 static void read_directory(void)
110 {
111 struct stat st;
112
113 clear_directory();
114
115 if (stat(inst_cwd, &st) < 0)
116 directory_mtime = 0;
117 else
118 directory_mtime = st.st_mtime;
119 /* if the stat call failed, this will probably break as well, but
120 at the very least, it'll add an entry for the root directory. */
121 if (dmoz_read(inst_cwd, &flist, NULL, dmoz_read_instrument_library) < 0)
122 log_perror(inst_cwd);
123
124 dmoz_filter_filelist(&flist,instgrep, ¤t_file, file_list_reposition);
125 dmoz_cache_lookup(inst_cwd, &flist, NULL);
126 file_list_reposition();
127 }
128
129 /* return: 1 = success, 0 = failure
130 TODO: provide some sort of feedback if something went wrong. */
change_dir(const char * dir)131 static int change_dir(const char *dir)
132 {
133 char *ptr = dmoz_path_normal(dir);
134 struct stat buf;
135
136 if (!ptr)
137 return 0;
138
139 dmoz_cache_update(inst_cwd, &flist, NULL);
140
141 if (stat(ptr, &buf) == 0 && S_ISDIR(buf.st_mode)) {
142 strncpy(cfg_dir_instruments, ptr, PATH_MAX);
143 cfg_dir_instruments[PATH_MAX] = 0;
144
145 }
146 strncpy(inst_cwd, ptr, PATH_MAX);
147 inst_cwd[PATH_MAX] = 0;
148 free(ptr);
149
150 read_directory();
151 return 1;
152 }
153
154 /* --------------------------------------------------------------------------------------------------------- */
155
load_instrument_draw_const(void)156 static void load_instrument_draw_const(void)
157 {
158 draw_fill_chars(6, 13, 67, 47, 0);
159 draw_thin_inner_box(50, 12, 61, 48, 0,0);
160 draw_box(5, 12, 68, 48, BOX_THICK | BOX_INNER | BOX_INSET);
161
162 }
163
164 /* --------------------------------------------------------------------------------------------------------- */
165
_common_set_page(void)166 static void _common_set_page(void)
167 {
168 struct stat st;
169
170 if (!inst_cwd[0]) {
171 strcpy(inst_cwd, cfg_dir_instruments);
172 }
173
174 /* if we have a list, the directory didn't change, and the mtime is the same, we're set */
175 if (flist.num_files > 0
176 && (status.flags & DIR_SAMPLES_CHANGED) == 0
177 && stat(inst_cwd, &st) == 0
178 && st.st_mtime == directory_mtime) {
179 return;
180 }
181
182 change_dir(inst_cwd);
183
184 status.flags &= ~DIR_INSTRUMENTS_CHANGED;
185
186 *selected_widget = 0;
187 slash_search_mode = -1;
188 }
189
load_instrument_set_page(void)190 static void load_instrument_set_page(void)
191 {
192 _library_mode = 0;
193 _common_set_page();
194 }
195
library_instrument_set_page(void)196 static void library_instrument_set_page(void)
197 {
198 _library_mode = 1;
199 _common_set_page();
200 }
201
202 /* --------------------------------------------------------------------------------------------------------- */
203
file_list_draw(void)204 static void file_list_draw(void)
205 {
206 int n, pos, fg, bg, i;
207 char buf[8];
208 char sbuf[32];
209 dmoz_file_t *file;
210
211 /* there's no need to have if (files) { ... } like in the load-module page,
212 because there will always be at least "/" in the list */
213 if (top_file < 0) top_file = 0;
214 if (current_file < 0) current_file = 0;
215 for (n = top_file, pos = 13; n < flist.num_files && pos < 48; n++, pos++) {
216 file = flist.files[n];
217
218 if (n == current_file && ACTIVE_PAGE.selected_widget == 0) {
219 fg = 0;
220 bg = 3;
221 } else {
222 fg = get_type_color(file->type);
223 bg = 0;
224 }
225
226 draw_text(numtostr(3, n, buf), 2, pos, 0, 2);
227 draw_text_len((file->title ? file->title : ""),
228 25, 6, pos, fg, bg);
229 draw_char(168, 31, pos, 2, bg);
230 draw_text_len((file->base ? file->base : ""),
231 18, 32, pos, fg, bg);
232
233 if (file->base && slash_search_mode > -1) {
234 if (strncasecmp(file->base,slash_search_str,slash_search_mode) == 0) {
235 for (i = 0 ; i < slash_search_mode; i++) {
236 if (tolower(((unsigned)file->base[i]))
237 != tolower(((unsigned)slash_search_str[i]))) break;
238 draw_char(file->base[i], 32+i, pos, 3,1);
239 }
240 }
241 }
242
243 if (file->sampsize > 1) {
244 sprintf(sbuf, "%u Samples", file->sampsize);
245 draw_text_len(sbuf, 10, 51, pos, fg, bg);
246 } else if (file->sampsize == 1) {
247 draw_text("1 Sample ", 51, pos, fg, bg);
248 } else if (file->type & TYPE_MODULE_MASK) {
249 draw_text("\x9a\x9a""Module\x9a\x9a", 51, pos, fg, bg);
250 } else {
251 draw_text(" ", 51, pos, fg, bg);
252 }
253 if (file->filesize > 1048576) {
254 sprintf(sbuf, "%lum", (unsigned long)(file->filesize / 1048576));
255 } else if (file->filesize > 1024) {
256 sprintf(sbuf, "%luk", (unsigned long)(file->filesize / 1024));
257 } else if (file->filesize > 0) {
258 sprintf(sbuf, "%lu", (unsigned long)(file->filesize));
259 } else {
260 *sbuf = 0;
261 }
262 draw_text_len(sbuf, 6, 62, pos, fg, bg);
263 }
264
265 /* draw the info for the current file (or directory...) */
266
267 while (pos < 48)
268 draw_char(168, 31, pos++, 2, 0);
269 }
270
do_enable_inst(UNUSED void * d)271 static void do_enable_inst(UNUSED void *d)
272 {
273 song_set_instrument_mode(1);
274 main_song_changed_cb();
275 set_page(PAGE_INSTRUMENT_LIST);
276 memused_songchanged();
277 }
278
dont_enable_inst(UNUSED void * d)279 static void dont_enable_inst(UNUSED void *d)
280 {
281 set_page(PAGE_INSTRUMENT_LIST);
282 }
283
reposition_at_slash_search(void)284 static void reposition_at_slash_search(void)
285 {
286 dmoz_file_t *f;
287 int i, j, b, bl;
288
289 if (slash_search_mode < 0) return;
290 bl = b = -1;
291 for (i = 0; i < flist.num_files; i++) {
292 f = flist.files[i];
293 if (!f || !f->base) continue;
294 for (j = 0; j < slash_search_mode; j++) {
295 if (tolower(((unsigned)f->base[j]))
296 != tolower(((unsigned)slash_search_str[j]))) break;
297 }
298 if (bl < j) {
299 bl = j;
300 b = i;
301 }
302 }
303 if (bl > -1) {
304 current_file = b;
305 file_list_reposition();
306 }
307 }
308
309 /* on the file list, that is */
handle_enter_key(void)310 static void handle_enter_key(void)
311 {
312 dmoz_file_t *file;
313 int cur = instrument_get_current();
314
315 if (current_file < 0 || current_file >= flist.num_files) return;
316 file = flist.files[current_file];
317 dmoz_cache_update(inst_cwd, &flist, NULL);
318
319 if (file->type & TYPE_BROWSABLE_MASK) {
320 change_dir(file->path);
321 status.flags |= NEED_UPDATE;
322 } else if (file->type & TYPE_INST_MASK) {
323 if (_library_mode) return;
324 status.flags |= SONG_NEEDS_SAVE;
325 if (file->instnum > -1) {
326 song_load_instrument_ex(cur, NULL,
327 file->path, file->instnum);
328 } else {
329 song_load_instrument(cur, file->path);
330 }
331 if (!song_is_instrument_mode()) {
332 dialog_create(DIALOG_YES_NO,
333 "Enable instrument mode?",
334 do_enable_inst, dont_enable_inst, 0, NULL);
335 } else {
336 set_page(PAGE_INSTRUMENT_LIST);
337 }
338 memused_songchanged();
339 }
340
341 /* TODO */
342 }
343
do_delete_file(UNUSED void * data)344 static void do_delete_file(UNUSED void *data)
345 {
346 int old_top_file, old_current_file;
347 char *ptr;
348
349 if (current_file < 0 || current_file >= flist.num_files)
350 return;
351
352 ptr = flist.files[current_file]->path;
353
354 /* would be neat to send it to the trash can if there is one */
355 unlink(ptr);
356
357 /* remember the list positions */
358 old_top_file = top_file;
359 old_current_file = current_file;
360
361 read_directory();
362
363 /* put the list positions back */
364 top_file = old_top_file;
365 current_file = old_current_file;
366 /* edge case: if this was the last file, move the cursor up */
367 if (current_file >= flist.num_files)
368 current_file = flist.num_files - 1;
369 file_list_reposition();
370 }
371
file_list_handle_key(struct key_event * k)372 static int file_list_handle_key(struct key_event * k)
373 {
374 dmoz_file_t *f;
375 int new_file = current_file;
376 int c = unicode_to_ascii(k->unicode);
377
378 new_file = CLAMP(new_file, 0, flist.num_files - 1);
379
380 if (k->mouse != MOUSE_NONE) {
381 if (k->x >= 6 && k->x <= 67 && k->y >= 13 && k->y <= 47) {
382 slash_search_mode = -1;
383 if (k->mouse == MOUSE_SCROLL_UP) {
384 new_file -= MOUSE_SCROLL_LINES;
385 } else if (k->mouse == MOUSE_SCROLL_DOWN) {
386 new_file += MOUSE_SCROLL_LINES;
387 } else {
388 new_file = top_file + (k->y - 13);
389 }
390 }
391 }
392 switch (k->sym) {
393 case SDLK_UP: new_file--; slash_search_mode = -1; break;
394 case SDLK_DOWN: new_file++; slash_search_mode = -1; break;
395 case SDLK_PAGEUP: new_file -= 35; slash_search_mode = -1; break;
396 case SDLK_PAGEDOWN: new_file += 35; slash_search_mode = -1; break;
397 case SDLK_HOME: new_file = 0; slash_search_mode = -1; break;
398 case SDLK_END: new_file = flist.num_files - 1; slash_search_mode = -1; break;
399
400 case SDLK_ESCAPE:
401 if (slash_search_mode < 0) {
402 if (k->state == KEY_RELEASE && NO_MODIFIER(k->mod))
403 set_page(PAGE_SAMPLE_LIST);
404 return 1;
405 } /* else fall through */
406 case SDLK_RETURN:
407 if (slash_search_mode < 0) {
408 if (k->state == KEY_PRESS)
409 return 0;
410 handle_enter_key();
411 slash_search_mode = -1;
412 } else {
413 if (k->state == KEY_PRESS)
414 return 1;
415 slash_search_mode = -1;
416 status.flags |= NEED_UPDATE;
417 return 1;
418 }
419 return 1;
420 case SDLK_DELETE:
421 if (k->state == KEY_RELEASE)
422 return 1;
423 slash_search_mode = -1;
424 if (flist.num_files > 0)
425 dialog_create(DIALOG_OK_CANCEL, "Delete file?", do_delete_file, NULL, 1, NULL);
426 return 1;
427 case SDLK_BACKSPACE:
428 if (slash_search_mode > -1) {
429 if (k->state == KEY_RELEASE)
430 return 1;
431 slash_search_mode--;
432 status.flags |= NEED_UPDATE;
433 reposition_at_slash_search();
434 return 1;
435 }
436 case SDLK_SLASH:
437 if (slash_search_mode < 0) {
438 if (k->orig_sym == SDLK_SLASH) {
439 if (k->state == KEY_PRESS)
440 return 0;
441 slash_search_mode = 0;
442 status.flags |= NEED_UPDATE;
443 return 1;
444 }
445 return 0;
446 } /* else fall through */
447 default:
448 f = flist.files[current_file];
449 if (c >= 32 && (slash_search_mode > -1 || (f && (f->type & TYPE_DIRECTORY)))) {
450 if (k->state == KEY_RELEASE)
451 return 1;
452 if (slash_search_mode < 0) slash_search_mode = 0;
453 if (slash_search_mode < PATH_MAX) {
454 slash_search_str[slash_search_mode++] = c;
455 reposition_at_slash_search();
456 status.flags |= NEED_UPDATE;
457 }
458 return 1;
459 }
460 if (!k->mouse) return 0;
461 }
462
463 if (k->mouse == MOUSE_CLICK) {
464 if (k->state == KEY_RELEASE)
465 return 0;
466 } else if (k->mouse == MOUSE_DBLCLICK) {
467 handle_enter_key();
468 return 1;
469 } else {
470 /* prevent moving the cursor twice from a single key press */
471 if (k->state == KEY_RELEASE)
472 return 1;
473 }
474
475 new_file = CLAMP(new_file, 0, flist.num_files - 1);
476 if (new_file != current_file) {
477 current_file = new_file;
478 file_list_reposition();
479 status.flags |= NEED_UPDATE;
480 }
481 return 1;
482 }
483
load_instrument_handle_key(struct key_event * k)484 static void load_instrument_handle_key(struct key_event * k)
485 {
486 if (k->state == KEY_RELEASE)
487 return;
488 if (k->sym == SDLK_ESCAPE && NO_MODIFIER(k->mod))
489 set_page(PAGE_INSTRUMENT_LIST);
490 }
491
492 /* --------------------------------------------------------------------------------------------------------- */
493
load_instrument_load_page(struct page * page)494 void load_instrument_load_page(struct page *page)
495 {
496 clear_directory();
497
498 page->title = "Load Instrument";
499 page->draw_const = load_instrument_draw_const;
500 page->set_page = load_instrument_set_page;
501 page->handle_key = load_instrument_handle_key;
502 page->total_widgets = 1;
503 page->widgets = widgets_loadinst;
504 page->help_index = HELP_GLOBAL;
505 create_other(widgets_loadinst + 0, 0, file_list_handle_key, file_list_draw);
506 widgets_loadinst[0].accept_text = 1;
507 }
508
library_instrument_load_page(struct page * page)509 void library_instrument_load_page(struct page *page)
510 {
511 page->title = "Instrument Library (Ctrl-F4)";
512 page->draw_const = load_instrument_draw_const;
513 page->set_page = library_instrument_set_page;
514 page->handle_key = load_instrument_handle_key;
515 page->total_widgets = 1;
516 page->widgets = widgets_loadinst;
517 page->help_index = HELP_GLOBAL;
518 }
519
520