1 /*
2   FXiTe - The Free eXtensIble Text Editor
3   Copyright (c) 2009-2011 Jeffrey Pohlmeyer <yetanothergeek@gmail.com>
4 
5   This program is free software; you can redistribute it and/or modify it
6   under the terms of the GNU General Public License version 3 as
7   published by the Free Software Foundation.
8 
9   This software is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13 
14   You should have received a copy of the GNU General Public License
15   along with this program; if not, write to the Free Software
16   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18 
19 
20 
21 #include <cctype>
22 #include <cerrno>
23 #include <sys/stat.h>
24 
25 #ifdef WIN32
26 # define lstat stat
27 # include <io.h>
28 #else
29 # include <unistd.h>
30 #endif
31 
32 #include <lua.hpp>
33 #include <fx.h>
34 #include <fxkeys.h>
35 
36 #include "intl.h"
37 #include "luafx.h"
38 
39 #if LUA_VERSION_NUM<502
40 # define lua_rawlen lua_objlen
41 #endif
42 
43 static FXWindow*main_window=NULL;
44 
45 static const char*default_title;
46 
47 #ifdef WIN32
48 
49 #include <windows.h>
50 
SystemErrorStr(DWORD * e=NULL)51 static const char* SystemErrorStr(DWORD *e=NULL)
52 {
53   DWORD code=e?*e:GetLastError();
54   static TCHAR lpMsgBuf[512];
55   lpMsgBuf[0]=0;
56   FormatMessage(
57       FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
58       NULL, code, MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), lpMsgBuf, sizeof(lpMsgBuf), NULL);
59   lpMsgBuf[sizeof(lpMsgBuf)-1]=0;
60   for (char*p=(char*)lpMsgBuf; *p; p++) { if (*p=='\r') { *p=' '; } }
61   return (const char*)lpMsgBuf;
62 }
63 
64 #else
65 
SystemErrorStr(int * e=NULL)66 static const char* SystemErrorStr(int *e=NULL)
67 {
68   return strerror(e?*e:errno);
69 }
70 
71 #endif
72 
ArgErrFmt(lua_State * L,int numarg,const char * fmt,...)73 static int ArgErrFmt(lua_State *L, int numarg, const char *fmt, ...)
74 {
75   FXString s;
76   va_list args;
77   va_start(args,fmt);
78   s.vformat(fmt,args);
79   va_end(args);
80   return luaL_argerror(L,numarg,s.text());
81 }
82 
83 
84 
85 // Limit dialog message text to 32 lines, maximum of 96 chars per line.
fixup_message(FXString & dst,const char * src)86 static void fixup_message(FXString &dst, const char*src)
87 {
88 #define msg_max_width 96
89 #define msg_max_height 32
90   char line[msg_max_width+1];
91   const char*p1=src;
92   const char*p2;
93   int numlines=0;
94   do {
95     p2=strchr(p1, '\n');
96     int width = p2 ? (p2-p1) : strlen(p1);
97     memset(line,0,sizeof(line));
98     strncpy(line, p1, width>msg_max_width?msg_max_width:width);
99     dst.append(line);
100     if (p2) { dst.append('\n'); } else { return; }
101     if (++numlines >= msg_max_height) { return; }
102     p1=p2+1;
103   } while (1);
104 }
105 
106 
107 
108 // void message([title,] message [,icon])
message(lua_State * L)109 static int message(lua_State*L)
110 {
111   const char* title=default_title;
112   const char* msg;
113   FXString fmsg;
114   const char* type="I";
115 
116   switch (lua_gettop(L)) {
117     case 0:
118     case 1: {
119       msg=luaL_checkstring(L,1);
120       break;
121     }
122     default: {
123       title=luaL_checkstring(L,1);
124       msg=luaL_checkstring(L,2);
125       type=luaL_optstring(L,3,"I");
126     }
127   }
128   fixup_message(fmsg, msg);
129   switch (toupper(type[0])) {
130     case 'W' : { FXMessageBox::warning(     main_window, MBOX_OK, title, "%s", fmsg.text()); break; }
131     case 'E' : { FXMessageBox::error(       main_window, MBOX_OK, title, "%s", fmsg.text()); break; }
132     default  : { FXMessageBox::information( main_window, MBOX_OK, title, "%s", fmsg.text()); break; }
133   }
134   return 0;
135 }
136 
137 
138 
139 // bool confirm(title, question,default)
confirm(lua_State * L)140 static int confirm(lua_State*L)
141 {
142   luaL_argcheck(L,(lua_gettop(L)>=3)&&lua_isboolean(L,3),3,_("expected boolean"));
143   const char* title=lua_isnil(L,1)?default_title:luaL_checkstring(L,1);
144   const char* msg=luaL_checkstring(L,2);
145   FXString fmsg;
146   bool deflt=lua_toboolean(L,3);
147   fixup_message(fmsg, msg);
148   FXuint rv=FXMessageBox::question(main_window, MBOX_YES_NO, title, "%s", fmsg.text());
149   lua_pushboolean(L, rv==MBOX_CLICKED_YES?true:rv==MBOX_CLICKED_NO?false:deflt);
150   return 1;
151 }
152 
153 
154 
155 // string input([title,] message [,default [,style]])
input(lua_State * L)156 static int input(lua_State*L)
157 {
158   const char* title=default_title;
159   const char* msg="";
160   FXString fmsg;
161   FXString txt="";
162   const char* type="S";
163   FXint opt=INPUTDIALOG_STRING;
164   switch (lua_gettop(L)) {
165     case 0:
166     case 1: {
167       msg=luaL_checkstring(L,1);
168       break;
169     }
170     default: {
171       title=luaL_checkstring(L,1);
172       msg=luaL_checkstring(L,2);
173       txt=luaL_optstring(L,3,"");
174       type=luaL_optstring(L,4,"S");
175     }
176   }
177   switch (toupper(type[0])) {
178     case 'I' : {
179       if (!lua_isnil(L,3)) {
180         txt.format("%ld", luaL_checkinteger(L,3));
181       }
182       opt=INPUTDIALOG_INTEGER;
183       break;
184     }
185     case 'R' : {
186       if (!lua_isnil(L,3)) { txt.format("%g", luaL_checknumber(L,3)); }
187       opt=INPUTDIALOG_REAL;
188       break;
189     }
190     case 'P' : {
191       opt=INPUTDIALOG_PASSWORD;
192       break;
193     }
194     default  : {
195       break;
196     }
197   }
198   fixup_message(fmsg, msg);
199   FXInputDialog dlg(main_window,title,fmsg.text(),NULL,opt);
200   dlg.setText(txt);
201   if (dlg.execute(PLACEMENT_OWNER)) {
202     lua_pushstring(L,dlg.getText().text());
203   } else {
204     lua_pushnil(L);
205   }
206   return 1;
207 }
208 
209 
210 
211 class PickList:public FXList {
212   FXDECLARE(PickList);
PickList()213   PickList(){}
214 public:
onPicked(FXObject * o,FXSelector sel,void * p)215   long onPicked(FXObject*o, FXSelector sel, void*p) {
216     switch (FXSELTYPE(sel)) {
217       case SEL_DOUBLECLICKED: { break; }
218       case SEL_KEYPRESS: {
219         FXint code=((FXEvent*)p)->code;
220         if ((code==KEY_Return)||(code==KEY_KP_Enter)) { break; }
221       }
222       default: return 0;
223     }
224     ((FXDialogBox*)getParent())->handle(this, FXSEL(SEL_COMMAND,FXMessageBox::ID_ACCEPT),NULL);
225     return 1;
226   }
PickList(FXComposite * p)227   PickList(FXComposite*p):FXList(p, this,FXList::ID_LAST,LAYOUT_FILL_X|LIST_BROWSESELECT){}
228 };
229 
230 
231 
232 FXDEFMAP(PickList) ClickListMap[] = {
233   FXMAPFUNC(SEL_DOUBLECLICKED, FXList::ID_LAST, PickList::onPicked),
234   FXMAPFUNC(SEL_KEYPRESS, FXList::ID_LAST, PickList::onPicked),
235 };
236 
FXIMPLEMENT(PickList,FXList,ClickListMap,ARRAYNUMBER (ClickListMap))237 FXIMPLEMENT(PickList, FXList, ClickListMap, ARRAYNUMBER(ClickListMap))
238 
239 
240 
241 // string choose([title,] message, {items})
242 static int choose(lua_State*L) {
243   int argmsg=1;
244   int argtbl=2;
245   FXString title=default_title;
246   if (lua_gettop(L)>2) {
247     title=luaL_checkstring(L,1);
248     argmsg++;
249     argtbl++;
250   }
251   const char*msg=luaL_checkstring(L,argmsg);
252   FXString fmsg;
253   int i,n;
254   luaL_argcheck(L, lua_istable(L,argtbl), argtbl, _("table expected") );
255   n=lua_rawlen(L,argtbl);
256   luaL_argcheck(L, n>0, argtbl, _("table can't be empty"));
257   for (i=1;i<=n; i++) {
258     lua_rawgeti(L,argtbl,i);
259     if (!lua_isstring(L, -1)) {
260       ArgErrFmt(L,argtbl,_("table element #%d is not a string"), i);
261     }
262     lua_pop(L, 1);
263   }
264   FXDialogBox dlg(main_window, title);
265   fixup_message(fmsg, msg);
266   new FXLabel(&dlg, fmsg.text());
267   FXList*list=new PickList(&dlg);
268   list->setNumVisible(n<12?n:12);
269   for (i=1;i<=n; i++) {
270     lua_rawgeti(L,argtbl,i);
271     list->appendItem(lua_tostring(L, -1));
272     lua_pop(L, 1);
273   }
274   FXHorizontalFrame*btns=new FXHorizontalFrame(&dlg,FRAME_NONE|LAYOUT_FILL|PACK_UNIFORM_WIDTH);
275   FXButton*ok=new FXButton(btns, _("&OK"), NULL, &dlg,FXDialogBox::ID_ACCEPT);
276   new FXButton(btns, _(" &Cancel "), NULL, &dlg,FXDialogBox::ID_CANCEL);
277   ok->setDefault(true);
278   list->setFocus();
279   dlg.create();
280   int want_width=list->getContentWidth()+list->verticalScrollBar()->getWidth()+dlg.getPadLeft()*2;
281   if (want_width<dlg.getDefaultWidth()) { want_width=dlg.getDefaultWidth(); }
282   int max_width=main_window->getApp()->getRootWindow()->getWidth()*0.75;
283   dlg.setWidth(want_width>max_width?max_width:want_width);
284   if ( dlg.execute(PLACEMENT_OWNER) ) {
285     lua_pushstring(L,list->getItemText(list->getCurrentItem()).text());
286   } else {
287     lua_pushnil(L);
288   }
289   return 1;
290 }
291 
292 
293 //pickfile(mode[[,path][,filter]])
pickfile(lua_State * L)294 static int pickfile(lua_State*L)
295 {
296   const char*modes[]={"open", "save", "dir", NULL};
297   int mode=luaL_checkoption(L,1,modes[0], modes);
298   FXString pathstr=FXSystem::getCurrentDirectory()+PATHSEPSTRING;
299   const char*path=luaL_optstring(L,2,pathstr.text());
300   const char*patt=luaL_optstring(L,3,_("All files (*)"));
301   if (path && !*path) { path=pathstr.text(); }
302   if (patt) {
303     FXFileDialog dlg(main_window, "");
304     if (mode!=2) {
305       if (FXStat::isDirectory(path)) {
306         dlg.setDirectory(path);
307       } else {
308         pathstr=FXPath::simplify(FXPath::absolute(path));
309         if (FXStat::isDirectory(pathstr)) {
310           dlg.setDirectory(pathstr);
311         } else {
312           if (FXStat::isDirectory(FXPath::directory(pathstr))) {
313             dlg.setFilename(FXPath::name(pathstr));
314             dlg.setDirectory(FXPath::directory(pathstr));
315           } else {
316             FXMessageBox::warning( main_window, MBOX_OK, _("No such directory"), "%s:\n%s",
317                _("Specified path does not exist"),
318                (FXPath::directory(pathstr)+PATHSEPSTRING).text()
319             );
320             dlg.setDirectory(FXSystem::getCurrentDirectory());
321           }
322         }
323       }
324     }
325 
326     dlg.setPatternList(patt);
327     bool rv=false;
328     switch (mode) {
329       case 0:{
330         dlg.setSelectMode(SELECTFILE_EXISTING);
331         dlg.setTitle(_("Open file"));
332         rv=dlg.execute(PLACEMENT_OWNER);
333         break;
334       }
335       case 1:{
336         dlg.setSelectMode(SELECTFILE_ANY);
337         dlg.setTitle(_("Save file as"));
338         while (dlg.execute(PLACEMENT_OWNER)) {
339           if (FXStat::exists(dlg.getFilename())) {
340             if (FXMessageBox::question(main_window, MBOX_YES_NO, _("Overwrite?"),
341                  "%s:\n%s\n\n%s", _("File exists"), dlg.getFilename().text(),
342                  _("Do you want to replace it?")
343                  )==MBOX_CLICKED_YES
344                )
345             {
346               rv=true;
347               break;
348             }
349           } else {
350             rv=true;
351             break;
352           }
353         }
354         break;
355       }
356       case 2: {
357         FXDirDialog dir(main_window, _("Select Directory"));
358         dir.setHeight(420);
359         if ( FXStat::exists(path) && !FXStat::isDirectory(path) ) {
360           dir.setDirectory(FXPath::directory(path));
361         } else {
362           dir.setDirectory(FXSystem::getCurrentDirectory()+PATHSEP);
363         }
364         rv=dir.execute(PLACEMENT_OWNER);
365         if (rv) { dlg.setFilename(dir.getDirectory()); } // Pass result to file dialog.
366         break;
367       }
368     }
369     if (rv) { lua_pushstring(L, dlg.getFilename().text()); } else { lua_pushnil(L); }
370     return 1;
371   } else {
372     return 0;
373   }
374 }
375 
376 
377 // function basename( pathstr ) -- Extract the filename portion of a path string.
basename(lua_State * L)378 static int basename(lua_State*L)
379 {
380   const char*s=luaL_checkstring(L,1);
381   lua_pushstring(L, FXPath::name(s).text());
382   return 1;
383 }
384 
385 
386 
387 // function dirname( pathstr ) -- Get the directory portion of a file's path.
dirname(lua_State * L)388 static int dirname(lua_State*L)
389 {
390   const char*s=luaL_checkstring(L,1);
391   lua_pushstring(L, FXPath::directory(s).text());
392   return 1;
393 }
394 
395 
396 
397 // function fullpath( filename ) -- Get the full path to a file.
fullpath(lua_State * L)398 static int fullpath(lua_State*L)
399 {
400   if (lua_gettop(L)>=2) {
401     const char*dn=luaL_checkstring(L,1);
402     const char*fn=luaL_checkstring(L,2);
403     lua_pushstring(L, FXPath::simplify(FXPath::absolute(dn,fn)).text());
404   } else {
405     const char*fn=luaL_checkstring(L,1);
406     lua_pushstring(L, FXPath::simplify(FXPath::absolute(fn)).text());
407   }
408   return 1;
409 }
410 
411 
412 
dirsep(lua_State * L)413 static int dirsep(lua_State*L)
414 {
415   lua_pushstring(L, PATHSEPSTRING);
416   return 1;
417 }
418 
419 
420 
421 #define SetTableValue(name,value,pusher) \
422   lua_pushstring(L, name); \
423   pusher(L, value); \
424   lua_rawset(L,-3);
425 
426 #define SetTableStr(name,value) SetTableValue(name,value,lua_pushstring)
427 #define SetTableBool(name,value) SetTableValue(name,value,lua_pushboolean)
428 #define SetTableNum(name,value) SetTableValue(name,(lua_Number)value,lua_pushnumber)
429 
430 
431 
432 typedef int (*statfunc) (const char *fn, struct stat *st);
433 
_stat(lua_State * L)434 static int _stat(lua_State* L)
435 {
436   statfunc sf=stat;
437   const char*fn=NULL;
438   struct stat st;
439   if (lua_gettop(L)>=2) {
440     luaL_argcheck(L, lua_isboolean(L,2), 2, _("expected boolean"));
441     sf=lua_toboolean(L,2)?lstat:stat;
442   }
443   fn=luaL_checkstring(L,1);
444   if (sf(fn,&st)==0) {
445     const char *ft=NULL;
446     switch ( st.st_mode & S_IFMT) {
447       case S_IFBLK:ft="b"; break;
448       case S_IFCHR:ft="c"; break;
449       case S_IFDIR:ft="d"; break;
450       case S_IFIFO:ft="p"; break;
451       case S_IFREG:ft="f"; break;
452       #ifndef WIN32
453       case S_IFLNK:ft="l"; break;
454       case S_IFSOCK:ft="s"; break;
455       #endif
456     }
457     lua_newtable(L);
458     SetTableNum("size",st.st_size);
459     SetTableNum("time",st.st_mtime);
460     SetTableStr("type",ft);
461     SetTableBool("read", (access(fn,R_OK)==0));
462     SetTableBool("write", (access(fn,W_OK)==0));
463     SetTableBool("exec", (access(fn,X_OK)==0));
464     return 1;
465   }
466   lua_pushnil(L);
467   lua_pushstring(L, SystemErrorStr());
468   return 2;
469 }
470 
471 
472 
473 // function wkdir ( [folder] ) -- Get or set the current working directory.
wkdir(lua_State * L)474 static int wkdir(lua_State* L)
475 {
476   if (lua_gettop(L)==0) {
477     lua_pushstring(L, FXSystem::getCurrentDirectory().text());
478     return 1;
479   } else {
480     const char*dn=luaL_checkstring(L,1);
481     if (FXSystem::setCurrentDirectory(dn)) {
482       lua_pushboolean(L, true);
483       return 1;
484     } else {
485       lua_pushboolean(L, false);
486       lua_pushstring(L, SystemErrorStr());
487       return 2;
488     }
489   }
490 }
491 
492 
493 
494 // function mkdir(path,parented) -- create a directory, create full path if parented
mkdir(lua_State * L)495 static int mkdir(lua_State* L)
496 {
497   const char*path=luaL_checkstring(L,1);
498   int parented = (lua_gettop(L)>=2) ? lua_toboolean(L,2) : false;
499   if (parented) {
500     FXString parts=FXPath::absolute(path);
501     FXString dirs=FXPath::root(parts);
502     parts.append(PATHSEP);
503     FXint nseps=parts.contains(PATHSEP);
504     FXint i;
505     for (i=1;i<nseps;i++) {
506       dirs.append(parts.section(PATHSEP,i));
507       if (!(FXStat::isDirectory(dirs)||FXDir::create(dirs,FXIO::OwnerFull))) {
508         lua_pushboolean(L, false);
509         lua_pushstring(L, SystemErrorStr());
510         return 2;
511       }
512       dirs.append(PATHSEP);
513     }
514     lua_pushboolean(L, true);
515     return 1;
516   } else {
517     if (FXDir::create(path)) {
518       lua_pushboolean(L, true);
519       return 1;
520     } else {
521       lua_pushboolean(L, false);
522       lua_pushstring(L, SystemErrorStr());
523       return 2;
524     }
525   }
526 }
527 
528 
529 
530 #ifdef FOX_1_6
dirlist_closure(lua_State * L)531 static int dirlist_closure(lua_State *L)
532 {
533   FXDir*dir=(FXDir*)lua_touserdata(L,lua_upvalueindex(1));
534   if (dir->next()) {
535     lua_pushstring(L,dir->name().text());
536     return 1;
537   } else {
538     dir->close();
539     delete dir;
540     return 0;
541   }
542 }
543 #else
dirlist_closure(lua_State * L)544 static int dirlist_closure(lua_State *L)
545 {
546   FXDir*dir=(FXDir*)lua_touserdata(L,lua_upvalueindex(1));
547   FXString name;
548   if (dir->next(name)) {
549     lua_pushstring(L,name.text());
550     return 1;
551   } else {
552     dir->close();
553     delete dir;
554     return 0;
555   }
556 }
557 #endif
558 
559 
560 
561 //  function dirlist( path ) -- List the contents of a folder.
dirlist(lua_State * L)562 static int dirlist(lua_State* L)
563 {
564   const char*dn=luaL_optstring(L,1, ".");
565   FXDir *dir=new FXDir(dn);
566   if (!dir->isOpen()) {
567 #ifdef WIN32
568     DWORD e=GetLastError();
569 #else
570     int e=errno;
571 #endif
572     delete dir;
573     return luaL_argerror(L,1,SystemErrorStr(&e));
574   }
575   lua_pushlightuserdata(L,dir);
576   lua_pushcclosure(L,&dirlist_closure,1);
577   return 1;
578 }
579 
580 
581 
window(lua_State * L)582 static int window(lua_State* L)
583 {
584   lua_pushnumber(L,(lua_Number)((FXuval)main_window->id()));
585   return(1);
586 }
587 
588 
589 
pid(lua_State * L)590 static int pid(lua_State* L)
591 {
592 #if (FOX_MAJOR>1) || \
593     ( (FOX_MAJOR==1) && (FOX_MINOR>7) ) || \
594     ( (FOX_MAJOR==1) && (FOX_MINOR==7) && (FOX_LEVEL>26) )
595   lua_pushnumber(L,FXProcess::current());
596 #else
597   lua_pushnumber(L,fxgetpid());
598 #endif
599 
600   return(1);
601 }
602 
603 
604 
605 
606 static const struct luaL_Reg fx_util_funcs[] = {
607   {"message", message},
608   {"confirm", confirm},
609   {"input", input},
610   {"choose", choose},
611   {"pickfile", pickfile},
612   {"basename", basename},
613   {"dirname", dirname},
614   {"fullpath", fullpath},
615   {"dirsep", dirsep},
616   {"stat", _stat},
617   {"wkdir", wkdir},
618   {"mkdir", mkdir},
619   {"dirlist", dirlist},
620   {"window", window},
621   {"pid", pid},
622   {NULL,NULL}
623 };
624 
625 
626 
LuaFxUtils(FXWindow * topwin,const char * exe_name)627 const luaL_Reg* LuaFxUtils(FXWindow*topwin, const char*exe_name)
628 {
629   default_title=exe_name;
630   if (!main_window) { main_window=topwin; }
631   return fx_util_funcs;
632 }
633 
634