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