ftp.nice.ch/pub/next/tools/emulators/vice.0.15.0.NeXT.sd.tgz#/vice-0.15.0/src/arch/msdos/tuifs.c

This is tuifs.c in view mode; [Download] [Up]

/*
 * tuifs.c - A simple text-based file selector.
 *
 * Written by
 *  Ettore Perazzoli (ettore@comm2000.it)
 *
 * This file is part of VICE, the Versatile Commodore Emulator.
 * See README for copyright notice.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 *  02111-1307  USA.
 *
 */

#include "vice.h"

#include <ctype.h>
#include <dirent.h>
#include <dos.h>
#include <errno.h>
#include <fnmatch.h>
#include <keys.h>
#include <limits.h>
#include <pc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#ifdef HAVE_ALLEGRO_H
#include <allegro.h>
#endif

#include "tui.h"
#include "utils.h"
#include "tuiview.h"

/* ------------------------------------------------------------------------- */

enum file_type { FT_NORMAL, FT_DIR };

struct file_item {
    char name[0x100];
    enum file_type type;
};

struct file_list {
    int num_items;
    int num_used_items;
    struct file_item *items;
};

static struct file_list *file_list_create(void)
{
    struct file_list *new_list;

    new_list = (struct file_list *)malloc(sizeof(struct file_list));
    new_list->num_items = new_list->num_used_items = 0;
    new_list->items = NULL;

    return new_list;
}

static void file_list_clear(struct file_list *fl)
{
    if (fl->items != NULL)
	free(fl->items);

    fl->items = NULL;
    fl->num_used_items = fl->num_items = 0;
}

static void file_list_free(struct file_list *fl)
{
    if (fl != NULL) {
	file_list_clear(fl);
	free(fl);
    }
}

static void file_list_add_item(struct file_list *fl, const char *name,
			       enum file_type type)
{
    if (fl->num_items == fl->num_used_items) {
	fl->num_items += 100;
	if (fl->items != NULL)
	    fl->items = (struct file_item *)xrealloc(fl->items,
                                                     fl->num_items
                                                     * sizeof(*fl->items));
	else
	    fl->items = (struct file_item *)xmalloc(fl->num_items
                                                    * sizeof(*fl->items));
    }

    strcpy(fl->items[fl->num_used_items].name, name);
    fl->items[fl->num_used_items].type = type;
    fl->num_used_items++;
}

static int file_list_sort_func(const void *e1, const void *e2)
{
    struct file_item *f1 = (struct file_item *)e1;
    struct file_item *f2 = (struct file_item *)e2;

    /* Directories always come first. */
    if (f1->type != f2->type) {
	if (f1->type == FT_DIR)
	    return -1;
	if (f2->type == FT_DIR)
	    return +1;
    }
    return strcmp(f1->name, f2->name);
}

static void file_list_sort(struct file_list *fl)
{
    qsort(fl->items, fl->num_used_items, sizeof(struct file_item),
	  file_list_sort_func);
}

/* XXX: Assumes `path' ends with a slash.  */
static struct file_list *file_list_read_lfn(const char *path,
                                            const char *pattern)
{
    struct dirent *d;
    struct file_list *fl;
    DIR *ds;
    int pathlen = strlen(path);

    if (path == NULL || *path == '\0')
	ds = opendir(".");
    else
	ds = opendir(path);

    if (ds == NULL)
	return NULL;

    fl = file_list_create();

    /* Skip ".".  */
    readdir(ds);

    {
	unsigned short old_djstat = _djstat_flags;

	/* This makes `stat()' faster.  FIXME: but it's still too slow
           imo...  */
	_djstat_flags = (_STAT_INODE
			 | _STAT_EXEC_EXT
			 | _STAT_EXEC_MAGIC
			 | _STAT_DIRSIZE
			 | _STAT_ROOT_TIME
			 | _STAT_WRITEBIT);

	while((d = readdir(ds)) != NULL) {
	    struct stat s;
	    int type;
	    /* Warning: Assumes `path' has a trailing '/'.  */
	    char *name = alloca(d->d_namlen + pathlen + 1);

	    memcpy(name, path, pathlen);
	    strcpy(name + pathlen, d->d_name);

	    if (stat(name, &s) != -1) {
		type = S_ISDIR(s.st_mode) ? FT_DIR : FT_NORMAL;
		if (pattern == NULL
		    || fnmatch(pattern, d->d_name, 0) == 0
		    || type == FT_DIR)
		    file_list_add_item(fl, d->d_name, type);
	    }
	}

	_djstat_flags = old_djstat;
    }

    file_list_sort(fl);
    closedir(ds);

    return fl;
}

