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, &current_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