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(¤t_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.