📄 libpg_dirview.c
字号:
/* $Id: libpg_dirview.c,v 1.2 2002/07/28 17:06:48 micahjd Exp $ * * libpg_dirview - A directory browser based on the source code of cli_c's * dlg_filepicker.c * * This code is oozing with Unixisms, so it will need to be ported to run * on other OSes... * * - The directory separator is assumed to be '/' * - The user's home directory is retrieved through $HOME * - Multiple drives (as in DOS) are not supported * - Unix functions are used to implement the file tools and validate * files for the PG_FILE_MUST* flags * * PicoGUI small and efficient client/server GUI * Copyright (C) 2000-2002 Micah Dowty <micahjd@users.sourceforge.net> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Contributors: * o Dirview component architecture and design: * Copyright (C) 2002 Pascal Bauermeister <pascal.bauermeister@urbanet.ch> * * * */#include <string.h> /* strcpy(), strcat() */#include <stdlib.h> /* For getenv() */#include <stdarg.h>#include <dirent.h>#include <unistd.h>#include <errno.h> /* Check errors when validating a filename */#include <ctype.h>#include <fcntl.h>#include <sys/stat.h> /* stat() */#include <sys/types.h> /* opendir(), readdir(), etc */#include "libpg_dirview.h"#include "properties.h"#include "protocol.h"#define DECLARE_DEBUG_VARS#include "debug.h"/*****************************************************************************/typedef struct { const char* name; LpgdvMenuItemEnableF menu_item_enable; LpgdvMenuItemsEnableF menu_items_enable;} MenuItemParams;typedef struct { const char* name; LpgdvVal place; int place_at; LpgdvColumnRenderF column_render; void* payload;} ColumnParams;typedef struct { /* Params passed in function call */ const char* title; LpgdvVal title_is_visible; LpgdvVal location_visible; LpgdvVal list_is_header_visible; int list_sort_by_col_nr; LpgdvVal list_do_include_parent; LpgdvVal list_sort; LpgdvVal list_group; LpgdvSelectionChangedF selection_changed; LpgdvItemFocusedF item_focused; LpgdvItemClickedF item_clicked; const char* menu_title; LpgdvVal menu_rendering; const char* protocol_default; LpgdvBrowseSiteEnterF browse_site_enter; LpgdvBrowseSiteLeaveF browse_site_leave; LpgdvBrowseDirEnterF browse_dir_enter; LpgdvBrowseDirLeaveF browse_dir_leave; LpgdvBrowseItemNextF browse_item_next; /* Calculated params */ int menu_items_nb; int columns_nb;} Parameters;/*****************************************************************************//* This is the size of the buffer files are returned in */#define FILEMAX 512/* This is the maximum size of an individual file name */#define NAMEMAX 80/* All items in the file list box have this as their payload * value, so we can easily see if a widget is a file item. * * Files will be the only widgets with payloads in this dialog, * but the danger here is of outside widgets. In the event that * the file dialog is called from a toolbar app, the toolbar could * send events while the file picker is open. Normally toolbar widgets * will not have payloads, but if one has this payload it could really * confuse the file picker. */#define FILETAG 0x19840713/* Save the current directory across instances. */char dirview_dir[FILEMAX];/* Static buffer for returning the file name */char dirview_buf[FILEMAX];/* A structure for passing data to dirview_setdir */struct filepickdata { /* Important containers */ pghandle wDirectory; pghandle wFileList; pghandle wScroll; /* Filter parameters */ int flags; pgfilter filefilter; const char *pattern; /* Fonts */ pghandle fDirectory, fLink, fHeading; /* Selected file */ pghandle sFileName, wFile;};/* This is the type of node used to hold a directory entry */struct filenode { char name[NAMEMAX]; struct stat st;};static int dirview_filter(struct filepickdata *dat, const char *name, struct stat *st);static void dirview_fullpath(const char *file);static void dirview_setdir(struct filepickdata *dat);static int dirview_compare(const void *a, const void *b);static void dirview_pathmenu(struct filepickdata *dat);/*****************************************************************************/static int __indent = 0;#define INDENT { ++__indent; }#define UNINDENT { --__indent; }#define _PR(x...) fprintf(stderr, x)#define _I { int i; for(i=0;i<__indent;++i) _PR(" "); }#define _DOTS ". . . . . . . . . . . . . . . . . . . ."#define _OUTM(m,f,v) { _PR(_DOTS " " f "\r", v); _I; _PR(m " \n"); }#define DUMP(f,v) { _I; _PR(f, v); }#define DUMP_STR(s) _OUTM(#s, "[%s]", p->s ? p->s : "<null>");#define DUMP_NUM(s) _OUTM(#s, "%d", p->s);#define DUMP_FUNC(s) _OUTM(#s, "0x%08x", p->s);#define DUMP_VAL(s) _OUTM(#s, "%s", ValTxt[p->s]);static const char* ValTxt[] = { "NO", "YES", "MENU", "BUTTONS", "EDITABLE", "SORT", "RSORT", "TOGGLESORT", "DIRS_FIRST", "DIRS_LAST", "AT_LEFT", "AT_RIGHT", "AT_ABS", "AT_REL",};static voiddump_menuitemparams(int nb, MenuItemParams* p){ int i; DUMP("MenuItemParams:\n", 0); INDENT; for (i=0; i<nb; ++i) { DUMP ("[%d]\n", i); INDENT; DUMP_STR (name); DUMP_FUNC (menu_item_enable); DUMP_FUNC (menu_items_enable); UNINDENT; ++p; } UNINDENT;}dump_columnparams(int nb, ColumnParams* p){ int i; DUMP("ColumnParams:\n", 0); INDENT; for (i=0; i<nb; ++i) { DUMP ("[%d]\n", i); INDENT; DUMP_STR (name); DUMP_VAL (place_at); DUMP_FUNC (column_render); DUMP_NUM (payload); UNINDENT; ++p; } UNINDENT;}static voiddump_params(Parameters *p){ DUMP("Params:\n", 0); INDENT; DUMP_STR (title); DUMP_VAL (title_is_visible); DUMP_VAL (location_visible); DUMP_VAL (list_is_header_visible); DUMP_NUM (list_sort_by_col_nr); DUMP_VAL (list_do_include_parent); DUMP_VAL (list_sort); DUMP_VAL (list_group); DUMP_FUNC (selection_changed); DUMP_FUNC (item_focused); DUMP_FUNC (item_clicked); DUMP_STR (menu_title); DUMP_VAL (menu_rendering); DUMP_FUNC (protocol_default); DUMP_FUNC (browse_site_enter); DUMP_FUNC (browse_site_leave); DUMP_FUNC (browse_dir_enter); DUMP_FUNC (browse_dir_leave); DUMP_FUNC (browse_item_next); DUMP_NUM (menu_items_nb); DUMP_NUM (columns_nb); UNINDENT;}/*****************************************************************************//* This is just like pgMenuFromString, except that we use * '/' as the separator and the items are listed backwards, * ending with a bare '/'. When the user chooses a path, * set the dialog's directory */#if 0static voiddirview_pathmenu(struct filepickdata *dat){ char *p; char *items = dirview_dir; pghandle str; int ret; int i; struct stat st; /* Create the menu popup in its own context */ pgEnterContext(); pgNewPopupAt(PG_POPUP_ATCURSOR,PG_POPUP_ATCURSOR,0,0); /* Add helpful tools! */ pgNewWidget(PG_WIDGET_MENUITEM,0,0); pgSetWidget(PGDEFAULT, PG_WP_SIDE,PG_S_BOTTOM, PG_WP_TEXT,pgNewString("Rename"), 0); pgSetPayload(PGDEFAULT,-4); pgNewWidget(PG_WIDGET_MENUITEM,0,0); pgSetWidget(PGDEFAULT, PG_WP_SIDE,PG_S_BOTTOM, PG_WP_TEXT,pgNewString("Delete"), 0); pgSetPayload(PGDEFAULT,-3); pgNewWidget(PG_WIDGET_MENUITEM,0,0); pgSetWidget(PGDEFAULT, PG_WP_SIDE,PG_S_BOTTOM, PG_WP_TEXT,pgNewString("New Directory"), 0); pgSetPayload(PGDEFAULT,-2); pgNewWidget(PG_WIDGET_MENUITEM,0,0); pgSetWidget(PGDEFAULT, PG_WP_SIDE,PG_S_BOTTOM, PG_WP_TEXT,pgNewString("Home"), 0); pgSetPayload(PGDEFAULT,-1); pgNewWidget(PG_WIDGET_LABEL,0,0); pgSetWidget(PGDEFAULT, PG_WP_SIDE,PG_S_BOTTOM, PG_WP_TEXT,pgNewString("Tools:"), 0); pgSetPayload(PGDEFAULT,-4); /* Add directories above this one */ i=0; do { /* Do a little fancy stuff to make the string handle. * This is like pgNewString but we get to specify the * length instead of having strlen() do it for us. */ if (!(p = strchr(items,'/'))) p = items + strlen(items); if (p==items) str = pgNewString("/"); /* Root directory */ else { _pg_add_request(PGREQ_MKSTRING,(void *) items,p-items); pgFlushRequests(); str = _pg_return.e.retdata; } items = p+1; /* Ignore the last part, because that's the current directory */ if (*p) { /* Create each menu item */ pgNewWidget(PG_WIDGET_MENUITEM,0,0); pgSetWidget(PGDEFAULT, PG_WP_TEXT,str, PG_WP_SIDE,PG_S_BOTTOM, 0); /* Payload is the index in dirview_dir to set to zero * if it is chosen */ i = items-dirview_dir; if (i>1) i--; pgSetPayload(PGDEFAULT,i); } } while (*p); /* Run the menu */ ret = pgGetPayload(pgGetEvent()->from); pgLeaveContext(); if (ret>0) { dirview_dir[ret] = 0; dirview_setdir(dat); } else if (ret<0) { /* Update selected file string */ if (dat->wFile) dat->sFileName = pgGetWidget(dat->wFile,PG_WP_TEXT); switch (-ret) { case 1 : /* Home */ dirview_dir[FILEMAX-1] = 0; strncpy(dirview_dir,getenv("HOME"),FILEMAX-1); dirview_setdir(dat); break; case 2: /* New Directory */ str = pgInputDialog("New Directory", "Name:",0); if (str) { dirview_fullpath(pgGetString(str)); pgDelete(str); if (mkdir(dirview_buf,0777)) pgMessageDialogFmt("Error",0,"Unable to create directory:\n%s", dirview_buf); else dirview_setdir(dat); } break; case 3: /* Delete */ if (dat->sFileName) { dirview_fullpath(pgGetString(dat->sFileName)); if (pgMessageDialog("Delete?",dirview_buf, PG_MSGBTN_YES | PG_MSGBTN_NO) == PG_MSGBTN_YES) { if (unlink(dirview_buf)) pgMessageDialogFmt("Error",0,"Unable to delete file:\n%s", dirview_buf); else dirview_setdir(dat); } } break; case 4: /* Rename */ if (dat->sFileName) { dirview_fullpath(pgGetString(dat->sFileName)); str = pgInputDialog("Rename File",dirview_buf,0); if (str) { char *oldname, *newname; oldname = strdup(dirview_buf); newname = pgGetString(str); if (newname[0] != '/') { dirview_fullpath(newname); newname = dirview_buf; } if (rename(oldname,newname)) pgMessageDialogFmt("Error",0,"Unable to rename file:\n%s\nto\n%s", oldname,newname); else dirview_setdir(dat); free(oldname); } } break; } }}#endif/*****************************************************************************//* Sort the files, in a way that should make sense to users. * Directories always come before files. Case is ignored, punctuation * is ignored. If the file has a trailing number, the numbers are sorted * numerically. */static intdirview_compare(const void *a, const void *b){ struct filenode *f1 = (struct filenode *) a; struct filenode *f2 = (struct filenode *) b; const char *n1 = f1->name; const char *n2 = f2->name; int d1 = S_ISDIR(f1->st.st_mode); int d2 = S_ISDIR(f2->st.st_mode); /* If one is a directory and one is not, the directory comes first. */ if (d1!=d2) { if (d1) return -1; else return 1; } /* Now sort them pseudo-alphabetically */ while (*n1 && *n2) { /* Skip punctuation */ if (!isalnum(*n1)) { n1++; continue; } if (!isalnum(*n2)) { n2++; continue; } if (isdigit(*n1) && isdigit(*n2)) { /* If they are both numbers, sort them numerically */ char *p; int u1,u2; u1 = strtoul(n1,&p,10); n1 = p-1; u2 = strtoul(n2,&p,10); n2 = p-1; if (u1<u2) return -1; else if (u1>u2) return 1; } else { /* Nope, do a case-insensitive asciibetical sort */ char c1,c2; c1 = tolower(*n1); c2 = tolower(*n2); if (c1<c2) return -1; else if (c1>c2) return 1; } /* The same so far, so advance both */ n1++; n2++; } /* Compare length */ if (n1) return -1; if (n2) return 1; return 0;}/*****************************************************************************//* Utility that, given the current filters and flags, determines if a file * should be visible. Returns nonzero if the file should be visible. */static intdirview_filter(struct filepickdata *dat, const char *name, struct stat *st){ /* Normally ignore device nodes. Listing the contents of /dev could * be disasterous, especially on a low-memory machine */ if ((S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) && !(dat->flags & PG_FILE_SHOWDEV)) return 0; /* Normally we should hide files beginning with a dot */ if (*name=='.') { /* Is it '.' or '..' ? */ if (name[1]=='.' || !name[1]) { if (!(dat->flags & PG_FILE_SHOWDOT)) return 0; } else { if (!(dat->flags & PG_FILE_SHOWHIDDEN)) return 0; } } /* Always include directories if they pass the dot-test above */ if (S_ISDIR(st->st_mode)) return 1; /* Normally skip editor backups */ if (!(dat->flags & PG_FILE_SHOWBAK)) { char first, last; first = *name; last = name[strlen(name)-1]; if (last == '~') return 0; if (first == '#' && last == '#') return 0; } /* If there is no filter, just include the file */ if (!dat->filefilter)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -