2 * Grace - GRaphing, Advanced Computation and Exploration of data
4 * Home page: http://plasma-gate.weizmann.ac.il/Grace/
6 * Copyright (c) 1991-1995 Paul J Turner, Portland, OR
7 * Copyright (c) 1996-2006 Grace Development Team
9 * Maintained by Evgeny Stambulchik
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
41 #include <sys/types.h>
45 #ifdef HAVE_SYS_SELECT_H
46 # include <sys/select.h>
56 #include "core_utils.h"
63 * number of rows to allocate for each call to realloc
68 * number of bytes in each line chunk
69 * (should be related to system pipe size, typically 4K)
72 # define PIPE_BUF 4096
74 #define CHUNKSIZE 2*PIPE_BUF
76 char *close_input
; /* name of real-time input to close */
78 struct timeval read_begin
= {0l, 0l}; /* used to check too long inputs */
80 static Input_buffer dummy_ib
= {-1, 0, 0, 0, 0, 0, NULL
, 0, 0, NULL
, 0l};
82 int nb_rt
= 0; /* number of real time file descriptors */
83 Input_buffer
*ib_tbl
= 0; /* table for each open input */
84 int ib_tblsize
= 0; /* number of elements in ib_tbl */
86 static int time_spent(void);
87 static int expand_ib_tbl(int delay
);
88 static int expand_line_buffer(char **adrBuf
, int *ptrSize
, char **adrPtr
);
89 static int reopen_real_time_input(GraceApp
*gr
, Input_buffer
*ib
);
90 static int read_real_time_lines(Input_buffer
*ib
);
91 static int process_complete_lines(GraceApp
*gapp
, Input_buffer
*ib
);
93 static int read_long_line(FILE *fp
, char **linebuf
, int *buflen
);
96 * part of the time sliced already spent in milliseconds
98 static int time_spent(void)
102 gettimeofday(&now
, NULL
);
104 return 1000 * (now
.tv_sec
- read_begin
.tv_sec
)
105 + (now
.tv_usec
- read_begin
.tv_usec
) / 1000;
111 * expand the table of monitored real time inputs
113 static int expand_ib_tbl(int delay
)
116 Input_buffer
*new_tbl
;
118 new_size
= (ib_tblsize
> 0) ? 2*ib_tblsize
: 5;
119 new_tbl
= xcalloc(new_size
, sizeof(Input_buffer
));
120 if (new_tbl
== NULL
) {
121 return RETURN_FAILURE
;
124 for (i
= 0; i
< new_size
; i
++) {
125 new_tbl
[i
] = (i
< ib_tblsize
) ? ib_tbl
[i
] : dummy_ib
;
126 new_tbl
[i
].delay
= delay
;
129 if (ib_tblsize
> 0) {
130 xfree((void *) ib_tbl
);
133 ib_tblsize
= new_size
;
135 return RETURN_SUCCESS
;
140 * expand a line buffer
142 static int expand_line_buffer(char **adrBuf
, int *ptrSize
, char **adrPtr
)
147 newsize
= *ptrSize
+ CHUNKSIZE
;
148 newbuf
= xmalloc(newsize
);
150 return RETURN_FAILURE
;
154 /* this is the first time through */
159 /* we are expanding an existing line */
160 strncpy(newbuf
, *adrBuf
, *ptrSize
);
162 *adrPtr
+= newbuf
- *adrBuf
;
170 return RETURN_SUCCESS
;
175 * reopen an Input_buffer (surely a fifo)
177 static int reopen_real_time_input(GraceApp
*gapp
, Input_buffer
*ib
)
182 /* in order to avoid race conditions (broken pipe on the write
183 side), we open a new file descriptor before closing the
185 fd
= open(ib
->name
, O_RDONLY
| O_NONBLOCK
);
187 sprintf(buf
, "Can't reopen real time input %s", ib
->name
);
189 unregister_real_time_input(ib
->name
);
190 return RETURN_FAILURE
;
197 /* swapping the file descriptors */
205 return RETURN_SUCCESS
;
211 * unregister a file descriptor no longer monitored
212 * (since Input_buffer structures dedicated to static inputs
213 * are not kept in the table, it is not an error to unregister
214 * an input not already registered)
216 void unregister_real_time_input(const char *name
)
224 for (ib
= ib_tbl
; ib
< ib_tbl
+ ib_tblsize
; ib
++) {
225 l2
= (ib
->name
== NULL
) ? -1 : strlen(ib
->name
);
226 if (l1
== l2
&& strcmp (name
, ib
->name
) == 0) {
227 /* name is usually the same pointer as ib->name so we cannot */
228 /* free the string and output the message using name afterwards */
238 /* this descriptor (if not dummy!) is still in use */
245 * register a file descriptor for monitoring
247 int register_real_time_input(GraceApp
*gapp
, int fd
, const char *name
, int reopen
)
252 /* some safety checks */
254 sprintf(buf
, "%s : internal error, wrong file descriptor", name
);
256 return RETURN_FAILURE
;
260 if (fcntl(fd
, F_GETFL
) & O_WRONLY
) {
262 "Descriptor %d not open for reading\n",
264 return RETURN_FAILURE
;
268 /* remove previous entry for the same set if any */
269 unregister_real_time_input(name
);
271 /* find an empty slot in the table */
272 for (ib
= ib_tbl
; ib
< ib_tbl
+ ib_tblsize
; ib
++) {
274 sprintf(buf
, "%s : internal error, file descriptor already in use",
277 return RETURN_FAILURE
;
278 } else if (ib
->fd
< 0) {
283 if (ib
== ib_tbl
+ ib_tblsize
) {
284 /* the table was full, we expand it */
285 int old_size
= ib_tblsize
;
286 if (expand_ib_tbl(gapp
->rt
->timer_delay
) != RETURN_SUCCESS
) {
287 return RETURN_FAILURE
;
289 ib
= ib_tbl
+ old_size
;
292 /* we keep the current buffer (even if 0),
293 and only say everything is available */
299 ib
->name
= copy_string(ib
->name
, name
);
307 return RETURN_SUCCESS
;
311 * read a real-time line (but do not process it)
313 static int read_real_time_lines(Input_buffer
*ib
)
316 int available
, nbread
;
319 cursor
= ib
->buf
+ ib
->used
;
320 available
= ib
->size
- ib
->used
;
322 /* have we enough space to store the characters ? */
324 if (expand_line_buffer(&(ib
->buf
), &(ib
->size
), &cursor
)
326 return RETURN_FAILURE
;
328 available
= ib
->buf
+ ib
->size
- cursor
;
331 /* read as much as possible */
332 nbread
= read(ib
->fd
, (void *) cursor
, available
- 1);
335 sprintf(buf
, "%s : read error on real time input",
338 return RETURN_FAILURE
;
345 ib
->buf
[ib
->used
] = '\0';
349 return RETURN_SUCCESS
;
354 * process complete lines that have already been read
356 static int process_complete_lines(GraceApp
*gapp
, Input_buffer
*ib
)
359 char *begin_of_line
, *end_of_line
;
363 return RETURN_SUCCESS
;
368 /* loop over the embedded lines */
369 begin_of_line
= (end_of_line
== NULL
) ? ib
->buf
: (end_of_line
+ 1);
370 end_of_line
= begin_of_line
;
372 while (end_of_line
!= NULL
&& *end_of_line
!= '\n') {
373 /* trying to find a complete line */
374 if (end_of_line
== ib
->buf
+ ib
->used
) {
377 if (*end_of_line
== '\0') {
384 if (end_of_line
!= NULL
) {
385 /* we have a whole line */
391 if (line_corrupted
||
392 graal_parse_line(grace_get_graal(gapp
->grace
),
393 begin_of_line
, gproject_get_top(gapp
->gp
)) != RETURN_SUCCESS
) {
394 sprintf(buf
, "Error at line %d", ib
->lineno
);
397 if (ib
->errors
> MAXERR
) {
400 /* this prevents from being called recursively by
401 the inner X loop of yesno */
404 if (yesno("Lots of errors, abort?", NULL
, NULL
, NULL
)) {
405 close_input
= copy_string(close_input
, "");
415 if (close_input
!= NULL
) {
416 /* something should be closed */
417 if (close_input
[0] == '\0') {
418 unregister_real_time_input(ib
->name
);
420 unregister_real_time_input(close_input
);
427 /* we have closed ourselves */
428 return RETURN_SUCCESS
;
435 } while (end_of_line
!= NULL
);
437 if (end_of_line
!= NULL
) {
438 /* the line has just been processed */
439 begin_of_line
= end_of_line
+ 1;
442 if (begin_of_line
> ib
->buf
) {
443 /* move the remaining data to the beginning */
444 ib
->used
-= begin_of_line
- ib
->buf
;
445 memmove(ib
->buf
, begin_of_line
, ib
->used
);
446 ib
->buf
[ib
->used
] = '\0';
450 return RETURN_SUCCESS
;
454 int real_time_under_monitoring(void)
460 * monitor the set of registered file descriptors for pending input
462 int monitor_input(GraceApp
*gapp
, Input_buffer
*tbl
, int tblsize
, int no_wait
)
468 struct timeval timeout
;
469 int highest
, first_time
, retsel
;
471 /* we don't want to get stuck here, we memorize the start date
472 and will check we do not exceed our allowed time slice */
473 gettimeofday(&read_begin
, NULL
);
476 while (((time_spent() < tbl
->delay
) || first_time
) && retsel
> 0) {
478 /* register all the monitored descriptors */
481 for (ib
= tbl
; ib
< tbl
+ tblsize
; ib
++) {
483 FD_SET(ib
->fd
, &rfds
);
484 if (ib
->fd
> highest
) {
491 /* there's nothing to do */
492 return RETURN_SUCCESS
;
496 /* just check for available data without waiting */
499 /* wait until data or end of time slice arrive */
500 remaining
= tbl
->delay
- time_spent();
505 timeout
.tv_sec
= remaining
/ 1000;
506 timeout
.tv_usec
= 1000l * (remaining
% 1000);
507 retsel
= select(highest
+ 1, &rfds
, NULL
, NULL
, &timeout
);
510 ((time_spent() < tbl
->delay
) || first_time
) && ib
< tbl
+ tblsize
;
512 if (ib
->fd
>= 0 && FD_ISSET(ib
->fd
, &rfds
)) {
513 /* there is pending input */
514 if (read_real_time_lines(ib
) != RETURN_SUCCESS
515 || process_complete_lines(gapp
, ib
) != RETURN_SUCCESS
) {
516 return RETURN_FAILURE
;
519 if (ib
->zeros
>= 5) {
520 /* we were told five times something happened, but
521 never got any byte : we assume the pipe (or
522 whatever) has been closed by the peer */
524 /* we should reset the input buffer, in case
525 the peer also reopens it */
526 if (reopen_real_time_input(gapp
, ib
) != RETURN_SUCCESS
) {
527 return RETURN_FAILURE
;
530 unregister_real_time_input(ib
->name
);
533 /* we have changed the table, we should end the loop */
539 /* after one pass, we obey timeout */
543 return RETURN_SUCCESS
;
545 return RETURN_FAILURE
;
550 * read a line increasing buffer as necessary
552 static int read_long_line(FILE * fp
, char **linebuf
, int *buflen
)
560 retval
= RETURN_FAILURE
;
562 /* do we have enough space to store the characters ? */
564 if (expand_line_buffer(linebuf
, buflen
, &cursor
)
566 return RETURN_FAILURE
;
569 available
= (int)(*linebuf
-cursor
) + *buflen
;
571 /* read as much as possible */
572 if (grace_fgets(cursor
, available
, fp
) == NULL
) {
575 nbread
= strlen(cursor
);
579 retval
= RETURN_SUCCESS
;
582 /* prepare next read */
586 } while (*(cursor
- 1) != '\n');
592 /* open a file for write */
593 FILE *gapp_openw(GraceApp
*gapp
, const char *fn
)
596 char buf
[GR_MAXPATHLEN
+ 50];
600 errmsg("No file name given");
602 } else if (strcmp(fn
, "-") == 0 || strcmp(fn
, "stdout") == 0) {
605 if (stat(fn
, &statb
) == 0) {
606 /* check to make sure this is a file and not a dir */
607 if (S_ISREG(statb
.st_mode
)) {
608 sprintf(buf
, "Overwrite %s?", fn
);
609 if (!yesno(buf
, NULL
, NULL
, NULL
)) {
613 sprintf(buf
, "%s is not a regular file!", fn
);
618 retval
= filter_write(gapp
, fn
);
620 sprintf(buf
, "Can't write to file %s, check permissions!", fn
);
627 char *gapp_exe_path(GraceApp
*gapp
, const char *fn
)
629 static char buf
[GR_MAXPATHLEN
], *epath
;
635 cp
= strchr(fn
, ' ');
637 epath
= grace_path(gapp
->grace
, fn
);
638 strcpy(buf
, exe_path_translate(epath
));
644 epath
= grace_path(gapp
->grace
, buf
);
649 return exe_path_translate(buf
);
654 /* open a file for read */
655 FILE *gapp_openr(GraceApp
*gapp
, const char *fn
, int src
)
659 char buf
[GR_MAXPATHLEN
+ 50];
663 errmsg("No file name given");
668 tfn
= grace_path(gapp
->grace
, fn
);
669 if (strcmp(tfn
, "-") == 0 || strcmp(tfn
, "stdin") == 0) {
672 } else if (stat(tfn
, &statb
)) {
673 sprintf(buf
, "Can't stat file %s", tfn
);
677 /* check to make sure this is a file and not a dir */
678 } else if (!S_ISREG(statb
.st_mode
)) {
679 sprintf(buf
, "%s is not a regular file", tfn
);
684 fp
= filter_read(gapp
, tfn
);
690 tfn
= gapp_exe_path(gapp
, fn
);
691 fp
= popen(tfn
, "r");
696 errmsg("Wrong call to gapp_openr()");
702 * close either a pipe or a file pointer
705 void gapp_close(FILE *fp
)
707 if (fp
== stdin
|| fp
== stderr
|| fp
== stdout
) {
710 if (pclose(fp
) == -1) {
715 FILE *gapp_tmpfile(char *templateval
)
718 #if defined(HAVE_MKSTEMP) && defined(HAVE_FDOPEN)
721 fd
= mkstemp(templateval
);
725 fp
= fdopen(fd
, "wb");
729 fp
= fopen(templateval
, "wb");
733 errmsg("Can't open temporary file");
740 int uniread(Quark
*pr
, FILE *fp
,
741 DataParser parse_cb
, DataStore store_cb
, void *udata
)
743 int nrows
, nrows_allocated
;
746 char *linebuf
= NULL
;
759 int ncols
, nncols
, nscols
;
762 if (read_long_line(fp
, &linebuf
, &linebuflen
) == RETURN_SUCCESS
) {
766 /* skip leading whitespaces */
767 while (*s
== ' ' || *s
== '\t') {
777 if (*s
== '\n' || *s
== '\0') {
780 if (parse_cb
&& parse_cb(s
, udata
) == RETURN_SUCCESS
) {
792 /* parse the data line */
793 if (parse_ss_row(pr
, s
, &nncols
, &nscols
, &formats
) != RETURN_SUCCESS
) {
794 errmsg("Can't parse data");
796 return RETURN_FAILURE
;
798 ncols
= nncols
+ nscols
;
801 q
= gapp_ssd_new(pr
);
802 if (!q
|| ssd_set_ncols(q
, ncols
, formats
) != RETURN_SUCCESS
) {
803 errmsg("Malloc failed in uniread()");
807 return RETURN_FAILURE
;
812 if (nrows
>= nrows_allocated
) {
813 if (!nrows_allocated
) {
814 nrows_allocated
= BUFSIZE
;
816 nrows_allocated
*= 2;
818 if (ssd_set_nrows(q
, nrows_allocated
) != RETURN_SUCCESS
) {
819 errmsg("Malloc failed in uniread()");
822 return RETURN_FAILURE
;
826 if (insert_data_row(q
, nrows
, s
) != RETURN_SUCCESS
) {
829 sprintf(tbuf
, "Error parsing line %d, skipped", linecount
);
832 if (readerror
> MAXERR
) {
833 if (yesno("Lots of errors, abort?", NULL
, NULL
, NULL
)) {
836 return RETURN_FAILURE
;
846 /* free excessive storage */
847 ssd_set_nrows(q
, nrows
);
849 /* store accumulated data */
850 if (store_cb
&& store_cb(q
, udata
) != RETURN_SUCCESS
) {
853 return RETURN_FAILURE
;
856 /* reset state registers */
865 return RETURN_SUCCESS
;
875 static int store_cb(Quark
*q
, void *udata
)
877 ascii_data
*adata
= (ascii_data
*) udata
;
880 quark_idstr_set(q
, adata
->label
);
882 return store_data(q
, adata
->load_type
, adata
->settype
);
885 int getdata(Quark
*gr
, char *fn
, int settype
, int load_type
)
889 GraceApp
*gapp
= gapp_from_quark(gr
);
892 fp
= gapp_openr(gapp
, fn
, SOURCE_DISK
);
894 return RETURN_FAILURE
;
898 adata
.load_type
= load_type
;
899 adata
.settype
= settype
;
901 retval
= uniread(gr
, fp
, NULL
, store_cb
, &adata
);
905 if (load_type
!= LOAD_BLOCK
) {
906 autoscale_graph(gr
, gapp
->rt
->autoscale_onread
);
913 int write_ssd(const Quark
*ssd
, unsigned int ncols
, const int *cols
, FILE *fp
)
916 unsigned int nrows
= ssd_get_nrows(ssd
), i
, j
;
918 unsigned int prec
= project_get_prec(get_parent_project(ssd
));
921 for (j
= 0; j
< ncols
; j
++) {
922 char *lab
= ssd_get_col_label(ssd
, cols
[j
]);
926 fputs(lab
? lab
:"?", fp
);
930 for (i
= 0; i
< nrows
; i
++) {
931 for (j
= 0; j
< ncols
; j
++) {
932 unsigned int col
= cols
[j
];
933 ss_column
*scol
= ssd_get_col(ssd
, col
);
935 return RETURN_FAILURE
;
942 if (scol
->format
== FFORMAT_STRING
) {
943 char **s
= ((char **) scol
->data
);
944 fprintf(fp
, " \"%s\"", escapequotes(s
[i
]));
946 double *x
= ((double *) scol
->data
);
947 fprintf(fp
, "%.*g", prec
, x
[i
]);
953 return RETURN_SUCCESS
;
958 * read data to the set from a file overriding the current contents
960 int update_set_from_file(Quark
*pset
)
965 dsp
= set_get_dataset(pset
);
968 retval
= RETURN_FAILURE
;
971 RunTime
*rt
= rt_from_quark(pset
);
973 fp
= gapp_openr(gapp_from_quark(pset
), dsp
->hotfile
, dsp
->hotsrc
);
976 rt
->curtype
= set_get_type(pset
);
977 retval
= uniread(get_parent_project(pset
), fp
, LOAD_SINGLE
, dsp
->hotfile
);
986 static int project_cb(Quark
*pr
, int etype
, void *data
)
988 if (etype
== QUARK_ETYPE_MODIFY
) {
991 if ((dirtystate
> SOME_LIMIT
) ||
992 (current_time
- autosave_time
> ANOTHER_LIMIT
) ) {
998 return RETURN_SUCCESS
;
1002 static int project_hook(Quark
*q
, void *udata
, QTraverseClosure
*closure
)
1004 switch (quark_fid_get(q
)) {
1005 case QFlavorProject
:
1006 quark_cb_add(q
, project_cb
, NULL
);
1009 quark_cb_add(q
, kill_ssd_cb
, NULL
);
1016 GProject
*load_any_project(GraceApp
*gapp
, const char *fn
)
1020 /* FIXME: A temporary hack */
1021 if (fn
&& strstr(fn
, ".xgr")) {
1022 gp
= load_xgr_project(gapp
, fn
);
1024 gp
= load_agr_project(gapp
, fn
);
1028 quark_traverse(gproject_get_top(gp
), project_hook
, NULL
);
1034 static GProject
*load_project_file(GraceApp
*gapp
, const char *fn
, int as_template
)
1037 Quark
*project
, *gr
, **graphs
;
1041 gp
= load_any_project(gapp
, fn
);
1043 errmsg("Failed loading project file");
1047 gapp
->rt
->print_file
[0] = '\0';
1050 grfile_free(gp
->grf
);
1054 gapp_add_gproject(gapp
, gp
);
1056 project
= gproject_get_top(gp
);
1058 amem
= quark_get_amem(project
);
1060 /* Set undo limit of 128MB */
1061 amem_set_undo_limit(amem
, 0x8000000L
);
1062 /* Get initial memory snapshot */
1063 amem_snapshot(amem
);
1065 /* try to switch to the first active graph */
1066 ngraphs
= project_get_graphs(project
, &graphs
);
1067 for (i
= 0; i
< ngraphs
; i
++) {
1069 if (select_graph(gr
) == RETURN_SUCCESS
) {
1078 int load_project(GraceApp
*gapp
, char *fn
)
1087 epath
= grace_path(gapp
->grace
, fn
);
1089 for (i
= 0; i
< gapp
->gpcount
; i
++) {
1090 docname
= gproject_get_docname(gapp
->gplist
[i
]);
1091 if (epath
&& docname
&& !strcmp(epath
, docname
)) {
1098 if (is_open
&& gapp
->gplist
[i
] != gapp
->gp
) {
1099 gapp_set_active_gproject(gapp
, gapp
->gplist
[i
]);
1103 return RETURN_SUCCESS
;
1106 gp
= load_project_file(gapp
, fn
, FALSE
);
1108 gapp_set_active_gproject(gapp
, gp
);
1110 return RETURN_SUCCESS
;
1112 return RETURN_FAILURE
;
1116 int revert_project(GraceApp
*gapp
, GProject
*gp
)
1119 GProject
*rgp
= NULL
;
1121 docname
= gproject_get_docname(gp
);
1123 rgp
= load_project_file(gapp
, docname
, FALSE
);
1125 rgp
= load_project_file(gapp
, "templates/Default.xgr", TRUE
);
1129 gapp_set_gproject_id(gapp
, rgp
, gapp_get_gproject_id(gapp
, gp
));
1131 if (gapp
->gp
== gp
) {
1132 gapp_set_active_gproject(gapp
, rgp
);
1134 quark_set_active2(gproject_get_top(rgp
), FALSE
);
1137 gapp_delete_gproject(gapp
, gp
);
1139 return RETURN_SUCCESS
;
1141 return RETURN_FAILURE
;
1146 int new_project(GraceApp
*gapp
, char *pr_template
)
1148 GProject
*gp
= NULL
;
1151 if (string_is_empty(pr_template
)) {
1152 gp
= load_project_file(gapp
, "templates/Default.xgr", TRUE
);
1153 } else if (pr_template
[0] == '/') {
1154 gp
= load_project_file(gapp
, pr_template
, TRUE
);
1156 s
= xmalloc(strlen("templates/") + strlen(pr_template
) + 1);
1158 sprintf(s
, "templates/%s", pr_template
);
1159 gp
= load_project_file(gapp
, s
, TRUE
);
1165 gapp_set_active_gproject(gapp
, gp
);
1167 return RETURN_SUCCESS
;
1169 return RETURN_FAILURE
;
1174 int save_project(GProject
*gp
, char *fn
)
1177 Quark
*project
= gproject_get_top(gp
);
1178 GUI
*gui
= gui_from_quark(project
);
1180 static int save_unsupported
= FALSE
;
1183 if (!project
|| !fn
) {
1184 return RETURN_FAILURE
;
1187 if (fn
&& strstr(fn
, ".agr")) {
1188 errmsg("Cowardly refusing to overwrite an agr file");
1189 return RETURN_FAILURE
;
1191 if (!save_unsupported
&&
1192 !yesno("The current format may be unsupported by the final release. Continue?",
1193 "Yeah, I'm brave!", NULL
, "doc/UsersGuide.html#unsupported_format")) {
1194 return RETURN_FAILURE
;
1196 save_unsupported
= TRUE
;
1198 noask_save
= gui
->noask
;
1199 if (strings_are_equal(gproject_get_docname(gp
), fn
)) {
1200 /* If saving under the same name, don't warn about overwriting */
1204 grf
= grfile_openw(fn
);
1206 return RETURN_FAILURE
;
1209 gui
->noask
= noask_save
;
1211 retval
= gproject_save(gp
, grf
);
1218 GProject
*load_xgr_project(GraceApp
*gapp
, const char *fn
)
1224 epath
= grace_path(gapp
->grace
, fn
);
1225 grf
= grfile_openr(epath
);
1231 gp
= gproject_load(gapp
->pc
, gapp
->grace
, grf
, AMEM_MODEL_LIBUNDO
);