1 // Helper functions for various purposes
2 // Some helper functions are also added to get large files support
3 // Also supports a timeout on the stat and lstat function (this is the reason
4 // why some standard FOX function cannot be used and are rewritten here)
5 
6 
7 #include "config.h"
8 #include "i18n.h"
9 
10 
11 #include <sys/time.h>
12 #include <sys/wait.h>
13 #include <time.h>
14 #include <libgen.h>
15 #include <sys/statvfs.h>
16 
17 #include <fx.h>
18 #include <fxkeys.h>
19 #include <FXPNGIcon.h>
20 
21 #include "xfedefs.h"
22 #include "icons.h"
23 #include "xfeutils.h"
24 #include "../st/x.h"
25 
26 
27 // Scaling factors for the UI
28 FXint scaleint;
29 double scalefrac;
30 
31 
32 // Get available space on the file system where the file is located
GetAvailableSpace(const FXString & filepath)33 FXlong GetAvailableSpace(const FXString& filepath)
34 {
35     struct statvfs stat;
36 
37     if (statvfs(filepath.text(), &stat) != 0)
38     {
39         // An error has occurred
40         return -1;
41     }
42 
43     // The available size is f_bsize * f_bavail
44     return stat.f_bsize * stat.f_bavail;
45 }
46 
47 // Decode filename to get original again
dequote(const FXString & file)48 FXString FXPath::dequote(const FXString& file)
49 {
50     FXString result(file);
51 
52     if (0 < result.length())
53     {
54         register int e = result.length();
55         register int b = 0;
56         register int r = 0;
57         register int q = 0;
58 
59         // Trim tail
60         while (0 < e && Ascii::isSpace(result[e-1]))
61         {
62             --e;
63         }
64 
65         // Trim head
66         while (b < e && Ascii::isSpace(result[b]))
67         {
68             ++b;
69         }
70 
71         // Dequote the rest
72         while (b < e)
73         {
74             if (result[b] == '\'')
75             {
76                 q = !q;
77                 b++;
78                 continue;
79             }
80             if ((result[b] == '\\') && (result[b+1] == '\'') && !q)
81             {
82                 b++;
83             }
84             result[r++] = result[b++];
85         }
86 
87         // Trunc to size
88         result.trunc(r);
89     }
90     return(result);
91 }
92 
93 
94 // Note : the original function from FXAccelTable is buggy!!
95 // Parse accelerator from string
_parseAccel(const FXString & string)96 FXHotKey _parseAccel(const FXString& string)
97 {
98     register FXuint code = 0, mods = 0;
99     register int    pos = 0;
100 
101     // Parse leading space
102     while (pos < string.length() && Ascii::isSpace(string[pos]))
103     {
104         pos++;
105     }
106 
107     // Parse modifiers
108     while (pos < string.length())
109     {
110         // Modifier
111         if (comparecase(&string[pos], "ctl", 3) == 0)
112         {
113             mods |= CONTROLMASK;
114             pos += 3;
115         }
116         else if (comparecase(&string[pos], "ctrl", 4) == 0)
117         {
118             mods |= CONTROLMASK;
119             pos += 4;
120         }
121         else if (comparecase(&string[pos], "alt", 3) == 0)
122         {
123             mods |= ALTMASK;
124             pos += 3;
125         }
126         else if (comparecase(&string[pos], "meta", 4) == 0)
127         {
128             mods |= METAMASK;
129             pos += 4;
130         }
131         else if (comparecase(&string[pos], "shift", 5) == 0)
132         {
133             mods |= SHIFTMASK;
134             pos += 5;
135         }
136         else
137         {
138             break;
139         }
140 
141         // Separator
142         if ((string[pos] == '+') || (string[pos] == '-') || Ascii::isSpace(string[pos]))
143         {
144             pos++;
145         }
146     }
147 
148     // Test for some special keys
149     if (comparecase(&string[pos], "home", 4) == 0)
150     {
151         code = KEY_Home;
152     }
153     else if (comparecase(&string[pos], "end", 3) == 0)
154     {
155         code = KEY_End;
156     }
157     else if (comparecase(&string[pos], "pgup", 4) == 0)
158     {
159         code = KEY_Page_Up;
160     }
161     else if (comparecase(&string[pos], "pgdn", 4) == 0)
162     {
163         code = KEY_Page_Down;
164     }
165     else if (comparecase(&string[pos], "left", 4) == 0)
166     {
167         code = KEY_Left;
168     }
169     else if (comparecase(&string[pos], "right", 5) == 0)
170     {
171         code = KEY_Right;
172     }
173     else if (comparecase(&string[pos], "up", 2) == 0)
174     {
175         code = KEY_Up;
176     }
177     else if (comparecase(&string[pos], "down", 4) == 0)
178     {
179         code = KEY_Down;
180     }
181     else if (comparecase(&string[pos], "ins", 3) == 0)
182     {
183         code = KEY_Insert;
184     }
185     else if (comparecase(&string[pos], "del", 3) == 0)
186     {
187         code = KEY_Delete;
188     }
189     else if (comparecase(&string[pos], "esc", 3) == 0)
190     {
191         code = KEY_Escape;
192     }
193     else if (comparecase(&string[pos], "tab", 3) == 0)
194     {
195         code = KEY_Tab;
196     }
197     else if (comparecase(&string[pos], "return", 6) == 0)
198     {
199         code = KEY_Return;
200     }
201     else if (comparecase(&string[pos], "enter", 5) == 0)
202     {
203         code = KEY_Return;
204     }
205     else if (comparecase(&string[pos], "back", 4) == 0)
206     {
207         code = KEY_BackSpace;
208     }
209     else if (comparecase(&string[pos], "spc", 3) == 0)
210     {
211         code = KEY_space;
212     }
213     else if (comparecase(&string[pos], "space", 5) == 0)
214     {
215         code = KEY_space;
216     }
217 
218     // Test for function keys
219     else if ((Ascii::toLower(string[pos]) == 'f') && Ascii::isDigit(string[pos+1]))
220     {
221         if (Ascii::isDigit(string[pos+2]))
222         {
223             // !!!! Hack to fix a bug in FOX !!!!
224             code = KEY_F1+10*(string[pos+1]-'0')+(string[pos+2]-'0')-1;
225             // !!!! End of hack !!!!
226         }
227         else
228         {
229             code = KEY_F1+string[pos+1]-'1';
230         }
231     }
232     // Test if hexadecimal code designator
233     else if (string[pos] == '#')
234     {
235         code = strtoul(&string[pos+1], NULL, 16);
236     }
237 
238     // Test if its a single character accelerator
239     else if (Ascii::isPrint(string[pos]))
240     {
241         if (mods&SHIFTMASK)
242         {
243             code = Ascii::toUpper(string[pos])+KEY_space-' ';
244         }
245         else
246         {
247             code = Ascii::toLower(string[pos])+KEY_space-' ';
248         }
249     }
250     return(MKUINT(code, mods));
251 }
252 
253 
254 // Find if the specified command exists
existCommand(const FXString cmd)255 FXbool existCommand(const FXString cmd)
256 {
257     struct stat linfo;
258 
259     // Command file path
260     FXString cmdpath = cmd.before(' ');
261 
262     // If first character is '/' then cmdpath is an absolute path
263     if (cmdpath[0] == PATHSEPCHAR)
264     {
265         // Check if command file name exists and is not a directory
266         if (!cmdpath.empty() && (lstatrep(cmdpath.text(), &linfo) == 0) && !S_ISDIR(linfo.st_mode))
267         {
268             return(true);
269         }
270     }
271 
272     // If first character is '~' then cmdpath is a path relative to home directory
273     else if (cmdpath[0] == '~')
274     {
275         // Form command absolute path
276         cmdpath = FXSystem::getHomeDirectory() + cmdpath.after('~');
277 
278         // Check if command file name exists and is not a directory
279         if (!cmdpath.empty() && (lstatrep(cmdpath.text(), &linfo) == 0) && !S_ISDIR(linfo.st_mode))
280         {
281             return(true);
282         }
283     }
284 
285     // Simple command name or path relative to the exec path
286     else
287     {
288         // Get exec path
289         FXString execpath = FXSystem::getExecPath();
290 
291         if (execpath != "")
292         {
293 			// Number of delimiters
294 			int nbseps = execpath.contains(':');
295 
296             // Loop over path components
297             for (int i = 0; i <= nbseps; i++)
298             {
299                 // Obtain path component
300                 FXString path = execpath.section(':', i);
301 
302                 // Form command absolute path
303                 path += PATHSEPSTRING + cmdpath;
304 
305                 // Check if command file name exists and is not a directory
306                 if (!path.empty() && (lstatrep(path.text(), &linfo) == 0) && !S_ISDIR(linfo.st_mode))
307                 {
308                     return(true);
309                 }
310             }
311         }
312     }
313 
314     return(false);
315 }
316 
317 
318 // Get key binding string from user input
319 // Code adapted from FXAccelTable::unparseAccel() and modified to get strings like 'Ctrl-A' instead of 'ctrl+a'
getKeybinding(FXEvent * event)320 FXString getKeybinding(FXEvent* event)
321 {
322     // Get modifiers and key
323     int mods = event->state;
324     int code = event->code;
325 
326     char     buffer[64];
327     FXString s;
328 
329     // Handle modifier keys
330     if (mods&CONTROLMASK)
331     {
332         s += "Ctrl-";
333     }
334     if (mods&ALTMASK)
335     {
336         s += "Alt-";
337     }
338     if (mods&SHIFTMASK)
339     {
340         s += "Shift-";
341     }
342     if (mods&METAMASK)
343     {
344         s += "Meta-";
345     }
346 
347     // Handle some special keys
348     switch (code)
349     {
350     case KEY_Home:
351         s += "Home";
352         break;
353 
354     case KEY_End:
355         s += "End";
356         break;
357 
358     case KEY_Page_Up:
359         s += "PgUp";
360         break;
361 
362     case KEY_Page_Down:
363         s += "PgDn";
364         break;
365 
366     case KEY_Left:
367         s += "Left";
368         break;
369 
370     case KEY_Right:
371         s += "Right";
372         break;
373 
374     case KEY_Up:
375         s += "Up";
376         break;
377 
378     case KEY_Down:
379         s += "Down";
380         break;
381 
382     case KEY_Insert:
383         s += "Ins";
384         break;
385 
386     case KEY_Delete:
387         s += "Del";
388         break;
389 
390     case KEY_Escape:
391         s += "Esc";
392         break;
393 
394     case KEY_Tab:
395         s += "Tab";
396         break;
397 
398     case KEY_Return:
399         s += "Return";
400         break;
401 
402     case KEY_BackSpace:
403         s += "Back";
404         break;
405 
406     case KEY_space:
407         s += "Space";
408         break;
409 
410     case KEY_F1:
411     case KEY_F2:
412     case KEY_F3:
413     case KEY_F4:
414     case KEY_F5:
415     case KEY_F6:
416     case KEY_F7:
417     case KEY_F8:
418     case KEY_F9:
419     case KEY_F10:
420     case KEY_F11:
421     case KEY_F12:
422     case KEY_F13:
423     case KEY_F14:
424     case KEY_F15:
425     case KEY_F16:
426     case KEY_F17:
427     case KEY_F18:
428     case KEY_F19:
429     case KEY_F20:
430     case KEY_F21:
431     case KEY_F22:
432     case KEY_F23:
433     case KEY_F24:
434     case KEY_F25:
435     case KEY_F26:
436     case KEY_F27:
437     case KEY_F28:
438     case KEY_F29:
439     case KEY_F30:
440     case KEY_F31:
441     case KEY_F32:
442     case KEY_F33:
443     case KEY_F34:
444     case KEY_F35:
445         snprintf(buffer, sizeof(buffer)-1, "F%d", code-KEY_F1+1);
446         s += buffer;
447         break;
448 
449     default:
450         if (Ascii::isPrint(code))
451         {
452             s += Ascii::toUpper(code);
453         }
454         else
455         {
456             s = "";  // Invalid case
457         }
458         break;
459     }
460 
461     return(s);
462 }
463 
464 
465 // Create a directory with its path, like 'mkdir -p'
466 // Return 0 if success or -1 if fail
467 // Original author : Niall O'Higgins */
468 // http://niallohiggins.com/2009/01/08/mkpath-mkdir-p-alike-in-c-for-unix
mkpath(const char * s,mode_t mode)469 int mkpath(const char* s, mode_t mode)
470 {
471     char* q, *r = NULL, *path = NULL, *up = NULL;
472     int   rv;
473 
474     rv = -1;
475     if ((strcmp(s, ".") == 0) || (strcmp(s, "/") == 0))
476     {
477         return(0);
478     }
479 
480     if ((path = strdup(s)) == NULL)
481     {
482         exit(EXIT_FAILURE);
483     }
484 
485     if ((q = strdup(s)) == NULL)
486     {
487         exit(EXIT_FAILURE);
488     }
489 
490     if ((r = (char*)dirname(q)) == NULL)
491     {
492         goto out;
493     }
494 
495     if ((up = strdup(r)) == NULL)
496     {
497         exit(EXIT_FAILURE);
498     }
499 
500     if ((mkpath(up, mode) == -1) && (errno != EEXIST))
501     {
502         goto out;
503     }
504 
505     if ((mkdir(path, mode) == -1) && (errno != EEXIST))
506     {
507         rv = -1;
508     }
509     else
510     {
511         rv = 0;
512     }
513 
514 out:
515     if (up != NULL)
516     {
517         free(up);
518     }
519     free(q);
520     free(path);
521     return(rv);
522 }
523 
524 
525 // Obtain a unique trash files path name based on the file path name
createTrashpathname(FXString pathname,FXString trashfileslocation)526 FXString createTrashpathname(FXString pathname, FXString trashfileslocation)
527 {
528     // Initial trash files path name
529     FXString trashpathname = trashfileslocation+PATHSEPSTRING+FXPath::name(pathname);
530 
531     // Eventually modify the trash files path name by adding a suffix like '_1', '_2', etc.,
532     // if the file already exists in the trash can files directory
533     for (int i = 1; ; i++)
534     {
535         if (existFile(trashpathname))
536         {
537             char suffix[32];
538             snprintf(suffix, sizeof(suffix)-1, "_%d", i);
539             FXString prefix = trashpathname.rbefore('_');
540             if (prefix == "")
541             {
542                 prefix = trashpathname;
543             }
544             trashpathname = prefix+suffix;
545         }
546         else
547         {
548             break;
549         }
550     }
551 
552     return(trashpathname);
553 }
554 
555 
556 // Create trashinfo file based on the pathname and the trashpathname
createTrashinfo(FXString pathname,FXString trashpathname,FXString trashfileslocation,FXString trashinfolocation)557 int createTrashinfo(FXString pathname, FXString trashpathname, FXString trashfileslocation, FXString trashinfolocation)
558 {
559     // Create trash can files if it doesn't exist
560     if (!existFile(trashfileslocation))
561     {
562         int mask = umask(0);
563         umask(mask);
564         int ret = mkpath(trashfileslocation.text(), 511 & ~mask);
565         return(ret);
566     }
567 
568     // Create trash can info if it doesn't exist
569     if (!existFile(trashinfolocation))
570     {
571         int mask = umask(0);
572         umask(mask);
573         int ret = mkpath(trashinfolocation.text(), 511 & ~mask);
574         return(ret);
575     }
576 
577     // Deletion date
578     struct timeval tv;
579     gettimeofday(&tv, NULL);
580     FXString deldate = FXSystem::time("%FT%T", tv.tv_sec);
581 
582     // Trash info path name
583     FXString trashinfopathname = trashinfolocation+PATHSEPSTRING+FXPath::name(trashpathname)+".trashinfo";
584 
585     // Create trash info file
586     FILE* fp;
587     int   ret;
588     if ((fp = fopen(trashinfopathname.text(), "w")) != NULL)
589     {
590         fprintf(fp, "[Trash Info]\n");
591         fprintf(fp, "Path=%s\n", pathname.text());
592         fprintf(fp, "DeletionDate=%s\n", deldate.text());
593         fclose(fp);
594         ret = 0;
595     }
596     else
597     {
598         ret = -1;
599     }
600 
601     return(ret);
602 }
603 
604 
605 // Return mime type of a file
606 // Makes use of the Unix file command, thus this function may be slow
mimetype(FXString pathname)607 FXString mimetype(FXString pathname)
608 {
609     FXString cmd = "/usr/bin/file -b -i " + pathname;
610     FILE*    filecmd = popen(cmd.text(), "r");
611 
612     if (!filecmd)
613     {
614         perror("popen");
615         exit(EXIT_FAILURE);
616     }
617     char     text[128] = { 0 };
618     FXString buf;
619     while (fgets(text, sizeof(text), filecmd))
620     {
621         buf += text;
622     }
623     pclose(filecmd);
624 
625     return(buf.rbefore('\n'));
626 }
627 
628 
629 // Quote a filename against shell substitutions
630 // Thanks to Glynn Clements <glynnc@users.sourceforge.net>
quote(FXString str)631 FXString quote(FXString str)
632 {
633     FXString    result = "'";
634     const char* p;
635 
636     for (p = str.text(); *p; p++)
637     {
638         if (*p == '\'')
639         {
640             result += "'\\''";
641         }
642         else
643         {
644             result += *p;
645         }
646     }
647 
648     result += '\'';
649     return(result);
650 }
651 
652 
653 // Test if a string is encoded in UTF-8
654 // "length" is the number of bytes of the string to consider
655 // Taken from the weechat project. Original author FlashCode <flashcode@flashtux.org>
isUtf8(const char * string,FXuint length)656 FXbool isUtf8(const char* string, FXuint length)
657 {
658     FXuint n = 0;
659 
660     while (n < length)
661     {
662         // UTF-8, 2 bytes, should be: 110vvvvv 10vvvvvv
663         if (((FXuchar)(string[0]) & 0xE0) == 0xC0)
664         {
665             if (!string[1] || (((FXuchar)(string[1]) & 0xC0) != 0x80))
666             {
667                 return(false);
668             }
669             string += 2;
670             n += 2;
671         }
672         // UTF-8, 3 bytes, should be: 1110vvvv 10vvvvvv 10vvvvvv
673         else if (((FXuchar)(string[0]) & 0xF0) == 0xE0)
674         {
675             if (!string[1] || !string[2] ||
676                 (((FXuchar)(string[1]) & 0xC0) != 0x80) ||
677                 (((FXuchar)(string[2]) & 0xC0) != 0x80))
678             {
679                 return(false);
680             }
681             string += 3;
682             n += 3;
683         }
684         // UTF-8, 4 bytes, should be: 11110vvv 10vvvvvv 10vvvvvv 10vvvvvv
685         else if (((FXuchar)(string[0]) & 0xF8) == 0xF0)
686         {
687             if (!string[1] || !string[2] || !string[3] ||
688                 (((FXuchar)(string[1]) & 0xC0) != 0x80) ||
689                 (((FXuchar)(string[2]) & 0xC0) != 0x80) ||
690                 (((FXuchar)(string[3]) & 0xC0) != 0x80))
691             {
692                 return(false);
693             }
694             string += 4;
695             n += 4;
696         }
697         // UTF-8, 1 byte, should be: 0vvvvvvv
698         else if ((FXuchar)(string[0]) >= 0x80)
699         {
700             return(false);
701         }
702         // Next byte
703         else
704         {
705             string++;
706             n++;
707         }
708     }
709     return(true);
710 }
711 
712 
713 #if defined(linux)
714 // Stat function used to test if a mount point is up or down
715 // Actually, this is simply the lstat() function
lstatmt(const char * filename,struct stat * buf)716 int lstatmt(const char* filename, struct stat* buf)
717 {
718     return(lstat(filename, buf));
719 }
720 #endif
721 
722 
723 #if !defined (__OpenBSD__)
724 // Safe strcpy function (Public domain, by C.B. Falconer)
725 // The destination string is always null terminated
726 // Size sz must be equal to strlen(src)+1
strlcpy(char * dst,const char * src,size_t sz)727 size_t strlcpy(char* dst, const char* src, size_t sz)
728 {
729     const char* start = src;
730 
731     if (src && sz--)
732     {
733         while ((*dst++ = *src))
734         {
735             if (sz--)
736             {
737                 src++;
738             }
739             else
740             {
741                 *(--dst) = '\0';
742                 break;
743             }
744         }
745     }
746     if (src)
747     {
748         while (*src++)
749         {
750             continue;
751         }
752         return(src - start - 1);
753     }
754     else if (sz)
755     {
756         *dst = '\0';
757     }
758     return(0);
759 }
760 
761 // Safe strcat function (Public domain, by C.B. Falconer)
762 // The destination string is always null terminated
strlcat(char * dst,const char * src,size_t sz)763 size_t strlcat(char* dst, const char* src, size_t sz)
764 {
765     char* start = dst;
766 
767     while (*dst++)      // assumes sz >= strlen(dst)
768     {
769         if (sz)
770         {
771             sz--;          // i.e. well formed string
772         }
773     }
774     dst--;
775     return(dst - start + strlcpy(dst, src, sz));
776 }
777 #endif
778 
779 // Obtain the non recursive size of a directory
dirsize(const char * path)780 FXulong dirsize(const char* path)
781 {
782     DIR* dp;
783     struct dirent* dirp;
784     struct stat statbuf;
785     char buf[MAXPATHLEN];
786     FXulong dsize = 0;
787     int ret;
788 
789     if ((dp = opendir(path)) == NULL)
790     {
791         return(0);
792     }
793 
794     while ((dirp = readdir(dp)))
795     {
796         if (streq(dirp->d_name, ".") || streq(dirp->d_name, ".."))
797         {
798             continue;
799         }
800 
801         if (streq(path, ROOTDIR))
802         {
803             snprintf(buf, sizeof(buf)-1, "%s%s", path, dirp->d_name);
804         }
805         else
806         {
807             snprintf(buf, sizeof(buf)-1, "%s/%s", path, dirp->d_name);
808         }
809 
810 #if defined(linux)
811         // Mount points are not processed to improve performances
812         if (mtdevices->find(buf))
813         {
814             continue;
815         }
816 #endif
817 
818         ret = lstatrep(buf, &statbuf);
819         if (ret == 0)
820         {
821             if (!S_ISDIR(statbuf.st_mode))
822             {
823                 dsize += (FXulong)statbuf.st_size;
824             }
825         }
826     }
827     if (closedir(dp) < 0)
828     {
829         fprintf(stderr, _("Error: Can't close folder %s\n"), path);
830     }
831     return(dsize);
832 }
833 
834 
835 // Obtain the recursive size of a directory
836 // The number of files and the number of sub directories is also stored in the nbfiles and nbsubdirs pointers
837 // Caution: this only works if nbfiles and nbsubdirs are initialized to 0 in the calling function
838 // After that, nbfiles contains the total number of files (including the count of sub directories),
839 // nbsubdirs contains the number of sub directories and totalsize the total directory size
840 // The pipes are used to write partial results, for inter process communication
pathsize(char * path,FXuint * nbfiles,FXuint * nbsubdirs,FXulong * totalsize,int pipes[2])841 FXulong pathsize(char* path, FXuint* nbfiles, FXuint* nbsubdirs, FXulong *totalsize, int pipes[2])
842 {
843     struct stat statbuf;
844     struct dirent* dirp;
845     char* ptr;
846     DIR* dp;
847     FXulong dsize;
848     int ret;
849 
850 	char buf[256];
851 
852     ret = lstatrep(path, &statbuf);
853     if (ret < 0)
854     {
855         return(0);
856     }
857     dsize = (FXulong)statbuf.st_size;
858 	(*totalsize) += dsize;
859     (*nbfiles)++;
860 
861 	// Write to pipe, if requested
862 	if (pipes != NULL)
863 	{
864 #if __WORDSIZE == 64
865     {
866 		snprintf(buf,sizeof(buf),"%lu %u %u/", *totalsize, *nbfiles, *nbsubdirs);
867     }
868 #else
869     {
870 		snprintf(buf,sizeof(buf),"%llu %u %u/", *totalsize, *nbfiles, *nbsubdirs);
871     }
872 #endif
873 		if (write(pipes[1], buf, strlen(buf)) == -1)
874 		{
875 			perror("write");
876 			exit(EXIT_FAILURE);
877 		};
878 	}
879 
880     // Not a directory
881     if (!S_ISDIR(statbuf.st_mode))
882     {
883        return(dsize);
884     }
885 
886     // Directory
887     (*nbsubdirs)++;
888 
889     ptr = (char*)path + strlen(path);
890     if (ptr[-1] != '/')
891     {
892         *ptr++ = '/';
893         *ptr = '\0';
894     }
895 
896     if ((dp = opendir(path)) == NULL)
897     {
898         return(0);
899     }
900 
901     while ((dirp = readdir(dp)))
902     {
903         if (streq(dirp->d_name, ".") || streq(dirp->d_name, ".."))
904         {
905             continue;
906         }
907         strlcpy(ptr, dirp->d_name, strlen(dirp->d_name)+1);
908 
909         // Recursive call
910         dsize += pathsize(path, nbfiles, nbsubdirs, totalsize, pipes);
911     }
912 
913     ptr[-1] = '\0'; // ??
914 
915     if (closedir(dp) < 0)
916     {
917         fprintf(stderr, _("Error: Can't close folder %s\n"), path);
918     }
919 
920     return(dsize);
921 }
922 
923 
924 // Write the file size in human readable form (bytes, kBytes, MBytes, GBytes)
925 // We use a decimal basis for kB, MB, GB count
hSize(char * size)926 FXString hSize(char* size)
927 {
928     int flag = 0;
929     char suf[64];
930     char buf[128];
931     FXString hsize;
932 
933     FXulong lsize = strtoull(size, NULL, 10);
934     float fsize = 0.0;
935 
936     strlcpy(suf, _("bytes"), sizeof(suf));
937     if (lsize > 1e9)
938     {
939         fsize = lsize/1e9;
940         strlcpy(suf, _("GB"), sizeof(suf));
941         flag = 1;
942     }
943     else if (lsize > 1e6)
944     {
945         fsize = lsize/1e6;
946         strlcpy(suf, _("MB"), sizeof(suf));
947         flag = 1;
948     }
949     else if (lsize > 1e3)
950     {
951         fsize = lsize/1e3;
952         strlcpy(suf, _("kB"), sizeof(suf));
953         flag = 1;
954     }
955 
956     if (flag)
957     {
958         if (fsize == (int)fsize)
959         {
960             snprintf(buf, sizeof(buf), "%.0f %s", fsize, suf);
961         }
962         else
963         {
964             snprintf(buf, sizeof(buf), "%.1f %s", fsize, suf);
965         }
966     }
967     else
968 
969 #if __WORDSIZE == 64
970     {
971         snprintf(buf, sizeof(buf), "%lu %s", lsize, suf);
972     }
973 #else
974     {
975         snprintf(buf, sizeof(buf), "%llu %s", lsize, suf);
976     }
977 #endif
978 
979     hsize = buf;
980     return(hsize);
981 }
982 
983 
984 // Remove terminating '/' on a path string to simplify a file or directory path
985 // Thus '/bla/bla////' becomes '/bla/bla'
986 // Special case : '/' stays to '/'
cleanPath(const FXString path)987 FXString cleanPath(const FXString path)
988 {
989     FXString in = path, out = path;
990 
991     while (1)
992     {
993         if ((in[in.length()-1] == '/') && (in.length() != 1))
994         {
995             out = in.trunc(in.length()-1);
996             in = out;
997         }
998         else
999         {
1000             break;
1001         }
1002     }
1003     return(out);
1004 }
1005 
1006 
1007 // Return the absolute path, based on the current directory path
1008 // Remove terminating '/' on a path string to simplify a file or directory path
1009 // Thus '/bla/bla////' becomes '/bla/bla'
1010 // Special case : '/' stays to '/'
filePath(const FXString path)1011 FXString filePath(const FXString path)
1012 {
1013     FXString in = path, out = path;
1014 
1015     while (1)
1016     {
1017         if ((in[in.length()-1] == '/') && (in.length() != 1))
1018         {
1019             out = in.trunc(in.length()-1);
1020             in = out;
1021         }
1022         else
1023         {
1024             break;
1025         }
1026     }
1027     FXString dir = FXSystem::getCurrentDirectory();
1028 
1029     // If absolute path
1030     if (ISPATHSEP(out[0]))
1031     {
1032         return(out);
1033     }
1034     else
1035     {
1036         return(dir+PATHSEPSTRING+out);
1037     }
1038 }
1039 
1040 
1041 // Return the absolute path, based on the specified directory path
1042 // Remove terminating '/' on a path string to simplify a file or directory path
1043 // Thus '/bla/bla////' becomes '/bla/bla'
1044 // Special case : '/' stays to '/'
filePath(const FXString path,const FXString dir)1045 FXString filePath(const FXString path, const FXString dir)
1046 {
1047     FXString in = path, out = path;
1048 
1049     while (1)
1050     {
1051         if ((in[in.length()-1] == '/') && (in.length() != 1))
1052         {
1053             out = in.trunc(in.length()-1);
1054             in = out;
1055         }
1056         else
1057         {
1058             break;
1059         }
1060     }
1061     // If absolute path
1062     if (ISPATHSEP(out[0]))
1063     {
1064         return(out);
1065     }
1066     else
1067     {
1068         return(dir+PATHSEPSTRING+out);
1069     }
1070 }
1071 
1072 
1073 // Obtain file path from URI specified as file:///bla/bla/bla...
1074 // If no 'file:' prefix is found, return the input string as is
fileFromURI(FXString uri)1075 FXString fileFromURI(FXString uri)
1076 {
1077     if (comparecase("file:", uri, 5) == 0)
1078     {
1079         if ((uri[5] == PATHSEPCHAR) && (uri[6] == PATHSEPCHAR))
1080         {
1081             return(uri.mid(7, uri.length()-7));
1082         }
1083         return(uri.mid(5, uri.length()-5));
1084     }
1085 
1086     return(uri);
1087 }
1088 
1089 
1090 // Return URI of filename
fileToURI(const FXString & file)1091 FXString fileToURI(const FXString& file)
1092 {
1093     return("file://"+file);
1094 }
1095 
1096 
1097 // Construct a target name by adding a suffix that tells it's a copy of the original target file name
buildCopyName(const FXString & target,const FXbool isDir)1098 FXString buildCopyName(const FXString& target, const FXbool isDir)
1099 {
1100     const FXString suffix = _("copy");
1101 
1102     FXString copytarget;
1103     FXString copystr = " (" + suffix;
1104     FXString name = FXPath::name(target);
1105 
1106     // Get file extensions
1107     FXString ext1 = name.rafter('.', 1);
1108     FXString ext2 = name.rafter('.', 2);
1109     FXString ext3 = ext2.before('.');
1110     FXString ext4 = name.before('.');
1111 
1112     // Case of folder or dot file names (hidden files or folders)
1113     if (isDir || name.before('.') == "")
1114     {
1115 		int pos = target.rfind(copystr);
1116 
1117 		// First copy
1118 		if (pos < 0)
1119 		{
1120 			copytarget = target + copystr + ")";
1121 		}
1122 
1123 		// Add a number to the suffix for next copies
1124 		else
1125 		{
1126 			FXString strnum = target.mid(pos+copystr.length(), target.length()-pos-copystr.length());
1127 			FXuint num = FXUIntVal(strnum);
1128 			num = (num == 0 ? num +2 : num +1);
1129 			copytarget = target.left(pos) + copystr + " " + FXStringVal(num) + ")";
1130 		}
1131 	}
1132 
1133     // Case of compressed tar archive names
1134     else if (ext3.lower() == "tar")
1135     {
1136         FXString basename = target.rbefore('.', 2);
1137         int pos = basename.rfind(copystr);
1138 
1139         if (pos < 0)
1140         {
1141             copytarget = basename + copystr + ")." + ext2;
1142         }
1143 
1144         else
1145         {
1146             // Add a number if it's not the first copy
1147             FXString strnum = target.mid(pos+copystr.length(), target.length()-pos-copystr.length()-ext2.length()-1);
1148             FXuint num = FXUIntVal(strnum);
1149             num = (num == 0 ? num +2 : num +1);
1150 
1151             copytarget = target.left(pos) + copystr + " " + FXStringVal(num) + ")." + ext2;
1152         }
1153     }
1154 
1155     // Other cases
1156     else
1157     {
1158         // File name has no extension
1159         if (ext1 == name)
1160         {
1161             int pos = target.rfind(copystr);
1162 
1163             // First copy
1164             if (pos < 0)
1165             {
1166                 copytarget = target + copystr + ")";
1167             }
1168 
1169             // Add a number to the suffix for next copies
1170             else
1171             {
1172                 FXString strnum = target.mid(pos+copystr.length(), target.length()-pos-copystr.length());
1173                 FXuint num = FXUIntVal(strnum);
1174                 num = (num == 0 ? num +2 : num +1);
1175                 copytarget = target.left(pos) + copystr + " " + FXStringVal(num) + ")";
1176             }
1177         }
1178 
1179         // File name has an extension
1180         else
1181         {
1182             FXString basename = target.rbefore('.', 1);
1183             int pos = basename.rfind(copystr);
1184 
1185             // First copy
1186             if (pos < 0)
1187             {
1188                 copytarget = basename + copystr + ")." + ext1;
1189             }
1190 
1191             // Add a number to the suffix for next copies
1192             else
1193             {
1194                 FXString strnum = target.mid(pos+copystr.length(), target.length()-pos-copystr.length()-ext1.length()-1);
1195                 FXuint num = FXUIntVal(strnum);
1196                 num = (num == 0 ? 2 : num +1);
1197                 copytarget = target.left(pos) + copystr + " " + FXStringVal(num) + ")." + ext1;
1198             }
1199         }
1200     }
1201 
1202     // Recursive call to avoid existing file names
1203     if (existFile(copytarget))
1204     {
1205         copytarget = buildCopyName(copytarget, isDir);
1206     }
1207 
1208     return(copytarget);
1209 }
1210 
1211 
1212 // Convert the deletion date to the number of seconds since the epoch
1213 // The string representing the deletion date must be in the format YYYY-MM-DDThh:mm:ss
deltime(FXString delstr)1214 FXlong deltime(FXString delstr)
1215 {
1216     // Decompose the date into year, month, day, hour, minutes and seconds
1217     FXString year = delstr.mid(0, 4);
1218     FXString mon = delstr.mid(5, 2);
1219     FXString mday = delstr.mid(8, 2);
1220     FXString hour = delstr.mid(11, 2);
1221     FXString min = delstr.mid(14, 2);
1222     FXString sec = delstr.mid(17, 2);
1223 
1224     // Convert date using mktime()
1225     tm tmval;
1226 
1227     tmval.tm_sec = atoi(sec.text());
1228     tmval.tm_min = atoi(min.text());
1229     tmval.tm_hour = atoi(hour.text())-1;
1230     tmval.tm_mday = atoi(mday.text());
1231     tmval.tm_mon = atoi(mon.text())-1;
1232     tmval.tm_year = atoi(year.text())-1900;
1233     tmval.tm_isdst = 0;
1234     FXlong t = (FXlong)mktime(&tmval);
1235 
1236     // If conversion failed, return 0
1237     if (t < 0)
1238     {
1239         t = 0;
1240     }
1241 
1242     return(t);
1243 }
1244 
1245 
1246 // Test if a directory is empty
1247 // Return -1 if not a directory, 1 if empty and 0 if not empty
isEmptyDir(const FXString directory)1248 int isEmptyDir(const FXString directory)
1249 {
1250     int ret = -1;
1251     DIR* dir;
1252     struct dirent* entry;
1253     int n = 0;
1254 
1255     if ((dir = opendir(directory.text())) != NULL)
1256     {
1257         // Skip . and .. and read the third entry
1258         while (n < 3)
1259         {
1260             entry = readdir(dir);
1261             n++;
1262         }
1263         if (entry == NULL)
1264         {
1265             ret = 1;
1266         }
1267         else
1268         {
1269             ret = 0;
1270         }
1271     }
1272     if (dir)
1273     {
1274         closedir(dir);
1275     }
1276     return(ret);
1277 }
1278 
1279 
1280 // Test if a directory has sub-directories
1281 // Return -1 if not a directory, 1 if has sub-directories, 0 if does not have
hasSubDirs(const FXString directory)1282 int hasSubDirs(const FXString directory)
1283 {
1284     int ret = -1;
1285     DIR* dir;
1286     struct dirent* entry;
1287 
1288     if ((dir = opendir(directory.text())) != NULL)
1289     {
1290         ret = 0;
1291 
1292         // Process directory entries
1293         while (1)
1294         {
1295             entry = readdir(dir);
1296 
1297             // No more entries
1298             if (entry == NULL)
1299             {
1300                 break;
1301             }
1302 
1303             // Entry is . or ..
1304             else if ((strcmp(entry->d_name, ".") == 0) || (strcmp(entry->d_name, "..") == 0))
1305             {
1306                 continue;
1307             }
1308 
1309             // Regular entry
1310             // We don't use dirent.d_type anymore because of portability issues
1311             // (e.g. reiserfs don't know dirent.d_type)
1312             else
1313             {
1314                 // Stat entry
1315                 struct stat entrystat;
1316                 FXString entrypath = directory + PATHSEPSTRING + entry->d_name;
1317                 if (statrep(entrypath.text(), &entrystat) != 0)
1318                 {
1319                     continue;
1320                 }
1321 
1322                 // If directory
1323                 if (S_ISDIR(entrystat.st_mode))
1324                 {
1325                     ret = 1;
1326                     break;
1327                 }
1328             }
1329         }
1330     }
1331     if (dir)
1332     {
1333         closedir(dir);
1334     }
1335     return(ret);
1336 }
1337 
1338 
1339 // Check if file or directory exists
existFile(const FXString & file)1340 FXbool existFile(const FXString& file)
1341 {
1342     struct stat linfo;
1343 
1344     return(!file.empty() && (lstatrep(file.text(), &linfo) == 0));
1345 }
1346 
1347 
1348 // Check if the file represents a directory
isDirectory(const FXString & file)1349 FXbool isDirectory(const FXString& file)
1350 {
1351     struct stat info;
1352 
1353     return(!file.empty() && (statrep(file.text(), &info) == 0) && S_ISDIR(info.st_mode));
1354 }
1355 
1356 
1357 // Check if file represents a file
isFile(const FXString & file)1358 FXbool isFile(const FXString& file)
1359 {
1360     struct stat info;
1361 
1362     return(!file.empty() && (statrep(file.text(), &info) == 0) && S_ISREG(info.st_mode));
1363 }
1364 
1365 
1366 // Check if current user is member of gid
1367 // (thanks to Armin Buehler <abuehler@users.sourceforge.net>)
isGroupMember(gid_t gid)1368 FXbool isGroupMember(gid_t gid)
1369 {
1370     static int ngroups = 0;
1371     static gid_t* gids = NULL;
1372     int i;
1373     int ret;
1374 
1375     // First call : initialization of the number of supplementary groups and the group list
1376     if (ngroups == 0)
1377     {
1378         ngroups = getgroups(0, gids);
1379 
1380         if (ngroups < 0)
1381         {
1382             goto err;
1383         }
1384         else
1385         {
1386             gids = new gid_t[ngroups];
1387             ret = getgroups(ngroups, gids);
1388             if (ret < 0)
1389             {
1390                 goto err;
1391             }
1392         }
1393     }
1394     if (ngroups == 0)
1395     {
1396         return(false);
1397     }
1398 
1399     // Check if the group id is contained within the group list
1400     i = ngroups;
1401     while (i--)
1402     {
1403         if (gid == gids[i])
1404         {
1405             return(true);
1406         }
1407     }
1408 
1409 err:
1410     int errcode = errno;
1411     if (errcode)
1412     {
1413         fprintf(stderr, _("Error: Can't read group list: %s"), strerror(errcode));
1414     }
1415     else
1416     {
1417         fprintf(stderr, _("Error: Can't read group list"));
1418     }
1419 
1420     return(false);
1421 }
1422 
1423 
1424 // Check if the file or the link refered file is readable AND executable
1425 // Function used to test if we can enter a directory
1426 // Uses the access() system function
isReadExecutable(const FXString & file)1427 FXbool isReadExecutable(const FXString& file)
1428 {
1429     struct stat info;
1430 
1431     // File exists and can be stated
1432     if (!file.empty() && (statrep(file.text(), &info) == 0))
1433     {
1434         int ret = access(file.text(), R_OK|X_OK);
1435         if (ret == 0)
1436         {
1437             return(true);
1438         }
1439         else
1440         {
1441             return(false);
1442         }
1443     }
1444 
1445     // File doesn't exist
1446     else
1447     {
1448         return(false);
1449     }
1450 }
1451 
1452 
1453 // Check if file is readable
isReadable(const FXString & file)1454 FXbool isReadable(const FXString& file)
1455 {
1456     struct stat info;
1457 
1458     // File exists and can be stated
1459     if (!file.empty() && (statrep(file.text(), &info) == 0))
1460     {
1461         int ret = access(file.text(), R_OK);
1462         if (ret == 0)
1463         {
1464             return(true);
1465         }
1466         else
1467         {
1468             return(false);
1469         }
1470     }
1471 
1472     // File doesn't exist
1473     else
1474     {
1475         return(false);
1476     }
1477 }
1478 
1479 
1480 // Check if file is writable
isWritable(const FXString & file)1481 FXbool isWritable(const FXString& file)
1482 {
1483     struct stat info;
1484 
1485     // File exists and can be stated
1486     if (!file.empty() && (statrep(file.text(), &info) == 0))
1487     {
1488         int ret = access(file.text(), W_OK);
1489         if (ret == 0)
1490         {
1491             return(true);
1492         }
1493         else
1494         {
1495             return(false);
1496         }
1497     }
1498 
1499     // File doesn't exist
1500     else
1501     {
1502         return(false);
1503     }
1504 }
1505 
1506 
1507 // Check if file represents a link
isLink(const FXString & file)1508 FXbool isLink(const FXString& file)
1509 {
1510     struct stat linfo;
1511 
1512     return(!file.empty() && (lstatrep(file.text(), &linfo) == 0) && S_ISLNK(linfo.st_mode));
1513 }
1514 
1515 
1516 // Get file info (file or link refered file)
info(const FXString & file,struct stat & inf)1517 FXbool info(const FXString& file, struct stat& inf)
1518 {
1519     return(!file.empty() && (statrep(file.text(), &inf) == 0));
1520 }
1521 
1522 
1523 // Return permissions string
1524 // (the FOX function FXSystem::modeString() seems to use another format for the mode field)
permissions(FXuint mode)1525 FXString permissions(FXuint mode)
1526 {
1527     char result[11];
1528 
1529     result[0] = S_ISLNK(mode) ? 'l' : S_ISREG(mode) ? '-' : S_ISDIR(mode) ? 'd' : S_ISCHR(mode) ? 'c' : S_ISBLK(mode) ? 'b' : S_ISFIFO(mode) ? 'p' : S_ISSOCK(mode) ? 's' : '?';
1530     result[1] = (mode&S_IRUSR) ? 'r' : '-';
1531     result[2] = (mode&S_IWUSR) ? 'w' : '-';
1532     result[3] = (mode&S_ISUID) ? 's' : (mode&S_IXUSR) ? 'x' : '-';
1533     result[4] = (mode&S_IRGRP) ? 'r' : '-';
1534     result[5] = (mode&S_IWGRP) ? 'w' : '-';
1535     result[6] = (mode&S_ISGID) ? 's' : (mode&S_IXGRP) ? 'x' : '-';
1536     result[7] = (mode&S_IROTH) ? 'r' : '-';
1537     result[8] = (mode&S_IWOTH) ? 'w' : '-';
1538     result[9] = (mode&S_ISVTX) ? 't' : (mode&S_IXOTH) ? 'x' : '-';
1539     result[10] = 0;
1540     return(result);
1541 }
1542 
1543 
1544 // Read symbolic link
readLink(const FXString & file)1545 FXString readLink(const FXString& file)
1546 {
1547     char lnk[MAXPATHLEN+1];
1548     int len = readlink(file.text(), lnk, MAXPATHLEN);
1549 
1550     if (0 <= len)
1551     {
1552         return(FXString(lnk, len));
1553     }
1554     else
1555     {
1556         return(FXString::null);
1557     }
1558 }
1559 
1560 
1561 // Return true if files are identical
1562 // Compare file names and inodes for case insensitive filesystems
identical(const FXString & file1,const FXString & file2)1563 FXbool identical(const FXString& file1, const FXString& file2)
1564 {
1565     if (file1 != file2)
1566     {
1567         struct stat linfo1, linfo2;
1568         return(!::lstatrep(file1.text(), &linfo1) && !::lstatrep(file2.text(), &linfo2) && linfo1.st_ino == linfo2.st_ino && linfo1.st_dev == linfo2.st_dev);
1569     }
1570     return(true);
1571 }
1572 
1573 
1574 // Start or stop wait cursor (start if type is BEGIN_CURSOR, stop if type is END_CURSOR)
1575 // Do nothing if type is QUERY_CURSOR or anything different from BEGIN_CURSOR and END_CURSOR)
1576 // Return wait cursor count (0 means wait cursor is not set)
setWaitCursor(FXApp * app,FXuint type)1577 int setWaitCursor(FXApp* app, FXuint type)
1578 {
1579     static int waitcount = 0;
1580 
1581     // Begin wait cursor
1582     if (type == BEGIN_CURSOR)
1583     {
1584         app->beginWaitCursor();
1585         waitcount++;
1586     }
1587 
1588     // End wait cursor
1589     else if (type == END_CURSOR)
1590     {
1591         app->endWaitCursor();
1592         if (waitcount >= 1)
1593         {
1594             waitcount--;
1595         }
1596         else
1597         {
1598             waitcount = 0;
1599         }
1600     }
1601 
1602     // Other cases : do nothing
1603     else
1604     {
1605     }
1606 
1607     return(waitcount);
1608 }
1609 
1610 
1611 // Run a command in an internal st terminal
1612 // Return 0 if success, -1 else
1613 // N.B.: zombie process should be dealt with in the main application class
runst(FXString cmd)1614 int runst(FXString cmd)
1615 {
1616     FXString str1, str2;
1617     int nbargs, i, j;
1618 
1619     // First pass to find the number of commmand arguments
1620     nbargs = 0;
1621     i = 0;
1622     j = 1;
1623     while (1)
1624     {
1625         str1 = cmd.section(' ', i);
1626         if (str1[0] == '\'')       // If a ' is found, ignore the spaces till the next '
1627         {
1628             str2 = cmd.section('\'', j);
1629             j += 2;
1630             i += str2.contains(' ');
1631             nbargs++;
1632         }
1633         else
1634         {
1635             nbargs++;
1636         }
1637 
1638         if (streq(str1.text(), ""))
1639         {
1640             break;
1641         }
1642 
1643         i++;
1644     }
1645     nbargs--;
1646 
1647     // Second pass to allocate the argument strings
1648     char** args = (char**)malloc((nbargs + 1)*sizeof(char*));
1649     nbargs = 0;
1650     i = 0;
1651     j = 1;
1652     while (1)
1653     {
1654         str1 = cmd.section(' ', i);
1655         if (str1[0] == '\'')
1656         {
1657             str2 = cmd.section('\'', j);
1658             j += 2;
1659             i += str2.contains(' ');
1660             args[nbargs] = (char*)malloc(str2.length()+1);
1661             strlcpy(args[nbargs], str2.text(), str2.length()+1);
1662             nbargs++;
1663         }
1664         else
1665         {
1666             args[nbargs] = (char*)malloc(str1.length()+1);
1667             strlcpy(args[nbargs], str1.text(), str1.length()+1);
1668             nbargs++;
1669         }
1670 
1671         if (streq(str1.text(), ""))
1672         {
1673             break;
1674         }
1675 
1676         i++;
1677     }
1678     nbargs--;
1679     args[nbargs] = NULL;
1680 
1681     // Launch the command in an internal st terminal
1682     int res;
1683     static pid_t childpid = 0;
1684     childpid = fork();
1685 
1686     // Fork succeeded
1687     if (childpid >= 0)
1688     {
1689         if (childpid == 0) // Child
1690         {
1691             st(nbargs, args);
1692             exit(EXIT_SUCCESS);
1693         }
1694         else // Parent
1695         {
1696             // Non blocking wait for child
1697             if (waitpid(childpid, NULL, WNOHANG) < 0)
1698             {
1699                 res = -1;
1700             }
1701             else
1702             {
1703                 res = 0;
1704             }
1705         }
1706     }
1707 
1708     // Fork failed
1709     else
1710     {
1711         fprintf(stderr, _("Error: Fork failed: %s\n"), strerror(errno));
1712         res = -1;
1713     }
1714 
1715     // Free allocated strings
1716     for (int i = 0; i < nbargs; i++)
1717     {
1718         free(args[i]);
1719     }
1720     free(args);
1721 
1722     return(res);
1723 }
1724 
1725 
1726 // Get the output of a Unix command
1727 // Return the command output or the error message id any
getCommandOutput(FXString cmd)1728 FXString getCommandOutput(FXString cmd)
1729 {
1730     FXString data;
1731     FILE* stream;
1732 
1733     const int max_buffer = 1024;
1734     char buffer[max_buffer];
1735 
1736     cmd += " 2>&1";
1737 
1738     stream = popen(cmd.text(), "r");
1739     if (stream)
1740     {
1741         while (!feof(stream))
1742         {
1743             if (fgets(buffer, max_buffer, stream) != NULL)
1744             {
1745                 data += buffer;
1746             }
1747         }
1748         pclose(stream);
1749     }
1750     return(data);
1751 }
1752 
1753 
1754 // Load a PNG icon from a file in the icon path
loadiconfile(FXApp * app,const FXString iconpath,const FXString iconname)1755 FXIcon* loadiconfile(FXApp* app, const FXString iconpath, const FXString iconname)
1756 {
1757     // Icon name is empty
1758     if (iconname.length() == 0)
1759     {
1760         return(NULL);
1761     }
1762 
1763     // New PNG icon
1764     FXIcon* icon = NULL;
1765     icon = new FXPNGIcon(app);
1766 
1767     if (icon)
1768     {
1769         // Find icon in the icon directory
1770         FXString iconfile = FXPath::search(iconpath, iconname.text());
1771 
1772         if (!iconfile.empty())
1773         {
1774             FXFileStream str;
1775 
1776             // Try open the file
1777             if (str.open(iconfile, FXStreamLoad))
1778             {
1779                 // Load it
1780                 icon->loadPixels(str);
1781 
1782 				// Scale it
1783 				icon->scale(scalefrac*icon->getWidth(), scalefrac*icon->getHeight());
1784 
1785                 // Create it
1786                 icon->create();
1787 
1788                 // Done
1789                 str.close();
1790 
1791                 return(icon);
1792             }
1793         }
1794 
1795         // Failed, delete the icon
1796         delete icon;
1797     }
1798     return(NULL);
1799 }
1800 
1801 
1802 // Truncate a string to the specified number of UTF-8 characters
1803 // and adds "..." to the end
truncLine(FXString str,FXuint maxlinesize)1804 FXString truncLine(FXString str, FXuint maxlinesize)
1805 {
1806     if (str.count() <= (int)maxlinesize)
1807     {
1808         return(str);
1809     }
1810 
1811     return(str.trunc(str.validate(maxlinesize)) + "...");
1812 }
1813 
1814 
1815 // Insert line breaks as needed to allow displaying string using lines
1816 // of specified maximum number of UTF-8 characters
multiLines(FXString str,FXuint maxlinesize)1817 FXString multiLines(FXString str, FXuint maxlinesize)
1818 {
1819     int pos1 = 0;
1820     int pos2;
1821 
1822     while (1)
1823     {
1824         // No more line breaks found
1825         pos2 = str.find('\n', pos1);
1826         if (pos2 < 0)
1827         {
1828             int nbc = str.count(pos1, str.length());
1829             if (nbc > (int)maxlinesize)
1830             {
1831                 int nbl = nbc/maxlinesize;
1832                 for (int n = 1; n <= nbl; n++)
1833                 {
1834                     str.insert(str.validate(pos1+n*maxlinesize), '\n'); // Use a valid UTF-8 position
1835                 }
1836             }
1837 
1838             break;
1839         }
1840 
1841         // Line break found
1842         else
1843         {
1844             int nbc = str.count(pos1, pos2);
1845             if (nbc > (int)maxlinesize)
1846             {
1847                 int nbl = nbc/maxlinesize;
1848                 for (int n = 1; n <= nbl; n++)
1849                 {
1850                     str.insert(str.validate(pos1+n*maxlinesize), '\n'); // Use a valid UTF-8 position
1851                     pos2++;
1852                 }
1853             }
1854             pos1 = pos2 + 1;
1855         }
1856     }
1857 
1858     return(str);
1859 }
1860