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