static struct file_list *file_list_read_nolfn(const char *path,
                                              const char *pattern)
{
    char *cwd = get_current_dir();
    struct file_list *fl = NULL;
    struct find_t f;

    if (cwd == NULL)
        return NULL;

    if (chdir(path) < 0)
        goto end;

    if (_dos_findfirst("*.*", (_A_NORMAL | _A_RDONLY | _A_HIDDEN
                               | _A_SYSTEM | _A_SUBDIR | _A_ARCH), &f))
        goto end;

    fl = file_list_create();

    /* (We skip `.' here.) */

    while (!_dos_findnext(&f)) {
        strlwr(f.name);
        if (pattern == NULL || (f.attrib & _A_SUBDIR)
            || fnmatch(pattern, f.name, 0) == 0) {
            file_list_add_item(fl, f.name,
                               (f.attrib & _A_SUBDIR) ? FT_DIR : FT_NORMAL);
        }
    }

    file_list_sort(fl);

end:
    chdir(cwd);
    return fl;
}

static struct file_list *file_list_read(const char *path,
                                        const char *pattern)
{
    /* XXX: This check is only half-OK.  We actually need Allegro to be up
       and running for this to work properly.  */
#ifdef HAVE_ALLEGRO_H
    if (os_type == OSTYPE_WIN95)
        return file_list_read_lfn(path, pattern);
    else
        return file_list_read_nolfn(path, pattern);
#else
    return file_list_read_lfn(path, pattern);
#endif
}

static int file_list_find(const struct file_list *fl, const char *str, int len)
{
    int i;

    for (i = 0; i < fl->num_used_items; i++)
	if (strncmp(fl->items[i].name, str, len) == 0)
	    return i;
    return -1;
}

/* ------------------------------------------------------------------------- */

static void file_selector_display_path(const char *path,
                                       int x, int y, int width)
{
    int i, xx;

    tui_set_attr(FIRST_LINE_FORE, FIRST_LINE_BACK, 0);

    tui_hline(x, y, 0xcd, width);

    for (i = strlen(path) - 1, xx = MIN(x + width - 1, x + i);
         i >= 0 && xx >= x;
         i--, xx--) {
        char c;

        /* Display ellipsis on the left if longer than the line.  */
        if (xx <= x + 1 && i > 1)
            c = '.';
        else
            c = path[i] == '/' ? '\\' : path[i];
        tui_put_char(xx, y, c);
    }
}

static void file_selector_display_item(struct file_list *fl, int num,
				       int first_item_num, int x, int y,
				       int width, int height, int num_cols)
{
    y += (num - first_item_num) % height;
    x += ((num - first_item_num) / height) * width;

    if (num >= fl->num_used_items) {
	tui_hline(x, y, ' ', width);
    } else {
        int len = strlen(fl->items[num].name);

	/* XXX: Assumes `width' is > 5!  */
	if (len > width - 2) {
	    char *name = alloca(width - 2 + 1);

	    if (fl->items[num].type == FT_DIR) {
		memcpy(name, fl->items[num].name, width - 3);
		name[width - 4] = name[width - 5] = '.';
		name[width - 3] = '/';
	    } else {
		memcpy(name, fl->items[num].name, width - 2);
		name[width - 3] = name[width - 4] = '.';
	    }
	    name[width - 2] = '\0';
	    tui_display(x, y, width, " %s ", name);
	} else {
	    if (fl->items[num].type == FT_DIR)
	      /* tui_display(x, y, width, " %s\\ ", fl->items[num].name); */
                tui_display(x, y, width, " %s/ ", fl->items[num].name);
	    else
	      tui_display(x, y, width, " %s ", fl->items[num].name);
	}
    }
}

static void file_selector_update(struct file_list *fl,
				 int first_item_num, int x, int y,
				 int width, int height, int num_cols)
{
    int i;

    for (i = 0; i < num_cols * height; i++)
	file_selector_display_item(fl, first_item_num + i, first_item_num,
				   x, y, width, height, num_cols);
}

/* ------------------------------------------------------------------------- */

/* Make sure there is a trailing '/' in `*path'.  */
static void slashize_path(char **path)
{
    int len = strlen(*path);

    if ((*path)[len - 1] != '/') {
        *path = xrealloc(*path, len + 2);
        (*path)[len] = '/';
        (*path)[len + 1] = '\0';
    }
}

/* Display a file selector with title `title' and containing the files
   that match `pattern' in directory `directory' (if NULL, uses the
   current one).  If `default_item' is not NULL, position the file
   selector bar to the file whose name is the same as `default_item',
   if present.  `read_contents_func', if not NULL, is a function that
   returns the contents of the file (as a malloced ASCII string)
   passed as the parameter; then if the user can press `space' on a
   file name, contents are extracted with this function and displayed.
   Return the name of the selected file, or NULL if the user pressed
   ESC to leave with no selection.  */

