1 /*  MikMod module player
2 	(c) 1998 - 2000 Miodrag Vallat and others - see file AUTHORS for
3 	complete list.
4 
5 	This program is free software; you can redistribute it and/or modify
6 	it under the terms of the GNU General Public License as published by
7 	the Free Software Foundation; either version 2 of the License, or
8 	(at your option) any later version.
9 
10 	This program is distributed in the hope that it will be useful,
11 	but WITHOUT ANY WARRANTY; without even the implied warranty of
12 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 	GNU General Public License for more details.
14 
15 	You should have received a copy of the GNU General Public License
16 	along with this program; if not, write to the Free Software
17 	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18 	02111-1307, USA.
19 */
20 
21 /*==============================================================================
22 
23   $Id: mlistedit.c,v 1.1.1.1 2004/01/16 02:07:43 raph Exp $
24 
25   The playlist editor
26 
27 ==============================================================================*/
28 
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32 
33 #ifdef HAVE_UNISTD_H
34 #include <unistd.h>
35 #endif
36 
37 #include <stdlib.h>
38 #include <stdarg.h>
39 #include <string.h>
40 #include <ctype.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 
44 #if defined(__MINGW32__) || defined(__EMX__) || defined(__DJGPP__)
45 #include <dirent.h>
46 #elif defined(__OS2__) /* Watcom */
47 #include <direct.h>
48 #elif defined(_WIN32) /* MSVC, etc. */
49 #include <direct.h>
50 #else
51 #include <dirent.h>
52 #endif
53 
54 #include <mikmod.h>
55 #include "mlistedit.h"
56 #include "mlist.h"
57 #include "player.h"
58 #include "mdialog.h"
59 #include "rcfile.h"
60 #include "mconfig.h"
61 #include "mconfedit.h"
62 #include "marchive.h"
63 #include "mwidget.h"
64 #include "keys.h"
65 #include "display.h"
66 #include "mutilities.h"
67 
68 #define FREQ_SEL '*'
69 #define FREQ_SEL_STR "*"
70 
71 #define FREQ_UNSEL ' '
72 #define FREQ_UNSEL_STR " "
73 
74 /* Function, which is called on Ok/Cancel button select
75    button: 0: Ok  1: Cancel
76    path: Selected file
77    data: user-pointer which was passed to freq_open()
78    Return: close fileselector? */
79 typedef BOOL (*handleFreqFunc) (int button, char *file, void *data);
80 
81 /* Function, which is called for every new directory during
82    directory scanning in scan_dir(). scan_dir() is canceled if
83    the function returns 1. */
84 typedef BOOL (*handleScandirFunc) (char *path, int added, int removed, void *data);
85 
86 typedef enum {
87 	FREQ_ADD,
88 	FREQ_TOGGLE,
89 	FREQ_REMOVE
90 } FREQ_MODE;
91 
92 typedef struct {
93 	WID_LIST *w;			/* the directory list */
94 	char path[PATH_MAX<<1];	/* path of currently displayed directory */
95 	BOOL before_add;		/* TRUE until first call of entry_add() */
96 	int actline;			/* pos in playlist for insertion */
97 							/* -1 -> append entries */
98 	int cnt_list;
99 	char **searchlist;		/* sorted playlist archives or files */
100 							/*   (if archives are not available)*/
101 	handleFreqFunc handle_freq;
102 	void *data;
103 } FREQ_DATA;
104 
105 typedef struct {
106 	WID_LIST *w;			/* the directory list */
107 	FREQ_DATA *freq;
108 } HLIST_DATA;
109 
110 typedef struct {
111 	MMENU *menu;
112 	int *actLine;
113 } MENU_DATA;
114 
115 typedef struct {
116 	WID_LABEL *w;
117 	BOOL stop;
118 } FREQ_SCAN_DATA;
119 
120 /* compare function for qsort on the searchlist */
searchlist_cmp(char ** key,char ** member)121 static int searchlist_cmp (char **key, char **member)
122 {
123 	return (filecmp(*key,*member));
124 }
125 
126 /* compare function for bsearch on the searchlist */
searchlist_search_cmp(char * key,char ** member)127 static int searchlist_search_cmp (char *key, char **member)
128 {
129 	return (filecmp(key,*member));
130 }
131 
132 /* compare function for qsort on the directory list */
dirlist_cmp(char ** small,char ** big)133 static int dirlist_cmp (char **small, char **big)
134 {
135 	if (IS_PATH_SEP((*small)[strlen(*small)-1])) {
136 		if (IS_PATH_SEP((*big)[strlen(*big)-1]))
137 			return(filecmp(*small+2,*big+2));
138 		else
139 			return -1;
140 	} else if (IS_PATH_SEP((*big)[strlen(*big)-1]))
141 		return 1;
142 	return(filecmp(*small+2,*big+2));
143 }
144 
145 /* compare function for bearch on the directory list */
dirlist_search_cmp(char * key,char ** member)146 static int dirlist_search_cmp (char *key, char **member)
147 {
148 	if (IS_PATH_SEP(key[strlen(key)-1])) {
149 		if (IS_PATH_SEP((*member)[strlen(*member)-1]))
150 			return(filecmp(key,*member+2));
151 		else
152 			return -1;
153 	} else if (IS_PATH_SEP((*member)[strlen(*member)-1]))
154 		return 1;
155 	return(filecmp(key,*member+2));
156 }
157 
158 /* Add/Remove tag marks to the files in entries (count: cnt) from
159    directory path according to the searchlist */
freq_set_marks(char ** entries,int cnt,const char * path,FREQ_DATA * data)160 static void freq_set_marks (char **entries, int cnt, const char *path, FREQ_DATA *data)
161 {
162 	int i;
163 	char file[PATH_MAX<<1], *fstart;
164 
165 	strcpy (file,path);
166 	fstart = file+strlen(file);
167 	for (i=0; i<cnt; i++) {
168 		strcpy (fstart,entries[i]+2);
169 		if (!IS_PATH_SEP(fstart[strlen(fstart)]) &&
170 			data->cnt_list > 0 &&
171 			bsearch (file,data->searchlist,data->cnt_list,
172 					 sizeof(char*),(int(*)())searchlist_search_cmp))
173 			*(entries[i]) = FREQ_SEL;
174 		else
175 			*(entries[i]) = FREQ_UNSEL;
176 	}
177 }
178 
179 /* Check if size of playlist has changed (due to e.g. resolving
180    of playlists). If so, rebuild searchlist. */
freq_check_searchlist(FREQ_DATA * data)181 static void freq_check_searchlist (FREQ_DATA *data)
182 {
183 	int i, len = PL_GetLength(&playlist);
184 
185 	if (len != data->cnt_list) {
186 		data->searchlist = (char **) realloc (data->searchlist, sizeof(char*) * len);
187 		data->cnt_list = len;
188 		if (len) {
189 			for (i=0; i<len; i++) {
190 				PLAYENTRY *entry = PL_GetEntry(&playlist, i);
191 				if (entry->archive)
192 					data->searchlist[i] = entry->archive;
193 				else
194 					data->searchlist[i] = entry->file;
195 			}
196 			qsort (data->searchlist, len, sizeof(char*),(int(*)())searchlist_cmp);
197 		}
198 		if (data->w) {
199 			freq_set_marks (data->w->entries,data->w->cnt,data->path,data);
200 			wid_repaint ((WIDGET*)data->w);
201 		}
202 	}
203 }
204 
205 /* Insert ins in (already enlarged) searchlist pl.
206    pl must be sorted (according to filecmp()).*/
entry_insert(int left,int right,char ** pl,char * ins)207 static void entry_insert (int left, int right, char **pl, char *ins)
208 {
209 	int pos=0, cmp=0, last = right;
210 
211 	if (right<0) {
212 		pl[0] = ins;
213 	} else {
214 		while (left<=right) {
215 			pos = (left+right)/2;
216 			cmp = filecmp(ins,pl[pos]);
217 			if (cmp<0)
218 				right = pos-1;
219 			else
220 				left = pos+1;
221 		}
222 		if (cmp>0) pos++;
223 		for (cmp=last; cmp>=pos; cmp--)
224 			pl[cmp+1] = pl[cmp];
225 		pl[pos] = ins;
226 	}
227 }
228 
229 /* Insert entry path+file at position data->actline into the playlist and
230    update the (before and afterwards sorted) searchlist and
231    actline from data.
232    Return: Number of added entries */
entry_add(char * path,char * file,FREQ_DATA * data)233 static int entry_add (char *path, char *file, FREQ_DATA *data)
234 {
235 	int len, old_len = PL_GetLength(&playlist);
236 	char buffer[STORAGELEN];
237 
238 	strcpy (buffer,path);
239 	if (file) strcat (buffer,file);
240 
241 	if (data) {
242 		if (data->actline < 0 && data->before_add) {
243 			/* "Load" was selected -> Remove old entries */
244 			data->before_add = 0;
245 			PL_ClearList(&playlist);
246 			old_len = PL_GetLength(&playlist);
247 			freq_check_searchlist (data);
248 		} else
249 			PL_StartInsert(&playlist, data->actline);
250 	}
251 	MA_FindFiles(&playlist, buffer);
252 	PL_StopInsert(&playlist);
253 
254 	len = PL_GetLength(&playlist);
255 	if (!old_len && len)
256 		PL_InitCurrent(&playlist);
257 
258 	/* Update the searchlist */
259 	if (len>old_len && data) {
260 		int i, start, end;
261 
262 		data->searchlist = (char **) realloc (data->searchlist, sizeof(char*) * len);
263 
264 		start = data->actline;
265 		if (start<0) start = old_len;
266 		end = start+len-old_len;
267 		for (i=start; i<end; i++) {
268 			PLAYENTRY *entry = PL_GetEntry(&playlist, i);
269 			char *ins = entry->archive ? entry->archive:entry->file;
270 			entry_insert (0,data->cnt_list-1,data->searchlist,ins);
271 			data->cnt_list++;
272 		}
273 		if (data->actline>=0) data->actline += len-old_len;
274 	}
275 	return len-old_len;
276 }
277 
278 /* remove all entries with archive==path+file (or file==path+file,
279    if archive not set) from the playlist and update the (before
280    and afterwards sorted) searchlist and actline from data.
281    Return: Number of removed entries */
entry_remove_by_name(char * path,char * file,FREQ_DATA * data)282 static int entry_remove_by_name(char *path, char *file, FREQ_DATA *data)
283 {
284 	int len = PL_GetLength(&playlist);
285 	char buffer[STORAGELEN];
286 	int cnt_remove = 0, i;
287 	char **pos;
288 
289 	strcpy (buffer,path);
290 	if (file) strcat (buffer,file);
291 
292 	/* Update the searchlist */
293 	while (data->cnt_list>0 &&
294 		   (pos = (char **) bsearch(buffer,data->searchlist,data->cnt_list,
295 						 sizeof(char*),(int(*)())searchlist_search_cmp))) {
296 		while (pos < data->searchlist + data->cnt_list - 1) {
297 			*pos = *(pos+1);
298 			pos++;
299 		}
300 		data->cnt_list--;
301 	}
302 	data->searchlist = (char **) realloc (data->searchlist, sizeof(char*) * data->cnt_list);
303 
304 	/* Remove the entries from the playlist */
305 	for (i=len-1; i>=0; i--) {
306 		PLAYENTRY *entry = PL_GetEntry(&playlist, i);
307 		if (!filecmp (entry->archive ? entry->archive:entry->file,
308 					  buffer)) {
309 			PL_DelEntry(&playlist, i);
310 			if (i < data->actline) data->actline--;
311 			cnt_remove++;
312 		}
313 	}
314 	return cnt_remove;
315 }
316 
317 /* Scan directory path for modules and add all files to the playlist,
318    which are not already in data->searchlist (if data!=NULL).
319    recursive: Scan recursively
320    links    : Follow links */
scan_dir(char * path,BOOL recursive,BOOL links,FREQ_DATA * freq_data,FREQ_MODE mode,handleScandirFunc func,void * data,int * added,int * removed)321 static void scan_dir (char *path, BOOL recursive, BOOL links,
322 					  FREQ_DATA *freq_data, FREQ_MODE mode,
323 					  handleScandirFunc func, void *data,
324 					  int *added, int *removed)
325 {
326 #define DIR_BLOCK 10
327 	DIR *dir;
328 	struct dirent *entry;
329 	struct stat statbuf;
330 	char file[PATH_MAX<<1], *pathend, **dirs=NULL;
331 	int cnt = 0, max = 0, i;
332 
333 	if (
334 #if !defined(__OS2__)&&!defined(__EMX__)&&!defined(__DJGPP__)&&!defined(_WIN32)&&!defined(_mikmod_amiga)
335 		!strcmp (path,"/proc/") ||
336 		!strcmp (path,"/dev/") ||
337 #endif
338 		!(dir = opendir (path_conv_sys(path))))
339 		return;
340 
341 	if (func) {
342 		int add=-1, rem=-1;
343 		if (added) add = *added;
344 		if (removed) rem = *removed;
345 		if (func (path,add,rem,data)) {
346 			closedir (dir);
347 			return;
348 		}
349 	}
350 
351 	strcpy (file,path);
352 	pathend = file+strlen(file);
353 
354 	while ((entry = readdir (dir))) {
355 		strcpy (pathend,entry->d_name);
356 		path_conv(pathend);
357 		if (!lstat(path_conv_sys(file), &statbuf)) {
358 			if (S_ISDIR(statbuf.st_mode)) {
359 				/* if dir, process it after the files */
360 				if (recursive &&
361 					(links || !S_ISLNK(statbuf.st_mode)) &&
362 					strcmp (entry->d_name,"..") &&
363 					strcmp (entry->d_name,".")) {
364 					/* FIXME: check for cyclic links is missing */
365 					if (cnt >= max) {
366 						max += DIR_BLOCK;
367 						dirs = (char **) realloc (dirs, sizeof(char*) * max);
368 					}
369 					dirs[cnt++] = strdup (entry->d_name);
370 				}
371 			} else if (!S_ISCHR(statbuf.st_mode) && !S_ISBLK(statbuf.st_mode) &&
372 					   !S_ISFIFO(statbuf.st_mode) && !S_ISSOCK(statbuf.st_mode) &&
373 					   MA_TestName (file, 0 , 0)) {
374 				/* file of known type: add/remove it */
375 				char **pos = NULL;
376 				int j = 0;
377 				if (freq_data && freq_data->cnt_list > 0)
378 					pos = (char **) bsearch(file,freq_data->searchlist,freq_data->cnt_list,
379 								 sizeof(char*),(int(*)())searchlist_search_cmp);
380 				if (pos) {
381 					if (mode != FREQ_ADD) {
382 						j = entry_remove_by_name(file, NULL, freq_data);
383 						if (removed) *removed += j;
384 					} else if (freq_data->actline < 0 && freq_data->before_add) {
385 						j = entry_add(file, NULL, freq_data);
386 						if (added) *added += j;
387 					}
388 				} else {
389 					if (mode != FREQ_REMOVE) {
390 						j = entry_add(file, NULL, freq_data);
391 						if (added) *added += j;
392 					}
393 				}
394 			}
395 		}
396 		while (win_main_iteration());
397 	}
398 	/* now process dirs after files are already processed */
399 	for (i=0; i<cnt; i++) {
400 		strcpy (pathend, dirs[i]);
401 		path_conv(pathend);
402 		strcat (pathend,PATH_SEP_STR);
403 		scan_dir (file, recursive, links, freq_data, mode,
404 				  func, data, added, removed);
405 		free (dirs[i]);
406 	}
407 	if (dirs) free (dirs);
408 	closedir (dir);
409 }
410 
411 /* read directory path and store the entries in entries */
freq_readdir(const char * path,char *** entries,int * cnt,FREQ_DATA * data)412 static void freq_readdir (const char *path, char ***entries, int *cnt, FREQ_DATA *data)
413 {
414 #define ENT_BLOCK 10
415 	int max;
416 	DIR *dir = opendir (path_conv_sys(path));
417 	char file[PATH_MAX<<1], *pathend, *help;
418 	struct dirent *entry;
419 	struct stat statbuf;
420 
421 	strcpy (file,path);
422 	pathend = file+strlen(file);
423 	*entries = NULL;
424 	*cnt = 0;
425 	if (dir) {
426 		max = *cnt = 0;
427 #ifdef _mikmod_amiga
428 		if (pathend != file && pathend[-1] != ':') {
429 			/* on AmigaOS variants, we won't get a ".." parentdir entry --
430 			   add a fake one here. */
431 			max += ENT_BLOCK;
432 			*entries = (char **) realloc (*entries, sizeof(char*) * max);
433 			strcpy (pathend,".."PATH_SEP_STR);
434 			path_conv (pathend);
435 			help = (char *) malloc (sizeof(char) * (strlen(pathend) + 3));
436 			strcpy (help,"  ");
437 			strcat (help,pathend);
438 			(*entries)[(*cnt)++] = help;
439 		}
440 #endif
441 		while ((entry = readdir (dir))) {
442 			if (*cnt >= max) {
443 				max += ENT_BLOCK;
444 				*entries = (char **) realloc (*entries, sizeof(char*) * max);
445 			}
446 			strcpy (pathend,entry->d_name);
447 			path_conv (pathend);
448 			if (!stat(path_conv_sys(file), &statbuf))
449 				if (S_ISDIR(statbuf.st_mode))
450 					strcat (pathend,PATH_SEP_STR);
451 
452 			help = (char *) malloc (sizeof(char) * (strlen(pathend) + 3));
453 			strcpy (help,"  ");
454 			strcat (help,pathend);
455 			(*entries)[(*cnt)++] = help;
456 		}
457 		freq_set_marks (*entries,*cnt,path,data);
458 		closedir (dir);
459 		if (*cnt)
460 			qsort (*entries, *cnt, sizeof(char*),(int(*)())dirlist_cmp);
461 	}
462 }
463 
464 /* free directory list read with freq_readdir() */
freq_freedir(char ** entries,int cnt)465 static void freq_freedir (char **entries, int cnt)
466 {
467 	int i;
468 	for (i=0; i<cnt; i++)
469 		if (entries[i]) free (entries[i]);
470 	free (entries);
471 }
472 
freq_set_title(FREQ_DATA * data)473 static void freq_set_title (FREQ_DATA *data)
474 {
475 	int max = data->w->w.width-2;
476 
477 	if (strlen(data->path) <= max)
478 		wid_list_set_title (data->w, data->path);
479 	else {
480 		char path[MAXWIDTH];
481 		strcpy (path, "...");
482 		strcat (path, &data->path[strlen(data->path)-max+3]);
483 		wid_list_set_title (data->w, path);
484 	}
485 	wid_repaint ((WIDGET*)data->w);
486 }
487 
488 /* change directory to path (read directory and display it) */
freq_changedir(const char * path,FREQ_DATA * data)489 static void freq_changedir (const char *path, FREQ_DATA *data)
490 {
491 	char **entries, *last= NULL, *end, **pos = NULL, ch;
492 	int cnt;
493 
494 	freq_readdir (path,&entries,&cnt,data);
495 	if (entries && cnt>0) {
496 		/* Check if new path is part of the old one and
497 		   find position in entries where the old path continues
498 		   to correctly reposition active entry  */
499 		if (strlen(path) < strlen(data->path)) {
500 			last = data->path+strlen(path);
501 			ch = *last;
502 			*last = '\0';
503 			if (!filecmp (data->path, path)) {
504 				*last = ch;
505 				end = last;
506 				while (*end && !IS_PATH_SEP(*end))
507 					end++;
508 				if (IS_PATH_SEP(*end)) {
509 					*(end+1) = '\0';
510 					pos=(char**) bsearch(last, entries, cnt, sizeof(char*),
511 								 (int(*)())dirlist_search_cmp);
512 				} else
513 					pos = NULL;
514 			}
515 		}
516 		if (!pos) pos = entries;
517 		strcpy (data->path, path);
518 		wid_list_set_entries (data->w, (const char **)entries, 0, cnt);
519 		wid_list_set_active (data->w, pos-entries);
520 		freq_set_title (data);
521 		freq_freedir (entries,cnt);
522 	} else
523 		dlg_error_show ("Unable to read directory \"%s\"!",path);
524 }
525 
hlist_close(HLIST_DATA * data)526 static void hlist_close (HLIST_DATA *data)
527 {
528 	dialog_close(data->w->w.d);
529 	free (data);
530 }
531 
cb_hlist_list_focus(struct WIDGET * w,int focus)532 static int cb_hlist_list_focus(struct WIDGET *w, int focus)
533 {
534 	if (focus == FOCUS_ACTIVATE) {
535 		HLIST_DATA *data = (HLIST_DATA *) w->data;
536 		int cur = ((WID_LIST*)w)->cur;
537 
538 		/* return in hotlist -> change to the selected dir */
539 		freq_check_searchlist (data->freq);
540 		if (cur < config.cnt_hotlist)
541 			freq_changedir (config.hotlist[cur],data->freq);
542 		hlist_close(data);
543 		return EVENT_HANDLED;
544 	}
545 	return focus;
546 }
547 
cb_hlist_button_focus(struct WIDGET * w,int focus)548 static int cb_hlist_button_focus(struct WIDGET *w, int focus)
549 {
550 	if (focus == FOCUS_ACTIVATE) {
551 		HLIST_DATA *data = (HLIST_DATA *) w->data;
552 		int button = ((WID_BUTTON *) w)->active;
553 		int cur = data->w->cur;
554 
555 		freq_check_searchlist (data->freq);
556 		switch (button) {
557 			case 0:				/* change To */
558 				if (cur < config.cnt_hotlist)
559 					freq_changedir (config.hotlist[cur],data->freq);
560 				hlist_close(data);
561 				break;
562 			case 1:				/* Add current */
563 				CF_string_array_insert (cur,&config.hotlist,&config.cnt_hotlist,
564 										data->freq->path,PATH_MAX);
565 				wid_list_set_entries (data->w,(const char **)config.hotlist,-1,config.cnt_hotlist);
566 				wid_repaint ((WIDGET*)data->w);
567 				break;
568 			case 2:				/* Remove */
569 				CF_string_array_remove (cur,&config.hotlist,&config.cnt_hotlist);
570 				wid_list_set_entries (data->w,(const char **)config.hotlist,-1,config.cnt_hotlist);
571 				wid_repaint ((WIDGET*)data->w);
572 				break;
573 			case 3:				/* Cancel */
574 				hlist_close(data);
575 				break;
576 		}
577 		return EVENT_HANDLED;
578 	}
579 	return focus;
580 }
581 
582 /* open the directory hotlist editor */
freq_hotlist(FREQ_DATA * freq_data)583 static void freq_hotlist (FREQ_DATA *freq_data)
584 {
585 	DIALOG *d = dialog_new();
586 	WIDGET *w;
587 	HLIST_DATA *data = (HLIST_DATA *) malloc (sizeof(HLIST_DATA));
588 
589 	w = wid_list_add(d, 1, (const char **)config.hotlist, config.cnt_hotlist);
590 	wid_set_size (w, 74, 10);
591 	data->w = (WID_LIST*)w;
592 	data->freq = freq_data;
593 	wid_set_func(w, NULL, cb_hlist_list_focus, data);
594 
595 	w = wid_button_add(d, 1, "<change &To>|&Add current|&Remove|&Cancel", 0);
596 	wid_set_func(w, NULL, cb_hlist_button_focus, data);
597 
598 	dialog_open(d, "Directory hotlist");
599 }
600 
601 /* Check if file is a directory and copy the resulting path from
602    path and file to dest */
path_update(char * dest,char * path,char * file)603 static BOOL path_update (char *dest, char *path, char *file)
604 {
605 	char *end;
606 
607 	if (!strcmp (file,".."PATH_SEP_STR)) {
608 		strcpy (dest, path);
609 		end = dest+strlen(dest)-2;
610 		while (end>dest && !IS_PATH_SEP(*end))
611 			*end-- = '\0';
612 	} else if (!strcmp (file,"."PATH_SEP_STR)) {
613 		strcpy (dest, path);
614 	} else if (IS_PATH_SEP(file[strlen(file)-1])) {
615 		strcpy (dest, path);
616 		strcat (dest, file);
617 	} else
618 		return 0;
619 	return 1;
620 }
621 
cb_scan_dir_stop_focus(struct WIDGET * w,int focus)622 static int cb_scan_dir_stop_focus(struct WIDGET *w, int focus)
623 {
624 	if (focus == FOCUS_ACTIVATE) {
625 		if (((WID_BUTTON *) w)->active == 0)
626 			((FREQ_SCAN_DATA*)w->data)->stop = 1;
627 
628 		return EVENT_HANDLED;
629 	}
630 	return focus;
631 }
632 
633 /* Show progress during directory scanning */
cb_freq_scan_dir(char * path,int added,int removed,void * data)634 BOOL cb_freq_scan_dir (char *path, int added, int removed, void *data)
635 {
636 	FREQ_SCAN_DATA *scan_data = (FREQ_SCAN_DATA*)data;
637 
638 	if (strlen(path) > 50)
639 		sprintf (storage,"Scanning ...%s...\n"
640 				        "%4d entrie(s) added, %4d entrie(s) removed",
641 				&path[strlen(path)-47], added, removed);
642 	else
643 		sprintf (storage,"Scanning %s...\n"
644 				        "%4d entrie(s) added, %4d entrie(s) removed",
645 				path, added, removed);
646 
647 	wid_label_set_label ((WID_LABEL*)(scan_data->w),storage);
648 	dialog_repaint (scan_data->w->w.d->win);
649 	win_refresh();
650 	return scan_data->stop;
651 }
652 
653 /* Scan directory path for modules and add/remove them to the playlist
654    according to mode */
freq_scan_dir(char * path,FREQ_DATA * data,FREQ_MODE mode)655 static void freq_scan_dir (char *path, FREQ_DATA *data, FREQ_MODE mode)
656 {
657 	int added=0, removed=0;
658 	DIALOG *d = dialog_new();
659 	WIDGET *w;
660 	FREQ_SCAN_DATA scan_data;
661 
662 	scan_data.stop = 0;
663 	if (strlen(path) > 50)
664 		sprintf (storage,"Scanning ...%-47s...\n"
665 				         "   0 entrie(s) added,    0 entrie(s) removed",
666 				&path[strlen(path)-47]);
667 	else
668 		sprintf (storage,"Scanning %-50s...\n"
669 				         "   0 entrie(s) added,    0 entrie(s) removed",path);
670 	scan_data.w = (WID_LABEL*)wid_label_add(d, 1, storage);
671 	w = wid_button_add(d, 2, "&Stop", 0);
672 	wid_set_func(w, NULL, cb_scan_dir_stop_focus, &scan_data);
673 
674 	dialog_open(d, "Message");
675 	win_refresh();
676 
677 	scan_dir (path, 1, 0, data, mode,
678 			  cb_freq_scan_dir, &scan_data, &added, &removed);
679 	dialog_close(d);
680 
681 	freq_set_marks (data->w->entries,data->w->cnt,data->path,data);
682 	sprintf (storage,"Added %d entrie(s) and removed %d entrie(s).",
683 			 added,removed);
684 	dlg_message_open(storage, "&Ok", 0, 0, NULL, NULL);
685 }
686 
687 /* Add/Remove entries to/from the playlist */
freq_add(FREQ_DATA * data,FREQ_MODE mode)688 static void freq_add (FREQ_DATA *data, FREQ_MODE mode)
689 {
690 	char *file = data->w->entries[data->w->cur];
691 	char *path = data->path;
692 	char help[PATH_MAX<<1];
693 
694 	if (path_update (help,path,file+2)) {
695 		freq_scan_dir (help, data, mode);
696 	} else if (*file == FREQ_SEL) {
697 		if (mode != FREQ_ADD) {
698 			if (entry_remove_by_name(path, file+2, data) > 0)
699 				*file = FREQ_UNSEL;
700 		} else if (data->actline < 0 && data->before_add)
701 			if (entry_add(path, file+2, data) > 0)
702 				*file = FREQ_SEL;
703 	} else {
704 		if (mode != FREQ_REMOVE) {
705 			if (entry_add(path, file+2, data) > 0)
706 				*file = FREQ_SEL;
707 		}
708 	}
709 	wid_list_set_active (data->w,data->w->cur+1);
710 	win_panel_repaint();
711 }
712 
freq_close(FREQ_DATA * data)713 static void freq_close (FREQ_DATA *data)
714 {
715 	if (data) {
716 		if (data->w) dialog_close(data->w->w.d);
717 		if (data->searchlist) free (data->searchlist);
718 		free (data);
719 	}
720 	PL_DelDouble(&playlist);
721 }
722 
723 /* Ok/Back was selected and data->handle_freq() is present -> call function
724    Return: close fileselector? */
freq_call_func(int button,FREQ_DATA * data)725 static BOOL freq_call_func (int button, FREQ_DATA *data)
726 {
727 	char file[PATH_MAX<<1];
728 
729 	strcpy (file, data->path);
730 	strcat (file, data->w->entries[data->w->cur]+2);
731 	return data->handle_freq (button,file,data->data);
732 }
733 
cb_freq_list_focus(struct WIDGET * w,int focus)734 static int cb_freq_list_focus(struct WIDGET *w, int focus)
735 {
736 	if (focus == FOCUS_ACTIVATE) {
737 		FREQ_DATA *data = (FREQ_DATA *) w->data;
738 		int cur = ((WID_LIST*)w)->cur;
739 		char path[PATH_MAX<<1], *cur_entry;
740 
741 		freq_check_searchlist (data);
742 
743 		path[0] = '\0';
744 		cur_entry = ((WID_LIST*)w)->entries[cur]+2;
745 
746 		/* Default action for dirs: change dir
747 		   For files: call user-function or add entry to playlist */
748 		if (!path_update(path,data->path,cur_entry)) {
749 			if (data->handle_freq) {
750 				if (freq_call_func (0,data))
751 					freq_close (data);
752 			} else
753 				freq_add (data,FREQ_ADD);
754 		}
755 		if (path[0] != '\0')
756 			freq_changedir (path,data);
757 
758 		return EVENT_HANDLED;
759 	}
760 	return focus;
761 }
762 
cb_freq_cd_do(WIDGET * w,int button,void * input,void * data)763 static BOOL cb_freq_cd_do (WIDGET *w,int button, void *input, void *data)
764 {
765 	if (button<=0) {
766 		char *pos;
767 		path_conv((char *)input);
768 		pos = (char*)input + strlen((char*)input);
769 		/* Check if path ends with '/' */
770 		if (!IS_PATH_SEP(*(pos-1))) {
771 			*pos = PATH_SEP;
772 			*(pos+1) = '\0';
773 		}
774 		freq_check_searchlist ((FREQ_DATA *)data);
775 		freq_changedir ((char *)input, (FREQ_DATA *)data);
776 	}
777 	return 1;
778 }
779 
freq_cd(FREQ_DATA * data)780 static void freq_cd (FREQ_DATA *data)
781 {
782 	dlg_input_str ("Change directory to:", "<&Ok>|&Cancel",
783 				   data->path, PATH_MAX, cb_freq_cd_do, data);
784 }
785 
cb_freq_list_key(WIDGET * w,int ch)786 static int cb_freq_list_key(WIDGET *w, int ch)
787 {
788 	FREQ_DATA *data = (FREQ_DATA *) w->data;
789 
790 	freq_check_searchlist (data);
791 	if ((ch < 256) && (isalpha(ch)))
792 		ch = toupper(ch);
793 	switch (ch) {
794 		case KEY_IC:				/* Insert -> Add */
795 			freq_add (data,FREQ_ADD);
796 			break;
797 		default:
798 			return 0;
799 	}
800 	return EVENT_HANDLED;
801 }
802 
cb_freq_button_focus(struct WIDGET * w,int focus)803 static int cb_freq_button_focus(struct WIDGET *w, int focus)
804 {
805 	if (focus == FOCUS_ACTIVATE) {
806 		FREQ_DATA *data = (FREQ_DATA *) w->data;
807 		int button = ((WID_BUTTON *) w)->active;
808 
809 		freq_check_searchlist (data);
810 		switch (button) {
811 			case 0:				/* Add */
812 				freq_add (data,FREQ_ADD);
813 				break;
814 			case 1:				/* Toggle */
815 				freq_add (data,FREQ_TOGGLE);
816 				break;
817 			case 2:				/* Cd */
818 				freq_cd (data);
819 				break;
820 			case 3:				/* HotList */
821 				freq_hotlist (data);
822 				break;
823 			case 4:				/* Back / Ok */
824 				if (!data->handle_freq || freq_call_func (0,data))
825 					freq_close (data);
826 				break;
827 			case 5:				/* Back */
828 				if (data->handle_freq && freq_call_func (1,data))
829 					freq_close (data);
830 				break;
831 		}
832 		return EVENT_HANDLED;
833 	}
834 	return focus;
835 }
836 
837 /* Init initial path and searchlist */
freq_data_init(const char * path)838 static FREQ_DATA *freq_data_init (const char *path)
839 {
840 	struct stat statbuf;
841 	FREQ_DATA *data = (FREQ_DATA *) malloc(sizeof(FREQ_DATA));
842 	char *pos;
843 
844 	data->path[0] = '\0';
845 	if (path_relative(path)) {
846 		getcwd (data->path,PATH_MAX);
847 		path_conv (data->path);
848 		if (!IS_PATH_SEP(data->path[strlen(data->path)-1]))
849 			strcat (data->path, PATH_SEP_STR);
850 	}
851 	strcat (data->path,path);
852 	if (stat(path_conv_sys(data->path), &statbuf) || !S_ISDIR(statbuf.st_mode))
853 		if ((pos = FIND_LAST_DIRSEP(data->path)) != NULL)
854 			*(pos+1) = '\0';
855 
856 	pos = data->path+strlen(data->path);
857 	if (!IS_PATH_SEP(*(pos-1))) {
858 		*pos = PATH_SEP;
859 		*(pos+1) = '\0';
860 	}
861 	data->w = NULL;
862 	data->before_add = 1;
863 	data->actline = -1;
864 	data->cnt_list = 0;
865 	data->searchlist = NULL;
866 
867 	freq_check_searchlist (data);
868 	return data;
869 }
870 
871 /* Open a file requester.
872    func!=NULL: func is called if Ok or Cancel is selected
873    func==NULL: no Ok button, Add is default */
freq_open(const char * title,const char * path,int actline,handleFreqFunc func,void * data)874 void freq_open (const char *title, const char *path, int actline,
875 				handleFreqFunc func, void *data)
876 {
877 	FREQ_DATA *freq_data;
878 	DIALOG *d = dialog_new();
879 	WIDGET *w;
880 	char **entries, *path_first = NULL;
881 	int cnt;
882 
883 	freq_data = freq_data_init (path);
884 	freq_data->actline = actline;
885 	freq_data->handle_freq = func;
886 	freq_data->data = data;
887 
888 	freq_readdir(freq_data->path,&entries,&cnt,freq_data);
889 	if (!entries || !cnt) {
890 		/* show error after file selector is open */
891 		path_first = strdup (freq_data->path);
892 
893 		/* error on initial path -> try root directory */
894 #if defined(__OS2__)||defined(__EMX__)||defined(__DJGPP__)||defined(_WIN32)
895 		strcpy (freq_data->path,"c:"PATH_SEP_STR);
896 #elif defined _mikmod_amiga
897 		strcpy (freq_data->path,"SYS:"); /* or use ":" instead??? */
898 #else
899 		strcpy (freq_data->path,PATH_SEP_STR);
900 #endif
901 		freq_readdir(freq_data->path,&entries,&cnt,freq_data);
902 		if (!entries || !cnt) {
903 			/* again an error -> give up */
904 			freq_close (freq_data);
905 			if (path_first) free (path_first);
906 			return;
907 		}
908 	}
909 
910 	w = wid_list_add(d, 1, (const char **)entries, cnt);
911 	freq_data->w = (WID_LIST*)w;
912 	wid_set_func(w, cb_freq_list_key, cb_freq_list_focus, freq_data);
913 	freq_freedir(entries, cnt);
914 
915 	if (func)
916 		w = wid_button_add(d, 1, "&Add|&Toggle|&Cd|&Hlist|<&Ok>|&Back", 0);
917 	else
918 		w = wid_button_add(d, 1, "<&Add>|&Toggle|&Cd|&Hlist|&Back", 0);
919 	wid_set_func(w, NULL, cb_freq_button_focus, freq_data);
920 
921 	dialog_open(d, title);
922 	/* Size of list widget is necessary -> set title after dialog_open() */
923 	freq_set_title (freq_data);
924 	if (path_first) {
925 		dlg_error_show ("Unable to read directory \"%s\"!",path_first);
926 		free (path_first);
927 	}
928 }
929 
cb_list_scan_dir(char * path,int added,int removed,void * data)930 static BOOL cb_list_scan_dir (char *path, int added, int removed, void *data)
931 {
932 	BOOL quiet = (BOOL)(SINTPTR_T)data;
933 	char str[70], *pos;
934 	int i;
935 
936 	if (!quiet) {
937 		if (strlen(path) > 43)
938 			sprintf (str,"\rScanning ...%s... (%d added)",
939 					 &path[strlen(path)-40],added);
940 		else
941 			sprintf (str,"\rScanning %s... (%d added)",path,added);
942 		pos = str+strlen(str);
943 		for (i=strlen(str); i<(70-1); i++)
944 			*pos++ = ' ';
945 		*pos = '\0';
946 		printf ("%s", str);
947 		fflush(stdout);
948 	}
949 	return 0;
950 }
951 
952 /* test if path is a directory and recursively scan it for modules */
list_scan_dir(char * path,BOOL quiet)953 int list_scan_dir (char *path, BOOL quiet)
954 {
955 	struct stat statbuf;
956 	int added = 0;
957 	char dir[PATH_MAX<<1]="", *pos;
958 
959 #if defined(__EMX__)||defined(__OS2__)||defined(__DJGPP__)||defined(_WIN32)
960 	if (*path!=PATH_SEP && *(path+1)!=':')
961 #else
962 	if (!IS_PATH_SEP(*path))
963 #endif
964 	{
965 		getcwd (dir,PATH_MAX);
966 		path_conv (dir);
967 		if (!IS_PATH_SEP(dir[strlen(dir)-1]))
968 			strcat (dir, PATH_SEP_STR);
969 	}
970 	strcat (dir,path);
971 	pos = dir+strlen(dir);
972 	if (!IS_PATH_SEP(*(pos-1))) {
973 		*pos = PATH_SEP;
974 		*(pos+1) = '\0';
975 	}
976 
977 	if (!stat(path_conv_sys(dir), &statbuf) && S_ISDIR(statbuf.st_mode))
978 		scan_dir (dir, 1, 0, NULL, FREQ_ADD,
979 				  cb_list_scan_dir, (void *)(SINTPTR_T)quiet, &added, NULL);
980 	return added;
981 }
982 
983 /* remove an entry from the playlist */
entry_remove(int entry)984 static void entry_remove (int entry)
985 {
986 	PL_DelEntry(&playlist, entry);
987 }
988 
989 /* remove an entry from the playlist and delete the associated module */
cb_delete_entry(WIDGET * w,int button,void * input,void * entry)990 static BOOL cb_delete_entry(WIDGET *w, int button, void *input, void *entry)
991 {
992 	if (button<=0) {
993 		PLAYENTRY *cur = PL_GetEntry(&playlist, (SINTPTR_T)entry);
994 		if (cur->archive) {
995 			if (unlink(path_conv_sys(cur->archive)) == -1)
996 				dlg_error_show("Error deleting archive \"%s\"!",cur->archive);
997 		} else {
998 			if (unlink(path_conv_sys(cur->file)) == -1)
999 				dlg_error_show("Error deleting file \"%s\"!",cur->file);
1000 		}
1001 		entry_remove((SINTPTR_T)entry);
1002 	}
1003 	return 1;
1004 }
1005 
1006 /* split a filename into the name and the last extension */
split_name(char * file,char ** name,char ** ext)1007 static void split_name(char *file, char **name, char **ext)
1008 {
1009 	*name = FIND_LAST_DIRSEP(file);
1010 	if (!*name)
1011 		*name = file;
1012 	*ext = strrchr(*name, '.');
1013 	if (!*ext)
1014 		*ext = &(*name[strlen(*name)]);
1015 }
1016 
1017 static BOOL sort_rev = 0;
1018 /* *INDENT-OFF* */
1019 static enum {
1020 	SORT_NAME,
1021 	SORT_EXT,
1022 	SORT_PATH,
1023 	SORT_TIME
1024 } sort_mode = SORT_NAME;
1025 /* *INDENT-ON* */
1026 
cb_cmp_sort(PLAYENTRY * small,PLAYENTRY * big)1027 static int cb_cmp_sort(PLAYENTRY * small, PLAYENTRY * big)
1028 {
1029 	char ch_s = ' ', ch_b = ' ', *ext_s, *ext_b, *name_s, *name_b;
1030 	int ret = 0;
1031 
1032 	switch (sort_mode) {
1033 	  case SORT_NAME:
1034 		split_name(small->file, &name_s, &ext_s);
1035 		split_name(big->file, &name_b, &ext_b);
1036 		ch_s = *ext_s;
1037 		ch_b = *ext_b;
1038 		*ext_s = '\0';
1039 		*ext_b = '\0';
1040 		ret = strcasecmp(name_s, name_b);
1041 		*ext_s = ch_s;
1042 		*ext_b = ch_b;
1043 		break;
1044 	  case SORT_EXT:
1045 		split_name(small->file, &name_s, &ext_s);
1046 		split_name(big->file, &name_b, &ext_b);
1047 		ret = strcasecmp(ext_s, ext_b);
1048 		break;
1049 	  case SORT_PATH:
1050 		ext_s = small->archive;
1051 		if (!ext_s)
1052 			ext_s = small->file;
1053 		name_s = FIND_LAST_DIRSEP(ext_s);
1054 		if (name_s) {
1055 			ch_s = *name_s;
1056 			*name_s = '\0';
1057 		}
1058 		ext_b = big->archive;
1059 		if (!ext_b)
1060 			ext_b = big->file;
1061 		name_b = FIND_LAST_DIRSEP(ext_b);
1062 		if (name_b) {
1063 			ch_b = *name_b;
1064 			*name_b = '\0';
1065 		}
1066 		ret = strcasecmp(ext_s, ext_b);
1067 		if (name_s)
1068 			*name_s = ch_s;
1069 		if (name_b)
1070 			*name_b = ch_b;
1071 		break;
1072 	  case SORT_TIME:
1073 		ret = (small->time == big->time ? 0 :
1074 			   (small->time < big->time ? -1 : 1));
1075 		break;
1076 	}
1077 	return (sort_rev) ? -ret : ret;
1078 }
1079 
1080 /* overwrites an existdng playlist */
cb_overwrite(WIDGET * w,int button,void * input,void * file)1081 static BOOL cb_overwrite (WIDGET *w, int button, void *input, void *file)
1082 {
1083 	if (button<=0) {
1084 		path_conv((char *)file);
1085 		if (PL_Save(&playlist, (char *)file))
1086 			rc_set_string(&config.pl_name, (char *)file, PATH_MAX);
1087 		else
1088 			dlg_error_show("Error saving playlist \"%s\"!",file);
1089 	}
1090 	if (file) free(file);
1091 	return 1;
1092 }
1093 
cb_browse(int button,char * file,void * data)1094 static BOOL cb_browse (int button, char *file, void *data)
1095 {
1096 	if (!button) {
1097 		wid_str_set_input ((WID_STR*)data, file, -1);
1098 		wid_repaint ((WIDGET*)data);
1099 	}
1100 	return 1;
1101 }
1102 
1103 /* saves a playlist */
cb_save_as(WIDGET * w,int button,void * input,void * data)1104 static BOOL cb_save_as(WIDGET *w, int button, void *input, void *data)
1105 {
1106 	path_conv((char *)input);
1107 	if (button == 0) {								/* Browse */
1108 		freq_open ("Select directory/file",(char*)input,(SINTPTR_T)data,
1109 				   cb_browse,w);
1110 		return 0;
1111 	} else if (button == 1 || button == -1) {		/* Ok / Str-Widget */
1112 		if (file_exist((char*)input)) {
1113 			char *f_copy = strdup((char*)input);
1114 			char *msg = str_sprintf("File \"%s\" exists.\n"
1115 									"Really overwrite the file?", f_copy);
1116 			dlg_message_open(msg, "&Yes|&No", 1, 1, cb_overwrite, f_copy);
1117 			free(msg);
1118 		} else {
1119 			if (PL_Save(&playlist, (char*)input))
1120 				rc_set_string(&config.pl_name, (char*)input, PATH_MAX);
1121 			else
1122 				dlg_error_show("Error saving playlist \"%s\"!",input);
1123 		}
1124 	}
1125 	return 1;
1126 }
1127 
1128 /* playlist menu handler */
cb_handle_menu(MMENU * menu)1129 static void cb_handle_menu(MMENU * menu)
1130 {
1131 	MENU_DATA *data = (MENU_DATA *) menu->data;
1132 	int actLine = *data->actLine;
1133 	PLAYENTRY *cur;
1134 	char *name, *msg;
1135 
1136 	/* main menu */
1137 	if (!menu->id) {
1138 		switch (menu->cur) {
1139 		  case 0:				/* play highlighted module */
1140 			if (actLine >= 0)
1141 				Player_SetNextMod(actLine);
1142 			break;
1143 		  case 1:				/* remove highlighted module */
1144 			if (actLine >= 0)
1145 				entry_remove(actLine);
1146 			break;
1147 		  case 2:				/* delete highlighted module */
1148 			cur = PL_GetEntry(&playlist, actLine);
1149 			if (!cur)
1150 				break;
1151 
1152 			if (cur->archive) {
1153 				name = FIND_LAST_DIRSEP(cur->file);
1154 				if (name)
1155 					name++;
1156 				else
1157 					name = cur->file;
1158 
1159 				if (strlen(cur->archive) > 60)
1160 					msg = str_sprintf2("File \"%s\" is in an archive!\n"
1161 									   "Really delete whole archive\n"
1162 									   "  \"...%s\"?", name,
1163 									   &(cur->
1164 										 archive[strlen(cur->archive) - 57]));
1165 				else
1166 					msg =
1167 					  str_sprintf2("File \"%s\" is in an archive!\n"
1168 								   "Really delete whole archive\n"
1169 								   "  \"%s\"?", name, cur->archive);
1170 				dlg_message_open(msg, "&Yes|&No", 1, 1, cb_delete_entry,
1171 								 (void *)(SINTPTR_T)actLine);
1172 			} else {
1173 				if (strlen(cur->file) > 50)
1174 					msg = str_sprintf("Delete file \"...%s\"?",
1175 									  &(cur->file[strlen(cur->file) - 47]));
1176 				else
1177 					msg = str_sprintf("Delete file \"%s\"?", cur->file);
1178 				dlg_message_open(msg, "&Yes|&No", 1, 1,
1179 								 cb_delete_entry, (void *)(SINTPTR_T)actLine);
1180 			}
1181 			free(msg);
1182 			break;
1183 		  case 5:				/* shuffle list */
1184 			PL_Randomize(&playlist);
1185 			break;
1186 		  case 7:				/* cancel */
1187 			break;
1188 		  default:
1189 			return;
1190 		}
1191 		/* file menu */
1192 	} else if (menu->id == 1) {
1193 		switch (menu->cur) {
1194 		  case 0:				/* load */
1195 			  freq_open ("Load modules/playlists",
1196 						 config.pl_name, -1, NULL, NULL);
1197 			  break;
1198 		  case 1:				/* insert */
1199 			  freq_open ("Insert modules/playlists",
1200 						 config.pl_name, actLine, NULL, NULL);
1201 			  break;
1202 		  case 2:				/* save */
1203 			if (!PL_Save(&playlist, config.pl_name))
1204 				dlg_error_show("Error saving playlist \"%s\"!",config.pl_name);
1205 			break;
1206 		  case 3:				/* save as */
1207 			dlg_input_str("Save playlist as:", "&Browse|<&Ok>|&Cancel",
1208 						  config.pl_name, PATH_MAX, cb_save_as,
1209 						  (void*)(SINTPTR_T)actLine);
1210 			break;
1211 		  default:
1212 			return;
1213 		}
1214 		/* sort menu */
1215 	} else {
1216 		/* reverse flag */
1217 		sort_rev = (SINTPTR_T)menu->entries[5].data;
1218 		switch (menu->cur) {
1219 		  case 0:				/* by name */
1220 			sort_mode = SORT_NAME;
1221 			PL_Sort(&playlist, cb_cmp_sort);
1222 			break;
1223 		  case 1:				/* by extension */
1224 			sort_mode = SORT_EXT;
1225 			PL_Sort(&playlist, cb_cmp_sort);
1226 			break;
1227 		  case 2:				/* by path */
1228 			sort_mode = SORT_PATH;
1229 			PL_Sort(&playlist, cb_cmp_sort);
1230 			break;
1231 		  case 3:				/* by time */
1232 			sort_mode = SORT_TIME;
1233 			PL_Sort(&playlist, cb_cmp_sort);
1234 			break;
1235 		  default:
1236 			return;
1237 		}
1238 	}
1239 	menu_close(data->menu);
1240 	return;
1241 }
1242 
list_open(int * actLine)1243 void list_open(int *actLine)
1244 {
1245 	static MENU_DATA menu_data;
1246 
1247 	static MENTRY file_entries[] = {
1248 		{"&Load...", 0, "Load new playlists/modules"},
1249 		{"&Insert...", 0, "Insert new playlists/modules in current playlist"},
1250 		{"&Save", 0, NULL},
1251 		{"Save &as...", 0, "Save playlist in a specified file"},
1252 		{NULL,NULL,NULL}
1253 	};
1254 	static MMENU file_menu =
1255 	  { 0, 0, -1, 1, file_entries, cb_handle_menu, NULL, &menu_data, 1 };
1256 
1257 	static MENTRY sort_entries[] = {
1258 		{"by &name", 0, "Sort list by name of modules"},
1259 		{"by &extension", 0, "Sort list by extension of modules"},
1260 		{"by &path", 0, "Sort list by path of modules/archives"},
1261 		{"by &time", 0, "Sort list by playing time of modules"},
1262 		{"%---------", 0, NULL},
1263 		{"[%c] &reverse", 0, "Smaller to bigger or reverse sort"},
1264 		{NULL,NULL,NULL}
1265 	};
1266 	static MMENU sort_menu =
1267 	  { 0, 0, -1, 1, sort_entries, cb_handle_menu, NULL, &menu_data, 2 };
1268 
1269 	static MENTRY entries[] = {
1270 		{"&Play", 0, "Play selected entry"},
1271 		{"&Remove", 0, "Remove selected entry from list"},
1272 		{"&Delete...", 0,
1273 		 "Remove selected entry from list and delete it on disk"},
1274 		{"%----------", 0, NULL},
1275 		{"&File        %>", &file_menu, "Load/Save playlist/modules"},
1276 		{"&Shuffle", 0, "Shuffle the list"},
1277 		{"S&ort        %>", &sort_menu, "Sort the list"},
1278 		{"&Back", 0, "Leave menu"},
1279 		{NULL,NULL,NULL}
1280 	};
1281 	static MMENU menu =
1282 	  { 0, 0, -1, 1, entries, cb_handle_menu, NULL, &menu_data, 0 };
1283 
1284 	menu_data.menu = &menu;
1285 	menu_data.actLine = actLine;
1286 	set_help(&file_entries[2], "Save list in '%s'", config.pl_name);
1287 	menu_open(&menu, 5, 5);
1288 }
1289