diff --git a/efiler/Jamfile b/efiler/Jamfile index e4431ac..d6914fe 100644 --- a/efiler/Jamfile +++ b/efiler/Jamfile @@ -10,7 +10,7 @@ SubDir TOP efiler ; -SOURCE = Fl_Icon_Browser.cxx EDE_Browser.cpp EDE_DirTree.cpp Util.cpp efiler.cpp fileops.cpp filesystem.cpp Flu_Wrap_Group.cpp EDE_FileIconView.cpp EDE_FileDetailsView.cpp ede_strverscmp.c mailcap.cpp OpenWith.cpp ; +SOURCE = Fl_Icon_Browser.cxx EDE_Browser.cpp EDE_DirTree.cpp Util.cpp efiler.cpp fileops.cpp filesystem.cpp Flu_Wrap_Group.cpp EDE_FileIconView.cpp EDE_FileDetailsView.cpp ede_strverscmp.c mailcap.cpp OpenWith.cpp Properties.cpp ; LinkAgainst efiler : -lXpm ; diff --git a/efiler/OpenWith.cpp b/efiler/OpenWith.cpp index a8b6426..79de4a6 100644 --- a/efiler/OpenWith.cpp +++ b/efiler/OpenWith.cpp @@ -1,3 +1,19 @@ +/* + * $Id$ + * + * EFiler - EDE File Manager + * Part of Equinox Desktop Environment (EDE). + * Copyright (c) 2000-2007 EDE Authors. + * + * This program is licenced under terms of the + * GNU General Public Licence version 2 or newer. + * See COPYING for details. + */ + + +// Open with.. dialog window + + #include "OpenWith.h" #include @@ -18,18 +34,32 @@ #include #include "Util.h" +#include "mailcap.h" + + +#define DIALOG_WIDTH 410 +#define DIALOG_HEIGHT 145 + +// Callbacks void openwith_cancel_cb(Fl_Widget*w, void*) { Fl_Window* win = (Fl_Window*)w->parent(); win->hide(); } +// This function is friend of OpenWith because it needs to access +// _type and _file void openwith_ok_cb(Fl_Widget*w, void*i) { Fl_Input* inpt = (Fl_Input*)i; OpenWith* win = (OpenWith*)w->parent(); -fprintf(stderr, "Opening %s '%s'\n", inpt->value(), win->file()); - int k = edelib::run_program(tsprintf("%s '%s'", inpt->value(), win->file()), /*wait=*/false); -fprintf(stderr, "retval: %d\n", k); + + // Handle Always use... button + if (win->always_use->value() == 1) { + mailcap_add_type(win->_type, inpt->value()); + } + + // Run program + int k = edelib::run_program(tsprintf("%s '%s'", inpt->value(), win->_file), /*wait=*/false); win->hide(); } @@ -39,6 +69,7 @@ void openwith_browse_cb(Fl_Widget*w, void*i) { inpt->value(file); } +// Callback that handles "autocomplete" in the openwith dialog void program_input_cb(Fl_Widget*w, void*p) { edelib::list* progs = (edelib::list*)p; Fl_Input *inpt = (Fl_Input*)w; @@ -74,7 +105,10 @@ void program_input_cb(Fl_Widget*w, void*p) { } -OpenWith::OpenWith() : edelib::Window(410,110, _("Choose program")), _file(0) { +// OpenWith constructor +// Also initializes various internal arrays + +OpenWith::OpenWith() : edelib::Window(DIALOG_WIDTH, DIALOG_HEIGHT, _("Choose program")), _file(0) { set_modal(); edelib::list::iterator it1, it2; @@ -83,23 +117,64 @@ OpenWith::OpenWith() : edelib::Window(410,110, _("Choose program")), _file(0) { char pn[FL_PATH_MAX]; Fl_Group *gr; - Fl_Box *img, *txt; - Fl_Button *but1, *but2, *but3; + Fl_Box *img; Fl_Image* i; + Fl_Button *but1, *but2, *but3; - // Extract all parts of $PATH + // Window design + begin(); + img = new Fl_Box(10, 10, 60, 55); // icon + + // informative text + txt = new Fl_Box(75, 10, w()-85, 25); + txt->align(FL_ALIGN_INSIDE | FL_ALIGN_LEFT | FL_ALIGN_WRAP); + + // input for program name + inpt = new Fl_Input(75, 45, w()-175, 25); + inpt->when(FL_WHEN_ENTER_KEY_CHANGED); + inpt->callback(program_input_cb, &programs); + + // Ok & Cancel buttons + but1 = new Fl_Return_Button(w()-200, 115, 90, 25, "&Ok"); + but2 = new Fl_Button(w()-100, 115, 90, 25, "&Cancel"); + but1->callback(openwith_ok_cb,inpt); + but2->callback(openwith_cancel_cb); + + // Browse button + but3 = new Fl_Button(w()-90, 45, 80, 25, "&Browse..."); + but3->callback(openwith_browse_cb,inpt); + + // Always use this program... + always_use = new Fl_Check_Button(75, 80, w()-85, 20); + always_use->align(FL_ALIGN_INSIDE | FL_ALIGN_LEFT | FL_ALIGN_TOP | FL_ALIGN_WRAP); + + end(); + + // Set icon + if(edelib::IconTheme::inited()) { + i = Fl_Shared_Image::get(edelib::IconTheme::get("dialog-question", edelib::ICON_SIZE_MEDIUM).c_str()); + if(!i) return; + + img->image(i); + } + + + // -- Find all executables in $PATH and add them to programs list + + // Split $PATH at ':' character char* path = strdup(getenv("PATH")); // original would get destroyed char *pathpart = strtok(path, ":"); while (pathpart) { // Get list of files in pathpart - int size = scandir(pathpart, &files,0,alphasort); + int size = scandir(pathpart, &files, 0, alphasort); for (int i=0; id_name; snprintf (pn,FL_PATH_MAX-1,"%s/%s",pathpart,n); - if (stat(pn,&buf)) continue; + if (stat(pn,&buf)) continue; // some kind of error + + // Skip all directories and non-executables if (!S_ISDIR(buf.st_mode) && (buf.st_mode&S_IXOTH)) { - // Not directory, executable edelib::String name(n); it1=programs.begin(); it2=programs.end(); bool exists=false; @@ -109,39 +184,30 @@ OpenWith::OpenWith() : edelib::Window(410,110, _("Choose program")), _file(0) { } if (!exists) programs.push_back(n); } + Fl::check(); } pathpart=strtok(NULL, ":"); + Fl::check(); } free(path); programs.sort(); - // Window design - begin(); - img = new Fl_Box(10, 10, 60, 55); - - txt = new Fl_Box(75, 10, w()-85, 25, _("Enter the name of application to open this file with:")); - inpt = new Fl_Input(75, 40, w()-175, 25); - - txt->align(FL_ALIGN_INSIDE | FL_ALIGN_LEFT | FL_ALIGN_WRAP); - - but1 = new Fl_Return_Button(w()-200, 75, 90, 25, "&Ok"); - but2 = new Fl_Button(w()-100, 75, 90, 25, "&Cancel"); - but3 = new Fl_Button(w()-90, 40, 80, 25, "&Browse..."); - - end(); - - but1->callback(openwith_ok_cb,inpt); - but2->callback(openwith_cancel_cb); - but3->callback(openwith_browse_cb,inpt); - - inpt->when(FL_WHEN_ENTER_KEY_CHANGED); - inpt->callback(program_input_cb, &programs); - - // Set icon - if(!edelib::IconTheme::inited()) return; - i = Fl_Shared_Image::get(edelib::IconTheme::get("dialog-question", edelib::ICON_SIZE_MEDIUM).c_str()); - if(!i) return; - - img->image(i); +} + + +void OpenWith::show(const char* pfile, const char* ptype, const char* pcomment) { + _file=pfile; + _type=ptype; + + // Clear input box + inpt->value(""); + + // Set textual description + txt->copy_label(tsprintf(_("Enter the name of application used to open file \"%s\":"),fl_filename_name(pfile))); + + // Set "always use" label + always_use->copy_label(tsprintf(_("Always use this program for opening files of type \"%s\""), pcomment)); + + edelib::Window::show(); } diff --git a/efiler/OpenWith.h b/efiler/OpenWith.h index 680e36d..a3bb8ee 100644 --- a/efiler/OpenWith.h +++ b/efiler/OpenWith.h @@ -1,3 +1,19 @@ +/* + * $Id$ + * + * EFiler - EDE File Manager + * Part of Equinox Desktop Environment (EDE). + * Copyright (c) 2000-2007 EDE Authors. + * + * This program is licenced under terms of the + * GNU General Public Licence version 2 or newer. + * See COPYING for details. + */ + + +// Open with.. dialog window + + #ifndef _OpenWith_h_ #define _OpenWith_h_ @@ -7,20 +23,29 @@ #include #include +#include +#include class OpenWith : public edelib::Window { private: - const char* _file; + const char* _file, * _type; edelib::list programs; Fl_Input *inpt; + Fl_Check_Button *always_use; + Fl_Box *txt; public: + // Create and initialize OpenWith dialog OpenWith(); - const char* file() { return _file; } - void show(const char* pfile) { - _file=pfile; - inpt->value(""); - edelib::Window::show(); - } + + // Show openwith dialog for file + // - pfile - full path to file + // - ptype - mimetype string e.g. "text/plain" + // - pcomment - mimetype description e.g. "Plain text document" + void show(const char* pfile, const char* ptype, const char* pcomment); + + // This function is friend of OpenWith because it needs to access + // _type and _file + friend void openwith_ok_cb(Fl_Widget*w, void*i); }; diff --git a/efiler/Properties.cpp b/efiler/Properties.cpp new file mode 100644 index 0000000..3e6cdbe --- /dev/null +++ b/efiler/Properties.cpp @@ -0,0 +1,306 @@ +/* + * $Id$ * + * EFiler - EDE File Manager + * Part of Equinox Desktop Environment (EDE). + * Copyright (c) 2000-2007 EDE Authors. + * + * This program is licenced under terms of the + * GNU General Public Licence version 2 or newer. + * See COPYING for details. + */ + + +// File properties window + +#include "Properties.h" + +#include // for free() +#include // for snprintf() +#include // for stat +#include // for getuid() +#include // for getuid() +#include // for getpwent() +#include // for getgrent() + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Util.h" + +#define DIALOG_WIDTH 320 +#define DIALOG_HEIGHT 290 + + +void properties_cancel_cb(Fl_Widget*w, void*) { + Fl_Window* win = (Fl_Window*)w->parent(); + win->hide(); +} + +void properties_ok_cb(Fl_Widget*w, void*i) { +} + + +Properties::Properties(const char* file) : edelib::Window(DIALOG_WIDTH, DIALOG_HEIGHT, _("File properties")), _file(0) { + _file=file; + set_modal(); + + Fl_Tabs *tabs; + Fl_Group *file_tab, *perms_tab, *progs_tab; + Fl_Button *ok_but, *canc_but; + + // Create path from filename + int k=0; + char filename[FL_PATH_MAX], filepath[FL_PATH_MAX]; + strncpy(filename, file, FL_PATH_MAX-1); + + // Fix bug in fl_filename_name which returns nothing for directories + if (filename[strlen(filename)-1] == '/') { + filename[strlen(filename)-1]='\0'; + k--; + } + const char *tmp = fl_filename_name(filename); + strcpy(filename, tmp); + + k+=strlen(file)-strlen(filename); + if (k>FL_PATH_MAX-1) k=FL_PATH_MAX-1; + strncpy(filepath, file, k); + filepath[k]='\0'; + + + // Init mimetype + mime_resolver.set(file); + + // Stat for current file + bool stat_succeeded = false; // shouldn't happen + struct stat64 stat_buffer; + + const char* owner_name, *group_name; + bool can_rename=false, can_chown=false, can_chmod=false; + int is_executable=0, n_owner=0, n_group=0, n_others=0; + char mode_nr[5]; + + int owner_uid=0, owner_gid=0; + + int user_uid=getuid(), user_gid=getgid(); + if (user_uid==0) can_chown=true; // FIXME + + if (!stat64(file,&stat_buffer)) { + stat_succeeded = true; + owner_uid=stat_buffer.st_uid; + owner_gid=stat_buffer.st_gid; + if (owner_uid==user_uid || owner_gid==user_gid) can_chmod=true; + + // Read modes + mode_t mode = stat_buffer.st_mode; + int m0=0, m1=0, m2=0, m3=0; + if (mode&S_ISUID) m0+=4; if (mode&S_ISGID) m0+=2; if (mode&S_ISVTX) m0+=1; + if (mode&S_IRUSR) m1+=4; if (mode&S_IWUSR) m1+=2; if (mode&S_IXUSR) m1+=1; + if (mode&S_IRGRP) m2+=4; if (mode&S_IWGRP) m2+=2; if (mode&S_IXGRP) m2+=1; + if (mode&S_IROTH) m3+=4; if (mode&S_IWOTH) m3+=2; if (mode&S_IXOTH) m3+=1; + snprintf(mode_nr, 5, "%d%d%d%d", m0, m1, m2, m3); + + // Simple modes + if (mode&S_IXUSR) is_executable=1; + if (mode&S_IWUSR) n_owner=0; + else if (mode&S_IRUSR) n_owner=1; + else n_owner=2; + if (mode&S_IWGRP) n_group=0; + else if (mode&S_IRGRP) n_group=1; + else n_group=2; + if (mode&S_IWOTH) n_others=0; + else if (mode&S_IROTH) n_others=1; + else n_others=2; + } + + // Stat for directory the file resides in + // Needed to see if user can rename file + if (!stat64(filepath,&stat_buffer)) { + if (stat_buffer.st_uid==user_uid || stat_buffer.st_gid==user_gid) + can_rename=true; + } + + // List of users + char *users_list[65535]; + for (int i=0; i<65535; i++) users_list[i]=0; + struct passwd *pwent; + setpwent(); + while (pwent = getpwent()) { + if (strcmp(pwent->pw_gecos, pwent->pw_name)==0 || strlen(pwent->pw_gecos)<2) // just one char + users_list[pwent->pw_uid] = strdup(pwent->pw_name); + else + users_list[pwent->pw_uid] = tasprintf("%s (%s)", pwent->pw_gecos, pwent->pw_name); + } + endpwent(); + + // List of groups + char *groups_list[65535]; + for (int i=0; i<65535; i++) groups_list[i]=0; + struct group *grent; + setgrent(); + while (grent = getgrent()) { + groups_list[grent->gr_gid] = strdup(grent->gr_name); + } + endgrent(); + + // Window design + begin(); + tabs = new Fl_Tabs(0, 2, w(), h()-37); + tabs->begin(); + file_tab = new Fl_Group(0, 25, w(), h()-60, _("&File")); + file_tab->begin(); + // Icon + if(edelib::IconTheme::inited()) { + Fl_Box *img = new Fl_Box(10, 35, 65, 65); + //img->box(FL_DOWN_BOX); + edelib::String icon = edelib::IconTheme::get(mime_resolver.icon_name().c_str(), edelib::ICON_SIZE_HUGE); + if (icon=="") icon = edelib::IconTheme::get("empty", edelib::ICON_SIZE_HUGE, edelib::ICON_CONTEXT_MIMETYPE); + Fl_Image *i = Fl_Shared_Image::get(icon.c_str()); + if(i) img->image(i); + } else { + // Icon theme doesn't work!? + } + + // Filename + { + Fl_Input *fn = new Fl_Input (135, 35, w()-145, 20, _("Name:")); + if (!can_rename) { + // filename->deactivate(); + // this looks prettier + fn->readonly(1); + fn->color(FL_BACKGROUND_COLOR); + } + fn->value(filename); + } + + // Directory + { + Fl_Input *fp = new Fl_Input (135, 65, w()-145, 20, _("Location:")); + fp->readonly(1); + fp->color(FL_BACKGROUND_COLOR); + fp->value(filepath); + } + + // Size + { + Fl_Box *sizelabel = new Fl_Box (75, 95, 60, 20, _("Size:")); + sizelabel->align (FL_ALIGN_RIGHT | FL_ALIGN_INSIDE); + + Fl_Box *sizebox = new Fl_Box (135, 95, w()-145, 20); + sizebox->align (FL_ALIGN_LEFT | FL_ALIGN_INSIDE); + if (stat_succeeded) sizebox->copy_label(nice_size(stat_buffer.st_size)); + } + + // Type + { + Fl_Box *typelabel = new Fl_Box (10, 125, 60, 20, _("Type:")); + typelabel->align (FL_ALIGN_RIGHT | FL_ALIGN_INSIDE); + + Fl_Box *typebox = new Fl_Box (70, 125, w()-80, 20); + typebox->align (FL_ALIGN_LEFT | FL_ALIGN_INSIDE); + typebox->copy_label(mime_resolver.type().c_str()); + + Fl_Box *ntypebox = new Fl_Box (70, 145, w()-80, 20); + ntypebox->align (FL_ALIGN_LEFT | FL_ALIGN_INSIDE); + ntypebox->copy_label(mime_resolver.comment().c_str()); + } + file_tab->end(); + + perms_tab = new Fl_Group(0, 25, w(), h()-60, _("&Permissions")); + perms_tab->begin(); + // Owner + { + Fl_Choice *owner = new Fl_Choice (70, 35, w()-80, 20, _("Owner:")); + int j=-1, selected=0; + for (int i=0; i<65535; i++) + if (users_list[i]!=0) { + owner->add(users_list[i]); + free(users_list[i]); + j++; + if (i==owner_uid) selected=j; + } + owner->value(selected); + if (!can_chown) owner->deactivate(); + } + + // Group + { + Fl_Choice *group = new Fl_Choice (70, 65, w()-80, 20, _("Group:")); + int j=-1, selected=0; + for (int i=0; i<65535; i++) + if (groups_list[i]!=0) { + group->add(groups_list[i]); + free(groups_list[i]); + j++; + if (i==owner_gid) selected=j; + } + group->value(selected); + if (!can_chown) group->deactivate(); + } + + // Executable + { + Fl_Check_Button *exec = new Fl_Check_Button (10, 95, w()-20, 20, _("File is an executable program")); + if (stat_succeeded) exec->value(is_executable); + if (!can_chmod) exec->deactivate(); + } + + // Owner permissions + { + Fl_Choice *ownerperms = new Fl_Choice (135, 125, w()-145, 20, _("Owner permissions:")); + ownerperms->add(_("Read and write")); + ownerperms->add(_("Read only")); + ownerperms->add(_("Not allowed")); + if (stat_succeeded) ownerperms->value(n_owner); + if (!can_chmod) ownerperms->deactivate(); + } + + // Group permissions + { + Fl_Choice *groupperms = new Fl_Choice (135, 155, w()-145, 20, _("Group permissions:")); + groupperms->add(_("Read and write")); + groupperms->add(_("Read only")); + groupperms->add(_("Not allowed")); + if (stat_succeeded) groupperms->value(n_group); + if (!can_chmod) groupperms->deactivate(); + } + + // Others permissions + { + Fl_Choice *otherperms = new Fl_Choice (135, 185, w()-145, 20, _("Others permissions:")); + otherperms->add(_("Read and write")); + otherperms->add(_("Read only")); + otherperms->add(_("Not allowed")); + if (stat_succeeded) otherperms->value(n_others); + if (!can_chmod) otherperms->deactivate(); + } + + // Number + // TODO: move to advanced window + { + Fl_Input *modenr = new Fl_Input (135, 215, 50, 20, _("Mode number:")); + if (stat_succeeded) modenr->value(mode_nr); + if (!can_chmod) modenr->deactivate(); + } + perms_tab->end(); + + progs_tab = new Fl_Group(0, 25, w(), h()-60, _("P&rograms")); + progs_tab->begin(); + progs_tab->end(); + tabs->end(); + + // Ok & Cancel buttons + ok_but = new Fl_Return_Button(w()-200, h()-30, 90, 25, "&Ok"); + canc_but = new Fl_Button(w()-100, h()-30, 90, 25, "&Cancel"); + ok_but->callback(properties_ok_cb); + canc_but->callback(properties_cancel_cb); + end(); +} diff --git a/efiler/Properties.h b/efiler/Properties.h new file mode 100644 index 0000000..02faf09 --- /dev/null +++ b/efiler/Properties.h @@ -0,0 +1,40 @@ +/* + * $Id$ + * + * EFiler - EDE File Manager + * Part of Equinox Desktop Environment (EDE). + * Copyright (c) 2000-2007 EDE Authors. + * + * This program is licenced under terms of the + * GNU General Public Licence version 2 or newer. + * See COPYING for details. + */ + + +// File properties window + + +#ifndef _Properties_h_ +#define _Properties_h_ + +#include +#include + +class Properties : public edelib::Window { +private: + const char* _file; + + // We need our own MIME resolver + edelib::MimeType mime_resolver; + +public: + // Construct window + Properties(const char* file); + + // This function is friend of Properties because it needs to access + // values of various widgets + friend void properties_ok_cb(Fl_Widget*w, void*i); + +}; + +#endif diff --git a/efiler/efiler.cpp b/efiler/efiler.cpp index 172b85d..e0bc8b7 100644 --- a/efiler/efiler.cpp +++ b/efiler/efiler.cpp @@ -46,6 +46,7 @@ #include "EDE_DirTree.h" // directory tree #include "Util.h" // ex-edelib #include "OpenWith.h" // Open with... window +#include "Properties.h" // File properties window #include "fileops.h" // file operations #include "filesystem.h" // filesystem support @@ -496,12 +497,22 @@ void file_open(const char* path, MailcapAction action) { fprintf (stderr, "run_program: %s\n", o2); if (action == MAILCAP_EXEC) { + // File is an executable - run it int k=edelib::run_program(path,false); fprintf(stderr, "Xretval: %d\n", k); + } else if (mime_resolver.type() == "application/x-executable") { + edelib::MessageBox mb; + mb.label(_("Warning")); + mb.set_text(tsprintf(_("The file \"%s\" is an executable program. Do you want to run it? It might be a virus!"), fl_filename_name(path))); + mb.set_theme_icon(MSGBOX_ICON_WARNING); + mb.add_button(_("&No"), edelib::MSGBOX_BUTTON_RETURN); + mb.add_button(_("&Yes")); + int k=mb.run(); + if (k==1) k=edelib::run_program(path,false); fprintf(stderr, "Xretval: %d\n", k); } else if (opener) { int k=edelib::run_program(o2,false); fprintf(stderr, "retval: %d\n", k); } else { - statusbar->copy_label(tsprintf(_("No program to open %s! (%s)"), fl_filename_name(path),mime_resolver.type().c_str())); - // TODO: open with... + // Show "Open with..." dialog + ow->show(path, mime_resolver.type().c_str(), mime_resolver.type().c_str()); } if (dumpcore_on_exec) { @@ -549,11 +560,9 @@ void open_cb(Fl_Widget*w, void*data) { // TODO: make a list of openers etc. void openwith_cb(Fl_Widget*, void*) { -// const char* app = edelib::input(_("Enter the name of application to open this file with:")); const char* file = view->path(view->get_focus()); -// edelib::run_program(tsprintf("%s '%s'", app, file), /*wait=*/false); - ow->show(file); -// Fl::run(); + mime_resolver.set(file); + ow->show(file, mime_resolver.type().c_str(), mime_resolver.comment().c_str()); } // File > New (efiler window) @@ -681,7 +690,11 @@ void fileprint_cb(Fl_Widget*w, void*) { void fileexec_cb(Fl_Widget*w, void*) { file_open(view->path(view->get_focus()), MAILCAP_EXEC); } -void pref_cb(Fl_Widget*, void*) { fprintf(stderr, "callback\n"); } +void props_cb(Fl_Widget*, void*) { + Properties p(view->path(view->get_focus())); + p.show(); + Fl::run(); +} // Other options use callbacks from Edit menu @@ -926,7 +939,7 @@ Fl_Menu_Item main_menu_definition[] = { {_("&Paste"), FL_CTRL+'v', paste_cb}, {_("&Rename"), FL_F+2, viewrename_cb}, {_("&Delete"), FL_Delete, delete_cb, 0, FL_MENU_DIVIDER}, - {_("Pre&ferences"), FL_CTRL+'p', pref_cb}, + {_("Prop&erties"), FL_CTRL+'p', props_cb}, {0}, {_("&View"), 0, 0, 0, FL_SUBMENU}, @@ -969,13 +982,13 @@ Fl_Menu_Item context_menu_definition[] = { {_("&View"), 0, open_cb, 0, FL_MENU_INVISIBLE}, {_("&Edit"), 0, fileedit_cb, 0, FL_MENU_INVISIBLE}, {_("Pri&nt"), 0, fileprint_cb, 0, FL_MENU_INVISIBLE}, - {_("&Execute"), 0, fileexec_cb, 0,FL_MENU_INVISIBLE}, - {_("Open &with..."), 0, openwith_cb, 0,FL_MENU_DIVIDER}, + {_("&Execute"), 0, fileexec_cb, 0, FL_MENU_INVISIBLE}, + {_("Open &with..."), 0, openwith_cb, 0, FL_MENU_DIVIDER}, {_("&Cut"), 0, cut_cb}, {_("Co&py"), 0, copy_cb}, {_("Pa&ste"), 0, paste_cb}, - {_("&Delete"), 0, delete_cb, 0,FL_MENU_DIVIDER}, - {_("P&references..."), 0, pref_cb}, + {_("&Delete"), 0, delete_cb, 0, FL_MENU_DIVIDER}, + {_("P&roperties..."), 0, props_cb}, {0} }; diff --git a/efiler/mailcap.cpp b/efiler/mailcap.cpp index f0f1f1c..7b6fcb4 100644 --- a/efiler/mailcap.cpp +++ b/efiler/mailcap.cpp @@ -35,6 +35,13 @@ // FIXME: use favourite aplications to find xterm #define TERM "xterm" +// Maximum length of a mailcap spec +// We could've used edelib::String, but that would make the whole code a bit +// more complex and a bit slower. There's no reason for mailcap to be longer +// (very long mailcaps are usually application specific codes /e.g. mutt/ which +// isn't useful to us) +#define BUFLEN 1000 + using namespace edelib; // this drastically shortened the code... @@ -45,9 +52,15 @@ struct mime_db_struct { String createcmd; String editcmd; String printcmd; + bool copiousoutput; + bool needsterminal; }; + +// database of parsed mimetypes list mime_db; + +// flag to know if we should call read_files() bool files_read=false; @@ -56,9 +69,9 @@ bool files_read=false; String mailcap_sanitize(char* cmd, String type) { String s(cmd); - unsigned int k = s.find("%s"); // no %s, file should be piped to stdin + unsigned int k = s.find("%s"); if (k==String::npos) { s = String("cat '%s' | ") + cmd; } else { @@ -87,12 +100,14 @@ String mailcap_sanitize(char* cmd, String type) { s = s.substr(0,k) + s.substr(k+2); k = s.find("}",k); // there has to be a closing brace... if (k!=String::npos) s= s.substr(0,k) + s.substr(k+1); - k = s.find("%{",k); + k = s.find("%{",k+1); } return s; } +// Read contents of mailcap file + void read_files() { list locations; @@ -100,7 +115,7 @@ void read_files() { stringtok(locations, String(MAILCAPS), ":"); // Add ~/.mailcap locations.push_front(dir_home() + E_DIR_SEPARATOR_STR".mailcap"); - // Add EDE specific configuracion + // Add EDE specific configuration if (user_config_dir() != "") locations.push_front(user_config_dir() + E_DIR_SEPARATOR_STR"ede"E_DIR_SEPARATOR_STR"mailcap"); @@ -120,8 +135,8 @@ void read_files() { // Read data into list FILE *fp = fopen(mailcap,"r"); - char buffer[1000]; - while (fgets(buffer,1000,fp)) { + char buffer[BUFLEN]; + while (fgets(buffer,BUFLEN,fp)) { str_trim(buffer); if (buffer[0]=='#') continue; //comment if (strlen(buffer)<2) continue; //empty line @@ -129,15 +144,15 @@ void read_files() { // Multiline if (buffer[strlen(buffer)-1]=='\\') { String line(buffer); //Use string for dynamic growing - while (fgets(buffer,1000,fp)) { + while (fgets(buffer,BUFLEN,fp)) { str_trim(buffer); line = line.substr(0, line.length()-2); line += buffer; if (buffer[strlen(buffer)-1]!='\\') break; } - // Use just the first 1000 chars - strncpy(buffer, line.c_str(), 1000); - buffer[999]='\0'; + // Use just the first BUFLEN chars + strncpy(buffer, line.c_str(), BUFLEN); + buffer[BUFLEN]='\0'; } // Parse line @@ -146,20 +161,20 @@ void read_files() { str_trim(tmp); parsed.type=tmp; - // View command + // Command for viewing file tmp = strtok(NULL, ";"); if (tmp==NULL) continue; // no view cmd in line!? str_trim(tmp); parsed.viewcmd = mailcap_sanitize(tmp, parsed.type); // Other mailcap parameters... + parsed.copiousoutput = parsed.needsterminal = false; while ((tmp = strtok(NULL, ";")) != NULL) { str_trim(tmp); if (strcmp(tmp,"copiousoutput")==0) - // TODO: use enotepad? when it supports stdin - parsed.viewcmd = TERM" -e \"" + parsed.viewcmd + " | less\""; + parsed.copiousoutput = true; else if (strcmp(tmp,"needsterminal")==0) - parsed.viewcmd = TERM" -e \"" + parsed.viewcmd + "\""; + parsed.needsterminal = true; else if (strncmp(tmp,"compose=",8)==0) parsed.createcmd = mailcap_sanitize(tmp+8, parsed.type); else if (strncmp(tmp,"print=",6)==0) @@ -175,6 +190,9 @@ void read_files() { files_read=true; } + +// Helper function for searching mime_db + list::iterator find_type(const char* type) { list::iterator it = mime_db.begin(), it_end = mime_db.end(); for(; it != it_end; ++it) @@ -189,17 +207,36 @@ list::iterator find_type(const char* type) { return 0; } + +// Command for performing given action on files of given type + const char* mailcap_opener(const char* type, MailcapAction action) { + static char buffer[BUFLEN]; if (!files_read) read_files(); list::iterator it = find_type(type); if (it==0) return 0; - if (action==MAILCAP_VIEW) return (*it).viewcmd.c_str(); + + if (action==MAILCAP_VIEW) { + const char *c = (*it).viewcmd.c_str(); + if ((*it).copiousoutput) { + // copiousoutput and needsterminal are mutually exclusive [2] + // TODO: use enotepad? - when it supports stdin + snprintf(buffer, BUFLEN-1, TERM" -e \"%s\" | less", c); + } else if ((*it).needsterminal) { + snprintf(buffer, BUFLEN-1, TERM" -e \"%s\"", c); + } else return c; + return buffer; + } + else if (action==MAILCAP_CREATE) return (*it).createcmd.c_str(); else if (action==MAILCAP_EDIT) return (*it).editcmd.c_str(); else if (action==MAILCAP_PRINT) return (*it).printcmd.c_str(); else return 0; } + +// List of actions available for given type + int mailcap_actions(const char* type) { if (!files_read) read_files(); list::iterator it = find_type(type); @@ -211,3 +248,62 @@ int mailcap_actions(const char* type) { if ((*it).printcmd != "") result += MAILCAP_PRINT; return result; } + + +// Add a new opener for given type +// NOTE: This will be written in EDE-specific location. If EDE-specific +// mailcap file doesn't exist, it will be created and existing mimetypes +// will be added. Thus, users of EDE will get a new type in addition to +// old ones, however other applications will not. + +void mailcap_add_type(const char* type, const char* opener) { + // Find old type, edit if it exists + list::iterator it = find_type(type); + if (it != 0) { + (*it).viewcmd = opener; + (*it).viewcmd += " %s"; + } else { + mime_db_struct newtype; + newtype.type = type; + newtype.viewcmd = opener; + newtype.viewcmd += " %s"; + newtype.createcmd = ""; + newtype.editcmd = ""; + newtype.printcmd = ""; + newtype.copiousoutput = false; + newtype.needsterminal = false; + mime_db.push_back(newtype); + } + + // Write mime_db contents to file + String ede_mailcap = user_config_dir() + E_DIR_SEPARATOR_STR"ede"E_DIR_SEPARATOR_STR"mailcap"; + FILE *fp = fopen(ede_mailcap.c_str(),"w"); + + it = mime_db.begin(); + list::iterator it_end = mime_db.end(); + while (it != it_end) { + char buffer[BUFLEN]; + snprintf(buffer, BUFLEN-1, "%s; %s", (*it).type.c_str(), (*it).viewcmd.c_str()); + if ((*it).createcmd != "") { + strncat(buffer, "; compose=", BUFLEN-1-strlen(buffer)); + strncat(buffer, (*it).createcmd.c_str(), BUFLEN-1-strlen(buffer)); + } + if ((*it).editcmd != "") { + strncat(buffer, "; edit=", BUFLEN-1-strlen(buffer)); + strncat(buffer, (*it).editcmd.c_str(), BUFLEN-1-strlen(buffer)); + } + if ((*it).printcmd != "") { + strncat(buffer, "; print=", BUFLEN-1-strlen(buffer)); + strncat(buffer, (*it).printcmd.c_str(), BUFLEN-1-strlen(buffer)); + } + if (strlen(buffer)>BUFLEN-2) + // Even if line is too long, we don't want to mangle the next one + buffer[strlen(buffer)-1]='\n'; + else + strcat(buffer, "\n"); + fputs(buffer, fp); + ++it; + } + fclose(fp); + +} diff --git a/efiler/mailcap.h b/efiler/mailcap.h index a64b81f..8f3c271 100644 --- a/efiler/mailcap.h +++ b/efiler/mailcap.h @@ -28,7 +28,6 @@ enum MailcapAction { // with filename. Parameter MAILCAP_TYPE indicates the type of action to // be performed - const char* mailcap_opener(const char* type, MailcapAction action=MAILCAP_VIEW); @@ -37,4 +36,9 @@ const char* mailcap_opener(const char* type, MailcapAction action=MAILCAP_VIEW); int mailcap_actions(const char* type); +// Add new type to the list of actions + +void mailcap_add_type(const char* type, const char* opener); + + #endif