char *tui_file_selector(const char *title, const char *directory,
			const char *pattern, const char *default_item,
			char *(*read_contents_func)(const char*))
{
    static char *return_path = NULL;
    struct file_list *fl;
    int curr_item, first_item, need_update;
    int x, y, width, height, num_cols, num_lines, field_width;
    int num_files;
    char str[0x100];
    int str_len = 0;
    tui_area_t backing_store = NULL;

    if (return_path == NULL)
	free(return_path);
    if (directory != NULL)
	return_path = stralloc(directory);
    else
	return_path = get_current_dir();

    slashize_path(&return_path);

    fl = file_list_read(return_path, pattern);
    if (fl == NULL)
	return NULL;

    first_item = curr_item = 0;
    num_cols = 4;
    field_width = 18;
    num_lines = 17;
    height = num_lines + 2;
    width = field_width * num_cols + 4;
    num_files = num_cols * num_lines;

    if (default_item != NULL && *default_item) {
	int i;

	for (i = 0; i < fl->num_items; i++) {
	    if (!strcasecmp(default_item, fl->items[i].name)) {
		curr_item = i;
		while (curr_item - first_item >= num_files)
		    first_item += num_lines;
		break;
	    }
	}
    }

    x = CENTER_X(width);
    y = CENTER_Y(height);

    need_update = 1;

    tui_area_get(&backing_store, x, y, width + 2, height + 1);

    tui_display_window(x, y, width, height, MENU_FORE, MENU_BACK,
                       title, NULL);

    while (1) {
	int key;

	tui_set_attr(MENU_FORE, MENU_BACK, 0);
	if (need_update) {
            file_selector_display_path(return_path, x + 1, y + height - 1,
                                       width - 2);
	    file_selector_update(fl, first_item, x + 2, y + 1,
				 field_width, num_lines, num_cols);
            tui_set_attr(FIRST_LINE_FORE, FIRST_LINE_BACK, 0);
            tui_display(0, tui_num_lines() - 1, tui_num_cols(),
                        "\030\031\033\032: Move  <enter>: Select  %s<ctrl>-<letter>: Change drive",
                        read_contents_func != NULL ? "<space>: Preview  " : "");
	    need_update = 0;
	}
	tui_set_attr(MENU_FORE, MENU_HIGHLIGHT, 0);
	file_selector_display_item(fl, curr_item, first_item, x + 2, y + 1,
				   field_width, num_lines, num_cols);
	key = getkey();
	tui_set_attr(MENU_FORE, MENU_BACK, 0);
	file_selector_display_item(fl, curr_item, first_item, x + 2, y + 1,
				   field_width, num_lines, num_cols);

	switch (key) {
	  case K_Escape:
	    tui_area_put(backing_store, x, y);
	    tui_area_free(backing_store);
	    return NULL;
	  case K_Left:
	    str_len = 0;
	    if (curr_item - num_lines >= 0) {
		curr_item -= num_lines;
		if (curr_item < first_item) {
		    if (first_item >= num_lines) {
			first_item -= num_lines;
			need_update = 1;
		    } else
			curr_item += num_lines;
		}
	    }
	    break;
	  case K_Up:
	    str_len = 0;
	    if (curr_item > 0) {
		curr_item--;
		if (curr_item < first_item) {
		    first_item = curr_item;
		    need_update = 1;
		}
	    }
	    break;
	  case K_Right:
	    str_len = 0;
	    if (curr_item + num_lines < fl->num_used_items) {
		curr_item += num_lines;
		if (curr_item - first_item >= num_files) {
		    first_item += num_lines;
		    need_update = 1;
		}
	    }
	    break;
	  case K_Down:
	    str_len = 0;
	    if (curr_item < fl->num_used_items - 1) {
		curr_item++;
		if (curr_item == first_item + num_files) {
		    first_item++;
		    need_update = 1;
		}
	    }
	    break;
	  case K_PageDown:
	    str_len = 0;
	    if (curr_item + num_files < fl->num_used_items) {
		curr_item += num_files;
		first_item += num_files;
	    }
	    need_update = 1;
	    break;
	  case K_PageUp:
	    str_len = 0;
	    if (curr_item - num_files >= 0) {
		curr_item -= num_files;
		first_item -= num_files;
		if (first_item < 0)
		    first_item = 0;
		need_update = 1;
	    }
	    break;
	  case K_Home:
	    str_len = 0;
	    curr_item = 0;
	    if (first_item != 0) {
		first_item = 0;
		need_update = 1;
	    }
	    break;
	  case K_End:
	    str_len = 0;
	    curr_item = fl->num_used_items - 1;
	    first_item = curr_item - num_files + 1;
	    if (first_item < 0)
		first_item = 0;
	    need_update = 1;
	    break;
	  case K_Return:
	    str_len = 0;
	    if (fl->items[curr_item].type == FT_DIR) {
		struct file_list *new_fl;
		char *new_path;

		if (strcmp(fl->items[curr_item].name, "..") == 0) {
		    char *p = return_path + strlen(return_path) - 1;

		    if (*p == '/')
			p--;
		    for (; *p != '/' && p > return_path; p--)
		        ;
		    if (p == return_path)
			new_path = stralloc(return_path);
		    else {
			new_path = xmalloc(p - return_path + 2);
			memcpy(new_path, return_path, p - return_path + 1);
			new_path[p - return_path + 1] = '\0';
		    }
		} else {
		    new_path = concat(return_path,
				      fl->items[curr_item].name, "/", NULL);
		}
		new_fl = file_list_read(new_path, pattern);
		if (new_fl != NULL) {
		    file_list_free(fl);
		    fl = new_fl;
		    first_item = curr_item = 0;
		    free(return_path);
		    return_path = new_path;
		    need_update = 1;
                    chdir(return_path);
		} else {
		    free(new_path);
		}
	    } else {
		char *p = concat(return_path, fl->items[curr_item].name,
				 NULL);

		free(return_path);
		return_path = p;
		tui_area_put(backing_store, x, y);
		tui_area_free(backing_store);
		return return_path;
	    }
	    break;
	  case K_BackSpace:
	    if (str_len > 1) {
		int n;
		str_len--;
		n = file_list_find(fl, str, str_len);
		if (n >= 0) {
		    curr_item = n;
		    if (curr_item < first_item) {
			first_item = curr_item;
			need_update = 1;
		    } else if (first_item + num_files <= curr_item) {
			first_item = curr_item - num_files + 1;
			need_update = 1;
		    }
		}
	    } else {
		str_len = 0;
		curr_item = 0;
		if (first_item != 0) {
		    first_item = 0;
		    need_update = 1;
		}
	    }
	    break;
	  case ' ':
	    if (read_contents_func != NULL) {
		char *name;
		char *contents;

		name = alloca(strlen(return_path)
			      + strlen(fl->items[curr_item].name)
			      + 1);
		sprintf(name, "%s%s", return_path, fl->items[curr_item].name);

		contents = read_contents_func(name);
                tui_display(0, tui_num_lines() - 1, tui_num_cols(), "");
		if (contents != NULL)
		    tui_view_text(40, 20, fl->items[curr_item].name, contents);
                need_update = 1;
		break;
	    } else
                tui_beep();
	  default:
            if (key >= 1 && key <= 26) {
                /* `C-a ... C-z' change the current drive.  */
                int num_available_drives;
                int current_drive;
                int drive = (int) key;

                _dos_getdrive(&current_drive);
                _dos_setdrive(current_drive, &num_available_drives);
                if (drive <= num_available_drives) {
                    char *new_path;

                    /* FIXME: This is a hack...  Maybe there is a cleaner way
                       to do it, but for now I just don't know.  */
                    _dos_setdrive(drive, &num_available_drives);
                    new_path = get_current_dir();
                    if (new_path != NULL) {
                        slashize_path(&new_path);
                        _dos_setdrive(current_drive, &num_available_drives);

                        if (new_path != NULL) {
                            struct file_list *new_fl;

                            new_fl = file_list_read(new_path, pattern);
                            if (new_fl != NULL) {
                                file_list_free(fl);
                                fl = new_fl;
                                first_item = curr_item = 0;
                                free(return_path);
                                return_path = new_path;
                                need_update = 1;
                                chdir(return_path);
                            } else {
                                free(new_path);
                            }
                        }
                    } else {
                        _dos_setdrive(current_drive, &num_available_drives);
                        tui_beep();
                    }
                } else {
                    tui_beep();
                }
            } else if (isprint(key) && str_len < 0x100) {
		int n;
		str[str_len] = key;
		n = file_list_find(fl, str, str_len + 1);
		if (n < 0) {
		    tui_beep();
		} else {
		    str_len++;
		    curr_item = n;
		    if (curr_item < first_item) {
			first_item = curr_item;
			need_update = 1;
		    } else if (first_item + num_files <= curr_item) {
			first_item = curr_item - num_files + 1;
			need_update = 1;
		    }
		}
	    }
	    break;
	}
    }
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.