1
/**************************************************************************
2
zfuncs collection of Linux and GDK/GTK utility functions
4
Copyright 2006, 2007, 2008, 2009 Michael Cornelison
5
source URL: kornelix.squarespace.com
6
contact: kornelix@yahoo.de
8
This program is free software: you can redistribute it and/or modify
9
it under the terms of the GNU General Public License as published by
10
the Free Software Foundation, either version 3 of the License, or
11
(at your option) any later version.
13
This program is distributed in the hope that it will be useful,
14
but WITHOUT ANY WARRANTY; without even the implied warranty of
15
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
GNU General Public License for more details.
18
You should have received a copy of the GNU General Public License
19
along with this program. If not, see <http://www.gnu.org/licenses/>.
21
***************************************************************************/
23
// zfuncs version v.2.23 2009.08.31
27
/**************************************************************************
28
system-level utility functions
29
***************************************************************************/
31
// crash with error message and traceback dump to stdout
34
void appcrash(cchar *pMess, ... )
42
va_start(arglist,pMess);
43
vsnprintf(message,200,pMess,arglist);
46
printf("appcrash: \n %s \n",message);
48
nstack = backtrace(stacklist,nstack); // good for g++ -rdynamic
49
stackents = backtrace_symbols(stacklist,nstack);
50
for (ii = 0; ii < nstack; ii++)
51
printf(" %s \n",stackents[ii]);
53
abort(); // good for gdb backtrace
57
// crash with error message and traceback dump in popup window
60
void zappcrash(cchar *pMess, ... ) // v.2.22
64
int ii, err, nstack = 50;
69
va_start(arglist,pMess);
70
vsnprintf(message,299,pMess,arglist);
73
fid = fopen("zappcrash","w");
75
fprintf(fid,"zappcrash: \n %s \n",message);
77
nstack = backtrace(stacklist,nstack);
78
stackents = backtrace_symbols(stacklist,nstack);
79
for (ii = 0; ii < nstack; ii++)
80
fprintf(fid," %s \n",stackents[ii]);
84
err = system("xdg-open zappcrash");
89
// Output a message to stdout and wait for user ACK.
92
void apppause(cchar *pMess, ... )
97
va_start(arglist,pMess);
98
vsnprintf(message,200,pMess,arglist);
101
printf("pause: %s \n",message);
102
printf("*** press return to continue: ");
109
printf("*** pause, press return to continue: ");
115
// catch segfaults and produce backtrace dumps on-screen
117
void sighandler(int signal)
119
zappcrash("segment fault");
124
// application initialization function to catch segfaults
126
void catch_signals() // v.2.22
128
struct sigaction sigact;
130
sigact.sa_handler = sighandler;
131
sigemptyset(&sigact.sa_mask);
133
sigaction(SIGSEGV,&sigact,0);
138
// get time in real seconds (since 2000.01.01 00:00:00) // v.2.12
144
gettimeofday(&time1,0);
145
return time1.tv_sec + 0.000001 * time1.tv_usec - 946684800.0;
149
// start a timer or get elapsed time with millisecond resolution.
151
void start_timer(double &time0) // double v.2.20
155
gettimeofday(&timev,0);
156
time0 = timev.tv_sec + 0.000001 * timev.tv_usec;
160
double get_timer(double &time0)
165
gettimeofday(&timev,0);
166
time = timev.tv_sec + 0.000001 * timev.tv_usec;
171
// start a process CPU timer or get elapsed process CPU time
172
// returns seconds with millisecond resolution
174
void start_CPUtimer(double &time0) // double v.2.20
180
double get_CPUtimer(double &time0)
182
return CPUtime() - time0;
186
// get elapsed CPU time used by current process
187
// returns seconds with millisecond resolution
191
clock_t ctime = clock();
192
double dtime = ctime / 1000000.0;
197
// start a detached thread using a simplified protocol // v.2.11
198
// thread exit: pthread_exit(0);
200
void start_detached_thread(void * threadfunc(void *), void * arg)
203
pthread_attr_t ptattr;
206
pthread_attr_init(&ptattr);
207
pthread_attr_setdetachstate(&ptattr,PTHREAD_CREATE_DETACHED);
208
pterr = pthread_create(&ptid,&ptattr,threadfunc,arg);
209
if (pterr) appcrash("start_detached_thread() failure");
214
// Synchronize execution of multiple threads.
215
// Simultaneously resume NT calling threads.
216
// from main(): synch_threads(NT) /* setup to synch NT threads */
217
// from each thread: synch_threads() /* suspend, resume simultaneously */
219
void synch_threads(int NT)
221
static pthread_barrier_t barrier;
222
static int bflag = 0;
224
if (NT) { // main(), initialize
225
if (bflag) pthread_barrier_destroy(&barrier);
226
pthread_barrier_init(&barrier,null,NT);
231
pthread_barrier_wait(&barrier); // thread(), wait for NT threads
236
// safely access parameters from multiple threads
237
// limitation: one lock for any number of parameters
239
mutex zget_lock = PTHREAD_MUTEX_INITIALIZER;
241
int zget_locked(int ¶m) // lock and return parameter
243
mutex_lock(&zget_lock);
247
void zput_locked(int ¶m, int value) // set and unlock parameter
250
mutex_unlock(&zget_lock);
254
int zadd_locked(int ¶m, int incr) // lock, increment, unlock, return
258
mutex_lock(&zget_lock);
259
retval = param + incr;
261
mutex_unlock(&zget_lock);
266
// sleep for specified time in seconds (double)
268
void zsleep(double dsecs)
270
unsigned isecs, nsecs;
273
if (dsecs == 0.0) return;
274
isecs = unsigned(dsecs);
275
nsecs = unsigned(1000000000.0 * (dsecs - isecs));
276
tsecs.tv_sec = isecs;
277
tsecs.tv_nsec = nsecs;
278
nanosleep(&tsecs,null);
283
// malloc() and free() wrappers with auto crash on failure and log option.
284
// overflow sentinel is placed after end of allocated memory
285
// zmalloc() and zfree() calls are logged if log flag is on
286
// benchmark zmalloc() + zfree(): usually < 1 microsec. on 2 GHz CPU
288
unsigned zmalloc_tot = 0;
289
#define zmalloc_add 12
291
char * zmalloc(size_t bytes, int log)
293
void *maddr = malloc(bytes + zmalloc_add);
294
if (! maddr) appcrash("Memory request for %zu bytes failed. \n" // v.2.22
295
"Application will now terminate.",bytes);
297
unsigned *pbytes = (unsigned *) maddr; // 0..3 caller bytes
298
char *pvalid = (char *) maddr + 4; // 4..7 validity key "zmal"
299
char *puser = (char *) maddr + 8; // 8..B+7 user data, B chars.
300
char *psent = (char *) puser + bytes; // B+8..B+11 overflow sentinel "sent"
303
strncpy(pvalid,"zmal",4);
304
memset(puser,0,bytes);
305
strncpy(psent,"sent",4);
306
zmalloc_tot += bytes;
307
if (log) printf("zmalloc loc: %p bytes: %zu total: %u \n",puser,bytes,zmalloc_tot);
312
// free memory allocated by zmalloc(). checks for overflow.
314
void zfree(void *puser, int log)
316
void *maddr = (char *) puser - 8;
317
unsigned *pbytes = (unsigned *) maddr;
318
char *pvalid = (char *) maddr + 4;
319
size_t bytes = *pbytes;
320
char *psent = (char *) puser + bytes;
322
if (! puser) appcrash("zfree: null address \n");
323
if (strncmp("zmal",pvalid,4)) appcrash("zfree: invalid address %p \n",puser);
324
if (strncmp("sent",psent,4)) appcrash("zfree: buffer overflow \n");
325
*pvalid = *psent = 0;
326
zmalloc_tot -= bytes;
328
if (log) printf("zfree loc: %p bytes: %zu total: %u \n",puser,bytes,zmalloc_tot);
333
// Run a shell command and get its outputs one record at a time.
334
// Start a new command with contx = 0. Do not change contx.
335
// NULL return means no more output.
336
// Get command exit status: err = command_status(contx)
337
// Caller owns returned strings which are candidates for zfree()
339
// strcat(buff," 2>&1"); // combine stdout and stderr v.2.14 removed
342
FILE * CO_contx[10] = { 0,0,0,0,0,0,0,0,0,0 };
345
char * command_output(int &contx, const char *command, ...) // simplify, allow parallel usage
349
char buff[1000], *prec;
351
if (contx == 0) // start new command
353
for (contx = 1; contx < 10; contx++)
354
if (CO_contx[contx] == 0) break;
355
if (contx == 10) zappcrash("command_output(), parallel usage > 9");
357
va_start(arglist,command); // format command
358
vsnprintf(buff,990,command,arglist);
361
fid = popen(buff,"r"); // execute command, output to FID
363
CO_status[contx] = errno; // failed to start
366
CO_contx[contx] = fid + 1000;
367
CO_status[contx] = -1; // mark context busy
370
fid = CO_contx[contx] - 1000;
371
prec = fgets_trim(buff,999,fid,1); // next output, less trailing \n
372
if (prec) return strdupz(prec); // return output to caller
374
CO_status[contx] = pclose(fid); // EOF, set status
375
CO_contx[contx] = 0; // mark context free
379
int command_status(int contx) // get command exit status
381
int err = CO_status[contx];
382
return WEXITSTATUS(err); // special BS for subprocess v.2.3
386
// signal a subprocess to pause, resume, or terminate
387
// return: 0: OK +-N: error
389
int signalProc(cchar *pname, cchar *signal)
394
int err, ignore, nsignal = 0;
396
sprintf(buff,"ps -C %s h o pid > /tmp/signalProc-temp",pname);
398
if (err) { err = 2; goto cleanup; }
400
fid = fopen("/tmp/signalProc-temp","r");
401
if (! fid) { err = 3; goto cleanup; }
403
pp = fgets(buff,100,fid);
405
if (! pp) { err = 4; goto cleanup; }
408
if (! pid) { err = 5; goto cleanup; }
410
if (strEqu(signal,"pause")) nsignal = SIGSTOP;
411
if (strEqu(signal,"resume")) nsignal = SIGCONT;
412
if (strEqu(signal,"kill")) nsignal = SIGKILL;
413
err = kill(pid,nsignal);
416
ignore = system("rm -f /tmp/signalProc-temp");
421
// run a command or program as root user
422
// sucomm: root user access command, "su" or "sudo"
423
// command: shell command or filespec of the program to start
424
// returns 0 if successfully started, else returns an error code
426
int runroot(const char *sucomm, const char *command) // v.2.10
431
if (strcmp(sucomm,"sudo") == 0)
433
snprintf(xtcommand,499,"xterm -geometry 40x3 -e sudo -S %s",command);
434
err = system(xtcommand);
438
if (strcmp(sucomm,"su") == 0)
440
snprintf(xtcommand,499,"xterm -geometry 40x3 -e su -c %s",command);
441
err = system(xtcommand);
449
// Check if a list of programs are all installed
450
// If any are missing, pop-up a window of missing programs
451
// Returns the number of missing programs (zero if none).
453
int checkinstall(const char *prog1, ...) // null terminated list // v.2.10
456
char *buff, errmessage[200] = "missing programs:\n";
457
const char *prog, *pp, *missprogs[20];
458
int contx, found, Nmiss = 0;
460
va_start(arglist,prog1);
470
buff = command_output(contx,"whereis %s",prog);
472
pp = strchr(buff,':');
473
if (pp) pp = strchr(pp,'/');
480
if (Nmiss == 20) break;
481
missprogs[Nmiss] = prog;
485
prog = va_arg(arglist,const char *);
491
for (int ii = 0; ii < Nmiss; ii++)
492
strncatv(errmessage,199,missprogs[ii]," ",0);
493
zmessageACK(errmessage);
500
// fgets with additional feature: trailing \n \r are removed
501
// optional bf flag: true if trailing blanks are to be removed
503
char * fgets_trim(char *buff, int maxcc, FILE *fid, int bf)
508
pp = fgets(buff,maxcc,fid);
511
if (bf) while (cc && buff[cc-1] > 0 && buff[cc-1] <= ' ') --cc; // utf8 v.2.4
512
else while (cc && buff[cc-1] > 0 && buff[cc-1] < ' ') --cc;
518
// return true if both files are in the same directory
519
// both files may be files or directories
521
int samedirk(const char *file1, const char *file2)
524
const char *pp1, *pp2; // v.2.15
526
if (! file1 || ! file2) return 0;
527
pp1 = strrchr(file1,'/');
528
pp2 = strrchr(file2,'/');
529
if (! pp1 && ! pp2) return 1;
530
if (pp1 && ! pp2) return 0;
531
if (! pp1 && pp2) return 0;
534
if (cc1 != cc2) return 0;
535
if (strncmp(file1,file2,cc1) == 0) return 1;
540
/**************************************************************************
542
Parse a pathname (filespec) and return its components.
543
Returned strings are allocated in static memory (no zfree needed).
544
Missing components are returned as null pointers.
548
/name1/name2/ directory /name1/name2/ with no file
549
/name1/name2 directory /name1/name2/ if name2 a directory,
550
otherwise directory /name1/ and file name2
551
/name1/name2.xxx if .xxx < 8 chars, returns file name2 and ext .xxx,
552
otherwise returns file name2.xxx and no ext
554
returns 0 if no error, else 1
558
int parsefile(cchar *ppath, char **pdirk, char **pfile, char **pext) // v.2.15
561
static char dirk[1000], file[200], ext[8];
565
*pdirk = *pfile = *pext = null;
568
if (cc1 > 999) return 1; // ppath too long
573
err = stat(dirk,&statb); // have directory only
574
if (! err && S_ISDIR(statb.st_mode)) return 0;
576
pp = (char *) strrchr(dirk,'/');
577
if (! pp) return 1; // illegal
581
if (cc2 < 2 || cc2 == cc1) return 0; // have /xxxx or /xxxx/
583
if (strlen(pp) > 199) return 1; // filename too long
585
strcpy(file,pp); // file part
587
*pp = 0; // remove from dirk part
589
pp = (char *) strrchr(file,'.');
590
if (! pp || strlen(pp) > 7) return 0; // file part, no .ext
592
strcpy(ext,pp); // .ext part
594
*pp = 0; // remove from file part
599
/**************************************************************************
601
monitor a directory for file additions and deletions v.2.16
603
action: open Start monitoring directory
604
event Return next event or zero if none pending
605
close Stop monitoring
607
dirk: Input directory to monitor (action = open)
609
file: Output filename (action = event) relative to directory
610
zfree() is called unless null
611
zmalloc() is called to allocate new space
615
0 OK (open, close), no event pending (event)
616
1 file was possibly added
618
3 monitored directory is gone
621
NOTE The Linux inotify() function is used and is not reliable
622
for files renamed. The remove is reported but not the add.
623
The 'other' event (with no file name) is reported.
627
int zmondirk(const char *action, const char *dirk, char **file)
629
struct inotify_event {
630
int wd; // Watch descriptor
631
int evt; // Event type
632
int cookie; // Associates related events (rename)
633
int len; // Size of file name field following
634
char fname[200]; // null-terminated file name within directory
637
inotify_event evbuff;
638
struct timeval waitime;
640
int wd, retval, cc, fcc;
641
int evbcc = sizeof(evbuff);
644
if (strEqu(action,"open")) // setup a monitored directory
646
fid = inotify_init();
650
wd = inotify_add_watch(fid,dirk,IN_ALL_EVENTS);
659
if (strEqu(action,"event")) // return pending event or zero
661
if (fid < 0) return -1;
664
if (*file) zfree(*file); // free prior memory
675
retval = select(fid+1, &fids, null, null, &waitime);
676
if (retval == 0) return 0; // nothing pending
678
if (retval == -1) { // error
684
cc = read(fid,&evbuff,evbcc); // get pending event
692
if (fcc > 199) return -1;
693
if (fcc > 0 && file) *file = strdupz(evbuff.fname); // return filename
695
if (evbuff.evt & (IN_CREATE + IN_MOVED_TO)) // file was added
697
if (evbuff.evt & (IN_CLOSE_WRITE + IN_CLOSE_NOWRITE + IN_MODIFY)) // file was possibly added
699
if (evbuff.evt & (IN_DELETE + IN_MOVED_FROM)) // file was deleted
701
if (evbuff.evt & (IN_DELETE_SELF + IN_MOVE_SELF)) // monitored directory gone
707
if (strEqu(action,"close")) // stop monitoring
709
if (fid > -1) retval = close(fid);
715
zappcrash("zmondirk() call error");
720
/**************************************************************************
722
utility to measure CPU time spent in various functions or code blocks v.2.7
724
cpu_profile_init() initialize at start of test
725
cpu_profile_report() report CPU time per function
726
cpu_profile_enter(fnum) at entry to a function // inline, defined in zfuncs.h
727
cpu_profile_exit(fnum) at exit from a function // inline, defined in zfuncs.h
731
volatile double cpu_profile_table[100];
732
volatile double cpu_profile_timer;
733
volatile double cpu_profile_elapsed;
734
volatile int cpu_profile_kill = 0;
736
void cpu_profile_init()
738
void * cpu_profile_timekeeper(void *);
740
for (int ii = 0; ii < 99; ii++)
741
cpu_profile_table[ii] = 0;
742
cpu_profile_elapsed = 0;
743
start_detached_thread(cpu_profile_timekeeper,null);
746
void cpu_profile_report()
750
printf("elapsed: %.2f \n",cpu_profile_elapsed);
752
for (int ii = 0; ii < 100; ii++)
754
double dtime = cpu_profile_table[ii];
755
if (dtime) printf("cpu profile func: %d time: %.2f \n",ii,dtime);
759
void * cpu_profile_timekeeper(void *)
761
timeval time0, time1;
763
gettimeofday(&time0,0);
767
gettimeofday(&time1,0);
768
cpu_profile_elapsed = time1.tv_sec - time0.tv_sec
769
+ 0.000001 * (time1.tv_usec - time0.tv_usec);
771
if (cpu_profile_kill) break;
774
cpu_profile_kill = 0;
779
/**************************************************************************
780
string utility functions
781
***************************************************************************
785
char * strField(const char *string, const char *delim, int Nth)
787
Get the Nth field in input string, which contains at least N fields
788
delimited by the character(s) in delim (e.g. blank, comma).
790
Returns a pointer to the found field (actually a pointer to a
791
copy of the found field, with a null terminator appended).
793
If a delimiter is immediately followed by another delimiter, it is
794
considered a field with zero length, and the string "" is returned.
796
Leading blanks in a field are omitted from the returned field.
797
A field with only blanks is returned as a single blank.
799
The last field may be terminated by null or a delimiter.
801
Characters within quotes (" or ') are treated as data within a
802
field, i.e. blanks and delimiters are not processed as such.
803
The quotes are removed from the returned field.
805
If there are less than N fields, a null pointer is returned.
807
The last 100 fields are saved and recycled in a circular manner.
808
The caller does not have to free memory. If more memory depth is
809
needed, caller must copy the returned data elsewhere.
811
The input string must be < 1000 characters.
812
The output string may be modified if the length is not increased.
814
Example: input string: ,a,bb, cc, ,dd"ee,ff"ggg,
815
(first and last characters are comma)
824
7: (null pointer >> no more fields)
826
***************************************************************************/
828
const char * strField(const char *string, const char *delim, int Nth)
830
static int ftf = 1, nret = 0;
831
static char *retf[100];
832
char *pf1, pf2[1000];
833
const char quote1 = '\'', quote2 = '"';
835
static char blankstring[2], nullstring[1];
837
if (ftf) // overall first call
840
for (ii = 0; ii < 100; ii++) retf[ii] = 0;
841
strcpy(blankstring," ");
845
if (strlen(string) > 999) return 0;
846
if (Nth < 1) return 0;
848
pf1 = (char *) string - 1; // start parse
853
pf1++; // start field
857
while (*pf1 == ' ') pf1++; // skip leading blanks
861
if (*pf1 == quote1) { // pass chars between single quotes
862
pf1++; // (but without the quotes)
863
while (*pf1 && *pf1 != quote1) pf2[fcc++] = *pf1++;
864
if (*pf1 == quote1) pf1++;
867
else if (*pf1 == quote2) { // same for double quotes
869
while (*pf1 && *pf1 != quote2) pf2[fcc++] = *pf1++;
870
if (*pf1 == quote2) pf1++;
873
else if (strchr(delim,*pf1) || *pf1 == 0) break; // found delimiter or null
875
else pf2[fcc++] = *pf1++; // pass normal character
878
if (*pf1 == 0) break;
881
if (nf < Nth) return 0; // no Nth field
883
if (fcc == 0) { // empty field
884
if (*string && pf1[-1] == ' ' && !strchr(delim,' ')) // all blanks and blank not delim.
885
return blankstring; // return one blank
886
if (*pf1 == 0) return 0; // no field
887
return nullstring; // return null string
890
if (++nret == 100) nret = 0; // use next return slot
891
if (retf[nret]) zfree(retf[nret]);
892
retf[nret] = zmalloc(fcc+2);
893
strncpy0(retf[nret],pf2,fcc+1);
897
const char * strField(const char *string, const char delim, int Nth) // alternative with one delimiter
899
char delims[2] = "x";
902
return strField(string,delims,Nth);
906
/**************************************************************************
908
stat = strParms(begin, input, pname, maxcc, pval)
910
Parse an input string with parameter names and values:
911
"pname1=pval1 | pname2 | pname3=pval3 | pname4 ..."
913
begin int & must be 1 to start new string, is modified
914
input const char * input string
915
pname char * output parameter name
916
maxcc int max. length for pname, including null
917
pval double & output parameter value
918
stat int status: 0=OK, -1=EOL, 1=parse error
920
Each call returns the next pname and pval.
921
A pname with no pval is assigned a value of 1 (present).
922
Input format: pname1 | pname2=pval2 | pname3 ... null
923
Leading blanks are ignored, and pnames may have imbedded blanks.
924
pvals must convert to double using convSD (accepts decimal point or comma)
926
***************************************************************************/
928
int strParms(int &begin, const char *input, char *pname, int maxcc, double &pval)
930
static int ii, beginx = 3579246;
931
const char *pnamex, *delim;
934
if (begin == 1) { // start new string
939
if (begin != beginx) zappcrash("strParms call error"); // thread safe, not reentrant
941
*pname = 0; // initz. outputs to nothing
944
while (input[ii] == ' ') ii++; // skip leading blanks
945
if (input[ii] == 0) return -1; // no more data
947
pnamex = input + ii; // next pname
950
{ // look for delimiter
951
if (pnamex[cc] == '=') break;
952
if (pnamex[cc] == '|') break;
953
if (pnamex[cc] == 0) break;
956
if (cc == 0) return 1; // err: 2 delimiters
957
if (cc >= maxcc) return 1; // err: pname too big
959
strncpy0(pname,pnamex,cc+1); // pname >> caller
960
strTrim(pname); // remove trailing blanks
962
if (pnamex[cc] == 0) { // pname + null
963
ii += cc; // position for next call
964
pval = 1.0; // pval = 1 >> caller
968
if (pnamex[cc] == '|') { // pname + |
969
ii += cc + 1; // position for next call
970
pval = 1.0; // pval = 1 >> caller
974
ii += cc + 1; // pname = pval
975
err = convSD(input + ii, pval, &delim); // parse pval (was strtod()
976
if (err > 1) return 1;
977
while (*delim == ' ') delim++; // skip poss. trailing blanks
978
if (*delim && *delim != '|') return 1; // err: delimiter not | or null
980
if (*delim) ii++; // position for next call
985
// Produce random value from hashed input string.
986
// Output range is 0 to max-1.
988
int strHash(const char *string, int max)
991
long *lstring = (long *) string;
994
if (cc > 99) appcrash("strHash, too long",null);
999
for (ii = 0; ii < cc; ii++) {
1000
hash = hash ^ lstring[ii];
1001
hash = hash ^ (hash << 5);
1002
hash = hash ^ (hash >> 7);
1003
hash = hash ^ (hash << 9);
1004
hash = hash ^ (hash >> 11);
1007
hash = hash & 0x3FFFFFFF;
1008
return (hash % max);
1012
// Hash an input string into a random printable (a-z) output string.
1013
// Returns outcc character random printable string in static memory.
1014
// Every output character is randomized from the entire input string.
1016
const char * strHash2(const char *instring, int outcc)
1018
int incc, ii, jj, rani = 0;
1020
static char outstring[40];
1022
incc = strlen(instring);
1023
if (outcc > 39) appcrash("strHash2() outcc > 39");
1025
for (ii = 0; ii < outcc; ii++)
1027
for (jj = 0; jj < incc; jj++)
1029
seed = seed + instring[jj];
1030
rani = lrandz(&seed);
1032
outstring[ii] = 'a' + rani % 26;
1040
// Copy string with specified max. length (including null terminator).
1041
// truncate if needed. null terminator is always supplied.
1043
int strncpy0(char *dest, const char *source, uint cc)
1045
strncpy(dest,source,cc);
1047
if (strlen(source) >= cc) return 1; // truncated v.2.4
1052
// Copy string with blank pad to specified length. No null.
1054
void strnPad(char *dest, const char *source, int cc)
1056
strncpy(dest,source,cc);
1057
int ii = strlen(source);
1058
for (int jj = ii; jj < cc; jj++) dest[jj] = ' ';
1062
// Remove trailing blanks from a string. Returns remaining length.
1064
int strTrim(char *dest, const char *source)
1066
if (dest != source) strcpy(dest,source);
1067
return strTrim(dest);
1070
int strTrim(char *dest)
1072
int ii = strlen(dest);
1073
while (ii && (dest[ii-1] == ' ')) dest[--ii] = 0;
1078
// Remove leading and trailing blanks from a string.
1079
// Returns remaining length, possibly zero.
1081
int strTrim2(char *dest, const char *source) // v.2.4
1083
const char *pp1, *pp2;
1087
pp2 = source + strlen(source) - 1;
1088
while (*pp1 == ' ') pp1++;
1089
while (*pp2 == ' ' && pp2 > pp1) pp2--;
1091
strncpy(dest,pp1,cc);
1097
// Remove all blanks from a string. Returns remaining length.
1099
int strCompress(char *dest, const char *source)
1101
if (dest != source) strcpy(dest,source);
1102
return strCompress(dest);
1105
int strCompress(char *string)
1109
for (ii = jj = 0; string[ii]; ii++)
1111
if (string[ii] != ' ')
1113
string[jj] = string[ii];
1122
// Concatenate multiple strings, staying within a specified overall length.
1123
// The destination string is also the first source string.
1124
// Null marks the end of the source strings (omission --> crash).
1125
// Output is truncated to fit within the specified length.
1126
// A final null is assured and is included in the length.
1127
// Returns 0 if OK, 1 if truncation was needed.
1129
int strncatv(char *dest, int maxcc, const char *source, ...)
1134
maxcc = maxcc - strlen(dest) - 1;
1135
if (maxcc < 0) return 1;
1136
va_start(arglist,source);
1141
strncat(dest,ps,maxcc);
1142
maxcc = maxcc - strlen(ps);
1143
if (maxcc < 0) break;
1144
ps = va_arg(arglist,const char *);
1148
if (maxcc < 0) return 1;
1153
// Match 1st string to N additional strings.
1154
// Return matching string number 1 to N or 0 if no match.
1155
// Supply a null argument for end of list.
1157
int strcmpv(const char *string, ...)
1163
va_start(arglist,string);
1167
stringN = va_arg(arglist, char *);
1168
if (stringN == null)
1175
if (strcmp(string,stringN) == 0)
1184
// convert string to upper case
1186
void strToUpper(char *string)
1190
const int delta = 'A' - 'a';
1192
for (ii = 0; (jj = string[ii]); ii++)
1193
if ((jj >= 'a') && (jj <= 'z')) string[ii] += delta;
1196
void strToUpper(char *dest, const char *source)
1198
strcpy(dest,source);
1203
// convert string to lower case
1205
void strToLower(char *string)
1209
const int delta = 'a' - 'A';
1211
for (ii = 0; (jj = string[ii]); ii++)
1212
if ((jj >= 'A') && (jj <= 'Z')) string[ii] += delta;
1215
void strToLower(char *dest, const char *source)
1217
strcpy(dest,source);
1222
// copy string strin to strout, replacing every occurrence
1223
// of the substring ssin with the substring ssout
1225
int repl_1str(cchar *strin, char *strout, cchar *ssin, cchar *ssout)
1227
int ccc, cc1, cc2, nfound;
1228
const char *ppp; // v.2.15
1231
cc2 = strlen(ssout);
1234
while ((ppp = strstr(strin,ssin)))
1238
strncpy(strout,strin,ccc);
1241
strncpy(strout,ssout,cc2);
1246
strcpy(strout,strin);
1251
// like repl_1str, but multiple pairs of substrings are processed
1252
// (... ssin1, ssout1, ssin2, ssout2, ... null)
1254
int repl_Nstrs(cchar *strin, char *strout, ...)
1257
cchar *ssin, *ssout;
1263
va_start(arglist,strout);
1267
ssin = va_arg(arglist, char *);
1269
ssout = va_arg(arglist, char *);
1273
nfound += repl_1str(strin,strout,ssin,ssout);
1277
strcpy(ftemp,strout);
1278
nfound += repl_1str(ftemp,strout,ssin,ssout);
1287
// Copy and convert string to hex string.
1288
// Each input character 'A' >> 3 output characters "41 "
1290
void strncpyx(char *out, const char *in, int ccin)
1293
char cx[] = "0123456789ABCDEF";
1295
if (! ccin) ccin = strlen(in);
1297
for (ii = 0, jj = 0; ii < ccin; ii++, jj += 3)
1299
c1 = (uchar) in[ii] >> 4;
1310
// Strip trailing zeros from ascii floating numbers
1311
// (e.g. 1.230000e+02 --> 1.23e+02)
1313
void StripZeros(char *pNum)
1320
if (ll >= 20) return;
1322
for (ii = 0; ii < ll; ii++)
1324
if (pNum[ii] == '.')
1328
for (++ii; ii < ll; ii++)
1330
if (pNum[ii] == '0')
1332
if (! k1) k1 = k2 = ii;
1337
if ((pNum[ii] >= '1') && (pNum[ii] <= '9'))
1348
if (k1 == pp + 1) k1++;
1349
if (k2 < k1) return;
1351
strcpy(work+k1,pNum+k2+1);
1359
// test for blank/null string
1361
int blank_null(const char *string)
1363
if (! string) return 1; // null string
1364
if (! *string) return 2; // zero length string
1365
int cc = strlen(string);
1366
for (int ii = 0; ii < cc; ii++)
1367
if (string[ii] != ' ') return 0; // non-blank string
1368
return 3; // blank string
1372
// make a copy of a string in non-volatile (heap) memory
1373
// returned string is subject for zfree();
1375
char * strdupz(const char *string, int more)
1377
char *pp = zmalloc(strlen(string)+1+more);
1383
// copy into existing 'zmalloc' string if present and long enough // v.2.8
1384
// else free memory and allocate a longer one
1385
// destination string is subject for zfree()
1387
int strdupz(const char *source, char *&zdest, int more)
1391
ccs = strlen(source);
1392
if (! zdest) zdest = zmalloc(ccs+1+more);
1393
ccd = (int) *(zdest-8);
1396
zdest = zmalloc(ccs+1+more);
1398
strcpy(zdest,source);
1403
// clean \x escape sequences and replace them with the escaped character
1404
// \n >> newline \" >> doublequote \\ >> backslash etc.
1405
// see $ man ascii for the complete list
1407
int clean_escapes(char *string)
1409
char *pp1 = string, *pp2 = string, *pp;
1411
char escapes[] = "abtnvfr";
1423
else if (char1 == '\\') {
1425
pp = strchr(escapes,char1);
1426
if (pp) char1 = pp - escapes + 7;
1435
// Compute the graphic character count for a UTF8 character string.
1436
// Depends on UTF8 rules:
1437
// - ascii characters are positive (actually 0x00 to 0x7F)
1438
// - 1st byte of multibyte sequence is negative (actually 0xC0 to 0xFD)
1439
// - subsequent bytes are negative and < 0xC0 (actually 0x80 to 0xBF)
1441
int utf8len(const char *utf8string) // v.2.3
1446
for (ii = cc = 0; utf8string[ii]; ii++)
1448
if (utf8string[ii] < 0) // multibyte character
1449
while (utf8string[ii+1] < xlimit) ii++; // skip extra bytes
1457
// Extract a UTF8 substring with a specified count of graphic characters.
1458
// utf8in input UTF8 string
1459
// utf8out output UTF8 string, which must be long enough
1460
// pos initial graphic character position to get (0 = first)
1461
// cc max. count of graphic characters to get
1462
// returns number of graphic characters extracted, <= cc
1463
// Output string is null terminated after last extracted character.
1465
int utf8substring(char *utf8out, const char *utf8in, int pos, int cc) // v.2.3
1467
int ii, jj, kk, posx, ccx;
1470
for (ii = posx = 0; posx < pos && utf8in[ii]; ii++)
1473
while (utf8in[ii+1] < xlimit) ii++;
1479
for (ccx = 0; ccx < cc && utf8in[jj]; jj++)
1482
while (utf8in[jj+1] < xlimit) jj++;
1488
strncpy(utf8out,utf8in+ii,kk);
1495
// check a string for valid utf8 encoding
1496
// returns: 0 = OK, 1 = bad string
1498
int utf8_check(const char *string) // v.2.4
1501
unsigned char ch1, ch2, nch;
1503
for (pp = string; *pp; pp++)
1506
if (ch1 < 0x7F) continue;
1507
if (ch1 > 0xBF && ch1 < 0xE0) nch = 1;
1508
else if (ch1 < 0xF0) nch = 2;
1509
else if (ch1 < 0xF8) nch = 3;
1510
else if (ch1 < 0xFC) nch = 4;
1511
else if (ch1 < 0xFE) nch = 5;
1516
if (ch2 < 0x80 || ch2 > 0xBF) return 1;
1525
// Find the Nth graphic character position within a UTF8 string
1526
// utf8in input UTF8 string
1527
// Nth graphic character position, zero based
1528
// returns starting character (byte) position of Nth graphic character
1530
int utf8_position(const char *utf8in, int Nth) // v.2.4
1535
for (ii = posx = 0; posx < Nth && utf8in[ii]; ii++)
1538
while (utf8in[ii+1] < xlimit) ii++;
1546
/**************************************************************************
1548
***************************************************************************/
1550
// create a new bitmap with specified bit length.
1551
// initially all bits are false.
1553
bitmap * bitmap_new(int nbits)
1558
bm = (bitmap *) zmalloc(sizeof(bitmap));
1560
cc = (nbits + 7) / 8;
1561
bm->bits = (uchar *) zmalloc(cc);
1562
for (ii = 0; ii < cc; ii++) bm->bits[ii] = 0;
1567
// set bit in bitmap to true or false
1569
void bitmap_set(bitmap *bm, int bit, bool value)
1574
if (bit >= bm->nbits) zappcrash("bitmap, bit %d too big",bit);
1579
if (value) bm->bits[ii] = bm->bits[ii] | bit1;
1582
bm->bits[ii] = bm->bits[ii] & bit1;
1589
// fetch bitmap bit, return true or false
1591
bool bitmap_get(bitmap *bm, int bit)
1598
bit1 = bm->bits[ii] << jj;
1599
if (bit1 < 127) return false;
1606
void bitmap_delete(bitmap *bm)
1614
/**************************************************************************
1615
variable list functions - array / list of strings
1616
***************************************************************************/
1618
// create new variable list with specified capacity
1620
pvlist * pvlist_create(int max)
1624
pv = (pvlist *) zmalloc(sizeof(pvlist));
1627
pv->list = (char **) zmalloc(max * sizeof(char *));
1631
// free memory for variable list
1633
void pvlist_free(pvlist *pv)
1637
for (ii = 0; ii < pv->act; ii++)
1638
zfree(pv->list[ii]);
1643
// append new entry to end of list (optional if unique)
1644
// if list if full, first entry is removed and rest are packed down
1645
// return: N >= 0: new entry added at position N
1646
// N = -1: not unique, not added
1648
int pvlist_append(pvlist *pv, const char *entry, int unique)
1652
if (unique && pvlist_find(pv,entry) >= 0) return -1; // not unique
1654
if (pv->act == pv->max) pvlist_remove(pv,0); // if list full, remove 1st entry
1657
pv->list[ii] = strdupz(entry); // add to end of list
1662
// prepend new entry to list (optional if unique)
1663
// prior list entries are pushed down to make room
1664
// if list is full, last entry is removed first
1665
// return: N = 0: new entry added at position 0
1666
// N = -1: not unique, not added
1668
int pvlist_prepend(pvlist *pv, const char *entry, int unique)
1672
if (unique && pvlist_find(pv,entry) >= 0) return -1; // not unique
1674
if (pv->act == pv->max) pvlist_remove(pv,pv->act-1); // if list full, remove last entry
1676
for (ii = pv->act; ii > 0; ii--) // push all list entries down
1677
pv->list[ii] = pv->list[ii-1];
1678
pv->list[0] = strdupz(entry); // add to start of list
1683
// find list entry by name, return -1 if not found
1685
int pvlist_find(pvlist *pv, const char *entry)
1689
for (ii = 0; ii < pv->act; ii++)
1690
if (strEqu(entry,pv->list[ii])) break;
1691
if (ii < pv->act) return ii;
1695
// remove an entry by name and repack list
1697
int pvlist_remove(pvlist *pv, const char *entry)
1701
ii = pvlist_find(pv,entry);
1702
if (ii < 0) return -1;
1703
pvlist_remove(pv,ii);
1707
// remove an entry by number and repack list
1709
int pvlist_remove(pvlist *pv, int ii)
1711
if (ii < 0 || ii >= pv->act) return -1;
1712
zfree(pv->list[ii]);
1713
for (++ii; ii < pv->act; ii++)
1714
pv->list[ii-1] = pv->list[ii];
1720
// return entry count
1722
int pvlist_count(pvlist *pv)
1728
// replace Nth entry with new one
1730
int pvlist_replace(pvlist * pv, int ii, char *entry)
1732
if (ii < 0 || ii >= pv->act) return -1;
1733
zfree(pv->list[ii]);
1734
pv->list[ii] = strdupz(entry);
1739
// return Nth entry or null
1741
char * pvlist_get(pvlist *pv, int Nth)
1743
if (Nth >= pv->act) return 0;
1744
return pv->list[Nth];
1748
// sort list in ascending order
1750
int pvlist_sort(pvlist *pv)
1752
HeapSort(pv->list,pv->act);
1757
/**************************************************************************
1759
Conversion Utilities
1761
convSI(string, inum, delim) string to int
1762
convSI(string, inum, low, high, delim) string to int with range check
1764
convSD(string, dnum, delim) string to double
1765
convSD(string, dnum, low, high, delim) string to double with range check
1767
convIS(inum, string, cc) int to string with returned cc
1769
convDS(fnum, digits, string, cc) double to string with specified
1770
digits of precision and returned cc
1772
string input (const char *) or output (char *)
1773
inum input (int) or output (int &)
1774
dnum input (double) or output (double &)
1775
delim optional output pointer to delimiter (null or const char **)
1776
low, high input range check (int or double)
1777
cc output string length (int &)
1778
digits input digits of precision (int) to be used for output string
1780
function status returned:
1781
0 normal conversion, no invalid digits, blank/null termination
1782
1 successful converstion, but trailing non-numeric found
1783
2 conversion OK, but outside specified limits
1784
3 null or blank string, converted to zero
1785
4 conversion error, invalid data in string
1786
overlapping statuses have following precedence: 4 3 2 1 0
1788
***************************************************************************/
1790
#define max10 (0x7fffffff / 10)
1793
// Convert string to integer
1795
int convSI(const char *string, int &inum, const char **delim)
1798
int sign = 0, digits = 0, tnb = 0;
1799
const char *pch = string;
1803
while ((ch = *pch) == ' ') pch++; // skip leading blanks
1805
if (ch == '-') sign = -1; // process leading +/- sign
1806
if (ch == '+') sign = 1; // (at most one sign character)
1809
while ((*pch >= '0') && (*pch <= '9')) // process digits 0 - 9
1811
if (inum > max10) goto conv_err; // value too big
1812
inum = 10 * inum + *pch - '0';
1817
if (delim) *delim = pch; // terminating delimiter
1818
if (*pch && (*pch != ' ')) tnb++; // not null or blank
1820
if (! digits) // no digits found
1822
if (tnb) return 4; // non-numeric (invalid) string
1823
else return 3; // null or blank string
1826
if (sign == -1) inum = -inum; // negate if - sign
1828
if (! tnb) return 0; // no trailing non-numerics
1829
else return 1; // trailing non-numerics
1837
int convSI(const char *string, int & inum, int lolim, int hilim, const char **delim)
1839
int stat = convSI(string,inum,delim);
1841
if (stat > 2) return stat; // invalid or null/blank
1842
if (inum < lolim) return 2; // return 2 if out of limits
1843
if (inum > hilim) return 2; // (has precedence over status 1)
1844
return stat; // limits OK, return 0 or 1
1848
// Convert string to double.
1850
int convSD(const char *string, double &dnum, const char **delim)
1853
int ii, sign = 0, digits = 0, ndec = 0;
1854
int exp = 0, esign = 0, edigits = 0, tnb = 0;
1855
const char *pch = string;
1857
static int first = 1;
1858
static double decimals[21], exponents[74];
1860
if (first) // first-time called
1862
first = 0; // pre-calculate constants
1863
for (ii = 1; ii <= 20; ii++) decimals[ii] = pow(10.0,-ii);
1864
for (ii = -36; ii <= 36; ii++) exponents[ii+37] = pow(10.0,ii);
1869
while ((ch = *pch) == ' ') pch++; // skip leading blanks
1871
if (ch == '-') sign = -1; // process leading +/- sign
1872
if (ch == '+') sign = 1; // (at most one sign character)
1877
while ((*pch >= '0') && (*pch <= '9')) // process digits 0 - 9
1879
dnum = 10.0 * dnum + (*pch - '0');
1885
if ((*pch == '.') || (*pch == ',')) // process decimal point
1886
{ // (allow comma or period)
1887
if (ndec) goto conv_err;
1893
if ((*pch == 'e') || (*pch == 'E')) // process optional exponent
1896
if (*pch == '+') esign = 1; // optional +/- sign
1897
if (*pch == '-') esign = -1;
1900
if ((*pch < '0') || (*pch > '9')) goto conv_err; // 1st digit
1905
if ((*pch >= '0') && (*pch <= '9')) // optional 2nd digit
1907
exp = 10 * exp + (*pch - '0');
1912
if ((exp < -36) || (exp > 36)) goto conv_err; // exponent too big
1915
if (delim) *delim = pch; // terminating delimiter
1916
if (*pch && (*pch != ' ')) tnb++; // not null or blank
1918
if (!(digits + edigits)) // no digits found
1920
if (tnb) return 4; // non-numeric (invalid) string
1921
else return 3; // null or blank string
1924
if (ndec > 1) dnum = dnum * decimals[ndec-1]; // compensate for decimal places
1926
if (sign == -1) dnum = - dnum; // negate if negative
1930
if (esign == -1) exp = -exp; // process exponent
1931
dnum = dnum * exponents[exp+37];
1934
if (! tnb) return 0; // no trailing non-numerics
1935
else return 1; // trailing non-numerics
1943
int convSD(const char *string, double &dnum, double lolim, double hilim, const char **delim)
1945
int stat = convSD(string,dnum,delim);
1947
if (stat > 2) return stat; // invalid or null/blank
1948
if (dnum < lolim) return 2; // return 2 if out of limits
1949
if (dnum > hilim) return 2; // (has precedence over status 1)
1950
return stat; // limits OK, return 0 or 1
1954
// Convert int to string with returned length.
1956
int convIS(int inum, char *string, int *cc)
1960
ccc = sprintf(string,"%d",inum);
1966
// Convert double to string with specified digits of precision.
1967
// Shortest length format (f/e) will be used.
1968
// Output length is returned in optional argument cc.
1970
int convDS(double dnum, int digits, char *string, int *cc)
1975
sprintf(string,"%.*g",digits,dnum);
1977
pstr = strstr(string,"e+");
1978
if (pstr) strcpy(pstr+1,pstr+2);
1980
pstr = strstr(string,"e0");
1981
if (pstr) strcpy(pstr+1,pstr+2);
1983
pstr = strstr(string,"e0");
1984
if (pstr) strcpy(pstr+1,pstr+2);
1986
pstr = strstr(string,"e-0");
1987
if (pstr) strcpy(pstr+2,pstr+3);
1989
pstr = strstr(string,"e-0");
1990
if (pstr) strcpy(pstr+2,pstr+3);
1992
ccc = strlen(string);
1999
/**************************************************************************
2001
Wildcard string match
2003
Match candidate string to wildcard string containing any number of
2004
'*' or '?' wildcard characters. '*' matches any number of characters,
2005
including zero characters. '?' matches any one character.
2007
Returns 0 if match, 1 if no match.
2009
***************************************************************************/
2011
int MatchWild(const char *pWild, const char *pString)
2018
while (pWild[0] == '*')
2026
for (ii = 0; pWild[ii] && (pWild[ii] != '*'); ii++)
2028
if (pWild[ii] != pString[ii])
2030
if (! pString[ii]) return 1;
2031
if (pWild[ii] == '?') continue;
2032
if (! star) return 1;
2038
if (pWild[ii] == '*')
2045
if (! pString[ii]) return 0;
2046
if (ii && pWild[ii-1] == '*') return 0;
2047
if (! star) return 1;
2053
/**************************************************************************
2055
SearchWild - wildcard file search
2057
Find all files with total /pathname/filename matching a pattern,
2058
which may have any number of the wildcard characters '*' and '?'
2059
in either or both the pathname and filename.
2061
const char * SearchWild(const char *wfilespec, int &flag)
2063
inputs: flag = 1 to start a new search
2064
flag = 2 abort a running search
2065
*** do not modify flag within a search ***
2067
wfilespec = filespec to search with optional wildcards
2068
e.g. "/name1/na*me2/nam??e3/name4*.ext?"
2070
return: a pointer to one matching file is returned per call,
2071
or null when there are no more matching files.
2073
The search may be aborted before completion, but make a final
2074
call with flag = 2 to clean up temp file. A new search with
2075
flag = 1 will also finish the cleanup.
2077
NOT THREAD SAFE - do not use in parallel threads
2079
shell find command is used for the initial search because this
2080
is much faster than recursive use of readdir() (why?).
2082
(#) is used in place of (*) in comments below to prevent
2083
compiler from interpreting (#/) as end of comments
2085
GNU find peculiarities:
2086
find /path/# omits "." files
2087
find /path/ includes "." files
2088
find /path/# recurses directories under /path/
2089
find /path/#.txt does not recurse directories
2090
find /path/#/ finds all files under /path/
2091
find /path/#/# finds files >= 1 directory level under /path/
2092
find /path/xxx# never finds anything
2094
SearchWild uses simpler and more intuitive matching:
2095
'/' and '.' are matched by '#'
2096
/path/#.txt finds all .txt files under /path/ at any directory level
2098
***************************************************************************/
2100
const char * SearchWild(const char *wpath, int &uflag)
2102
static FILE *fid = 0;
2103
static char tempfile[60];
2104
static char matchfile[maxfcc];
2105
char searchpath[maxfcc];
2106
char command[maxfcc];
2107
int cc, err, ignore;
2111
if ((uflag == 1) || (uflag == 2)) { // first call or stop flag
2113
fclose(fid); // if file open, close it
2115
sprintf(command,"rm -f %s",tempfile); // and delete it
2116
ignore = system(command);
2120
if (uflag == 2) return 0; // kill flag, done
2122
if (uflag == 1) // first call flag
2125
if (cc == 0) return 0;
2126
if (cc > maxfcc-20) appcrash("SearchWild: wpath > max");
2128
pp = (char *) wpath;
2129
repl_Nstrs(pp,searchpath,"$","\\$","\"","\\\"",null); // init. search path, escape $ and "
2131
pp = strchr(searchpath,'*');
2132
if (pp) { // not efficient but foolproof
2133
while ((*pp != '/') && (pp > searchpath)) pp--; // /aaa/bbb/cc*cc... >>> /aaa/bbb/
2134
if (pp > searchpath) *(pp+1) = 0;
2138
sprintf(tempfile,"/tmp/searchwild-%u",pid); // unique temp file name
2139
sprintf(command,"find \"%s\" -type f -or -type l > %s", // find files (ordinary, symlink)
2140
searchpath,tempfile); // output to temp file
2141
err = system(command); // ignore error
2142
fid = fopen(tempfile,"r"); // open temp file
2143
uflag = 763568954; // begin search
2146
if (uflag != 763568954) appcrash("SearchWild, uflag invalid");
2150
pp = fgets(matchfile,maxfcc-2,fid); // next matching file
2152
fclose(fid); // no more
2154
sprintf(command,"rm -f %s \n",tempfile);
2155
ignore = system(command);
2159
cc = strlen(matchfile); // get rid of trailing \n
2160
matchfile[cc-1] = 0;
2162
err = MatchWild(wpath,matchfile); // wildcard match?
2163
if (err) continue; // no
2165
return matchfile; // return file
2170
/**************************************************************************/
2172
// perform a binary search on sorted list of integers
2173
// return matching element or -1 if not found
2175
int bsearch(int element, int nn, int list[])
2177
int ii, jj, kk, rkk;
2179
ii = nn / 2; // next element to search
2180
jj = (ii + 1) / 2; // next increment
2181
nn--; // last element
2186
kk = list[ii] - element; // check element
2190
ii -= jj; // too high, go down
2191
if (ii < 0) return -1;
2196
ii += jj; // too low, go up
2197
if (ii > nn) return -1;
2200
else if (kk == 0) return ii; // matched
2202
jj = jj / 2; // reduce increment
2206
jj = 1; // step by 1 element
2207
if (! rkk) rkk = kk; // save direction
2210
if (rkk > 0) { if (kk < 0) return -1; } // if change direction, fail
2211
else if (kk > 0) return -1;
2218
/**************************************************************************
2220
***************************************************************************/
2222
#define SWAP(x,y) (temp = (x), (x) = (y), (y) = temp)
2225
// heapsort for array of integers
2227
static void adjust(int vv[], int n1, int n2)
2229
int *bb, jj, kk, temp;
2237
if (kk < n2 && bb[kk] < bb[kk+1]) kk++;
2238
if (bb[jj] < bb[kk]) SWAP(bb[jj],bb[kk]);
2244
void HeapSort(int vv[], int nn)
2248
for (jj = nn/2; jj > 0; jj--) adjust(vv,jj,nn);
2252
for (jj = nn-1; jj > 0; jj--)
2254
SWAP(bb[1], bb[jj+1]);
2260
// heapsort for array of floats
2262
static void adjust(float vv[], int n1, int n2)
2273
if (kk < n2 && bb[kk] < bb[kk+1]) kk++;
2274
if (bb[jj] < bb[kk]) SWAP(bb[jj],bb[kk]);
2280
void HeapSort(float vv[], int nn)
2285
for (jj = nn/2; jj > 0; jj--) adjust(vv,jj,nn);
2289
for (jj = nn-1; jj > 0; jj--)
2291
SWAP(bb[1], bb[jj+1]);
2297
// heapsort for array of doubles
2299
static void adjust(double vv[], int n1, int n2)
2310
if (kk < n2 && bb[kk] < bb[kk+1]) kk++;
2311
if (bb[jj] < bb[kk]) SWAP(bb[jj],bb[kk]);
2317
void HeapSort(double vv[], int nn)
2322
for (jj = nn/2; jj > 0; jj--) adjust(vv,jj,nn);
2326
for (jj = nn-1; jj > 0; jj--)
2328
SWAP(bb[1], bb[jj+1]);
2334
// heapsort array of pointers to strings in ascending order of strings
2335
// pointers are sorted, strings are not changed.
2337
static void adjust(char *vv[], int n1, int n2)
2348
if (kk < n2 && strcmp(bb[kk],bb[kk+1]) < 0) kk++;
2349
if (strcmp(bb[jj],bb[kk]) < 0) SWAP(bb[jj],bb[kk]);
2355
void HeapSort(char *vv[], int nn)
2360
for (jj = nn/2; jj > 0; jj--) adjust(vv,jj,nn);
2364
for (jj = nn-1; jj > 0; jj--)
2366
SWAP(bb[0], bb[jj]);
2372
// heapsort array of pointers to strings in user-defined order.
2373
// pointers are sorted, strings are not changed.
2375
static void adjust(char *vv[], int n1, int n2, HeapSortUcomp fcomp)
2386
if (kk < n2 && fcomp(bb[kk],bb[kk+1]) < 0) kk++;
2387
if (fcomp(bb[jj],bb[kk]) < 0) SWAP(bb[jj],bb[kk]);
2393
void HeapSort(char *vv[], int nn, HeapSortUcomp fcomp)
2398
for (jj = nn/2; jj > 0; jj--) adjust(vv,jj,nn,fcomp);
2402
for (jj = nn-1; jj > 0; jj--)
2404
SWAP(bb[0], bb[jj]);
2405
adjust(vv,1,jj,fcomp);
2410
// heapsort for array of strings or records,
2411
// using caller-supplied record compare function.
2412
// HeapSortUcomp returns [ -1 0 +1 ] for rec1 [ < = > ] rec2
2413
// method: build array of pointers and sort these, then
2414
// use this sorted array to re-order the records at the end.
2416
static int *vv1, *vv2;
2418
static void adjust(char *recs, int RL, int n1, int n2, HeapSortUcomp fcomp)
2420
int *bb, jj, kk, temp;
2429
rec1 = recs + RL * bb[kk];
2430
rec2 = recs + RL * bb[kk+1];
2431
if (kk < n2 && fcomp(rec1,rec2) < 0) kk++;
2432
rec1 = recs + RL * bb[jj];
2433
rec2 = recs + RL * bb[kk];
2434
if (fcomp(rec1,rec2) < 0) SWAP(bb[jj],bb[kk]);
2440
void HeapSort(char *recs, int RL, int NR, HeapSortUcomp fcomp)
2442
int *bb, jj, kk, temp, flag;
2446
for (jj = 0; jj < NR; jj++) vv1[jj] = jj;
2448
for (jj = NR/2; jj > 0; jj--) adjust(recs,RL,jj,NR,fcomp);
2452
for (jj = NR-1; jj > 0; jj--)
2454
SWAP(bb[1], bb[jj+1]);
2455
adjust(recs,RL,1,jj,fcomp);
2459
for (jj = 0; jj < NR; jj++) vv2[vv1[jj]] = jj;
2461
vvrec = new char[RL];
2466
for (jj = 0; jj < NR; jj++)
2469
if (kk == jj) continue;
2470
memmove(vvrec,recs+jj*RL,RL);
2471
memmove(recs+jj*RL,recs+kk*RL,RL);
2472
memmove(recs+kk*RL,vvrec,RL);
2473
SWAP(vv2[jj],vv2[kk]);
2484
/**************************************************************************
2486
int MemSort (char *RECS, int RL, int NR, int KEYS[][3], int NK)
2488
RECS is an array of records, to be sorted in-place.
2489
(record length = RL, record count = NR)
2491
KEYS[NK,3] is an integer array defined as follows:
2492
[N,0] starting position of Nth key field in RECS
2493
[N,1] length of Nth key field in RECS
2494
[N,2] type of sort for Nth key:
2497
3 = int*4 ascending (int, long)
2498
4 = int*4 descending
2499
5 = float*4 ascending (float)
2500
6 = float*4 descending
2501
7 = float*8 ascending (double)
2502
8 = float*8 descending
2504
***************************************************************************/
2506
int MemSortComp(const char *rec1, const char *rec2);
2507
int MemSortKeys[10][3], MemSortNK;
2509
int MemSort(char *RECS, int RL, int NR, int KEYS[][3], int NK)
2513
if (NR < 2) return 1;
2515
if (NK > 10) appcrash("MemSort, bad NK");
2516
if (NK < 1) appcrash("MemSort, bad NK");
2520
for (ii = 0; ii < NK; ii++)
2522
MemSortKeys[ii][0] = KEYS[ii][0];
2523
MemSortKeys[ii][1] = KEYS[ii][1];
2524
MemSortKeys[ii][2] = KEYS[ii][2];
2527
HeapSort(RECS,RL,NR,MemSortComp);
2531
int MemSortComp(const char *rec1, const char *rec2)
2533
int ii, stat, kpos, ktype, kleng;
2536
double dnum1, dnum2;
2537
const char *p1, *p2;
2539
for (ii = 0; ii < MemSortNK; ii++) // loop each key
2541
kpos = MemSortKeys[ii][0]; // relative position
2542
kleng = MemSortKeys[ii][1]; // length
2543
ktype = MemSortKeys[ii][2]; // type
2545
p1 = rec1 + kpos; // absolute position
2550
case 1: // char ascending
2551
stat = strncmp(p1,p2,kleng); // compare 2 key values
2552
if (stat) return stat; // + if rec1 > rec2, - if <
2553
break; // 2 keys are equal, check next key
2555
case 2: // char descending
2556
stat = strncmp(p1,p2,kleng);
2557
if (stat) return -stat;
2560
case 3: // int ascending
2561
memmove(&inum1,p1,4);
2562
memmove(&inum2,p2,4);
2563
if (inum1 > inum2) return 1;
2564
if (inum1 < inum2) return -1;
2567
case 4: // int descending
2568
memmove(&inum1,p1,4);
2569
memmove(&inum2,p2,4);
2570
if (inum1 > inum2) return -1;
2571
if (inum1 < inum2) return 1;
2574
case 5: // float ascending
2575
memmove(&rnum1,p1,4);
2576
memmove(&rnum2,p2,4);
2577
if (rnum1 > rnum2) return 1;
2578
if (rnum1 < rnum2) return -1;
2581
case 6: // float descending
2582
memmove(&rnum1,p1,4);
2583
memmove(&rnum2,p2,4);
2584
if (rnum1 > rnum2) return -1;
2585
if (rnum1 < rnum2) return 1;
2588
case 7: // double ascending
2589
memmove(&dnum1,p1,8);
2590
memmove(&dnum2,p2,8);
2591
if (dnum1 > dnum2) return 1;
2592
if (dnum1 < dnum2) return -1;
2595
case 8: // double descending
2596
memmove(&dnum1,p1,8);
2597
memmove(&dnum2,p2,8);
2598
if (dnum1 > dnum2) return -1;
2599
if (dnum1 < dnum2) return 1;
2602
default: // key type not 1-8
2603
appcrash("MemSort, bad KEYS sort type");
2607
return 0; // records match on all keys
2611
/**************************************************************************/
2613
// random number generators with explicit context
2614
// and improved randomness over a small series
2616
int lrandz(int64 *seed) // returns 0 to 0x7fffffff
2618
*seed = *seed ^ (*seed << 17);
2619
*seed = *seed ^ (*seed << 20);
2620
return nrand48((unsigned int16 *) seed);
2623
double drandz(int64 *seed) // returns 0.0 to 0.99999...
2625
*seed = *seed ^ (*seed << 17);
2626
*seed = *seed ^ (*seed << 20);
2627
return erand48((unsigned int16 *) seed);
2631
/**************************************************************************
2632
Quick Math Functions
2634
These functions duplicate C library standard functions but provide
2635
faster execution at the expense of precision. The precision is
2636
generally 5 or 6 digits, but test and use with caution.
2638
They all build a table of values on first call (1-2 millisecs),
2639
which is used to interpolate values in subsequent calls.
2641
The argument range is also limited - see comments in each function.
2643
Following benchmarks done with Intel Core 2 Duo at 2.4 GHz
2645
double qsine(double) 3.2 times as fast as sin()
2646
double qcosine(double) 3.2 times as fast as cos()
2647
double qarcsine(double) 5.1 times as fast as asin()
2648
double qsqrt(double) 2.1 times as fast as sqrt()
2650
***************************************************************************/
2652
double qsine(double x) // sine(x) for x = -pi to +pi
2653
{ // (seg. fault if out of range)
2656
double v1, s1, s2, ans;
2657
static double base = 3.15; // pi + a bit more
2658
static double K1 = base / 500.0;
2659
static double K2 = 500.0 / base;
2660
static double v[1002], sinv[1002];
2664
for (ii = 0; ii < 1002; ii++)
2666
v1 = ii * K1 - base; // -pi to +pi (a bit more each end)
2672
ii = int(K2 * (x + base));
2676
ans = s1 + (s2 - s1) * (x - v1) * K2;
2680
double qcosine(double x) // cosine(x) for x = -pi to +pi
2681
{ // (seg. fault if out of range)
2684
double v1, s1, s2, ans;
2685
static double base = 3.15; // pi + a bit more
2686
static double K1 = base / 500.0;
2687
static double K2 = 500.0 / base;
2688
static double v[1002], cosinv[1002];
2692
for (ii = 0; ii < 1002; ii++)
2694
v1 = ii * K1 - base; // -pi to +pi (a bit more each end)
2696
cosinv[ii] = cos(v1);
2700
ii = int(K2 * (x + base));
2704
ans = s1 + (s2 - s1) * (x - v1) * K2;
2708
double qarcsine(double x) // arcsine(x) for x = -1 to +1
2709
{ // (seg. fault if out of range)
2712
double v1, s1, s2, ans;
2713
static double base = 1.0;
2714
static double K1 = base / 1000.0;
2715
static double K2 = 1000.0 / base;
2716
static double v[2001], arcsinv[2001];
2720
for (ii = 0; ii < 2001; ii++)
2722
v1 = ii * K1 - base; // -1 to +1, exactly
2724
arcsinv[ii] = asin(v1);
2728
ii = int(K2 * (x + base));
2729
if (ii < 50 || ii > 1950) return asin(x); // improve accuracy near 90 deg.
2733
ans = s1 + (s2 - s1) * (x - v1) * K2;
2737
double qsqrt(double x) // sqrt(x) for x = 0 to 1
2738
{ // (seg. fault if out of range)
2741
static double *sqrtv;
2745
sqrtv = (double *) zmalloc(100001 * sizeof(double));
2746
for (ii = 0; ii < 100001; ii++)
2747
sqrtv[ii] = sqrt(0.00001 * ii + 0.000005);
2750
ii = int(100000.0 * x);
2751
if (ii < 100) return sqrt(x); // improve accuracy near zero
2753
return sqrtv[ii]; // interpolation slower than sqrt()
2754
} // (done in cpu microcode)
2757
/**************************************************************************
2759
spline1: define a curve using a set of data points (x and y values) v.2.11
2760
spline2: for a given x-value, return a y-value fitting the curve
2762
For spline1, the no. of curve-defining points must be < 100.
2763
For spline2, the given x-value must be within the range defined in spline1.
2765
***************************************************************************/
2767
namespace splinedata
2770
double px1[100], py1[100], py2[100];
2773
using namespace splinedata;
2776
void spline1(int dnn, double *dx1, double *dy1)
2778
double sig, p, u[100];
2782
if (nn > 100) zappcrash("spline1(), > 100 data points");
2784
for (ii = 0; ii < nn; ii++)
2788
if (ii && px1[ii] <= px1[ii-1])
2789
zappcrash("spline1(), x-value not increasing");
2794
for (ii = 1; ii < nn-1; ii++)
2796
sig = (px1[ii] - px1[ii-1]) / (px1[ii+1] - px1[ii-1]);
2797
p = sig * py2[ii-1] + 2;
2798
py2[ii] = (sig - 1) / p;
2799
u[ii] = (6 * ((py1[ii+1] - py1[ii]) / (px1[ii+1] - px1[ii]) - (py1[ii] - py1[ii-1])
2800
/ (px1[ii] - px1[ii-1])) / (px1[ii+1] - px1[ii-1]) - sig * u[ii-1]) / p;
2805
for (ii = nn-2; ii >= 0; ii--)
2806
py2[ii] = py2[ii] * py2[ii+1] + u[ii];
2812
double spline2(double x)
2814
int kk, klo = 0, khi = nn-1;
2817
while (khi - klo > 1)
2819
kk = (khi + klo) / 2;
2820
if (px1[kk] > x) khi = kk;
2824
h = px1[khi] - px1[klo];
2825
a = (px1[khi] - x) / h;
2826
b = (x - px1[klo]) / h;
2827
y = a * py1[klo] + b * py1[khi] + ((a*a*a - a) * py2[klo]
2828
+ (b*b*b - b) * py2[khi]) * (h*h) / 6;
2833
using namespace std;
2836
/**************************************************************************
2837
Initialize application files according to following conventions:
2838
+ use application directories defined by Makefile
2839
+ user data files are in /home/user/.appname/
2840
+ if not already attached to a terminal, redirect stdout/stderr
2841
to log file at /home/user/.appname/appname.log
2843
zappname application name: dcopp, fotoxx, etc.
2844
zdatadir installed data files translations, parameters, etc.
2845
zdocdir user documentation userguide, README, CHANGES
2846
zicondir icons icon files .png
2847
zuserdir /home/user/.appname/ log file, user parameters
2848
***************************************************************************/
2851
char zdatadir[200], zdocdir[200], zicondir[200], zuserdir[200];
2852
char zlanguage[8] = "en"; // "lc" or "lc_RC" v.2.14
2854
const char * get_zuserdir() { return zuserdir; } // /home/user/.appname
2855
const char * get_zdatadir() { return zdatadir; } // parameters, icons
2857
int initz_appfiles(const char *appname, ...)
2859
char work[200], *pp;
2860
const char *appfile;
2863
struct stat statdat;
2866
catch_signals(); // catch segfault, backtrace v.2.22
2868
strcpy(zappname,appname);
2870
strcpy(zdatadir,DATADIR); // macros from build script
2871
strcpy(zdocdir,DOCDIR);
2872
strcpy(zicondir,zdatadir);
2873
strcat(zicondir,"/icons");
2876
if (strEqu(pp,"root")) snprintf(zuserdir,199,"/root/.%s",zappname); // get /root/.appname
2877
else snprintf(zuserdir,199,"%s/.%s",getenv("HOME"),zappname); // or /home/user/.appname
2879
err = stat(zuserdir,&statdat); // does it exist already?
2881
snprintf(work,199,"mkdir -m 0700 %s",zuserdir); // no, create it
2883
if (err) zappcrash("%s, %s \n",wstrerror(err),work); // cannot
2885
va_start(arglist,appname); // copy req. application files
2886
while (true) { // from install directory to
2887
appfile = va_arg(arglist, const char *); // to /home/user/.appname/
2888
if (! appfile) break;
2889
snprintf(work,199,"cp %s/%s %s",zdatadir,appfile,zuserdir);
2891
if (err) printf("%s, %s \n",wstrerror(err),work); // GTK not started yet
2897
snprintf(work,199,"%s/%s.log",zuserdir,zappname); // if not attached to a terminal,
2898
fid = freopen(work,"w",stdout); // redirect output to log file
2899
fid = freopen(work,"w",stderr); // /home/user/.appname/appname.log
2900
printf("%s message and error log \n",zappname); // append to old file v.2.5
2907
// display help file in a separate process so application is not blocked
2909
void showz_helpfile()
2912
char docfile[200], lang[4];
2914
snprintf(docfile,199,"%s/userguide-%s.html",zdocdir,zlanguage); // .../userguide-lc_RC.html v.2.14
2915
err = access(docfile,R_OK);
2917
strncpy0(lang,zlanguage,3);
2918
snprintf(docfile,199,"%s/userguide-%s.html",zdocdir,lang); // .../userguide-lc.html
2919
err = access(docfile,R_OK);
2923
snprintf(docfile,199,"%s/userguide-en.html",zdocdir); // .../userguide-en.html
2924
err = access(docfile,R_OK);
2928
zmessageACK(ZTX("help file not found: %s"),docfile);
2932
showz_html(docfile); // v.2.18
2937
// display various admin text files in popup window
2942
snprintf(command,199,"cat %s/README",zdocdir);
2943
popup_command(command,500,300);
2947
void showz_changelog() // 1st 50 lines
2950
snprintf(command,199,"head -n 50 %s/CHANGES",zdocdir);
2951
popup_command(command,500,300);
2955
void showz_translations()
2958
snprintf(command,199,"cat %s/TRANSLATIONS",zdocdir);
2959
popup_command(command,500,300);
2964
// create a desktop icon / launcher with icon // v.2.18
2965
// target system needs to be LSB compliant
2967
void zmake_launcher(const char *categories, const char *genericname)
2969
char dtfile[200], work[200];
2973
snprintf(dtfile,199,"%s/Desktop/kornelix-%s.desktop",getenv("HOME"),zappname);
2974
fid = fopen(dtfile,"w");
2976
zmessageACK(ZTX("error: %s"),strerror(errno));
2980
fputs("[Desktop Entry]\n",fid);
2981
snprintf(work,199,"Name=%s\n",zappname);
2983
snprintf(work,199,"Categories=%s\n",categories);
2985
fputs("Type=Application\n",fid);
2986
fputs("Terminal=false\n",fid);
2987
snprintf(work,199,"Exec=%s/%s\n",BINDIR,zappname);
2989
snprintf(work,199,"Icon=%s/icons/%s.png\n",DATADIR,zappname);
2991
snprintf(work,199,"GenericName=%s\n",genericname);
2995
snprintf(work,199,"xdg-desktop-menu install %s",dtfile);
2997
if (err) zmessLogACK("error: %s",wstrerror(err));
3002
/**************************************************************************
3004
Translation Functions v.2.9 revised to use .po files
3006
Translation files are standard .po files as used in the Gnu gettext
3007
system. However the .po files are used directly, and there is no need
3008
to merge and compile them into a binary format (.mo files).
3010
Initialize: int ZTXinit(const char *lang)
3011
lang is "lc" or "lc_RC" or current locale if null.
3012
status: 0 = OK, 1 = cannot process .po translation file(s).
3014
If files are found in both .../lc/xxx.po and .../lc_RC/xxx.po,
3015
both sets of files are processed. If a translation is present in
3016
both sets, the regional dialect (lc_RC) will be used.
3018
Translate a text string: const char * ZTX(const char *english)
3019
english: text string to translate, possibly containing
3020
printf formats (%d %s ...) and escaped characters (\" \n ...).
3022
If the user language is English or if no translation is found,
3023
the input string is returned, else the translated string.
3025
A text string may have a context part "context::string", where
3026
"context" is any string < 30 characters and "string" is the
3027
English text or the translation text. The context part "context::"
3028
is removed in the returned string. This is to handle the case where
3029
a single English string may need multiple translations, depending
3030
on context. The English string may be present multiple times in a
3031
.po file, each one marked with a different context and having a
3032
different translation. Context is optional in translation strings.
3038
printf(ZTX("answer: %d %s \n more on next line"), 123, "qwerty");
3040
A German .po file would have the following entry:
3042
msgid: "answer: %d %s \n"
3043
" more on next line"
3044
msgstr: "Antwort: %d %s \n"
3045
" mehr in der nƤchsten Zeile"
3047
***************************************************************************/
3049
namespace ZTXnames // remove GOFUNC usage becasue of
3050
{ // GCC optimization errors v.2.18
3052
char buff[ZTXmaxcc], *porec, *ppq1, *ppq2, lc_RC[8];
3053
char *pstring, Estring[ZTXmaxcc], Tstring[ZTXmaxcc];
3055
void ZTXread_pofiles();
3056
void ZTXgetstring();
3057
char **ZTXenglish = null; // English strings and
3058
char **ZTXtrans = null; // corresp. translations
3061
using namespace ZTXnames;
3063
int ZTXinit(const char *lang) // initialize translations
3069
for (ii = 0; ZTXenglish[ii]; ii++) { // free prior translation
3070
zfree(ZTXenglish[ii]);
3071
if (ZTXtrans[ii]) zfree(ZTXtrans[ii]);
3075
ZTXenglish = ZTXtrans = null; // set no translation
3078
if (! blank_null(lang)) strncpy0(zlanguage,lang,6); // use language from caller
3079
else { // help Linux chaos v.2.9.1
3080
pp = getenv("LANG"); // use $LANG if defined
3081
if (! pp) pp = getenv("LANGUAGE"); // use $LANGUAGE if defined
3082
if (! pp) pp = setlocale(LC_MESSAGES,""); // use locale if defined
3083
if (pp) strncpy0(zlanguage,pp,6); // "lc_RC" lang/region codes v.2.14
3084
else strcpy(zlanguage,"en"); // use English
3087
if (*zlanguage < 'a') strcpy(zlanguage,"en"); // use English if garbage
3088
printf("language: %s \n",zlanguage);
3090
if (strEqu(zlanguage,"en")) return 0; // English, do nothing
3092
ZTXenglish = (char **) zmalloc(ZTXmaxent * sizeof(char *)); // allocate slots for english text
3093
ZTXtrans = (char **) zmalloc(ZTXmaxent * sizeof(char *)); // strings and translations
3096
strncpy0(lc_RC,zlanguage,6); // process .../locales/lc_RC/*.po
3097
if (strlen(lc_RC) > 3 && strNeq(lc_RC,"en_US"))
3100
strncpy0(lc_RC,zlanguage,3); // process .../locales/lc/*.po
3101
if (strNeq(lc_RC,"en")) ZTXread_pofiles();
3103
ZTXenglish[ent] = ZTXtrans[ent] = null; // mark both EOLs
3107
void ZTXnames::ZTXread_pofiles()
3110
char *pofile, command[200];
3112
snprintf(command,199,"find %s/locales/%s/*.po 2>/dev/null",zdatadir,lc_RC);
3114
while ((pofile = command_output(contx,command)))
3116
fid = fopen(pofile,"r"); // open .po file
3118
printf("cannot open translation file: %s \n",pofile); // error, ignore file
3123
porec = 0; // no .po record yet
3124
*Estring = *Tstring = 0; // no strings yet
3128
if (! porec) porec = fgets_trim(buff,ZTXmaxcc,fid); // get next .po record
3129
if (! porec) break; // EOF
3131
if (blank_null(porec)) { // blank record
3135
if (*porec == '#') { // comment
3140
if (strnEqu(porec,"msgid",5)) // start new english string
3142
if (*Estring) { // two in a row
3143
printf(" no translation: %s \n",Estring);
3147
if (*Tstring) { // should not happen
3148
printf(" orphan translation: %s \n",Tstring);
3152
porec += 5; // parse string (multiple pieces)
3158
strcpy(Tstring,Estring); // borrow Tstring
3159
clean_escapes(Tstring);
3160
for (ii = 0; ZTXenglish[ii]; ii++) // find existing entry, if any
3161
if (strEqu(Tstring,ZTXenglish[ii])) break;
3163
{ /*** duplicate english ***/ }
3168
else { /*** empty english string ***/ }
3171
else if (strnEqu(porec,"msgstr",6)) // start new translation
3173
porec += 6; // parse the string
3179
if (*Estring) printf(" no translation: %s \n",Estring);
3180
*Estring = *Tstring = 0;
3182
else if (! *Estring) {
3183
{ /*** orphan translation ***/ }
3184
*Estring = *Tstring = 0;
3190
printf(" unrecognized input record: %s \n",porec);
3195
if (*Estring && *Tstring)
3197
if (ent == ZTXmaxent)
3198
zappcrash("more than %d translations",ZTXmaxent);
3199
clean_escapes(Estring);
3200
clean_escapes(Tstring);
3201
ZTXenglish[ent] = strdupz(Estring); // add new English string
3202
ZTXtrans[ent] = strdupz(Tstring); // and translation
3204
*Estring = *Tstring = 0;
3215
void ZTXnames::ZTXgetstring()
3219
while (true) // join multiple quoted strings
3221
while (*porec && *porec != '"') porec++; // find opening string quote
3224
porec = fgets_trim(buff,ZTXmaxcc,fid); // get next .po record
3225
if (! porec) return;
3226
if (strnEqu(porec,"msgid",5)) return;
3227
if (strnEqu(porec,"msgstr",6)) return;
3232
while ((*ppq2 && *ppq2 != '"') || // find closing (non-escaped) quote
3233
(*ppq2 == '"' && *(ppq2-1) == '\\')) ppq2++;
3234
if (! *ppq2) return;
3235
cc = ppq2 - ppq1 - 1;
3236
if (cc && scc + cc + 1 < ZTXmaxcc) { // truncate if too long
3237
strncpy0(pstring+scc,ppq1+1,cc+1); // accum. substrings, minus quotes
3246
using namespace std;
3249
// Translate the input english string or return the input string.
3250
// Look for "context::string" and return "string" only if context found.
3252
// This function is not efficient: may need a few microseconds.
3253
// This can be improved if needed by sorting the english strings and
3254
// using a binary search (1000 strings >> 10 compares instead of 1-1000).
3256
const char * ZTX(const char *english)
3258
const char *pp, *pp2;
3260
if (! english) return 0;
3266
for (int ii = 0; ZTXenglish[ii]; ii++) // find translation
3268
if (strEqu(english,ZTXenglish[ii])) {
3275
if (! pp) pp = english;
3277
for (pp2 = pp; *pp2 && pp2 < pp+30; pp2++) // remove context if present v.2.11
3278
if (*pp2 == ':' && *(pp2+1) == ':') return pp2+2;
3284
/**************************************************************************
3285
GTK windowing system utility functions
3286
***************************************************************************/
3288
// functions to lock GTK calls from threads only
3289
// - do nothing if called from main thread (avoid fatal bug)
3290
// - detect nested calls and make inoccuous (avoid fatal bug)
3292
#define tmax 20 // max. simultaneous GTK threads
3293
pthread_t tid_main = 0;
3294
pthread_t tids[tmax];
3299
void zlockInit() // initz. call from main()
3301
tid_main = pthread_self();
3302
mutex_init(&zmutex,null);
3305
for (int ii = 0; ii < tmax; ii++) {
3312
void zlock() // lock GTK if in a thread
3317
if (! zinit) zappcrash("zlock(): zinit() not done");
3319
tid_me = pthread_self();
3320
if (pthread_equal(tid_main,tid_me)) return; // main() thread, do nothing
3322
mutex_lock(&zmutex);
3324
for (ii = 0; ii < tmax; ii++) // find my thread slot
3325
if (pthread_equal(tids[ii],tid_me)) goto zret;
3326
for (ii = 0; ii < tmax; ii++) // find a free slot
3327
if (tids[ii] == 0) goto zret;
3328
zappcrash("zlock(): too many threads");
3333
mutex_unlock(&zmutex);
3335
if (tlocks[ii] == 1) gdk_threads_enter(); // 1st lock, lock GTK
3339
void zunlock() // unlock GTK if in a thread
3344
tid_me = pthread_self();
3345
if (pthread_equal(tid_main,tid_me)) return; // main() thread, do nothing
3347
for (ii = 0; ii < tmax; ii++) // find this thread
3348
if (pthread_equal(tids[ii],tid_me)) break;
3349
if (ii == tmax) zappcrash("zunlock(): not locked");
3351
--tlocks[ii]; // decrement locks
3352
if (tlocks[ii] == 0) {
3353
tids[ii] = 0; // last lock removed, free slot
3355
gdk_threads_leave(); // unlock GTK
3361
// iterate main loop every "skip" calls and only if in main() thread
3363
void zmainloop(int skip) // v.2.8
3365
static int xskip = 0;
3369
if (++xskip < skip) return;
3373
if (! zinit) zappcrash("zmainloop(): zinit() not done");
3374
tid_me = pthread_self();
3375
if (! pthread_equal(tid_main,tid_me)) return;
3377
while (gtk_events_pending()) gtk_main_iteration();
3382
// write message to text view window
3383
// line: +N existing lines from top (replace)
3384
// -N existing lines from bottom (replace)
3385
// 0 next line (add new line at bottom)
3386
// scroll logic assumes only one \n per message
3388
void wprintx(GtkWidget *mLog, int line, const char *message, const char *font)
3390
static GtkTextMark *endMark = 0;
3391
GtkTextBuffer *textBuff;
3392
GtkTextIter iter1, iter2;
3393
static GtkTextTag *fontag, *fontags[20];
3394
static char *fontmem[20];
3395
static int nfonts = 0;
3396
int ii, nlines, scroll = 0;
3400
textBuff = gtk_text_view_get_buffer(GTK_TEXT_VIEW(mLog));
3402
endMark = gtk_text_buffer_get_mark(textBuff,"wpxend"); // new buffer?
3404
gtk_text_buffer_get_end_iter(textBuff,&iter1); // yes, set my end mark
3405
endMark = gtk_text_buffer_create_mark(textBuff,"wpxend",&iter1,0);
3406
for (ii = 0; ii < nfonts; ii++) zfree(fontmem[ii]); // free font tags memory
3411
for (ii = 0; ii < nfonts; ii++)
3412
if (strEqu(font,fontmem[ii])) break; // use existing font tag if poss.
3414
if (nfonts == 20) zappcrash("wprintx: exceed 20 font tags");
3415
fontmem[ii] = strdupz(font); // create new font tag
3416
fontags[ii] = gtk_text_buffer_create_tag(textBuff,0,"font",font,0);
3419
fontag = fontags[ii];
3422
nlines = gtk_text_buffer_get_line_count(textBuff); // lines now in buffer
3424
if (line == 0) scroll++; // auto scroll is on
3427
line = nlines + line + 1; // last lines: -1, -2 ...
3428
if (line < 1) line = 1; // above top, use line 1
3431
if (line > nlines) line = 0; // below bottom, treat as append
3433
if (line == 0) gtk_text_buffer_get_end_iter(textBuff,&iter1); // append new line
3436
gtk_text_buffer_get_iter_at_line(textBuff,&iter1,line-1); // old line start
3438
gtk_text_buffer_get_iter_at_line(textBuff,&iter2,line); // old line end
3439
if (line == nlines) // or buffer end
3440
gtk_text_buffer_get_end_iter(textBuff,&iter2);
3441
gtk_text_buffer_delete(textBuff,&iter1,&iter2); // delete old line
3444
if (font) // with optional font
3445
gtk_text_buffer_insert_with_tags(textBuff,&iter1,message,-1,fontag,null);
3446
else gtk_text_buffer_insert(textBuff,&iter1,message,-1);
3448
if (scroll) // scroll line into view
3449
gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(mLog),endMark,0,0,1,1);
3456
void wprintf(GtkWidget *mLog, int line, cchar *mess, ... ) // "printf" version
3462
va_start(arglist,mess);
3463
cc = vsnprintf(message,999,mess,arglist);
3466
wprintx(mLog,line,message);
3470
void wprintf(GtkWidget *mLog, cchar *mess, ... ) // "printf", scrolling output
3476
va_start(arglist,mess);
3477
cc = vsnprintf(message,999,mess,arglist); // stop overflow, remove warning
3480
wprintx(mLog,0,message);
3485
// scroll a text view window to put a given line on screen
3486
// 1st line = 1. for last line use line = 0.
3488
void wscroll(GtkWidget *mLog, int line) // v.2.3
3490
GtkTextBuffer *textbuff;
3494
textbuff = gtk_text_view_get_buffer(GTK_TEXT_VIEW(mLog));
3495
if (line <= 0) line = gtk_text_buffer_get_line_count(textbuff);
3497
gtk_text_buffer_get_iter_at_line(textbuff,&iter,line);
3498
gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(mLog),&iter,0,0,0,0);
3504
// clear a text view window and get a new buffer (a kind of defrag)
3506
void wclear(GtkWidget *mLog)
3508
GtkTextBuffer *buff;
3511
buff = gtk_text_view_get_buffer(GTK_TEXT_VIEW(mLog));
3512
gtk_text_buffer_set_text(buff,"",-1);
3518
// clear a text view window from designated line to end of buffer
3520
void wclear(GtkWidget *mLog, int line)
3522
GtkTextBuffer *textBuff;
3523
GtkTextIter iter1, iter2;
3527
textBuff = gtk_text_view_get_buffer(GTK_TEXT_VIEW(mLog));
3528
gtk_text_buffer_get_iter_at_line(textBuff,&iter1,line-1); // iter at line start
3529
gtk_text_buffer_get_end_iter(textBuff,&iter2);
3530
gtk_text_buffer_delete(textBuff,&iter1,&iter2); // delete existing line
3538
// get text records from a text view window, one per call
3539
// removes trailing new line characters ( \n )
3541
char * wscanf(GtkWidget *mLog, int & ftf)
3543
GtkTextBuffer *textBuff;
3544
GtkTextIter iter1, iter2;
3545
static char *precs = 0, *prec1, *pret;
3549
{ // get all window text
3551
if (precs) g_free(precs); // free prior memory if there
3553
textBuff = gtk_text_view_get_buffer(GTK_TEXT_VIEW(mLog)); // get all text
3554
gtk_text_buffer_get_bounds(textBuff,&iter1,&iter2);
3555
precs = gtk_text_buffer_get_text(textBuff,&iter1,&iter2,0);
3556
prec1 = precs; // 1st record
3560
if (! precs || (*prec1 == 0)) // no more records
3562
if (precs) g_free(precs);
3568
while ((prec1[cc] != 0) && (prec1[cc] != '\n')) cc++; // scan for terminator
3570
prec1 = prec1 + cc; // next record
3571
if (*prec1 == '\n') prec1++;
3572
pret[cc] = 0; // replace \n with 0
3577
// dump text window into file
3578
// return: 0: OK +N: error
3580
int wfiledump_maxcc = 0;
3582
int wfiledump(GtkWidget *mLog, char *filespec)
3588
fid = fopen(filespec,"w"); // open file
3590
zmessageACK(ZTX("cannot open file %s"),filespec);
3594
wfiledump_maxcc = 0;
3599
prec = wscanf(mLog,ftf); // get text line
3601
fprintf(fid,"%s\n",prec); // output with \n
3603
if (cc > wfiledump_maxcc) wfiledump_maxcc = cc;
3606
err = fclose(fid); // close file
3607
if (err) { zmessageACK("file close error"); return 2; }
3612
// save text window to file, via file chooser dialog
3614
void wfilesave(GtkWidget *mLog)
3619
file = zgetfile(ZTX("save screen to file"),"screen-save.txt","save");
3621
err = wfiledump(mLog,file);
3622
if (err) zmessageACK("save screen failed (%d)",err);
3628
// print text window to default printer
3629
// use landscape mode if max. print line > A4 width
3631
void wprintp(GtkWidget *mLog)
3634
char tempfile[50], command[200];
3637
snprintf(tempfile,49,"/tmp/wprintp-%d",pid);
3638
err = wfiledump(mLog,tempfile);
3641
if (wfiledump_maxcc < 97)
3642
snprintf(command,199,"lp -o %s -o %s -o %s -o %s -o %s -o %s %s",
3643
"cpi=14","lpi=8","page-left=50","page-top=50",
3644
"page-right=40","page-bottom=40",tempfile);
3647
snprintf(command,199,"lp -o %s -o %s -o %s -o %s -o %s -o %s -o %s %s",
3648
"landscape","cpi=14","lpi=8","page-left=50","page-top=50",
3649
"page-right=40","page-bottom=40",tempfile);
3651
err = system(command);
3652
if (err) zmessLogACK("print error %s",wstrerror(err));
3657
/**************************************************************************
3658
simplified GTK menu bar, tool bar, status bar functions
3659
***************************************************************************/
3661
int mbIconSize = 16; // valid during menu bar construction
3662
int tbIconSize = 24; // valid during toolbar construction
3663
GtkTooltips *tbTooltips = 0; // one instance for all toolbars
3666
// create menu bar and add to vertical packing box
3668
GtkWidget * create_menubar(GtkWidget *vbox, int iconsize)
3672
wmbar = gtk_menu_bar_new();
3673
gtk_box_pack_start(GTK_BOX(vbox),wmbar,0,0,0);
3674
mbIconSize = iconsize;
3679
// add menu item to menu bar
3681
GtkWidget * add_menubar_item(GtkWidget *wmbar, const char *mname, mtFunc func)
3685
wmitem = gtk_menu_item_new_with_label(mname);
3686
gtk_menu_shell_append(GTK_MENU_SHELL(wmbar),wmitem);
3687
if (func) G_SIGNAL(wmitem,"activate",func,mname);
3692
// add submenu item (with optional icon) to menu item
3694
GtkWidget * add_submenu_item(GtkWidget *wmitem, cchar *mlab, cchar *icon, mtFunc func)
3696
GtkWidget *wmsub, *wmsubitem;
3697
GError **gerror = 0;
3699
GtkWidget *wicon = 0;
3700
char iconpath[1000];
3702
wmsub = gtk_menu_item_get_submenu(GTK_MENU_ITEM(wmitem));
3703
if (wmsub == null) {
3704
wmsub = gtk_menu_new();
3705
gtk_menu_item_set_submenu(GTK_MENU_ITEM(wmitem),wmsub);
3710
strncatv(iconpath,999,zicondir,"/",icon,null);
3711
pixbuf = gdk_pixbuf_new_from_file_at_scale(iconpath,mbIconSize,mbIconSize,1,gerror);
3712
if (pixbuf) wicon = gtk_image_new_from_pixbuf(pixbuf);
3715
if (strEqu(mlab,"separator")) // v.2.16
3716
wmsubitem = gtk_separator_menu_item_new();
3719
wmsubitem = gtk_image_menu_item_new_with_label(mlab);
3720
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(wmsubitem),wicon);
3722
else wmsubitem = gtk_menu_item_new_with_label(mlab);
3725
gtk_menu_shell_append(GTK_MENU_SHELL(wmsub),wmsubitem);
3727
if (func) G_SIGNAL(wmsubitem,"activate",func,mlab);
3732
// create toolbar and add to vertical packing box
3734
GtkWidget * create_toolbar(GtkWidget *vbox, int iconsize, int vert)
3738
wtbar = gtk_toolbar_new();
3739
if (vert) gtk_toolbar_set_orientation(GTK_TOOLBAR(wtbar),GTK_ORIENTATION_VERTICAL);
3740
gtk_box_pack_start(GTK_BOX(vbox),wtbar,0,0,0);
3741
tbIconSize = iconsize;
3743
if (! tbTooltips) tbTooltips = gtk_tooltips_new();
3748
// add toolbar button with stock icon ("gtk-quit") or custom icon ("iconfile.png")
3750
GtkWidget * add_toolbar_button(GtkWidget *wtbar, cchar *blab, cchar *btip, cchar *icon, mtFunc func)
3752
GtkToolItem *tbutton;
3753
GError **gerror = 0;
3755
GtkWidget *wicon = 0;
3756
char iconpath[1000];
3758
if (icon == null || *icon == 0)
3759
tbutton = gtk_tool_button_new(0,0);
3761
else if (strnEqu(icon,"gtk-",4))
3762
tbutton = gtk_tool_button_new_from_stock(icon);
3766
strncatv(iconpath,999,zicondir,"/",icon,null);
3767
pixbuf = gdk_pixbuf_new_from_file_at_scale(iconpath,tbIconSize,tbIconSize,1,gerror);
3768
if (pixbuf) wicon = gtk_image_new_from_pixbuf(pixbuf);
3769
if (wicon) tbutton = gtk_tool_button_new(wicon,0);
3770
else tbutton = gtk_tool_button_new_from_stock("gtk-missing-image");
3773
gtk_tool_button_set_label(GTK_TOOL_BUTTON(tbutton),blab);
3774
if (btip) gtk_tool_item_set_tooltip(tbutton,tbTooltips,btip,"");
3775
gtk_toolbar_insert(GTK_TOOLBAR(wtbar),GTK_TOOL_ITEM(tbutton),-1);
3776
G_SIGNAL(tbutton,"clicked",func,blab);
3777
return (GtkWidget *) tbutton;
3781
// create a status bar and add to the start of a packing box
3783
GtkWidget * create_stbar(GtkWidget *pbox)
3786
static PangoFontDescription *fontdesc;
3788
stbar = gtk_statusbar_new();
3789
fontdesc = pango_font_description_from_string("Monospace 9");
3790
gtk_widget_modify_font(stbar,fontdesc); // *** GTK does not work ***
3791
gtk_box_pack_start(GTK_BOX(pbox),stbar,0,0,0);
3792
gtk_widget_show(stbar);
3797
// display message in status bar - callable from threads
3799
int stbar_message(GtkWidget *wstbar, const char *message)
3801
static int ctx = -1;
3804
if (ctx == -1) ctx = gtk_statusbar_get_context_id(GTK_STATUSBAR(wstbar),"all");
3805
gtk_statusbar_pop(GTK_STATUSBAR(wstbar),ctx);
3806
gtk_statusbar_push(GTK_STATUSBAR(wstbar),ctx,message);
3813
/**************************************************************************
3814
simplified GTK dialog functions
3815
***************************************************************************/
3817
// private functions for interception of widget events and dialog completion
3819
void zdialog_widget_event(GtkWidget *, zdialog *zd);
3820
void zdialog_response_event(GtkWidget *, int stat, zdialog *zd);
3823
// create a new zdialog dialog
3824
// optional arguments: list of 0-5 button names, null
3825
// returned dialog status: N = button N (1 to 5)
3826
// -4 = "X" destroy button
3828
zdialog * zdialog_new(const char *title, GtkWidget *parent, ...) // parent added v.2.5
3835
GtkDialogFlags flags = (GtkDialogFlags) 0;
3836
GtkWindow *wparent = (GtkWindow *) parent;
3838
zd = (zdialog *) zmalloc(sizeof(zdialog));
3840
for (ii = 0; ii < 5; ii++) butt[ii] = 0;
3842
va_start(arglist,parent);
3844
for (ii = 0; ii < 5; ii++)
3846
butt[ii] = va_arg(arglist, char *); // get up to 5 buttons
3847
if (! butt[ii]) break;
3852
zlock(); // create dialog box
3853
dialog = gtk_dialog_new_with_buttons(title,wparent,flags,
3854
butt[0],1, butt[1],2, butt[2],3, butt[3],4, butt[4],5, null);
3855
gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox),5);
3856
gtk_window_set_position(GTK_WINDOW(dialog),GTK_WIN_POS_MOUSE); // v.2.7.1
3860
zd->sentinel = zdsentinel; // validity sentinel v.2.2
3861
zd->eventCB = 0; // no user event callback
3862
zd->complCB = 0; // no user dialog completion callback
3863
zd->zstat = 0; // no zdialog status
3864
zd->disabled = 1; // widget signals disabled
3866
zd->widget[0].type = "dialog"; // set up 1st widget = dialog
3867
zd->widget[0].name = "dialog";
3868
zd->widget[0].pname = 0;
3869
zd->widget[0].data = strdupz(title);
3870
zd->widget[0].cblist = 0;
3871
zd->widget[0].widget = dialog;
3873
zd->widget[1].type = 0; // eof - no contained widgets yet
3878
// add widget to existing zdialog
3880
int zdialog_add_widget (
3881
zdialog *zd, const char *type, const char *name, const char *pname, // mandatory args
3882
const char *data, int scc, int homog, int expand, int space) // optional args (default = 0)
3884
GtkWidget *widget = 0, *pwidget = 0;
3885
GtkTextBuffer *editBuff = 0;
3887
const char *pp, *ptype = 0;
3889
double min, max, step, val;
3890
int iiw, iip, kk, err;
3892
static PangoFontDescription *monofont = 0;
3894
if (! zd) zappcrash("zdialog null pointer"); // detect destroyed dialog v.2.2
3895
if (zd->sentinel != zdsentinel) zappcrash("zdialog not valid");
3897
for (iiw = 1; zd->widget[iiw].type; iiw++); // find next avail. slot
3898
if (iiw > zdmaxwidgets-2) zappcrash("too many widgets: %d",iiw);
3900
zd->widget[iiw].type = strdupz(type); // init. widget struct
3901
zd->widget[iiw].name = strdupz(name); // all strings in nonvolatile mem
3902
zd->widget[iiw].pname = strdupz(pname);
3903
zd->widget[iiw].data = 0;
3904
zd->widget[iiw].cblist = 0;
3905
zd->widget[iiw].scc = scc;
3906
zd->widget[iiw].homog = homog;
3907
zd->widget[iiw].expand = expand;
3908
zd->widget[iiw].space = space;
3909
zd->widget[iiw].widget = 0;
3911
zd->widget[iiw+1].type = 0; // new EOF marker
3913
if (strcmpv(type,"dialog","hbox","vbox","hsep","vsep","frame","scrwin",
3914
"label","entry","edit","button","togbutt","check","combo",
3915
"comboE","radio","spin","hscale","vscale","colorbutt", null) == 0)
3916
zappcrash("zdialog, bad widget type: %s",type);
3918
for (iip = iiw-1; iip >= 0; iip--) // find parent (container) widget
3919
if (strEqu(pname,zd->widget[iip].name)) break;
3920
if (iip < 0) zappcrash("zdialog, no parent for widget: %s",name);
3922
pwidget = zd->widget[iip].widget; // parent widget, type
3923
ptype = zd->widget[iip].type;
3925
if (strcmpv(ptype,"dialog","hbox","vbox","frame","scrwin",null) == 0)
3926
zappcrash("zdialog, bad widget parent type: %s",ptype);
3930
if (! monofont) monofont = pango_font_description_from_string("Monospace");
3932
if (strEqu(type,"hbox")) widget = gtk_hbox_new(homog,space); // expandable container boxes
3933
if (strEqu(type,"vbox")) widget = gtk_vbox_new(homog,space);
3935
if (strEqu(type,"hsep")) widget = gtk_hseparator_new(); // horiz. & vert. separators
3936
if (strEqu(type,"vsep")) widget = gtk_vseparator_new();
3938
if (strEqu(type,"frame")) { // frame around contained widgets
3939
widget = gtk_frame_new(data);
3940
gtk_frame_set_shadow_type(GTK_FRAME(widget),GTK_SHADOW_IN);
3943
if (strEqu(type,"scrwin")) { // scrolled window container
3944
widget = gtk_scrolled_window_new(0,0);
3945
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(widget), // v.2.10
3946
GTK_POLICY_AUTOMATIC,GTK_POLICY_AUTOMATIC);
3949
if (strEqu(type,"label")) widget = gtk_label_new(data); // label (static text)
3951
if (strEqu(type,"entry")) { // 1-line text entry
3952
widget = gtk_entry_new();
3953
if (data) gtk_entry_set_text(GTK_ENTRY(widget),data);
3954
if (scc) gtk_entry_set_width_chars(GTK_ENTRY(widget),scc);
3955
gtk_widget_modify_font(widget,monofont);
3956
G_SIGNAL(widget,"changed",zdialog_widget_event,zd)
3959
if (strEqu(type,"edit")) { // multiline edit box
3960
widget = gtk_text_view_new();
3961
editBuff = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
3962
if (data) gtk_text_buffer_set_text(editBuff,data,-1);
3963
gtk_text_view_set_editable(GTK_TEXT_VIEW(widget),1);
3964
gtk_widget_modify_font(widget,monofont);
3965
G_SIGNAL(editBuff,"changed",zdialog_widget_event,zd) // buffer signals, not widget
3968
if (strEqu(type,"button")) { // button
3969
widget = gtk_button_new_with_label(data);
3970
G_SIGNAL(widget,"clicked",zdialog_widget_event,zd)
3973
if (strEqu(type,"togbutt")) { // toggle button
3974
widget = gtk_toggle_button_new_with_label(data);
3975
G_SIGNAL(widget,"toggled",zdialog_widget_event,zd)
3978
if (strEqu(type,"check")) { // checkbox
3979
if (data) widget = gtk_check_button_new_with_label(data);
3980
else widget = gtk_check_button_new();
3981
G_SIGNAL(widget,"toggled",zdialog_widget_event,zd)
3984
if (strEqu(type,"combo")) { // combo box
3985
widget = gtk_combo_box_new_text();
3986
zd->widget[iiw].cblist = pvlist_create(zdcbmax); // for drop-down list
3987
if (! blank_null(data)) {
3988
pvlist_append(zd->widget[iiw].cblist,data); // add data to drop-down list
3989
gtk_combo_box_append_text(GTK_COMBO_BOX(widget),data);
3990
gtk_combo_box_set_active(GTK_COMBO_BOX(widget),0);
3992
gtk_widget_modify_font(widget,monofont);
3993
G_SIGNAL(widget,"changed",zdialog_widget_event,zd)
3996
if (strEqu(type,"comboE")) { // combo box with entry box
3997
widget = gtk_combo_box_entry_new_text();
3998
zd->widget[iiw].cblist = pvlist_create(zdcbmax); // for drop-down list
3999
if (! blank_null(data)) {
4000
gtk_entry_set_text(GTK_ENTRY(GTK_BIN(widget)->child),data); // entry = initial data
4001
pvlist_append(zd->widget[iiw].cblist,data); // add data to drop-down list
4002
gtk_combo_box_append_text(GTK_COMBO_BOX(widget),data);
4004
gtk_widget_modify_font(widget,monofont);
4005
G_SIGNAL(widget,"changed",zdialog_widget_event,zd)
4008
if (strEqu(type,"radio")) { // radio button
4009
for (kk = iip+1; kk <= iiw; kk++)
4010
if (strEqu(zd->widget[kk].pname,pname) && // find first radio button
4011
strEqu(zd->widget[kk].type,"radio")) break; // with same container
4013
widget = gtk_radio_button_new_with_label(null,data); // this one is first
4015
widget = gtk_radio_button_new_with_label_from_widget // not first, add to group
4016
(GTK_RADIO_BUTTON(zd->widget[kk].widget),data);
4017
G_SIGNAL(widget,"toggled",zdialog_widget_event,zd)
4020
if (strcmpv(type,"spin","hscale","vscale",null)) { // spin button or sliding scale
4021
pp = strField(data,'|',1); err = convSD(pp,min); // locale fix
4022
pp = strField(data,'|',2); err += convSD(pp,max);
4023
pp = strField(data,'|',3); err += convSD(pp,step);
4024
pp = strField(data,'|',4); err += convSD(pp,val);
4025
if (err) { min = 0; max = 100; step = 1; val = 50; }
4028
widget = gtk_spin_button_new_with_range(min,max,step);
4029
gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget),val);
4032
widget = gtk_hscale_new_with_range(min,max,step);
4033
gtk_range_set_value(GTK_RANGE(widget),val);
4034
gtk_scale_set_draw_value(GTK_SCALE(widget),0);
4037
widget = gtk_vscale_new_with_range(min,max,step);
4038
gtk_range_set_value(GTK_RANGE(widget),val);
4039
gtk_scale_set_draw_value(GTK_SCALE(widget),0);
4041
G_SIGNAL(widget,"value-changed",zdialog_widget_event,zd)
4042
sprintf(vdata,"%g",val);
4046
if (strEqu(type,"colorbutt")) { // color edit button v.2.17
4047
if (! data) data = "0|0|0"; // data format: "nnn|nnn|nnn" = RGB
4048
pp = strField(data,'|',1); gdkcolor.red = 256 * atoi(pp);
4049
pp = strField(data,'|',2); gdkcolor.green = 256 * atoi(pp);
4050
pp = strField(data,'|',3); gdkcolor.blue = 256 * atoi(pp);
4051
widget = gtk_color_button_new_with_color(&gdkcolor);
4052
G_SIGNAL(widget,"color-set",zdialog_widget_event,zd)
4055
// all widget types come here
4057
zd->widget[iiw].widget = widget; // set widget in zdialog
4059
if (strEqu(ptype,"hbox") || strEqu(ptype,"vbox")) // add to hbox/vbox
4060
gtk_box_pack_start(GTK_BOX(pwidget),widget,expand,expand,space);
4061
if (strEqu(ptype,"frame")) // add to frame
4062
gtk_container_add(GTK_CONTAINER(pwidget),widget);
4063
if (strEqu(ptype,"scrwin")) // add to scroll window
4064
gtk_container_add(GTK_CONTAINER(pwidget),widget);
4065
if (strEqu(ptype,"dialog")) // add to dialog box
4066
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(pwidget)->vbox),
4067
widget,expand,expand,space);
4068
if (data) zd->widget[iiw].data = strdupz(data); // data is nonvolatile memory
4075
// add widget to existing zdialog - alternative form (clearer and easier code)
4076
// options: "scc=nn | homog | expand | space=nn" (all optional, any order)
4078
int zdialog_add_widget(zdialog *zd, const char *type, const char *name,
4079
const char *parent, const char *data, const char *options)
4081
int stat, scc = 0, homog = 0, expand = 0, space = 0, begin = 1;
4087
stat = strParms(begin,options,pname,8,pval);
4088
if (stat == -1) break;
4089
if (stat == 1) zappcrash("bad zdialog options: %s",options);
4090
if (strEqu(pname,"scc")) scc = (int(pval));
4091
else if (strEqu(pname,"homog")) homog = 1;
4092
else if (strEqu(pname,"expand")) expand = 1;
4093
else if (strEqu(pname,"space")) space = (int(pval));
4094
else zappcrash("bad zdialog options: %s",options);
4097
stat = zdialog_add_widget(zd,type,name,parent,data,scc,homog,expand,space);
4102
// resize dialog to a size greater than initial size
4103
// (as determined by the included widgets)
4105
int zdialog_resize(zdialog *zd, int width, int height)
4107
if (! zd) zappcrash("zdialog null pointer"); // detect destroyed dialog v.2.2
4108
if (zd->sentinel != zdsentinel) zappcrash("zdialog not valid");
4111
GtkWidget *window = zd->widget[0].widget;
4112
gtk_window_set_default_size(GTK_WINDOW(window),width,height);
4118
// put data into a zdialog widget
4120
int zdialog_put_data(zdialog *zd, const char *name, const char *data)
4123
GtkTextBuffer *textBuff;
4125
int iiw, iip, nn, kk;
4126
const char *type, *pname, *pp;
4130
if (! zd || zd->sentinel != zdsentinel) { // detect destroyed dialog v.2.2
4131
printf("zdialog_put_data(%s,%s), zdialog invalid \n",name,data);
4135
for (iiw = 1; zd->widget[iiw].type; iiw++) // find widget
4136
if (strEqu(zd->widget[iiw].name,name)) break;
4137
if (! zd->widget[iiw].type) return 0;
4139
type = zd->widget[iiw].type;
4140
widget = zd->widget[iiw].widget;
4141
pname = zd->widget[iiw].pname;
4143
wdata = zd->widget[iiw].data;
4144
if (wdata) zfree(wdata); // free prior data memory
4145
zd->widget[iiw].data = 0;
4148
wdata = strdupz(data); // set new data for widget
4149
zd->widget[iiw].data = wdata;
4150
if (utf8_check(wdata))
4151
printf("zdialog: bad UTF8 encoding %s \n",wdata); // v.2.4
4154
zd->disabled++; // disable for widget stuffing v.2.9
4158
if (strEqu(type,"label"))
4159
gtk_label_set_text(GTK_LABEL(widget),data);
4161
if (strEqu(type,"entry"))
4162
gtk_entry_set_text(GTK_ENTRY(widget),data);
4164
if (strEqu(type,"button")) // change button label v.2.21
4165
gtk_button_set_label(GTK_BUTTON(widget),data);
4167
if (strEqu(type,"edit")) {
4168
textBuff = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
4169
gtk_text_buffer_set_text(textBuff,data,-1);
4172
if (strcmpv(type,"togbutt","check","radio",null))
4174
if (! data) kk = nn = 0;
4175
else kk = convSI(data,nn);
4176
if (kk != 0) nn = 0; // data not integer, force zero
4177
if (nn <= 0) nn = 0; else nn = 1;
4178
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),nn); // set gtk widget value
4180
for (iip = 1; zd->widget[iip].type; iip++) // find widgets of same type
4181
{ // having same parent v.2.0
4182
if (iip == iiw) continue;
4183
if (strEqu(zd->widget[iip].pname,pname) && strEqu(zd->widget[iip].type,type))
4185
wdata = zd->widget[iip].data;
4186
if (wdata) zfree(wdata); // free prior data memory
4187
if (nn == 0) zd->widget[iip].data = strdupz("1");
4188
else zd->widget[iip].data = strdupz("0"); // family members = opposite v.2.0
4193
if (strEqu(type,"spin")) {
4194
kk = convSD(data,val);
4195
if (kk != 0) val = 0.0;
4196
gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget),val);
4199
if (strEqu(type,"colorbutt")) { // color button v.2.17
4200
pp = strField(data,'|',1); gdkcolor.red = 256 * atoi(pp);
4201
pp = strField(data,'|',2); gdkcolor.green = 256 * atoi(pp);
4202
pp = strField(data,'|',3); gdkcolor.blue = 256 * atoi(pp);
4203
gtk_color_button_set_color(GTK_COLOR_BUTTON(widget),&gdkcolor);
4206
if (strcmpv(type,"hscale","vscale",null)) {
4207
kk = convSD(data,val);
4208
if (kk != 0) val = 0.0;
4209
gtk_range_set_value(GTK_RANGE(widget),val);
4212
if (strEqu(type,"combo")) {
4213
if (! blank_null(data)) {
4214
kk = pvlist_prepend(zd->widget[iiw].cblist,data,1); // add to drop-down list
4215
if (kk == 0) // (only if unique)
4216
gtk_combo_box_prepend_text(GTK_COMBO_BOX(widget),data);
4217
kk = pvlist_find(zd->widget[iiw].cblist,data);
4218
gtk_combo_box_set_active(GTK_COMBO_BOX(widget),kk); // make the active entry v.2.7
4220
else gtk_combo_box_set_active(GTK_COMBO_BOX(widget),-1); // make no active entry
4223
if (strEqu(type,"comboE")) {
4224
if (! blank_null(data)) {
4225
kk = pvlist_prepend(zd->widget[iiw].cblist,data,1); // add to drop-down list
4226
if (kk == 0) // (only if unique)
4227
gtk_combo_box_prepend_text(GTK_COMBO_BOX(widget),data);
4228
gtk_entry_set_text(GTK_ENTRY(GTK_BIN(widget)->child),data); // stuff entry box with new data
4230
else gtk_entry_set_text(GTK_ENTRY(GTK_BIN(widget)->child),""); // stuff entry box with nothing
4234
zd->disabled--; // re-enable dialog v.2.9
4239
// get data from a dialog widget based on its name
4241
const char * zdialog_get_data(zdialog *zd, const char *name)
4243
if (! zd) return 0; // detect destroyed dialog v.2.2
4244
if (zd->sentinel != zdsentinel) return 0;
4246
for (int ii = 1; zd->widget[ii].type; ii++)
4247
if (strEqu(zd->widget[ii].name,name))
4248
return zd->widget[ii].data;
4253
// get GTK widget from zdialog and widget name
4255
GtkWidget * zdialog_widget(zdialog *zd, const char *name)
4257
if (! zd) return 0; // detect destroyed dialog v.2.2
4258
if (zd->sentinel != zdsentinel) return 0;
4260
for (int ii = 1; zd->widget[ii].type; ii++)
4261
if (strEqu(zd->widget[ii].name,name)) return zd->widget[ii].widget;
4267
// if modal, return after complete with status of button (OK, cancel ...)
4268
// if not modal, return immediately with dialog active
4270
int zdialog_run(zdialog *zd, zdialog_event evfunc, zdialog_compl compfunc)
4273
GtkWidget *widget, *dialog;
4275
if (! zd) zappcrash("zdialog null pointer"); // detect destroyed dialog v.2.2
4276
if (zd->sentinel != zdsentinel) zappcrash("zdialog not valid");
4280
dialog = zd->widget[0].widget;
4281
gtk_widget_show_all(dialog); // activate dialog
4283
for (ii = 1; zd->widget[ii].type; ii++) // *** stop auto-selection
4284
{ // (GTK "feature")
4285
if (strEqu(zd->widget[ii].type,"entry")) {
4286
widget = zd->widget[ii].widget;
4287
gtk_editable_set_position(GTK_EDITABLE(widget),-1);
4291
if (strEqu(zd->widget[ii].type,"comboE")) { // bugfix, remove "combo" v.2.8
4292
widget = zd->widget[ii].widget;
4293
gtk_editable_set_position(GTK_EDITABLE(GTK_BIN(widget)->child),-1);
4298
if (evfunc) zd->eventCB = (void *) evfunc; // link to user event callback
4300
if (zd->disabled) zd->disabled--; // enable widget events v.2.23
4303
G_SIGNAL(dialog,"response",zdialog_response_event,zd); // internal dialog response function
4304
zd->complCB = (void *) compfunc; // link to user completion callback
4306
return 0; // return now, dialog is non-modal
4309
else zstat = gtk_dialog_run(GTK_DIALOG(dialog)); // modal dialog, return when complete
4310
zd->zstat = zstat; // set zdialog status (from button)
4312
return zstat; // and return status
4316
// zdialog event handler - private function called when a widget is edited.
4317
// Updates data in zdialog, calls user callback function (if present).
4318
// This function always runs in main() thread, so zlock() unnecessary.
4320
void zdialog_widget_event(GtkWidget *widget, zdialog *zd)
4322
GtkTextView *textView = 0;
4323
GtkTextBuffer *textBuff = 0;
4324
GtkTextIter iter1, iter2;
4326
static GtkWidget *lastwidget = 0;
4328
const char *name, *type, *wdata;
4331
static int cbadded = 0;
4333
zdialog_event *callbackfunc = 0; // user event callback function
4335
if (! zd) zappcrash("zdialog null pointer"); // detect destroyed dialog v.2.2
4336
if (zd->sentinel != zdsentinel) zappcrash("zdialog not valid");
4338
if (zd->disabled) return; // stop re-entrance from own updates
4340
for (ii = 1; zd->widget[ii].type; ii++) // find widget in zdialog
4341
if (zd->widget[ii].widget == widget) goto found_widget;
4343
for (ii = 1; zd->widget[ii].type; ii++) { // failed, test if buffer
4344
if (strEqu(zd->widget[ii].type,"edit")) { // of text view widget
4345
textView = GTK_TEXT_VIEW(zd->widget[ii].widget);
4346
textBuff = gtk_text_view_get_buffer(textView);
4347
if (widget == (GtkWidget *) textBuff) goto found_widget;
4351
return; // widget not found, ignore
4355
zd->disabled++; // stop re-entrance from own updates
4357
name = zd->widget[ii].name;
4358
type = zd->widget[ii].type;
4361
if (strEqu(type,"button")) wdata = "clicked";
4363
if (strEqu(type,"entry"))
4364
wdata = gtk_entry_get_text(GTK_ENTRY(widget));
4366
if (strEqu(type,"edit")) {
4367
gtk_text_buffer_get_bounds(textBuff,&iter1,&iter2);
4368
wdata = gtk_text_buffer_get_text(textBuff,&iter1,&iter2,0);
4371
if (strcmpv(type,"radio","check","togbutt",null))
4373
nn = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
4374
if (nn == 0) wdata = "0";
4378
if (strEqu(type,"combo"))
4379
wdata = gtk_combo_box_get_active_text(GTK_COMBO_BOX(widget));
4381
if (strEqu(type,"comboE"))
4383
if (widget == lastwidget && cbadded) {
4384
pvlist_remove(zd->widget[ii].cblist,0); // detect multiple edits (keystrokes)
4385
gtk_combo_box_remove_text(GTK_COMBO_BOX(widget),0); // and replace prior entry with new
4387
wdata = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(widget)->child));
4389
if (! blank_null(wdata)) {
4390
nn = pvlist_prepend(zd->widget[ii].cblist,wdata,1); // add entry to drop-down list
4391
if (nn == 0) { // (only if unique)
4392
gtk_combo_box_prepend_text(GTK_COMBO_BOX(widget),wdata);
4398
if (strEqu(type,"spin"))
4400
dval = gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget));
4401
sprintf(sdata,"%g",dval);
4405
if (strEqu(type,"colorbutt")) // color button v.2.17
4407
gtk_color_button_get_color(GTK_COLOR_BUTTON(widget),&gdkcolor);
4408
sprintf(sdata,"%d|%d|%d",gdkcolor.red/256,gdkcolor.green/256,gdkcolor.blue/256);
4412
if (strcmpv(type,"hscale","vscale",null))
4414
dval = gtk_range_get_value(GTK_RANGE(widget));
4415
sprintf(sdata,"%g",dval);
4419
// all widgets come here
4421
if (zd->widget[ii].data) zfree(zd->widget[ii].data); // clear prior data
4422
zd->widget[ii].data = 0;
4424
if (wdata) zd->widget[ii].data = strdupz(wdata); // set new data
4426
lastwidget = widget; // remember last widget updated
4429
callbackfunc = (zdialog_event *) zd->eventCB; // do user callback function
4430
callbackfunc(zd,name);
4438
// zdialog response handler - private function called when dialog is completed.
4439
// status corresponds to completion button from user (OK, cancel ...)
4441
void zdialog_response_event(GtkWidget *, int zstat, zdialog *zd)
4443
zdialog_compl *callbackfunc = 0;
4445
if (! zd) zappcrash("zdialog null pointer"); // detect destroyed dialog v.2.2
4446
if (zd->sentinel != zdsentinel) zappcrash("zdialog not valid");
4448
zd->zstat = zstat; // set zdialog status
4451
callbackfunc = (zdialog_compl *) zd->complCB; // do user callback function
4452
callbackfunc(zd,zstat);
4459
// send an event to an active dialog
4460
// returns: 0 = no active dialog, 1 = OK
4462
int zdialog_send_event(zdialog *zd, cchar *event) // new v.2.17
4464
zdialog_event * evfunc = 0;
4468
if (zd && zd->sentinel == zdsentinel) { // check dialog is active
4469
evfunc = (zdialog_event *) zd->eventCB;
4470
if (evfunc) evfunc(zd,event); // call dialog event function
4471
else { zunlock(); return 0; }
4473
else { zunlock(); return 0; }
4480
// Complete a dialog and give it a status, without user action.
4481
// Dialog completion function or blocked caller will run.
4482
// returns: 0 = no active dialog, 1 = OK
4484
int zdialog_send_response(zdialog *zd, int zstat) // new v.2.23
4486
if (! zd) return 0; // detect destroyed dialog
4487
if (zd->sentinel != zdsentinel) return 0;
4490
gtk_dialog_response(GTK_DIALOG(zd->widget[0].widget), zstat);
4497
// Destroy the zdialog - must be done by zdialog_run() caller
4498
// (else dialog continues active even after completion button).
4499
// Data in widgets remains valid until zdialog_free() is called.
4501
int zdialog_destroy(zdialog *zd)
4503
if (! zd) return 0; // detect destroyed dialog v.2.2
4504
if (zd->sentinel != zdsentinel) return 0;
4506
zlock(); // bugfix v.2.13
4507
if (zd->widget[0].widget) // multiple destroys OK v.2.7.1
4508
gtk_widget_destroy(zd->widget[0].widget); // destroy GTK dialog
4509
zd->widget[0].widget = 0;
4512
zmainloop(); // avoid GTK bug v.2.12
4517
// free zdialog memory (destroy first, if not already)
4519
int zdialog_free(zdialog *zd)
4521
if (! zd) return 0; // detect destroyed dialog v.2.2
4522
if (zd->sentinel != zdsentinel) return 0;
4524
zdialog_destroy(zd); // destroy GTK dialog if there
4526
zd->sentinel = 0; // mark invalid v.2.2
4528
for (int ii = 1; zd->widget[ii].type; ii++) // loop through widgets
4530
zfree((char *) zd->widget[ii].type); // free strings
4531
zfree((char *) zd->widget[ii].name);
4532
zfree((char *) zd->widget[ii].pname);
4533
if (zd->widget[ii].data) zfree(zd->widget[ii].data); // free data
4534
if (strcmpv(zd->widget[ii].type,"combo","comboE",0)) // if combo box, free drop-down list
4535
pvlist_free(zd->widget[ii].cblist);
4538
zfree(zd); // free zdialog memory
4543
// put cursor at named widget
4545
int zdialog_goto(zdialog *zd, const char *name) // v.2.23
4549
widget = zdialog_widget(zd, name);
4550
if (! widget) return 0;
4551
gtk_editable_select_region(GTK_EDITABLE(widget),0,-1); // focus on widget
4552
gtk_widget_grab_focus(widget);
4557
// convenience functions for stuffing and retrieving widget data
4559
int zdialog_stuff(zdialog *zd, const char *name, const char *data) // stuff a string
4561
zdialog_put_data(zd, name, data);
4565
int zdialog_stuff(zdialog *zd, const char *name, int idata) // stuff an integer
4569
sprintf(string,"%d",idata);
4570
zdialog_put_data(zd,name,string);
4574
int zdialog_stuff(zdialog *zd, const char *name, double ddata) // stuff a double
4578
snprintf(string,31,"%g",ddata); // outputs decimal point or comma
4579
zdialog_put_data(zd,name,string); // (per locale)
4583
int zdialog_fetch(zdialog *zd, const char *name, char *data, int maxcc) // fetch string data
4587
zdata = zdialog_get_data(zd,name);
4593
return strncpy0(data,zdata,maxcc); // 0 = OK, 1 = truncation v.2.4
4596
int zdialog_fetch(zdialog *zd, const char *name, int &idata) // fetch an integer
4600
zdata = zdialog_get_data(zd,name);
4606
idata = atoi(zdata);
4610
int zdialog_fetch(zdialog *zd, const char *name, double &ddata) // fetch a double
4615
zdata = zdialog_get_data(zd,name);
4621
stat = convSD(zdata,ddata); // period or comma decimal point OK
4622
if (stat < 4) return 1;
4627
// append new item to combo box list without changing entry box
4629
int zdialog_cb_app(zdialog *zd, const char *name, const char *data)
4633
if (! zd) zappcrash("zdialog null pointer"); // detect destroyed dialog v.2.2
4634
if (zd->sentinel != zdsentinel) zappcrash("zdialog not valid");
4636
if (blank_null(data)) return 0; // find widget
4637
for (ii = 1; zd->widget[ii].type; ii++)
4638
if (strEqu(zd->widget[ii].name,name)) break;
4639
if (! zd->widget[ii].type) return 0; // not found
4640
if (! strcmpv(zd->widget[ii].type,"combo","comboE",0)) return 0; // not combo box
4642
nn = pvlist_append(zd->widget[ii].cblist,data,1); // append unique
4644
gtk_combo_box_append_text(GTK_COMBO_BOX(zd->widget[ii].widget),data);
4649
// prepend new item to combo box list without changing entry box
4651
int zdialog_cb_prep(zdialog *zd, const char *name, const char *data)
4655
if (! zd) zappcrash("zdialog null pointer"); // detect destroyed dialog v.2.2
4656
if (zd->sentinel != zdsentinel) zappcrash("zdialog not valid");
4658
if (blank_null(data)) return 0; // find widget
4659
for (ii = 1; zd->widget[ii].type; ii++)
4660
if (strEqu(zd->widget[ii].name,name)) break;
4661
if (! zd->widget[ii].type) return 0; // not found
4662
if (! strcmpv(zd->widget[ii].type,"combo","comboE",0)) return 0; // not combo box
4664
nn = pvlist_prepend(zd->widget[ii].cblist,data,1); // append unique
4666
gtk_combo_box_prepend_text(GTK_COMBO_BOX(zd->widget[ii].widget),data);
4671
// get combo box drop-down list entry
4673
char * zdialog_cb_get(zdialog *zd, const char *name, int Nth)
4677
if (! zd) return 0; // detect destroyed dialog v.2.2
4678
if (zd->sentinel != zdsentinel) return 0;
4680
for (ii = 1; zd->widget[ii].type; ii++) // find widget
4681
if (strEqu(zd->widget[ii].name,name)) break;
4682
if (! zd->widget[ii].type) return 0; // not found
4683
if (! strcmpv(zd->widget[ii].type,"combo","comboE",0)) return 0; // not combo box
4684
return pvlist_get(zd->widget[ii].cblist,Nth);
4688
// delete entry by name from combo drop down list // v.2.4
4690
int zdialog_cb_delete(zdialog *zd, const char *name, const char *data)
4694
if (! zd) return 0; // detect destroyed dialog v.2.2
4695
if (zd->sentinel != zdsentinel) return 0;
4697
for (ii = 1; zd->widget[ii].type; ii++) // find widget
4698
if (strEqu(zd->widget[ii].name,name)) break;
4699
if (! zd->widget[ii].type) return 0; // not found
4700
if (! strcmpv(zd->widget[ii].type,"combo","comboE",0)) return 0; // not combo box
4702
nn = pvlist_find(zd->widget[ii].cblist,data); // find entry by name
4703
if (nn < 0) return -1;
4704
pvlist_remove(zd->widget[ii].cblist,nn); // remove from memory list
4705
gtk_combo_box_remove_text(GTK_COMBO_BOX(zd->widget[ii].widget),nn); // and from widget
4706
gtk_combo_box_set_active(GTK_COMBO_BOX(zd->widget[ii].widget),-1); // set no active entry
4711
// delete all entries from combo drop down list
4713
int zdialog_cb_clear(zdialog *zd, const char *name)
4717
if (! zd) return 0; // detect destroyed dialog v.2.2
4718
if (zd->sentinel != zdsentinel) return 0;
4720
for (ii = 1; zd->widget[ii].type; ii++) // find widget
4721
if (strEqu(zd->widget[ii].name,name)) break;
4722
if (! zd->widget[ii].type) return 0; // not found
4723
if (! strcmpv(zd->widget[ii].type,"combo","comboE",0)) return 0; // not combo box
4725
nn = pvlist_count(zd->widget[ii].cblist); // entry count
4726
for (jj = nn-1; jj >= 0; jj--) {
4727
pvlist_remove(zd->widget[ii].cblist,jj); // remove from memory list
4728
gtk_combo_box_remove_text(GTK_COMBO_BOX(zd->widget[ii].widget),jj); // remove from widget
4731
gtk_combo_box_set_active(GTK_COMBO_BOX(zd->widget[ii].widget),-1); // set no active entry
4732
if (strEqu(zd->widget[ii].type,"comboE")) // stuff entry box with nothing
4733
gtk_entry_set_text(GTK_ENTRY(GTK_BIN(zd->widget[ii].widget)->child),"");
4738
/**************************************************************************
4739
GTK misc. utility functions
4740
***************************************************************************/
4742
// output text to a popup window
4743
// action: open, write, close // v.2.4
4745
zdialog *wtpopup_zd = 0; // v.2.18 bugfix
4746
GtkWidget *wtpopup_wedit = 0;
4748
int write_popup_text(const char *action, const char *text, int ww, int hh)
4750
int wtpopup_zdcompl(zdialog *zd, int zstat);
4752
if (strEqu(action,"open"))
4754
if (wtpopup_zd) zappcrash("write_popup_text re-entered");
4756
wtpopup_zd = zdialog_new(text,null,"OK",null);
4757
zdialog_add_widget(wtpopup_zd,"scrwin","scrwin","dialog",0,"expand");
4758
zdialog_add_widget(wtpopup_zd,"edit","edit","scrwin");
4759
wtpopup_wedit = zdialog_widget(wtpopup_zd,"edit");
4760
zdialog_resize(wtpopup_zd,ww,hh);
4761
zdialog_run(wtpopup_zd,null,wtpopup_zdcompl);
4764
if (strEqu(action,"write"))
4765
if (wtpopup_wedit) wprintf(wtpopup_wedit," %s\n",text);
4767
if (strEqu(action,"close"))
4769
if (wtpopup_zd) zdialog_free(wtpopup_zd);
4774
zmainloop(); // v.2.18
4778
int wtpopup_zdcompl(zdialog *zd, int zstat)
4780
zdialog_free(wtpopup_zd);
4787
// execute a command and show the output in a scrolling popup window
4789
int popup_command(const char *command, int ww, int hh)
4791
int popup_zdcompl(zdialog*zd, int zstat);
4798
zd = zdialog_new(command,null,"OK",null); // create dialog to show outputs
4799
zdialog_add_widget(zd,"scrwin","scrwin","dialog",0,"expand");
4800
zdialog_add_widget(zd,"edit","edit","scrwin");
4801
wedit = zdialog_widget(zd,"edit");
4802
zdialog_resize(zd,ww,hh);
4803
zdialog_run(zd,null,popup_zdcompl); // show dialog with command outputs
4805
while ((buff = command_output(contx,command))) // v.2.3
4807
wprintf(wedit," %s \n",buff); // *** GTK memory leak is here ***
4811
wscroll(wedit,1); // scroll back to first line v.2.3
4813
err = command_status(contx);
4814
return err; // v.2.12
4817
int popup_zdcompl(zdialog *zd, int zstat)
4824
// display message box and wait for user OK
4825
// *** be careful about calling this before gtk_main() is started ***
4827
void zmessageACK(cchar *pMess, ... )
4832
const char *blanks = " ";
4834
va_start(arglist,pMess);
4835
vsnprintf(message,400,pMess,arglist);
4838
if (strlen(message) < 20) strcat(message,blanks); // make box longer
4841
dialog = gtk_message_dialog_new(null,GTK_DIALOG_MODAL,
4842
GTK_MESSAGE_INFO,GTK_BUTTONS_OK, message, null);
4843
gtk_window_set_position(GTK_WINDOW(dialog),GTK_WIN_POS_MOUSE); // v.2.7.1
4844
gtk_dialog_run(GTK_DIALOG(dialog));
4845
gtk_widget_destroy(dialog);
4852
// log error message to STDOUT as well as message box and user OK
4854
void zmessLogACK(cchar *pMess, ...) // v.2.9
4859
va_start(arglist,pMess);
4860
vsnprintf(message,200,pMess,arglist);
4863
printf("%s \n",message);
4864
zmessageACK(message);
4869
// display message box and wait for user Yes or No response
4870
// *** be careful about calling this before gtk_main() is started ***
4871
// *** uses text buttons "yes" and "no" regardless of locale ***
4873
int zmessageYN(cchar *pMess, ... )
4880
va_start(arglist,pMess);
4881
vsnprintf(message,400,pMess,arglist);
4885
dialog = gtk_message_dialog_new(null,GTK_DIALOG_MODAL,
4886
GTK_MESSAGE_QUESTION,GTK_BUTTONS_YES_NO, message, null);
4887
gtk_window_set_position(GTK_WINDOW(dialog),GTK_WIN_POS_MOUSE); // v.2.7.1
4888
stat = gtk_dialog_run(GTK_DIALOG(dialog));
4889
gtk_widget_destroy(dialog);
4892
if (stat == GTK_RESPONSE_YES) return 1;
4897
// get text input from a popup dialog
4899
const char * dialogText(const char *title, const char *inittext)
4903
zd = zdialog_new(title,null,"OK",ZTX("cancel"),null);
4904
zdialog_add_widget(zd,"frame","fred","dialog");
4905
zdialog_add_widget(zd,"edit","edit","fred");
4906
zdialog_stuff(zd,"edit",inittext);
4908
int zstat = zdialog_run(zd);
4909
zdialog_destroy(zd);
4910
if (zstat != 1) return 0;
4911
return zdialog_get_data(zd,"edit");
4915
// File chooser dialog.
4917
// Action: "open" select existing file
4918
// "save" select existing or new file
4919
// "folder" select existing folder
4920
// "create folder" select existing or new folder
4922
// buttx: "hidden" button toggles display of hidden files
4923
// "quality" button to set JPG file save quality
4925
// Memory for returned filespec should be freed via zfree()
4927
char JPGquality[4] = "85"; // JPG file save quality (extern)
4929
char * zgetfile(cchar *title, cchar *initfile, cchar *action, cchar *buttx)
4931
void zgetfile_preview(GtkWidget *dialog, GtkWidget *pvwidget); // private function v.2.0
4933
GtkFileChooserAction fcact = GTK_FILE_CHOOSER_ACTION_OPEN;
4936
GtkWidget *pvwidget = gtk_image_new();
4937
int fcstat, err, bcode = 0, qnum, hide = 0;
4938
char *pp, *gfile, *rfile;
4939
const char *qual, *button1 = 0, *fakeit;
4944
if (strEqu(action,"open")) {
4945
fcact = GTK_FILE_CHOOSER_ACTION_OPEN;
4946
button1 = ZTX("open");
4949
if (strEqu(action,"save")) {
4950
fcact = GTK_FILE_CHOOSER_ACTION_SAVE;
4951
button1 = ZTX("save");
4954
if (strEqu(action,"folder")) {
4955
fcact = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
4956
button1 = ZTX("open folder");
4959
if (strEqu(action,"create folder")) {
4960
fcact = GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER;
4961
button1 = ZTX("create folder");
4965
if (strnEqu(buttx,"hidden",6)) bcode = 103;
4966
if (strEqu(buttx,"quality")) bcode = 104;
4967
fakeit = ZTX("hidden"); // generate text for translation
4968
fakeit = ZTX("quality");
4971
dialog = gtk_file_chooser_dialog_new(title, null, fcact, // create file selection dialog
4972
button1, GTK_RESPONSE_ACCEPT,
4973
ZTX("cancel"), GTK_RESPONSE_CANCEL,
4974
ZTX(buttx), bcode, null);
4975
gtk_window_set_position(GTK_WINDOW(dialog),GTK_WIN_POS_MOUSE);
4977
if (! strstr(action,"folder")) { // connect preview maker
4978
gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog),pvwidget);
4979
G_SIGNAL(dialog,"update-preview",zgetfile_preview,pvwidget);
4982
if (initfile) { // pre-select filespec v.2.19
4983
err = stat(initfile,&fstat);
4984
if (err) { // attempt to set folder and file name
4985
gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),initfile);
4986
pp = (char *) strrchr(initfile,'/');
4987
if (pp) gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog),pp+1);
4989
else if (S_ISREG(fstat.st_mode))
4990
gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),initfile);
4991
else if (S_ISDIR(fstat.st_mode))
4992
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),initfile);
4995
if (strEqu(action,"save")) // overwrite confirmation
4996
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog),1);
4998
gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog),0); // default: no show hidden
5002
fcstat = gtk_dialog_run(GTK_DIALOG(dialog)); // run dialog, get status button
5004
if (fcstat == 103) { // show/hide hidden files
5005
hide = gtk_file_chooser_get_show_hidden(GTK_FILE_CHOOSER(dialog));
5007
gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog),hide);
5010
else if (fcstat == 104) { // get JPG quality parameter
5012
qual = dialogText(ZTX("JPG quality 0-100"),JPGquality);
5013
if (! qual) break; // cancel = no change
5014
err = convSI(qual,qnum,0,100);
5015
if (err) continue; // enforce 0-100
5016
snprintf(JPGquality,4,"%d",qnum);
5021
else break; // some other button
5024
if (fcstat == GTK_RESPONSE_ACCEPT)
5025
gfile = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); // get chosen file
5028
gtk_widget_destroy(dialog);
5030
if (gfile) { // copy into own memory
5031
rfile = strdupz(gfile);
5032
g_free(gfile); // free GTK resource
5041
// zgetfile private function - get preview images for image files // v.2.0
5043
void zgetfile_preview(GtkWidget *dialog, GtkWidget *pvwidget)
5045
GdkPixbuf *thumbnail;
5047
struct stat statbuf;
5051
filename = gtk_file_chooser_get_preview_filename(GTK_FILE_CHOOSER(dialog));
5052
err = stat(filename,&statbuf);
5053
if (! err && ! S_ISDIR(statbuf.st_mode)) // not for directories v.2.9
5054
thumbnail = image_thumbnail(filename,128); // use 128x128 pixels v.2.9
5058
gtk_image_set_from_pixbuf(GTK_IMAGE(pvwidget),thumbnail);
5059
gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(dialog),1);
5060
g_object_unref(thumbnail); // v.2.5 leak
5063
gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(dialog),0);
5068
// show a local or remote html file using the user's preferred browser // v.2.18
5070
void showz_html(const char *url)
5073
static char prog[20];
5074
static int ftf = 1, err;
5078
strcpy(prog,"xdg-open");
5079
err = system("xdg-open --version");
5081
strcpy(prog,"firefox");
5082
err = system("firefox -v");
5088
zmessLogACK("no xdg-open or firefox, cannot show document");
5092
snprintf(command,499,"%s %s &",prog,url); // add '&' v.2.18
5093
err = system(command);
5098
// display message box indefinitely, until caller kills it v.2.6
5100
GtkWidget * zmessage_post(cchar *pMess, ... )
5102
GtkWidget *dialog, *label1, *label2, *label3;
5106
const char *b20 = " ";
5109
va_start(arglist,pMess);
5110
vsnprintf(message1,400,pMess,arglist);
5113
cc1 = strlen(message1);
5114
if (cc1 > 20) strcpy(message2,message1);
5116
cc2 = (22 - cc1) / 2;
5117
strncpy(message2,b20,cc2);
5118
strcpy(message2+cc2,message1);
5119
strncpy(message2+cc2+cc1,b20,cc2);
5120
message2[cc2+cc1+cc2] = 0;
5124
dialog = gtk_dialog_new_with_buttons("",null,(GtkDialogFlags) 0,null);
5125
label1 = gtk_label_new(" ");
5126
label2 = gtk_label_new(message2);
5127
label3 = gtk_label_new(" ");
5128
gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),label1);
5129
gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),label2);
5130
gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),label3);
5131
gtk_widget_show_all(dialog);
5137
void zmessage_kill(GtkWidget *dialog)
5140
gtk_widget_destroy(dialog);
5146
// connect a user callback function to a window drag-drop event
5148
void drag_drop_connect(GtkWidget *window, drag_drop_func *ufunc) // v.2.19
5150
int drag_drop_connect2(GtkWidget *, void *, int, int, void *, int, int, void *);
5151
char string[] = "STRING";
5152
GtkTargetEntry file_drop_target = { string, 0, 0 };
5154
gtk_drag_dest_set(window, GTK_DEST_DEFAULT_ALL, &file_drop_target, 1, GDK_ACTION_COPY);
5155
G_SIGNAL(window, "drag-data-received", drag_drop_connect2, ufunc)
5161
// get dropped file, clean escapes, pass to user function
5162
// passed filespec is subject for zfree()
5164
int drag_drop_connect2(GtkWidget *, void *, int mpx, int mpy, void *sdata, int, int, void *ufunc)
5166
char * drag_drop_unescape(const char *escaped_string);
5167
drag_drop_func *ufunc2;
5169
char *text, *text2, *file, *file2;
5172
text = (char *) ((GtkSelectionData *) sdata)->data;
5173
ufunc2 = (drag_drop_func *) ufunc;
5175
if (strstr(text,"file://")) // text is a filespec
5177
file = strdupz(text+7); // get rid of junk added by GTK
5179
while (file[cc-1] < ' ') cc--;
5181
file2 = drag_drop_unescape(file); // clean %xx escapes from Nautilus
5183
ufunc2(mpx,mpy,file2); // pass file to user function
5188
text2 = strdupz(text);
5189
ufunc2(mpx,mpy,text2);
5196
// Clean %xx escapes from strange Nautilus drag-drop file names
5198
char * drag_drop_unescape(const char *inp)
5200
int drag_drop_convhex(char ch);
5202
char inch, *out, *outp;
5205
out = zmalloc(strlen(inp) + 1);
5208
while ((inch = *inp++))
5212
nib1 = drag_drop_convhex(*inp++);
5213
nib2 = drag_drop_convhex(*inp++);
5214
*outp++ = nib1 << 4 | nib2;
5216
else *outp++ = inch;
5223
// private function - convert character 0-F to number 0-15
5225
int drag_drop_convhex(char ch)
5227
if (ch >= '0' && ch <= '9') return ch - '0';
5228
if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10;
5229
if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10;
5234
/**************************************************************************
5235
GDK graphics utilities
5236
***************************************************************************/
5238
/**************************************************************************
5240
GdkPixbuf * gdk_pixbuf_rotate(GdkPixbuf *pixbuf, double angle, int acolor)
5242
Rotate a pixbuf through an arbitrary angle (degrees).
5244
The returned image has the same size as the original, but the
5245
pixbuf envelope is increased to accomodate the rotated original
5246
(e.g. a 100x100 pixbuf rotated 45 deg. needs a 142x142 pixbuf).
5248
Pixels added around the rotated image have all RGB values = acolor. // v.2.17
5249
Angle is in degrees. Positive direction is clockwise.
5250
Pixbuf must have 8 bits per channel and 3 or 4 channels.
5251
Loss of resolution is < 1 pixel.
5252
Speed is about 2.4 million pixels/sec. for a 2 GHz CPU.
5254
NULL is returned if the function fails for one of the following:
5255
- pixbuf not 8 bits/channel or < 3 channels
5256
- unable to create output pixbuf (lack of memory?)
5259
create output pixbuf big enough for rotated input pixbuf
5260
loop all output pixels
5261
get next output pixel (px2,py2)
5262
compute (R,theta) from center of pixbuf
5263
rotate theta by -angle
5264
(R,theta) is now within the closest input pixel
5265
convert to input pixel (px1,py1)
5266
if outside of pixmap
5267
output pixel = black
5269
for 4 input pixels based at (px0,py0) = (int(px1),int(py1))
5270
compute overlap (0 to 1) with (px1,py1)
5271
sum RGB values * overlap
5272
output aggregate RGB to pixel (px2,py2)
5274
***************************************************************************/
5276
GdkPixbuf * gdk_pixbuf_rotate(GdkPixbuf *pixbuf1, double angle, int acolor)
5278
typedef unsigned char *pixel; // 3 RGB values, 0-255 each
5281
GdkColorspace color;
5283
int nch, nbits, alpha;
5284
int pbW1, pbH1, pbR1, pbW2, pbH2, pbR2;
5285
int px2, py2, px0, py0;
5286
pixel ppix1, ppix2, pix0, pix1, pix2, pix3;
5287
double rx1, ry1, rx2, ry2, R, theta, px1, py1;
5288
double f0, f1, f2, f3, red, green, blue;
5289
double pi = 3.141592654;
5291
nch = gdk_pixbuf_get_n_channels(pixbuf1);
5292
nbits = gdk_pixbuf_get_bits_per_sample(pixbuf1);
5293
if (nch < 3) return 0; // must have 3+ channels (colors)
5294
if (nbits != 8) return 0; // must be 8 bits per channel
5296
color = gdk_pixbuf_get_colorspace(pixbuf1); // get input pixbuf1 attributes
5297
alpha = gdk_pixbuf_get_has_alpha(pixbuf1);
5298
pbW1 = gdk_pixbuf_get_width(pixbuf1);
5299
pbH1 = gdk_pixbuf_get_height(pixbuf1);
5300
pbR1 = gdk_pixbuf_get_rowstride(pixbuf1);
5302
while (angle < -180) angle += 360; // normalize, -180 to +180
5303
while (angle > 180) angle -= 360;
5304
angle = angle * pi / 180; // radians, -pi to +pi
5306
if (fabs(angle) < 0.001) { // bugfix 0.01 >> 0.001 v.2.1
5307
pixbuf2 = gdk_pixbuf_copy(pixbuf1); // angle is zero within my precision
5311
pbW2 = int(pbW1*fabs(cos(angle)) + pbH1*fabs(sin(angle))); // rectangle containing rotated image
5312
pbH2 = int(pbW1*fabs(sin(angle)) + pbH1*fabs(cos(angle)));
5314
pixbuf2 = gdk_pixbuf_new(color,alpha,nbits,pbW2,pbH2); // create output pixbuf2
5315
if (! pixbuf2) return 0;
5316
pbR2 = gdk_pixbuf_get_rowstride(pixbuf2);
5318
ppix1 = gdk_pixbuf_get_pixels(pixbuf1); // input pixel array
5319
ppix2 = gdk_pixbuf_get_pixels(pixbuf2); // output pixel array
5321
for (py2 = 0; py2 < pbH2; py2++) // loop through output pixels
5322
for (px2 = 0; px2 < pbW2; px2++) // outer loop y v.2.11
5324
rx2 = px2 - 0.5 * pbW2; // (rx2,ry2) = center of pixel
5325
ry2 = py2 - 0.5 * pbH2;
5326
R = sqrt(rx2*rx2 + ry2*ry2); // convert to (R,theta)
5327
if (R < 0.1) theta = 0;
5328
else theta = qarcsine(ry2 / R); // quick arc sine
5330
if (theta < 0) theta = - pi - theta; // adjust for quandrant
5331
else theta = pi - theta;
5334
theta = theta - angle; // rotate theta backwards
5335
if (theta > pi) theta -= 2 * pi; // range -pi to +pi
5336
if (theta < -pi) theta += 2 * pi;
5338
rx1 = R * qcosine(theta); // quick cosine, sine
5339
ry1 = R * qsine(theta);
5340
px1 = rx1 + 0.5 * pbW1; // (px1,py1) = corresponding
5341
py1 = ry1 + 0.5 * pbH1; // point within input pixels
5343
px0 = int(px1); // pixel containing (px1,py1)
5346
if (px1 < 0 || px0 >= pbW1-1 || py1 < 0 || py0 >= pbH1-1) { // if outside input pixel array
5347
pix2 = ppix2 + py2 * pbR2 + px2 * nch; // output is acolor v.2.17
5348
pix2[0] = pix2[1] = pix2[2] = acolor;
5352
pix0 = ppix1 + py0 * pbR1 + px0 * nch; // 4 input pixels based at (px0,py0)
5355
pix3 = pix0 + pbR1 + nch;
5357
f0 = (px0+1 - px1) * (py0+1 - py1); // overlap of (px1,py1)
5358
f1 = (px0+1 - px1) * (py1 - py0); // in each of the 4 pixels
5359
f2 = (px1 - px0) * (py0+1 - py1);
5360
f3 = (px1 - px0) * (py1 - py0);
5362
red = f0 * pix0[0] + f1 * pix1[0] + f2 * pix2[0] + f3 * pix3[0]; // sum the weighted inputs
5363
green = f0 * pix0[1] + f1 * pix1[1] + f2 * pix2[1] + f3 * pix3[1];
5364
blue = f0 * pix0[2] + f1 * pix1[2] + f2 * pix2[2] + f3 * pix3[2];
5366
if (red == acolor && green == acolor && blue == acolor) { // avoid acolor in image v.2.17
5367
if (blue == 0) blue = 1;
5371
pix2 = ppix2 + py2 * pbR2 + px2 * nch; // output pixel
5373
pix2[1] = int(green);
5374
pix2[2] = int(blue);
5381
/**************************************************************************
5383
functions for navigtion of image files in a directory // overhauled v.2.3
5384
- get first or last image, previous or next image
5385
- create a window of thumbnail images (index window)
5386
- use thumbnail window to navigate and select images
5388
**************************************************************************/
5390
namespace image_navi {
5392
#define flimit 10000 // max image files in one directory
5393
#define indexfont "sans 8" // font for thumbnail labels
5394
#define imagefiles ".jpeg .jpg .png .tif .tiff .bmp .gif .svg .xpm" // supported file types
5396
#define nodither GDK_RGB_DITHER_NONE,0,0
5397
#define interp GDK_INTERP_BILINEAR
5398
#define colorspace GDK_COLORSPACE_RGB
5400
#define thumbfilesize 256 // thumbnail file image size
5401
#define thumbxx 5 // thumbx array size
5402
int thumbx[5] = { 256, 180, 128, 90, 64 }; // thumbnail image step sizes v.2.9
5404
char dirkx[maxfcc]; // image directory
5405
int nfiles = 0; // image file count
5406
char **flist = 0; // image file list
5408
image_cbfunc *userfunc; // callback function for clicked image
5410
GtkWidget *windx = 0, *vboxx, *dwindx; // thumbnail index and drawing window
5411
GdkGC *gdkgc = 0; // graphics context
5412
GError **gerror = 0;
5414
char *filex = 0; // index window anchor file
5415
int xwinW = 1000, xwinH = 700; // index window size (dwindx)
5416
int thumbsize = thumbfilesize; // thumbnail image <= thumbnail file
5417
int thumbW, thumbH; // index window thumbnail cell size
5418
int xrows, xcols; // index window thumbnail rows, cols
5419
int xmargW, xmargH; // cell margin from left and top edge
5421
// private functions
5422
void windx_paint(); // index window paint function
5423
void windx_destroy(); // index window destroy event function
5424
void draw_xtext(GtkWidget *win, char *text, int x, int y); // draw text in index window
5425
void menufuncx(GtkWidget *win, const char *menu); // function for index window buttons
5426
void mouse_xevent(GtkWidget *, GdkEventButton *, void *); // index window mouse event function
5427
int KBxpress(GtkWidget *, GdkEventKey *, void *); // index window key press event func.
5428
int KBxrelease(GtkWidget *, GdkEventKey *, void *); // index window key release event
5429
char * image_navigate(cchar *filez, cchar *action, int Nth = 0); // image file list setup and navigate
5430
int image_fcomp(const char *file1, const char *file2); // file name compare (special sort)
5433
using namespace image_navi;
5436
/**************************************************************************
5438
public function to create/update thumbnail window (index window) v.2.3
5440
Make window of thumbnails starting with filez (anchor)
5441
Handle window buttons (up row, down page, open file, etc.)
5442
Call ufunc() when thumbnail image is clicked
5444
filez: anchor file or directory of image files
5446
action: init: get internal list of image files and subdirectories
5447
initF: filez contains a list of image files to use v.2.4
5448
find return filez if in current list, else null
5449
prev: return filez Nth previous file
5450
next: return filez Nth next file
5451
first: return first file in list (may be a subdirectory)
5452
last: return Nth last file in list
5453
paint1: create or refresh thumbnail window, anchor = filez
5454
paint2: refresh thumbnail window if present, anchor = filez
5456
Nth: 1 means filez pevious, filez next, or last file in list
5457
greater values mean correspondingly greater offsets
5459
void ufunc(char *filez)
5460
- receives filename (path) of selected thumbnail
5461
- filez belongs to caller and is a subject for zfree()
5464
For init, initF, paint1, paint2: null
5465
For find, prev, next, first, last: filespec or null if not found
5466
The returned file belongs to caller and is subject for zfree().
5468
***************************************************************************/
5470
char * image_xthumbs(cchar *filez, cchar *action, int Nth, image_cbfunc ufunc)
5474
if (ufunc) userfunc = ufunc; // save callback function
5476
if (strstr("init initF find prev next first last",action))
5477
return image_navigate(filez,action,Nth); // create or navigate image file list
5479
if (filez) strdupz(filez,filex); // set new anchor file v.2.4
5481
if (strEqu(action,"paint2") && ! windx) return 0; // refresh, but window not active
5483
if (windx) { // refresh existing index window
5484
windx_paint(); // repaint
5485
gtk_window_present(GTK_WINDOW(windx)); // bring index window to top
5489
windx = gtk_window_new(GTK_WINDOW_TOPLEVEL); // create index window
5490
gtk_window_set_position(GTK_WINDOW(windx),GTK_WIN_POS_CENTER);
5491
gtk_window_set_default_size(GTK_WINDOW(windx),xwinW,xwinH+56); // (+ toolbar size)
5493
vboxx = gtk_vbox_new(0,0); // vertical packing box
5494
gtk_container_add(GTK_CONTAINER(windx),vboxx); // add to main window
5496
tbarx = create_toolbar(vboxx,24); // add toolbar and buttons
5498
add_toolbar_button(tbarx, ZTX("bigger"), ZTX("increase thumbnail size"), "bigger.png", menufuncx);
5499
add_toolbar_button(tbarx, ZTX("smaller"), ZTX("reduce thumbnail size"), "smaller.png", menufuncx);
5500
add_toolbar_button(tbarx, ZTX("prev row"), ZTX("previous row"), "prev-row.png", menufuncx);
5501
add_toolbar_button(tbarx, ZTX("next row"), ZTX("next row"), "next-row.png", menufuncx);
5502
add_toolbar_button(tbarx, ZTX("prev page"), ZTX("previous page"), "prev-page.png", menufuncx);
5503
add_toolbar_button(tbarx, ZTX("next page"), ZTX("next page"), "next-page.png", menufuncx);
5504
add_toolbar_button(tbarx, ZTX("first page"), ZTX("jump to first file"), "first-page.png", menufuncx);
5505
add_toolbar_button(tbarx, ZTX("last page"), ZTX("jump to last file"), "last-page.png", menufuncx);
5506
add_toolbar_button(tbarx, ZTX("file"), ZTX("jump to specific file"), "file.png", menufuncx);
5507
add_toolbar_button(tbarx, ZTX("folder"), ZTX("jump to new directory"), "folder.png", menufuncx);
5508
add_toolbar_button(tbarx, ZTX("close"), ZTX("close thumbnail window"), "gtk-close", menufuncx);
5510
dwindx = gtk_drawing_area_new(); // add drawing window
5511
gtk_container_add(GTK_CONTAINER(vboxx),dwindx); // add to main window
5513
gtk_widget_show_all(windx); // show all widgets (will paint)
5515
gdkgc = gdk_gc_new(dwindx->window); // initz. graphics context
5517
G_SIGNAL(windx,"destroy",windx_destroy,0) // connect window events
5518
G_SIGNAL(dwindx,"expose-event",windx_paint,0)
5519
gtk_widget_add_events(dwindx,GDK_BUTTON_PRESS_MASK); // connect mouse events
5520
G_SIGNAL(dwindx,"button-press-event",mouse_xevent,0)
5521
G_SIGNAL(windx,"key-press-event",KBxpress,0) // connect KB events
5522
G_SIGNAL(windx,"key-release-event",KBxrelease,0)
5529
// get current image position and total count
5531
void image_position(const char *filez, int &posn, int &count)
5535
for (ii = nn = 0; ii < nfiles; ii++) // search for filez in file list
5537
if (flist[ii][0] == '!') nn++; // directories are marked
5538
if (strcmp(filez,flist[ii]) == 0) break;
5541
posn = ii + 1 - nn; // return position, 1 - max
5542
count = nfiles - nn; // return total count
5548
// determine if a file is a directory or a supported image file type v.2.3
5549
// return: 0 = error, 1 = directory, 2 = image file, 3 = other
5551
int image_file_type(const char *file)
5555
struct stat statbuf;
5557
if (! file) return 0;
5558
err = stat(file,&statbuf);
5561
if (S_ISDIR(statbuf.st_mode)) { // directory
5564
pp = file + cc - 12;
5565
if (strEqu(pp,"/.thumbnails")) return 3; // .thumbnails
5570
if (S_ISREG(statbuf.st_mode)) { // reg. file
5571
pp = strrchr(file,'.');
5573
pp = strcasestr(imagefiles,pp); // supported image type
5582
// Get thumbnail image for given file, from .thumbnails directory if found. v.2.3
5583
// Add missing thumbnail to cache if .thumbnails directory is present.
5584
// Returned thumbnail belongs to caller: g_object_unref() is necessary.
5586
GdkPixbuf * image_thumbnail(const char *fpath, int size)
5588
char bpath[maxfcc+20], *bfile;
5589
const char *pfile; // v.2.15
5590
GdkPixbuf *thumbpxb, *temppxb;
5591
int err, sizew, sizeh;
5592
struct stat statf, statb;
5594
if (! size) size = thumbfilesize; // default max. size v.2.9
5596
err = stat(fpath,&statf);
5599
if (S_ISDIR(statf.st_mode)) { // if directory, return folder image
5601
strncatv(bpath,maxfcc,zicondir,"/folder256.png",null);
5602
thumbpxb = gdk_pixbuf_new_from_file_at_size(bpath,size,size,gerror);
5606
pfile = strrchr(fpath,'/');
5607
if (! pfile) return 0;
5608
pfile++; // file name part
5610
strcpy(bpath,fpath); // construct thumbnail path:
5611
bfile = bpath + (pfile - fpath); // /directory/.thumbnails/filename.png
5612
strcpy(bfile,".thumbnails/"); // use filename instead of hash
5614
strcpy(bfile,pfile);
5615
strcat(bfile,".png");
5617
sizew = sizeh = thumbfilesize; // use max. size
5618
thumbpxb = gdk_pixbuf_new_from_file_at_size(bpath,sizew,sizeh,gerror); // get thumbnail
5620
err = stat(bpath,&statb); // found, compare date to image date
5621
if (err || (statb.st_mtime < statf.st_mtime)) {
5622
g_object_unref(thumbpxb); // stale - regenerate
5627
if (! thumbpxb) { // generate thumbnail from image file
5628
thumbpxb = gdk_pixbuf_new_from_file_at_size(fpath,sizew,sizeh,gerror);
5629
if (! thumbpxb) return 0; // hopeless
5630
gdk_pixbuf_save(thumbpxb,bpath,"png",gerror,null); // save to thumbnail cache (if present)
5633
if (size == thumbfilesize) return thumbpxb; // return thumbnail if right size
5635
sizew = gdk_pixbuf_get_width(thumbpxb) * size / thumbfilesize; // rescale to right size
5636
sizeh = gdk_pixbuf_get_height(thumbpxb) * size / thumbfilesize;
5637
temppxb = gdk_pixbuf_scale_simple(thumbpxb,sizew,sizeh,interp);
5638
g_object_unref(thumbpxb);
5644
// paint index window - draw all thumbnail images that can fit
5646
void image_navi::windx_paint()
5650
char *fileC, *fileN, *pp, *fname;
5653
snprintf(wintitle,199,"%s %d files",dirkx,nfiles); // v.2.9
5654
gtk_window_set_title(GTK_WINDOW(windx),wintitle);
5655
gdk_window_clear(dwindx->window);
5657
xwinW = dwindx->allocation.width; // curr. index window size
5658
xwinH = dwindx->allocation.height;
5660
thumbW = thumbsize + 10; // thumbnail cell size
5661
thumbH = thumbsize + 30;
5663
xmargW = xmargH = 5; // edge margins
5665
xrows = int(0.2 + 1.0 * xwinH / thumbH); // get thumbnail rows and cols that
5666
xcols = int(0.3 + 1.0 * xwinW / thumbW); // (almost) fit in window
5667
if (xrows < 1) xrows = 1;
5668
if (xcols < 1) xcols = 1;
5670
if (! filex) return;
5671
fileC = strdupz(filex); // start with anchor file
5673
for (row = 0; row < xrows; row++) // draw thumbnails
5674
for (col = 0; col < xcols; col++)
5676
x = col * thumbW + xmargW;
5677
y = row * thumbH + xmargH;
5679
pp = (char *) strrchr(fileC,'/'); // draw file name
5680
if (pp) fname = pp + 1;
5682
draw_xtext(dwindx,fname,x,y);
5684
pxbT = image_thumbnail(fileC,thumbsize); // get thumbnail, draw it
5686
gdk_draw_pixbuf(dwindx->window,0,pxbT,0,0,x,y+20,-1,-1,nodither);
5687
g_object_unref(pxbT);
5690
fileN = image_navigate(fileC,"next",1); // get next image file
5692
if (! fileN) return;
5701
// write text for thumbnail limited by width of thumbnail
5703
void image_navi::draw_xtext(GtkWidget *win, char *text, int x, int y)
5705
static PangoFontDescription *pfont = 0;
5706
static PangoLayout *playout = 0;
5712
pfont = pango_font_description_from_string(indexfont); // first call, get font sizing poop
5713
playout = gtk_widget_create_pango_layout(win,0);
5714
pango_layout_set_font_description(playout,pfont);
5717
for (cc = 50; cc > 7; cc--) // allow up to 50 graphic chars.
5718
{ // and reduce until fits v.2.3
5719
cc = utf8substring(text2,text,0,cc); // get substring up to cc chars.
5720
pango_layout_set_text(playout,text2,-1); // compute layout
5721
pango_layout_get_pixel_size(playout,&ww,&hh); // pixel width of layout
5722
if (ww < thumbsize+5) break; // stop if it fits thumbnail size
5725
gdk_draw_layout(win->window,gdkgc,x,y,playout);
5731
// index window destroy event - track if window is active or not
5733
void image_navi::windx_destroy()
5735
windx = 0; // no window
5736
if (filex) zfree(filex); // no anchor file
5742
// private function - menu function for index window
5743
// - scroll window as requested
5744
// - jump to new file or folder as requested
5746
void image_navi::menufuncx(GtkWidget *win, const char *menu)
5752
if (strEqu(menu,ZTX("bigger"))) { // next bigger thumbnail size
5753
for (ii = 0; ii < thumbxx; ii++)
5754
if (thumbsize == thumbx[ii]) break;
5755
if (ii == 0) return;
5756
thumbsize = thumbx[ii-1];
5761
if (strEqu(menu,ZTX("smaller"))) { // next smaller
5762
for (ii = 0; ii < thumbxx; ii++)
5763
if (thumbsize == thumbx[ii]) break;
5764
if (ii == thumbxx-1) return;
5765
thumbsize = thumbx[ii+1];
5770
if (strEqu(menu,ZTX("file"))) { // go to specific file
5771
filez = zgetfile(ZTX("select new file"),filex,"open"); // file chooser dialog
5773
image_navigate(filez,"init"); // get new file list
5779
if (strEqu(menu,ZTX("folder"))) { // initz. all files in folder
5780
filez = zgetfile(ZTX("select new folder"),filex,"folder"); // file chooser dialog (folder)
5782
image_navigate(filez,"init"); // get new file list
5788
if (strEqu(menu,ZTX("close"))) {
5789
gtk_widget_destroy(windx); // close thumbnail window
5793
if (! filex) return; // no anchor file
5795
if (strEqu(menu,ZTX("prev row"))) {
5800
if (strEqu(menu,ZTX("next row"))) {
5805
if (strEqu(menu,ZTX("prev page"))) {
5807
count = xcols * xrows;
5810
if (strEqu(menu,ZTX("next page"))) {
5812
count = xcols * xrows;
5815
if (strEqu(menu,ZTX("first page"))) {
5820
if (strEqu(menu,ZTX("last page"))) {
5822
count = xcols * xrows; // last + prior fitting in window
5825
filez = image_navigate(filex,action,count); // get file starting prev/next row/page
5826
if (! filez) return;
5828
filex = filez; // new anchor file
5829
windx_paint(); // refresh window
5836
// mouse event function for index window - get selected thumbnail and file
5838
void image_navi::mouse_xevent(GtkWidget *, GdkEventButton *event, void *)
5841
int row, col, Nth, err;
5845
mousex = int(event->x);
5846
mousey = int(event->y);
5848
row = (mousey - xmargH) / thumbH; // find selected row, col
5849
col = (mousex - xmargW) / thumbW;
5851
if (row < 0 || row >= xrows) return;
5852
if (col < 0 || col >= xcols) return;
5854
Nth = xcols * row + col;
5856
if (Nth == 0) filez = strdupz(filex); // anchor file selected
5858
filez = image_navigate(filex,"next",Nth); // else get Nth file after anchor
5859
if (! filez) return;
5862
err = stat(filez,&statb); // file is gone?
5864
image_navigate(filex,"init"); // initz. window v.2.11
5869
if (S_ISDIR(statb.st_mode)) { // if directory, go there
5870
image_navigate(filez,"init");
5871
if (filex) zfree(filex);
5872
filex = image_navigate(filez,"first"); // set anchor file
5877
if (userfunc) userfunc(filez); // pass clicked file to user function
5883
// KB event function - respond to keyboard navigation keys
5885
int image_navi::KBxpress(GtkWidget *win, GdkEventKey *event, void *) // prevent propagation of key-press
5886
{ // events to toolbar buttons v.2.9
5890
int image_navi::KBxrelease(GtkWidget *win, GdkEventKey *event, void *)
5894
KBkey = event->keyval;
5896
if (KBkey == GDK_plus) menufuncx(win,ZTX("bigger")); // +/- = bigger/smaller thumbnails
5897
if (KBkey == GDK_equal) menufuncx(win,ZTX("bigger")); // v.2.5
5898
if (KBkey == GDK_minus) menufuncx(win,ZTX("smaller"));
5900
if (KBkey == GDK_Left) menufuncx(win,ZTX("prev page")); // left arrow = previous page
5901
if (KBkey == GDK_Right) menufuncx(win,ZTX("next page")); // right arrow = next page
5902
if (KBkey == GDK_Up) menufuncx(win,ZTX("prev row")); // up arrow = previous row
5903
if (KBkey == GDK_Down) menufuncx(win,ZTX("next row")); // down arrow = next row
5905
if (KBkey == GDK_Escape) gtk_widget_destroy(win); // Escape = quit thumbnail window
5911
/**************************************************************************
5913
private function - manage list of image files within a directory
5915
get an image file in the same directory as given file or directory
5917
action: init initz. list of image files and subdirectories
5918
initF initz. from given file list
5919
find return filez if in list, else null
5920
prev return filez Nth previous file
5921
next return filez Nth next file
5922
first return first file in list
5923
last return Nth last file in list
5925
Nth = 1 means filez -1, filez +1, and last file
5927
For init and initF, null is returned.
5928
For the others, filespec is returned or null if not found.
5929
Returned file belongs to caller and is a subject for zfree().
5931
***************************************************************************/
5933
char * image_navi::image_navigate(cchar *filez, cchar *action, int Nth)
5936
const char *findcommand = "find \"%s\" -maxdepth 1";
5937
char filezz[maxfcc], *pp, *file2;
5938
int err, ii, nn = 0, ftyp, contx = 0;
5940
struct stat statbuf;
5942
if (! strstr("init initF find first last prev next",action)) // v.2.5
5943
zappcrash("image_navigate %s",action);
5945
if (strEqu(action,"init")) // initialize from given directory
5947
for (int ii = 0; ii < nfiles; ii++) zfree(flist[ii]); // free prior list memory
5948
if (flist) zfree(flist);
5949
flist = (char **) zmalloc(flimit * sizeof(char *)); // list of file pointers
5951
nfiles = 0; // no files
5952
if (filex) zfree(filex); // no anchor file
5955
strncpy0(dirkx,filez,maxfcc-2); // copy input file
5956
err = stat(dirkx,&statbuf);
5958
pp = (char *) strrchr(dirkx,'/'); // bad file, check directory part
5961
err = stat(dirkx,&statbuf);
5962
if (err) return 0; // give up, empty file list
5965
if (S_ISREG(statbuf.st_mode)) { // if a file, get directory part
5966
pp = (char *) strrchr(dirkx,'/');
5971
while ((buff = command_output(contx,findcommand,dirkx))) // find all files
5973
if (strEqu(buff,dirkx)) { // skip self directory
5978
ftyp = image_file_type(buff);
5979
if (ftyp != 1 && ftyp != 2) { // not directory or image file type
5980
zfree(buff); // (.thumbnails not ftyp 1)
5984
flist[nfiles] = buff; // add to file list
5985
if (ftyp == 1) flist[nfiles][0] = '!'; // if directory, make it sort first
5987
if (nfiles == flimit) zappcrash("more than %d files",flimit);
5990
if (nfiles == 0) return 0; // no files
5992
HeapSort(flist,nfiles,image_fcomp); // Heap Sort - pointers to strings
5995
if (strEqu(action,"initF")) // initialize from given list v.2.4
5997
for (int ii = 0; ii < nfiles; ii++) zfree(flist[ii]); // free prior list memory
5998
if (flist) zfree(flist);
5999
flist = (char **) zmalloc(flimit * sizeof(char *)); // list of file pointers
6001
nfiles = 0; // no files
6002
if (filex) zfree(filex); // no anchor file
6005
strncpy0(dirkx,filez,maxfcc-1); // copy input file
6007
fid = fopen(dirkx,"r"); // open file
6008
if (! fid) return 0;
6010
buff = zmalloc(maxfcc);
6012
while (true) // read list of files
6014
pp = fgets_trim(buff,maxfcc-1,fid,1);
6016
flist[nfiles] = strdupz(buff); // add files to memory list
6018
if (nfiles == flimit) zappcrash("more than %d files",flimit);
6024
if (nfiles == 0) return 0; // no files
6026
HeapSort(flist,nfiles,image_fcomp); // Heap Sort - pointers to strings
6029
if (nfiles == 0) return 0; // file list is empty
6031
if (! filez) *filezz = 0; // no filez arg v.2.5
6032
else strncpy0(filezz,filez,maxfcc); // make modifiable copy of filez
6033
err = stat(filezz,&statbuf);
6034
if (! err && S_ISDIR(statbuf.st_mode)) filezz[0] = '!'; // if directory, match with sorted list
6036
for (ii = 0; ii < nfiles; ii++) { // search for filez in file list
6037
nn = image_fcomp(filezz,flist[ii]); // = 0: filez = flist[ii]
6038
if (nn <= 0) break; // < 0: flist[ii-1] < filez < flist[ii]
6041
if (ii == nfiles) ii--; // filez > last file in list
6043
if (strnEqu(action,"init",4)) { // init or initF
6044
filex = strdupz(flist[ii]); // anchor file = 1st file
6045
filex[0] = '/'; // restore initial '/'
6049
if (strEqu(action,"find")) // is filez in list or not v.2.4
6050
if (nn != 0) return 0;
6052
if (strEqu(action,"first")) // return first file
6055
if (strEqu(action,"last")) { // Nth last file (1 = last)
6057
if (ii < 0) ii = 0; // if < first file, return first
6060
if (strEqu(action,"prev")) { // Nth previous file
6061
if (ii == 0) return 0; // filez <= first in list
6063
if (ii < 0) ii = 0; // first file
6066
if (strEqu(action,"next")) { // Nth next file
6067
if (nn == 0) ii += Nth; // get Nth next file v.2.6
6068
if (ii > nfiles-1) return 0; // beyond last file
6071
file2 = strdupz(flist[ii]); // copy file into new memory
6072
file2[0] = '/'; // restore initial '/'
6073
err = stat(file2,&statbuf);
6074
if (! err) return file2; // return file
6076
if (strcmpv(action,"prev","next",null)) // try to skip over missing file
6077
return image_navigate(file2,action,1); // v.2.16
6082
// private function for special file name compare
6083
// directories sort first, upper/lower case names sort together
6085
int image_navi::image_fcomp(const char *file1, const char *file2)
6088
nn = strcasecmp(file1,file2); // compare ignoring case
6089
if (nn != 0) return nn;
6090
nn = strcmp(file1,file2); // if equal, do normal compare
6094
using namespace std;
6097
/**************************************************************************
6098
parameter management functions
6099
***************************************************************************/
6101
struct t_parmlist { // parameter list in memory
6102
int max; // max parameter count
6103
int count; // actual parameter count
6104
char **name; // pointer to names (list of char *)
6105
double *value; // pointer to values (list of double)
6108
int parmlistvalid = 0; // flag
6109
char zparmfile[1000]; // last used parm file
6112
// initialize parameter list - must be called first
6114
int initParmlist(int max)
6116
if (! parmlistvalid) { // start with default parms file
6117
strcpy(zparmfile,get_zuserdir());
6118
strcat(zparmfile,"/parameters"); // /home/user/.appname/parameters
6121
if (parmlistvalid) { // delete old parms
6122
delete [] parmlist.name;
6123
delete [] parmlist.value;
6128
char **names = new char*[max]; // allocate max pointers for names
6129
double *values = new double[max]; // allocate max doubles for values
6130
parmlist.name = names;
6131
parmlist.value = values;
6137
// Load user parameters if the file exists, else initialize the
6138
// user parameters file from default application parameters.
6140
int initz_userParms()
6142
if (! parmlistvalid) zappcrash("parmlistvalid = 0");
6144
int np = loadParms("parameters");
6146
saveParms("parameters");
6147
zmessageACK(ZTX("Initial parameters file created. \n"
6148
"Inspect and revise if necessary."));
6154
// load parameters from a file, with file selection dialog
6161
if (! parmlistvalid) zappcrash("parmlistvalid = 0");
6163
pfile = zgetfile(ZTX("load parameters from a file"),zparmfile,"open","hidden");
6164
if (! pfile) return 0;
6166
np = loadParms(pfile);
6173
// load parameters from a file
6174
// returns no. parameters loaded
6176
int loadParms(const char *pfile)
6179
int Nth, np1, np2 = 0, err;
6180
char buff[100], *fgs, *pp;
6181
cchar *pname, *pvalue;
6184
if (! parmlistvalid) zappcrash("parmlistvalid = 0");
6186
if (! pfile) pfile = zparmfile;
6188
if (*pfile != '/') { // if parm file name only,
6189
pp = (char *) strrchr(zparmfile,'/'); // make complete absolute path
6190
if (pp) strcpy(pp+1,pfile); // in same directory as prior
6194
fid = fopen(pfile,"r");
6195
if (! fid) return 0; // bad file
6196
strncpy0(zparmfile,pfile,999); // set current parm file
6198
while (true) // read file
6200
fgs = fgets_trim(buff,99,fid,1);
6201
if (! fgs) break; // EOF
6203
pp = strchr(buff,'#'); // eliminate comments
6206
Nth = 1; // parse parm name, value
6207
pname = strField(buff,' ',Nth++);
6208
if (! pname) continue;
6209
pvalue = strField(buff,' ',Nth);
6210
if (! pvalue) continue;
6211
err = convSD(pvalue,dvalue);
6213
np1 = setParm(pname,dvalue); // set the parameter
6214
if (! np1) continue;
6218
fclose(fid); // close file
6219
return np2; // return parameter count
6223
// save parameters to a file, with file selection dialog
6230
if (! parmlistvalid) zappcrash("parmlistvalid = 0");
6232
pfile = zgetfile(ZTX("save parameters to a file"),zparmfile,"save","hidden");
6233
if (! pfile) return 0;
6235
np = saveParms(pfile);
6242
// save parameters to a file
6244
int saveParms(const char *pfile)
6250
if (! parmlistvalid) zappcrash("parmlistvalid = 0");
6252
if (*pfile != '/') { // if parm file name only,
6253
pp = (char *) strrchr(zparmfile,'/'); // make complete absolute path
6254
if (pp) strcpy(pp+1,pfile); // in same directory as prior
6258
fid = fopen(pfile,"w");
6260
zmessageACK(ZTX("cannot open file %s"),pfile);
6264
strncpy0(zparmfile,pfile,999);
6266
for (np = 0; np < parmlist.count; np++)
6267
fprintf(fid," \"%s\" %.12g \n",parmlist.name[np],parmlist.value[np]);
6274
// create a new paramater or change value of existing parameter
6276
int setParm(const char *parmname, double parmval)
6281
if (! parmlistvalid) zappcrash("parmlistvalid = 0");
6283
for (ii = 0; ii < parmlist.count; ii++)
6284
if (strEqu(parmlist.name[ii],parmname)) break;
6286
if (ii == parmlist.max) return 0;
6288
if (ii == parmlist.count) {
6290
cc = strlen(parmname);
6291
ppname = new char[cc+1];
6292
strTrim(ppname,parmname);
6293
parmlist.name[ii] = ppname;
6296
parmlist.value[ii] = parmval;
6297
return parmlist.count;
6301
// get parameter value from parameter name
6303
double getParm(const char *parmname)
6305
if (! parmlistvalid) zappcrash("parmlistvalid = 0");
6307
for (int ii = 0; ii < parmlist.count; ii++)
6309
if (strNeq(parmlist.name[ii],parmname)) continue;
6310
return parmlist.value[ii];
6317
// get Nth parameter name (zero-based)
6319
char * getParm(int Nth)
6321
if (! parmlistvalid) zappcrash("parmlistvalid = 0");
6322
if (Nth >= parmlist.count) return null;
6323
return parmlist.name[Nth];
6327
// list parameters in supplied text entry window
6329
int listParms(GtkWidget *textWin)
6335
for (ii = 0; ii < parmlist.count; ii++)
6337
pname = getParm(ii);
6338
pvalue = getParm(pname);
6339
wprintf(textWin," %s %.12g \n",pname,pvalue);
6342
return parmlist.count;
6346
// edit parameters with a GUI
6347
// textWin != null enables button to list parameters in window
6348
// addp != 0 enables button to add new parameters
6349
// return: 0 if cancel, else parameter count // v.2.7
6351
int editParms(GtkWidget *textWin, int addp)
6353
GtkWidget *peDialog, *peLabel[100], *peEdit[100], *peHbox[100];
6355
const char *pchval, *pname;
6357
int ii, err, iie = -1, zstat, floaded = 0;
6358
int bcancel=1, bapply=2, bload=3, bsave=4, blist=5, baddp=6;
6360
if (! parmlistvalid) zappcrash("parmlistvalid = 0");
6362
build_dialog: // build parameter edit dialog
6364
if (parmlist.count > 100) zappcrash("more than 100 parameters");
6366
if (textWin && addp)
6367
peDialog = gtk_dialog_new_with_buttons
6368
(ZTX("edit parameters"), null, GTK_DIALOG_MODAL,
6369
ZTX("load\nfile"),bload, ZTX("save\nfile"),bsave, ZTX("list\nall"),blist,
6370
ZTX("add\nnew"),baddp, ZTX("cancel"),bcancel, ZTX("apply"),bapply, null);
6373
peDialog = gtk_dialog_new_with_buttons
6374
(ZTX("edit parameters"), null, GTK_DIALOG_MODAL,
6375
ZTX("load\nfile"),bload, ZTX("save\nfile"),bsave, ZTX("list\nall"),blist,
6376
ZTX("cancel"),bcancel, ZTX("apply"),bapply, null);
6379
peDialog = gtk_dialog_new_with_buttons
6380
(ZTX("edit parameters"), null, GTK_DIALOG_MODAL,
6381
ZTX("load\nfile"),bload, ZTX("save\nfile"),bsave,
6382
ZTX("add\nnew"),baddp, ZTX("cancel"),bcancel, ZTX("apply"),bapply, null);
6385
gtk_dialog_new_with_buttons
6386
(ZTX("edit parameters"), null, GTK_DIALOG_MODAL,
6387
ZTX("load\nfile"),bload, ZTX("save\nfile"),bsave,
6388
ZTX("cancel"),bcancel, ZTX("apply"),bapply, null);
6390
gtk_window_set_position(GTK_WINDOW(peDialog),GTK_WIN_POS_MOUSE); // v.2.7.1
6392
for (ii = 0; ii < parmlist.count; ii++) // labels and edit boxes side by side
6393
{ // (parm names and parm values)
6394
peLabel[ii] = gtk_label_new(parmlist.name[ii]);
6395
gtk_misc_set_alignment(GTK_MISC(peLabel[ii]),1,0.5);
6396
gtk_label_set_width_chars(GTK_LABEL(peLabel[ii]),30);
6397
peEdit[ii] = gtk_entry_new();
6398
gtk_entry_set_width_chars(GTK_ENTRY(peEdit[ii]),12);
6399
sprintf(ptemp,"%.12g",parmlist.value[ii]);
6400
gtk_entry_set_text(GTK_ENTRY(peEdit[ii]),ptemp);
6401
peHbox[ii] = gtk_hbox_new(0,0);
6402
gtk_box_pack_start(GTK_BOX(peHbox[ii]),peLabel[ii],0,0,5);
6403
gtk_box_pack_start(GTK_BOX(peHbox[ii]),peEdit[ii],0,0,5);
6404
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(peDialog)->vbox),peHbox[ii],1,1,2);
6407
run_dialog: // display dialog and get inputs
6411
gtk_editable_select_region(GTK_EDITABLE(peEdit[iie]),0,-1); // focus on new or bad parameter
6412
gtk_widget_grab_focus(peEdit[iie]);
6416
gtk_widget_show_all(peDialog);
6417
zstat = gtk_dialog_run(GTK_DIALOG(peDialog));
6419
if (zstat <= bcancel) // kill, cancel
6422
zstat = zmessageYN(ZTX("apply?")); // if file loaded, clarify v.2.9
6424
gtk_widget_destroy(peDialog);
6431
if (zstat == bload) // load from file
6434
gtk_widget_destroy(peDialog);
6439
for (ii = 0; ii < parmlist.count; ii++) // capture inputs and check if OK
6441
pchval = gtk_entry_get_text(GTK_ENTRY(peEdit[ii]));
6442
err = convSD(pchval,pvalue);
6443
if (err && iie < 0) iie = ii; // remember 1st error
6446
if (iie >= 0) goto run_dialog; // re-get bad input
6448
if (zstat == bapply) // apply new values
6450
for (ii = 0; ii < parmlist.count; ii++) // capture inputs and save them v.2.7
6452
pchval = gtk_entry_get_text(GTK_ENTRY(peEdit[ii]));
6453
err = convSD(pchval,parmlist.value[ii]);
6455
gtk_widget_destroy(peDialog); // done
6456
return parmlist.count;
6459
if (zstat == bsave) // save to file
6461
for (ii = 0; ii < parmlist.count; ii++) // apply new values v.2.9
6463
pchval = gtk_entry_get_text(GTK_ENTRY(peEdit[ii]));
6464
err = convSD(pchval,parmlist.value[ii]);
6471
if (zstat == blist) // list parameters
6477
if (zstat == baddp) // add parameter
6479
pname = dialogText(ZTX("add parameter"),ZTX("(new parm name)"));
6480
if (! pname) goto run_dialog;
6483
iie = parmlist.count - 1; // focus on new parm
6484
gtk_widget_destroy(peDialog);
6488
gtk_widget_destroy(peDialog); // unknown status
6493
/**************************************************************************
6494
xstring class (dynamic length string)
6495
***************************************************************************/
6497
#define wmiv 1648734981
6499
int xstring::tcount = 0; // initz. static members
6500
int xstring::tmem = 0;
6503
xstring::xstring(int cc) // new xstring(cc)
6506
xmem = (cc & 0x7ffffff8) + 8; // mod 8 length
6507
xpp = new char[xmem]; // allocate
6508
if (! xpp) appcrash("xstring NEW failure",null);
6509
tcount++; // incr. object count
6510
tmem += xmem; // incr. allocated memory
6511
xcc = 0; // string cc = 0
6512
*xpp = 0; // string = null
6516
xstring::xstring(const char *string) // new xstring("initial string")
6520
if (string) xcc = strlen(string); // string length
6521
xmem = (xcc & 0x7ffffff8) + 8; // mod 8 length
6522
xpp = new char[xmem]; // allocate
6523
if (! xpp) appcrash("xstring NEW failure",null);
6524
tcount++; // incr. object count
6525
tmem += xmem; // incr. allocated memory
6527
if (xcc) strcpy(xpp,string); // copy string
6531
xstring::xstring(const xstring & xstr) // new xstring2(xstring1)
6534
xmem = xstr.xmem; // allocate same length
6536
xpp = new char[xmem];
6537
if (! xpp) appcrash("xstring NEW failure",null);
6538
tcount++; // incr. object count
6539
tmem += xmem; // incr. allocated memory
6540
strcpy(xpp,xstr.xpp); // copy string
6544
xstring::~xstring() // delete xstring
6547
delete[] xpp; // release allocated memory
6549
tcount--; // decr. object count
6550
tmem -= xmem; // decr. allocated memory
6551
if (tcount < 0) appcrash("xstring count < 0",null);
6552
if (tmem < 0) appcrash("xstring memory < 0",null);
6553
if (tcount == 0 && tmem > 0) appcrash("xstring memory leak",null);
6557
xstring xstring::operator= (const xstring & xstr) // xstring2 = xstring1
6561
if (this == &xstr) return *this;
6565
delete[] xpp; // expand memory if needed
6567
xmem = (xcc & 0x7ffffff8) + 8; // mod 8 length
6568
xpp = new char[xmem];
6569
if (! xpp) appcrash("xstring NEW failure",null);
6572
strcpy(xpp,xstr.xpp); // copy string
6577
xstring xstring::operator= (const char *str) // xstring = "some string"
6582
if (str) xcc = strlen(str);
6585
delete[] xpp; // expand memory if needed
6587
xmem = (xcc & 0x7ffffff8) + 8; // mod 8 length
6588
xpp = new char[xmem];
6589
if (! xpp) appcrash("xstring NEW failure",null);
6592
if (xcc) strcpy(xpp,str); // copy string
6597
xstring operator+ (const xstring & x1, const xstring & x2) // xstring1 + xstring2
6601
xstring temp(x1.xcc + x2.xcc); // build temp xstring
6602
strcpy(temp.xpp,x1.xpp); // with both input strings
6603
strcpy(temp.xpp + x1.xcc, x2.xpp);
6604
temp.xcc = x1.xcc + x2.xcc;
6610
xstring operator+ (const xstring & x1, const char *s2) // xstring + "some string"
6614
if (s2) cc2 = strlen(s2);
6615
xstring temp(x1.xcc + cc2); // build temp xstring
6616
strcpy(temp.xpp,x1.xpp); // with both input strings
6617
if (s2) strcpy(temp.xpp + x1.xcc, s2);
6618
temp.xcc = x1.xcc + cc2;
6624
xstring operator+ (const char *s1, const xstring & x2) // "some string" + xstring
6628
if (s1) cc1 = strlen(s1);
6629
xstring temp(cc1 + x2.xcc); // build temp xstring
6630
if (s1) strcpy(temp.xpp,s1); // with both input strings
6631
strcpy(temp.xpp + cc1, x2.xpp);
6632
temp.xcc = cc1 + x2.xcc;
6638
void xstring::insert(int pos, const char *string, int cc) // insert cc chars from string at pos
6639
{ // pad if pos > xcc or cc > string
6642
int scc = strlen(string);
6645
int pad = pos - xcc;
6646
if (pad < 0) pad = 0;
6648
if (xmem < xcc + cc + pad + 1) // allocate more memory if needed
6650
int newmem = xcc + cc + pad;
6651
newmem = (newmem & 0x7ffffff8) + 8; // mod 8 length
6652
char * xpp2 = new char[newmem];
6653
if (! xpp2) appcrash("xstring NEW failure",null);
6654
strcpy(xpp2,xpp); // copy to new space
6657
tmem += newmem - xmem;
6661
if (pad) memset(xpp+xcc,' ',pad); // add blanks up to pos
6663
for (int ii = xcc + pad; ii >= pos; ii--) // make hole for inserted string
6664
*(xpp+ii+cc) = *(xpp+ii);
6666
if (cc > scc) memset(xpp+pos+scc,' ',cc-scc); // blank pad if cc > string
6667
if (cc < scc) scc = cc;
6668
strncpy(xpp+pos,string,scc); // insert string, without null
6670
xcc += cc + pad; // set new length
6676
void xstring::overlay(int pos, const char *string, int cc) // overlay substring
6680
int scc = strlen(string);
6683
if (xmem < pos + cc + 1) // allocate more memory if needed
6685
int newmem = pos + cc;
6686
newmem = (newmem & 0x7ffffff8) + 8; // mod 8 length
6687
char * xpp2 = new char[newmem];
6688
if (! xpp2) appcrash("xstring NEW failure",null);
6689
strcpy(xpp2,xpp); // copy to new space
6692
tmem += newmem - xmem;
6696
if (pos > xcc) memset(xpp+xcc,' ',pos-xcc); // add blanks up to pos
6698
if (cc > scc) memset(xpp+pos+scc,' ',cc-scc); // blank pad if cc > string
6699
if (cc < scc) scc = cc;
6700
strncpy(xpp+pos,string,scc); // insert string, without null
6702
if (pos + cc > xcc) xcc = pos + cc; // set new length
6708
void xstring::getStats(int & tcount2, int & tmem2) // get statistics
6715
void xstring::validate() const // validate integrity
6717
if (wmi != wmiv) appcrash("xstring bad wmi",null);
6718
if (xmem < xcc+1) appcrash("xstring xmem < xcc+1",null);
6719
if (xcc != (int) strlen(xpp)) appcrash("xstring xcc != strlen(xpp)",null);
6723
/**************************************************************************
6724
Vxstring class (array or vector of xstring)
6725
***************************************************************************/
6727
Vxstring::Vxstring(int ii) // constructor
6731
if (nd) pdata = new xstring[nd];
6732
if (nd && !pdata) appcrash("Vxstring NEW fail",null);
6736
Vxstring::~Vxstring() // destructor
6738
if (nd) delete[] pdata;
6744
Vxstring::Vxstring(const Vxstring & pold) // copy constructor
6747
nd = pold.nd; // set size
6748
if (nd) pdata = new xstring[nd]; // allocate memory
6749
if (nd && !pdata) appcrash("Vxstring NEW fail");
6750
for (int ii = 0; ii < nd; ii++) pdata[ii] = pold[ii]; // copy defined elements
6754
Vxstring Vxstring::operator= (const Vxstring & vdstr) // operator =
6756
if (nd) delete[] pdata; // delete old memory
6759
if (nd) pdata = new xstring[nd]; // allocate new memory
6760
if (nd && !pdata) appcrash("Vxstring NEW fail",null);
6761
for (int ii = 0; ii < nd; ii++) pdata[ii] = vdstr.pdata[ii]; // copy elements
6766
xstring & Vxstring::operator[] (int ii) // operator []
6768
static xstring xnull(0);
6769
if (ii < nd) return pdata[ii]; // return reference
6770
appcrash("Vxstring index invalid %d %d",nd,ii,null);
6775
const xstring & Vxstring::operator[] (int ii) const // operator []
6777
static xstring xnull(0);
6778
if (ii < nd) return pdata[ii]; // return reference
6779
appcrash("Vxstring index invalid %d %d",nd,ii,null);
6784
int Vxstring::search(const char *string) // find element in unsorted Vxstring
6786
for (int ii = 0; ii < nd; ii++)
6787
if (strEqu(pdata[ii],string)) return ii;
6792
int Vxstring::bsearch(const char *string) // find element in sorted Vxstring
6793
{ // (binary search)
6794
int nn, ii, jj, kk, rkk;
6797
if (! nn) return 0; // empty list
6799
ii = nn / 2; // next element to search
6800
jj = (ii + 1) / 2; // next increment
6801
nn--; // last element
6806
kk = strcmp(pdata[ii],string); // check element
6810
ii -= jj; // too high, go down
6811
if (ii < 0) return -1;
6816
ii += jj; // too low, go up
6817
if (ii > nn) return -1;
6820
else if (kk == 0) return ii; // matched
6822
jj = jj / 2; // reduce increment
6826
jj = 1; // step by 1 element
6827
if (! rkk) rkk = kk; // save direction
6830
if (rkk > 0) { if (kk < 0) return -1; } // if change direction, fail
6831
else if (kk > 0) return -1;
6838
static int VDsortKeys[10][3], VDsortNK;
6840
int Vxstring::sort(int NK, int keys[][3]) // sort elements by subfields
6841
{ // key[ii][0] = position
6842
int NR, RL, ii; // [1] = length
6843
HeapSortUcomp VDsortComp; // [2] = 1/2 = ascending/desc.
6844
// = 3/4 = + ignore case
6846
if (NR < 2) return 1;
6848
RL = sizeof(xstring);
6850
if (NK < 1) appcrash("Vxstring::sort, bad NK",null);
6851
if (NK > 10) appcrash("Vxstring::sort, bad NK",null);
6854
for (ii = 0; ii < NK; ii++)
6856
VDsortKeys[ii][0] = keys[ii][0];
6857
VDsortKeys[ii][1] = keys[ii][1];
6858
VDsortKeys[ii][2] = keys[ii][2];
6861
HeapSort((char *) pdata,RL,NR,VDsortComp);
6867
int VDsortComp(const char *r1, const char *r2)
6870
const char *p1, *p2;
6871
int ii, stat, kpos, ktype, kleng;
6873
d1 = (xstring *) r1;
6874
d2 = (xstring *) r2;
6878
for (ii = 0; ii < VDsortNK; ii++) // compare each key
6880
kpos = VDsortKeys[ii][0];
6881
kleng = VDsortKeys[ii][1];
6882
ktype = VDsortKeys[ii][2];
6886
stat = strncmp(p1+kpos,p2+kpos,kleng);
6887
if (stat) return stat;
6891
else if (ktype == 2)
6893
stat = strncmp(p1+kpos,p2+kpos,kleng);
6894
if (stat) return -stat;
6898
else if (ktype == 3)
6900
stat = strncasecmp(p1+kpos,p2+kpos,kleng);
6901
if (stat) return stat;
6905
else if (ktype == 4)
6907
stat = strncasecmp(p1+kpos,p2+kpos,kleng);
6908
if (stat) return -stat;
6912
appcrash("Vxstring::sort, bad KEYS sort type",null);
6919
int Vxstring::sort(int pos, int cc) // sort elements ascending
6923
if (! cc) cc = 999999;
6934
/**************************************************************************
6936
***************************************************************************/
6938
// static members (robust for tables up to 60% full)
6940
int HashTab::trys1 = 100; // Add() tries
6941
int HashTab::trys2 = 200; // Find() tries
6944
HashTab::HashTab(int _cc, int _cap) // constructor
6946
cc = 4 * (_cc + 4) / 4; // + 1 + mod 4 length
6949
table = new char [len];
6950
if (! table) appcrash("HashTab() new %d fail",len,null);
6951
memset(table,0,len);
6955
HashTab::~HashTab() // destructor
6962
// Add a new string to table
6964
int HashTab::Add(const char *string)
6966
int pos, fpos, trys;
6968
pos = strHash(string,cap); // get random position
6971
for (trys = 0, fpos = -1; // find next free slot
6972
trys < trys1; // at/after position
6975
if (pos >= cap * cc) pos = 0; // last position wraps to 1st
6977
if (! table[pos]) // empty slot: string not found
6979
if (fpos != -1) pos = fpos; // use prior deleted slot if there
6980
strncpy(table+pos,string,cc); // insert new string
6981
table[pos+cc-1] = 0; // insure null terminator
6982
return (pos/cc); // return rel. table entry
6985
if (table[pos] == -1) // deleted slot
6987
if (fpos == -1) fpos = pos; // remember 1st one found
6991
if (strEqu(string,table+pos)) return -2; // string already present
6994
return -3; // table full (trys1 exceeded)
6998
// Delete a string from table
7000
int HashTab::Del(const char *string)
7004
pos = strHash(string,cap); // get random position
7007
for (trys = 0; // search for string
7008
trys < trys2; // at/after position
7011
if (pos >= cap * cc) pos = 0; // last position wraps to 1st
7013
if (! table[pos]) return -1; // empty slot, string not found
7015
if (strEqu(string,table+pos)) // string found
7017
table[pos] = -1; // delete table entry
7018
return (pos/cc); // return rel. table entry
7022
appcrash("HashTab::Del() bug",null); // exceed trys2, must not happen
7023
return 0; // (table too full to function)
7027
// Find a table entry.
7029
int HashTab::Find(const char *string)
7033
pos = strHash(string,cap); // get random position
7036
for (trys = 0; // search for string
7037
trys < trys2; // at/after position
7040
if (pos >= cap * cc) pos = 0; // last position wraps to 1st
7041
if (! table[pos]) return -1; // empty slot, string not found
7042
if (strEqu(string,table+pos)) return (pos/cc); // string found, return rel. entry
7045
appcrash("HashTab::Find() bug",null); // cannot happen
7050
// return first or next table entry
7052
int HashTab::GetNext(int & ftf, char *string)
7056
if (ftf) // initial call
7062
while (pos < (cap * cc))
7064
if ((table[pos] == 0) || (table[pos] == -1))
7070
strcpy(string,table+pos); // return string
7083
for (ii = 0; ii < cap; ii++)
7086
if (table[pos] > 0) printf("%d %s \n", pos, table + pos);
7087
if (table[pos] == -1) printf("%d deleted \n", pos);
7093
/**************************************************************************
7094
class for queue of dynamic strings
7095
***************************************************************************/
7097
Queue::Queue(int cap) // constructor
7101
err = mutex_init(&qmutex, 0); // create mutex = queue lock
7102
if (err) appcrash("Queue(), mutex init fail",null);
7104
qcap = cap; // queue capacity
7105
ent1 = entN = qcount = 0; // state = empty
7106
vd = new Vxstring(qcap); // create vector of xstring's
7107
if (! vd) appcrash("Queue(), NEW fail %d",cap,null);
7108
strcpy(wmi,"queue");
7113
Queue::~Queue() // destructor
7115
if (strNeq(wmi,"queue")) appcrash("~Queue wmi fail",null);
7117
mutex_destroy(&qmutex); // destroy mutex
7118
qcount = qcap = ent1 = entN = -1;
7125
void Queue::lock() // lock queue (private)
7128
err = mutex_lock(&qmutex); // reserve mutex or suspend
7129
if (err) appcrash("Queue mutex lock fail",null);
7134
void Queue::unlock() // unlock queue (private)
7137
err = mutex_unlock(&qmutex); // release mutex
7138
if (err) appcrash("Queue mutex unlock fail",null);
7143
int Queue::getCount() // get current entry count
7145
if (strNeq(wmi,"queue")) appcrash("Queue getCount wmi fail",null);
7150
int Queue::push(const xstring *newEnt, double wait) // add entry to queue, with max. wait
7155
if (strNeq(wmi,"queue")) appcrash("Queue::push wmi fail",null);
7157
lock(); // lock queue
7158
while (qcount == qcap) { // queue full
7159
unlock(); // unlock queue
7160
if (elaps >= wait) return -1; // too long, return -1 status
7161
usleep(1000); // sleep in 1 millisec. steps
7162
elaps += 0.001; // until queue not full
7163
lock(); // lock queue
7166
(* vd)[entN] = *newEnt; // copy new entry into queue
7167
entN++; // incr. end pointer
7168
if (entN == qcap) entN = 0;
7169
qcount++; // incr. queue count
7171
unlock(); // unlock queue
7172
return count; // return curr. queue count
7176
xstring *Queue::pop1() // get 1st (oldest) entry and remove
7180
if (strNeq(wmi,"queue")) appcrash("Queue::pop1 wmi fail",null);
7182
lock(); // lock queue
7184
if (qcount == 0) entry = 0; // queue empty
7186
entry = &(* vd)[ent1]; // get first entry
7187
ent1++; // index pointer to next
7188
if (ent1 == qcap) ent1 = 0;
7189
qcount--; // decr. queue count
7192
unlock(); // unlock queue
7197
xstring *Queue::popN() // get last (newest) entry and remove
7201
if (strNeq(wmi,"queue")) appcrash("Queue::popN wmi fail",null);
7203
lock(); // lock queue
7205
if (qcount == 0) entry = 0; // queue empty
7207
if (entN == 0) entN = qcap; // index pointer to prior
7209
qcount--; // decr. queue count
7210
entry = &(* vd)[entN]; // get last entry
7213
unlock(); // unlock queue
7218
/**************************************************************************
7220
Tree class, tree-structured data storage without limits
7222
Store any amount of data at any depth within a tree-structure with named nodes.
7223
Data can be found using an ordered list of node names or node numbers.
7225
Node numbers are in the sequence added using put() with names,
7226
or the same as those numbers used in put() with numbers.
7228
Internal code conventions:
7229
- caller level is node 0, next level is node 1, etc.
7230
- node names and numbers in calls to get() and put() refer to next levels
7231
- number of levels = 1+nn, where nn is max. in calls to put(...nodes[], nn)
7233
***************************************************************************/
7235
#define wmid 1374602859
7240
Tree::Tree(const char *name)
7251
int cc = strlen(name);
7252
tname = new char[cc+1];
7253
if (! tname) appcrash("Tree, no memory",null);
7263
if (wmi != wmid) appcrash("not a Tree",null);
7264
if (tname) delete [] tname;
7266
if (tmem) free(tdata);
7269
for (int ii = 0; ii < nsub; ii++) delete psub[ii];
7270
if (psub) free(psub);
7276
// put data by node names[]
7278
int Tree::put(void *data, int dd, char *nodes[], int nn)
7282
if (wmi != wmid) appcrash("not a Tree",null);
7283
tnode = make(nodes,nn);
7284
if (tnode->tdata) free(tnode->tdata);
7285
tnode->tdata = new char[dd];
7286
if (! tnode->tdata) appcrash("Tree, no memory",null);
7288
memmove(tnode->tdata,data,dd);
7293
// put data by node numbers[]
7295
int Tree::put(void *data, int dd, int nodes[], int nn)
7299
if (wmi != wmid) appcrash("not a Tree",null);
7300
tnode = make(nodes,nn);
7301
if (tnode->tdata) free(tnode->tdata);
7302
tnode->tdata = new char[dd];
7303
if (! tnode->tdata) appcrash("Tree, no memory",null);
7305
memmove(tnode->tdata,data,dd);
7310
// get data by node names[]
7312
int Tree::get(void *data, int dd, char *nodes[], int nn)
7314
Tree *tnode = find(nodes,nn);
7315
if (! tnode) return 0;
7316
if (! tnode->tmem) return 0;
7317
if (dd > tnode->tmem) dd = tnode->tmem;
7318
memmove(data,tnode->tdata,dd);
7323
// get data by node numbers[]
7325
int Tree::get(void *data, int dd, int nodes[], int nn)
7327
Tree *tnode = find(nodes,nn);
7328
if (! tnode) return 0;
7329
if (! tnode->tmem) return 0;
7330
if (dd > tnode->tmem) dd = tnode->tmem;
7331
memmove(data,tnode->tdata,dd);
7336
// find a given node by names[]
7338
Tree * Tree::find(char *nodes[], int nn)
7342
for (ii = 0; ii < nsub; ii++)
7343
if (psub[ii]->tname && strEqu(nodes[0],psub[ii]->tname)) break;
7344
if (ii == nsub) return 0;
7345
if (nn == 1) return psub[ii];
7346
return psub[ii]->find(&nodes[1],nn-1);
7350
// find a given node by numbers[]
7352
Tree * Tree::find(int nodes[], int nn)
7355
if (ii >= nsub) return 0;
7356
if (! psub[ii]) return 0;
7357
if (nn == 1) return psub[ii];
7358
return psub[ii]->find(&nodes[1],nn-1);
7362
// find or create a given node by names[]
7364
Tree * Tree::make(char *nodes[], int nn)
7369
for (ii = 0; ii < nsub; ii++)
7370
if (psub[ii]->tname && strEqu(nodes[0],psub[ii]->tname)) break;
7374
psub2 = new Tree * [nsub+1];
7375
if (! psub2) appcrash("Tree, no memory",null);
7376
for (ii = 0; ii < nsub; ii++) psub2[ii] = psub[ii];
7380
psub[ii] = new Tree(nodes[0]);
7381
if (! psub[ii]) appcrash("Tree, no memory",null);
7384
if (nn == 1) return psub[ii];
7385
return psub[ii]->make(&nodes[1],nn-1);
7389
// find or create a given node by numbers[]
7391
Tree * Tree::make(int nodes[], int nn)
7397
if ((ii < nsub) && psub[ii])
7399
if (nn == 1) return psub[ii];
7400
return psub[ii]->make(&nodes[1],nn-1);
7405
psub2 = new Tree * [ii+1];
7406
if (psub2 == null) appcrash("Tree, no memory",null);
7407
for (jj = 0; jj < nsub; jj++) psub2[jj] = psub[jj];
7408
for (jj = nsub; jj < ii; jj++) psub2[jj] = 0;
7414
psub[ii] = new Tree("noname");
7415
if (! psub[ii]) appcrash("Tree, no memory",null);
7417
if (nn == 1) return psub[ii];
7418
return psub[ii]->make(&nodes[1],nn-1);
7422
// dump tree data to stdout (call with level 0)
7424
void Tree::dump(int level)
7428
if (! tname) name = "noname";
7430
printf("%*s level: %d name: %s subs: %d mem: %d \n",
7431
level*2,"",level,name,nsub,tmem);
7432
for (int ii = 0; ii < nsub; ii++)
7433
if (psub[ii]) psub[ii]->dump(level+1);
7437
// get node counts and total data per level
7438
// level 0 + nn more levels, as given in calls to put(...nodes[],nn)
7439
// caller must initialize counters to zero
7441
void Tree::stats(int nn[], int nd[])
7445
for (int ii = 0; ii < nsub; ii++)
7446
if (psub[ii]) psub[ii]->stats(&nn[1],&nd[1]);