gschem
|
00001 /* gEDA - GPL Electronic Design Automation 00002 * gschem - gEDA Schematic Capture 00003 * Copyright (C) 1998-2010 Ales Hvezda 00004 * Copyright (C) 1998-2011 gEDA Contributors (see ChangeLog for details) 00005 * 00006 * This program is free software; you can redistribute it and/or modify 00007 * it under the terms of the GNU General Public License as published by 00008 * the Free Software Foundation; either version 2 of the License, or 00009 * (at your option) any later version. 00010 * 00011 * This program is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 * GNU General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU General Public License 00017 * along with this program; if not, write to the Free Software 00018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00019 */ 00020 #include <config.h> 00021 00022 #include <stdio.h> 00023 #include <ctype.h> 00024 #ifdef HAVE_STRING_H 00025 #include <string.h> 00026 #endif 00027 #include <libgen.h> 00028 00029 #include <sys/types.h> 00030 #include <sys/stat.h> 00031 #include <unistd.h> 00032 00033 #include "gschem.h" 00034 00035 #ifdef HAVE_LIBDMALLOC 00036 #include <dmalloc.h> 00037 #endif 00038 00039 /* break with the tradition here and input a list */ 00048 void o_edit(GSCHEM_TOPLEVEL *w_current, GList *list) 00049 { 00050 OBJECT *o_current; 00051 const gchar *str = NULL; 00052 00053 if (list == NULL) { 00054 w_current->inside_action = 0; 00055 i_set_state(w_current, SELECT); 00056 return; 00057 } 00058 00059 o_current = (OBJECT *) list->data; 00060 if (o_current == NULL) { 00061 fprintf(stderr, _("Got an unexpected NULL in o_edit\n")); 00062 exit(-1); 00063 } 00064 00065 /* for now deal with only the first item */ 00066 switch(o_current->type) { 00067 00068 /* also add the ability to multi attrib edit: nets, busses, pins */ 00069 case(OBJ_COMPLEX): 00070 case(OBJ_PLACEHOLDER): 00071 case(OBJ_NET): 00072 case(OBJ_PIN): 00073 case(OBJ_BUS): 00074 x_multiattrib_open (w_current); 00075 break; 00076 00077 case(OBJ_PICTURE): 00078 picture_change_filename_dialog(w_current); 00079 break; 00080 case(OBJ_ARC): 00081 arc_angle_dialog(w_current, o_current); 00082 break; 00083 case(OBJ_TEXT): 00084 str = o_text_get_string (w_current->toplevel, o_current); 00085 if (o_attrib_get_name_value (o_current, NULL, NULL) && 00086 /* attribute editor only accept 1-line values for attribute */ 00087 o_text_num_lines (str) == 1) { 00088 attrib_edit_dialog(w_current,o_current, FROM_MENU); 00089 } else { 00090 o_text_edit(w_current, o_current); 00091 } 00092 break; 00093 } 00094 00095 /* has to be more extensive in the future */ 00096 /* some sort of redrawing? */ 00097 } 00098 00104 /* This locks the entire selected list. It does lock components, but does NOT 00105 * change the color (of primatives of the components) though 00106 * this cannot be called recursively */ 00107 void o_lock(GSCHEM_TOPLEVEL *w_current) 00108 { 00109 OBJECT *object = NULL; 00110 GList *s_current = NULL; 00111 00112 /* skip over head */ 00113 s_current = geda_list_get_glist( w_current->toplevel->page_current->selection_list ); 00114 00115 while(s_current != NULL) { 00116 object = (OBJECT *) s_current->data; 00117 if (object) { 00118 /* check to see if locked_color is already being used */ 00119 if (object->locked_color == -1) { 00120 object->selectable = FALSE; 00121 object->locked_color = object->color; 00122 object->color = LOCK_COLOR; 00123 w_current->toplevel->page_current->CHANGED=1; 00124 } else { 00125 s_log_message(_("Object already locked\n")); 00126 } 00127 } 00128 00129 s_current = g_list_next(s_current); 00130 } 00131 00132 if (!w_current->SHIFTKEY) o_select_unselect_all(w_current); 00133 o_undo_savestate(w_current, UNDO_ALL); 00134 i_update_menus(w_current); 00135 } 00136 00142 /* You can unlock something by selecting it with a bounding box... */ 00143 /* this will probably change in the future, but for now it's a 00144 something.. :-) */ 00145 /* this cannot be called recursively */ 00146 void o_unlock(GSCHEM_TOPLEVEL *w_current) 00147 { 00148 OBJECT *object = NULL; 00149 GList *s_current = NULL; 00150 00151 s_current = geda_list_get_glist( w_current->toplevel->page_current->selection_list ); 00152 00153 while(s_current != NULL) { 00154 object = (OBJECT *) s_current->data; 00155 if (object) { 00156 /* only unlock if the object is locked */ 00157 if (object->selectable == FALSE) { 00158 object->selectable = TRUE; 00159 object->color = object->locked_color; 00160 object->locked_color = -1; 00161 w_current->toplevel->page_current->CHANGED = 1; 00162 } else { 00163 s_log_message(_("Object already unlocked\n")); 00164 } 00165 } 00166 00167 s_current = g_list_next(s_current); 00168 } 00169 o_undo_savestate(w_current, UNDO_ALL); 00170 } 00171 00188 void o_rotate_world_update(GSCHEM_TOPLEVEL *w_current, 00189 int centerx, int centery, int angle, GList *list) 00190 { 00191 TOPLEVEL *toplevel = w_current->toplevel; 00192 OBJECT *o_current; 00193 GList *o_iter; 00194 00195 /* this is okay if you just hit rotate and have nothing selected */ 00196 if (list == NULL) { 00197 w_current->inside_action = 0; 00198 i_set_state(w_current, SELECT); 00199 return; 00200 } 00201 00202 o_invalidate_glist (w_current, list); 00203 00204 /* Find connected objects, removing each object in turn from the 00205 * connection list. We only _really_ want those objects connected 00206 * to the selection, not those within in it. 00207 */ 00208 for (o_iter = list; o_iter != NULL; o_iter = g_list_next (o_iter)) { 00209 o_current = o_iter->data; 00210 00211 s_conn_remove_object (toplevel, o_current); 00212 } 00213 00214 o_glist_rotate_world( toplevel, centerx, centery, angle, list ); 00215 00216 /* Find connected objects, adding each object in turn back to the 00217 * connection list. We only _really_ want those objects connected 00218 * to the selection, not those within in it. 00219 */ 00220 for (o_iter = list; o_iter != NULL; o_iter = g_list_next (o_iter)) { 00221 o_current = o_iter->data; 00222 00223 s_conn_update_object (toplevel, o_current); 00224 } 00225 00226 o_invalidate_glist (w_current, list); 00227 00228 /* Run rotate-objects-hook */ 00229 g_run_hook_object_list (w_current, "%rotate-objects-hook", list); 00230 00231 /* Don't save the undo state if we are inside an action */ 00232 /* This is useful when rotating the selection while moving, for example */ 00233 toplevel->page_current->CHANGED = 1; 00234 if (!w_current->inside_action) { 00235 o_undo_savestate(w_current, UNDO_ALL); 00236 } 00237 } 00238 00244 void o_mirror_world_update(GSCHEM_TOPLEVEL *w_current, int centerx, int centery, GList *list) 00245 { 00246 TOPLEVEL *toplevel = w_current->toplevel; 00247 OBJECT *o_current; 00248 GList *o_iter; 00249 00250 if (list == NULL) { 00251 w_current->inside_action = 0; 00252 i_set_state(w_current, SELECT); 00253 return; 00254 } 00255 00256 o_invalidate_glist (w_current, list); 00257 00258 /* Find connected objects, removing each object in turn from the 00259 * connection list. We only _really_ want those objects connected 00260 * to the selection, not those within in it. 00261 */ 00262 for (o_iter = list; o_iter != NULL; o_iter = g_list_next (o_iter)) { 00263 o_current = o_iter->data; 00264 00265 s_conn_remove_object (toplevel, o_current); 00266 } 00267 00268 o_glist_mirror_world( toplevel, centerx, centery, list ); 00269 00270 /* Find connected objects, adding each object in turn back to the 00271 * connection list. We only _really_ want those objects connected 00272 * to the selection, not those within in it. 00273 */ 00274 for (o_iter = list; o_iter != NULL; o_iter = g_list_next (o_iter)) { 00275 o_current = o_iter->data; 00276 00277 s_conn_update_object (toplevel, o_current); 00278 } 00279 00280 o_invalidate_glist (w_current, list); 00281 00282 /* Run mirror-objects-hook */ 00283 g_run_hook_object_list (w_current, "%mirror-objects-hook", list); 00284 00285 toplevel->page_current->CHANGED=1; 00286 o_undo_savestate(w_current, UNDO_ALL); 00287 } 00288 00294 void o_edit_show_hidden_lowlevel (GSCHEM_TOPLEVEL *w_current, 00295 const GList *o_list) 00296 { 00297 TOPLEVEL *toplevel = w_current->toplevel; 00298 OBJECT *o_current; 00299 const GList *iter; 00300 00301 iter = o_list; 00302 while (iter != NULL) { 00303 o_current = (OBJECT *)iter->data; 00304 if (o_current->type == OBJ_TEXT && !o_is_visible (toplevel, o_current)) { 00305 00306 /* don't toggle the visibility flag */ 00307 o_text_recreate (toplevel, o_current); 00308 } 00309 00310 if (o_current->type == OBJ_COMPLEX || o_current->type == OBJ_PLACEHOLDER) { 00311 o_edit_show_hidden_lowlevel(w_current, o_current->complex->prim_objs); 00312 o_recalc_single_object(toplevel, o_current); 00313 } 00314 00315 iter = g_list_next (iter); 00316 } 00317 } 00318 00324 void o_edit_show_hidden (GSCHEM_TOPLEVEL *w_current, const GList *o_list) 00325 { 00326 /* this function just shows the hidden text, but doesn't toggle it */ 00327 /* this function does not change the CHANGED bit, no real changes are */ 00328 /* made to the schematic */ 00329 00330 /* toggle show_hidden_text variable, which when it is true */ 00331 /* means that hidden text IS drawn */ 00332 w_current->toplevel->show_hidden_text = !w_current->toplevel->show_hidden_text; 00333 i_show_state(w_current, NULL); /* update screen status */ 00334 00335 o_edit_show_hidden_lowlevel(w_current, o_list); 00336 o_invalidate_all (w_current); 00337 00338 if (w_current->toplevel->show_hidden_text) { 00339 s_log_message(_("Hidden text is now visible\n")); 00340 } else { 00341 s_log_message(_("Hidden text is now invisible\n")); 00342 } 00343 } 00344 00345 #define FIND_WINDOW_HALF_SIZE (5000) 00346 00347 OBJECT *last_o = NULL; 00348 int skiplast; 00349 00357 int o_edit_find_text (GSCHEM_TOPLEVEL *w_current, const GList *o_list, 00358 char *stext, int descend, int skip) 00359 { 00360 TOPLEVEL *toplevel = w_current->toplevel; 00361 char *attrib = NULL; 00362 int count = 0; 00363 PAGE *parent = NULL; 00364 char *current_filename = NULL; 00365 int page_control = 0; 00366 int pcount = 0; 00367 int rv; 00368 int text_screen_height; 00369 const GList *iter; 00370 00371 OBJECT *o_current; 00372 00373 skiplast = skip; 00374 00375 iter = o_list; 00376 while (iter != NULL) { 00377 o_current = (OBJECT *)iter->data; 00378 00379 if (descend) { 00380 if (o_current->type == OBJ_COMPLEX) { 00381 parent = toplevel->page_current; 00382 attrib = o_attrib_search_attached_attribs_by_name (o_current, 00383 "source", count); 00384 00385 /* if above is null, then look inside symbol */ 00386 if (attrib == NULL) { 00387 attrib = o_attrib_search_inherited_attribs_by_name (o_current, 00388 "source", count); 00389 /* looking_inside = TRUE; */ 00390 } 00391 00392 if (attrib) { 00393 pcount = 0; 00394 current_filename = u_basic_breakup_string(attrib, ',', pcount); 00395 if (current_filename != NULL) { 00396 PAGE *child_page = 00397 s_hierarchy_down_schematic_single(toplevel, 00398 current_filename, 00399 parent, 00400 page_control, 00401 HIERARCHY_NORMAL_LOAD); 00402 00403 if (child_page != NULL) { 00404 page_control = child_page->page_control; 00405 rv = o_edit_find_text (w_current, 00406 s_page_objects (child_page), 00407 stext, descend, skiplast); 00408 if (!rv) { 00409 s_page_goto( toplevel, child_page ); 00410 return 0; 00411 } 00412 } 00413 } 00414 } 00415 } 00416 } 00417 00418 if (o_current->type == OBJ_TEXT && 00419 (o_is_visible (toplevel, o_current) || toplevel->show_hidden_text)) { 00420 00421 const gchar *str = o_text_get_string (toplevel, o_current); 00422 /* replaced strcmp with strstr to simplify the search */ 00423 if (strstr (str,stext)) { 00424 if (!skiplast) { 00425 int x1, y1, x2, y2; 00426 00427 a_zoom(w_current, ZOOM_FULL, DONTCARE, A_PAN_DONT_REDRAW); 00428 g_assert( world_get_single_object_bounds (toplevel, o_current, &x1, &y1, &x2, &y2) ); 00429 text_screen_height = SCREENabs (w_current, y2 - y1); 00430 /* this code will zoom/pan till the text screen height is about */ 00431 /* 50 pixels high, perhaps a future enhancement will be to make */ 00432 /* this number configurable */ 00433 while (text_screen_height < 50) { 00434 a_zoom(w_current, ZOOM_IN, DONTCARE, A_PAN_DONT_REDRAW); 00435 text_screen_height = SCREENabs (w_current, y2 - y1); 00436 } 00437 a_pan_general(w_current, 00438 o_current->text->x, o_current->text->y, 00439 1, 0); 00440 00441 /* Make sure the titlebar and scrollbars are up-to-date */ 00442 x_window_set_current_page(w_current, 00443 w_current->toplevel->page_current ); 00444 00445 last_o = o_current; 00446 break; 00447 } 00448 if (last_o == o_current) { 00449 skiplast = 0; 00450 } 00451 00452 } /* if (strstr(o_current->text->string,stext)) */ 00453 } /* if (o_current->type == OBJ_TEXT) */ 00454 iter = g_list_next (iter); 00455 00456 if (iter == NULL) { 00457 return 1; 00458 } 00459 } 00460 return (iter == NULL); 00461 } 00462 00463 00469 void o_edit_hide_specific_text (GSCHEM_TOPLEVEL *w_current, 00470 const GList *o_list, 00471 char *stext) 00472 { 00473 TOPLEVEL *toplevel = w_current->toplevel; 00474 OBJECT *o_current; 00475 const GList *iter; 00476 00477 iter = o_list; 00478 while (iter != NULL) { 00479 o_current = (OBJECT *)iter->data; 00480 00481 if (o_current->type == OBJ_TEXT) { 00482 const gchar *str = o_text_get_string (w_current->toplevel, o_current); 00483 if (!strncmp (stext, str, strlen (stext))) { 00484 if (o_is_visible (toplevel, o_current)) { 00485 o_set_visibility (toplevel, o_current, INVISIBLE); 00486 o_text_recreate(toplevel, o_current); 00487 00488 toplevel->page_current->CHANGED = 1; 00489 } 00490 } 00491 } 00492 iter = g_list_next (iter); 00493 } 00494 o_undo_savestate(w_current, UNDO_ALL); 00495 o_invalidate_all (w_current); 00496 } 00497 00503 void o_edit_show_specific_text (GSCHEM_TOPLEVEL *w_current, 00504 const GList *o_list, 00505 char *stext) 00506 { 00507 TOPLEVEL *toplevel = w_current->toplevel; 00508 OBJECT *o_current; 00509 const GList *iter; 00510 00511 iter = o_list; 00512 while (iter != NULL) { 00513 o_current = (OBJECT *)iter->data; 00514 00515 if (o_current->type == OBJ_TEXT) { 00516 const gchar *str = o_text_get_string (w_current->toplevel, o_current); 00517 if (!strncmp (stext, str, strlen (stext))) { 00518 if (!o_is_visible (toplevel, o_current)) { 00519 o_set_visibility (toplevel, o_current, VISIBLE); 00520 o_text_recreate(toplevel, o_current); 00521 00522 toplevel->page_current->CHANGED = 1; 00523 } 00524 } 00525 } 00526 iter = g_list_next (iter); 00527 } 00528 o_undo_savestate(w_current, UNDO_ALL); 00529 } 00530 00531 00546 OBJECT * 00547 o_update_component (GSCHEM_TOPLEVEL *w_current, OBJECT *o_current) 00548 { 00549 TOPLEVEL *toplevel = w_current->toplevel; 00550 OBJECT *o_new; 00551 PAGE *page; 00552 GList *new_attribs; 00553 GList *old_attribs; 00554 GList *iter; 00555 const CLibSymbol *clib; 00556 00557 g_return_val_if_fail (o_current != NULL, NULL); 00558 g_return_val_if_fail (o_current->type == OBJ_COMPLEX, NULL); 00559 g_return_val_if_fail (o_current->complex_basename != NULL, NULL); 00560 00561 page = o_get_page (toplevel, o_current); 00562 00563 /* Force symbol data to be reloaded from source */ 00564 clib = s_clib_get_symbol_by_name (o_current->complex_basename); 00565 s_clib_symbol_invalidate_data (clib); 00566 00567 if (clib == NULL) { 00568 s_log_message (_("Could not find symbol [%s] in library. Update failed.\n"), 00569 o_current->complex_basename); 00570 return NULL; 00571 } 00572 00573 /* Unselect the old object. */ 00574 o_selection_remove (toplevel, page->selection_list, o_current); 00575 00576 /* Create new object and set embedded */ 00577 o_new = o_complex_new (toplevel, OBJ_COMPLEX, DEFAULT_COLOR, 00578 o_current->complex->x, 00579 o_current->complex->y, 00580 o_current->complex->angle, 00581 o_current->complex->mirror, 00582 clib, o_current->complex_basename, 00583 1); 00584 if (o_complex_is_embedded (o_current)) { 00585 o_embed (toplevel, o_new); 00586 } 00587 00588 new_attribs = o_complex_promote_attribs (toplevel, o_new); 00589 00590 /* Cull any attributes from new COMPLEX that are already attached to 00591 * old COMPLEX. Note that the new_attribs list is kept consistent by 00592 * setting GList data pointers to NULL if their OBJECTs are 00593 * culled. At the end, the new_attribs list is updated by removing 00594 * all list items with NULL data. This is slightly magic, but 00595 * works. */ 00596 for (iter = new_attribs; iter != NULL; iter = g_list_next (iter)) { 00597 OBJECT *attr_new = iter->data; 00598 gchar *name; 00599 gchar *value; 00600 00601 g_assert (attr_new->type == OBJ_TEXT); 00602 00603 o_attrib_get_name_value (attr_new, &name, NULL); 00604 00605 value = o_attrib_search_attached_attribs_by_name (o_current, name, 0); 00606 if (value != NULL) { 00607 o_attrib_remove (toplevel, &o_new->attribs, attr_new); 00608 s_delete_object (toplevel, attr_new); 00609 iter->data = NULL; 00610 } 00611 00612 g_free (name); 00613 g_free (value); 00614 } 00615 new_attribs = g_list_remove_all (new_attribs, NULL); 00616 00617 /* Detach attributes from old OBJECT and attach to new OBJECT */ 00618 old_attribs = g_list_copy (o_current->attribs); 00619 o_attrib_detach_all (toplevel, o_current); 00620 o_attrib_attach_list (toplevel, old_attribs, o_new, 1); 00621 g_list_free (old_attribs); 00622 00623 /* Add new attributes to page */ 00624 s_page_append_list (toplevel, page, new_attribs); 00625 00626 /* Update pinnumbers for current slot */ 00627 s_slot_update_object (toplevel, o_new); 00628 00629 /* Replace old OBJECT with new OBJECT */ 00630 s_page_replace (toplevel, page, o_current, o_new); 00631 s_delete_object (toplevel, o_current); 00632 00633 /* Select new OBJECT */ 00634 o_selection_add (toplevel, page->selection_list, o_new); 00635 00636 /* mark the page as modified */ 00637 toplevel->page_current->CHANGED = 1; 00638 o_undo_savestate (w_current, UNDO_ALL); 00639 00640 return o_new; 00641 } 00642 00650 void o_autosave_backups(GSCHEM_TOPLEVEL *w_current) 00651 { 00652 TOPLEVEL *toplevel = w_current->toplevel; 00653 GList *iter; 00654 PAGE *p_save, *p_current; 00655 gchar *backup_filename; 00656 gchar *real_filename; 00657 gchar *only_filename; 00658 gchar *dirname; 00659 mode_t saved_umask; 00660 mode_t mask; 00661 struct stat st; 00662 00663 /* save current page */ 00664 p_save = toplevel->page_current; 00665 00666 for ( iter = geda_list_get_glist( toplevel->pages ); 00667 iter != NULL; 00668 iter = g_list_next( iter ) ) { 00669 00670 p_current = (PAGE *)iter->data; 00671 00672 if (p_current->do_autosave_backup == 0) { 00673 continue; 00674 } 00675 if (p_current->ops_since_last_backup != 0) { 00676 /* make p_current the current page of toplevel */ 00677 s_page_goto (toplevel, p_current); 00678 00679 /* Get the real filename and file permissions */ 00680 real_filename = follow_symlinks (p_current->page_filename, NULL); 00681 00682 if (real_filename == NULL) { 00683 s_log_message (_("o_autosave_backups: Can't get the real filename of %s."), p_current->page_filename); 00684 } else { 00685 /* Get the directory in which the real filename lives */ 00686 dirname = g_path_get_dirname (real_filename); 00687 only_filename = g_path_get_basename(real_filename); 00688 00689 backup_filename = g_strdup_printf("%s%c"AUTOSAVE_BACKUP_FILENAME_STRING, 00690 dirname, G_DIR_SEPARATOR, only_filename); 00691 00692 /* If there is not an existing file with that name, compute the 00693 * permissions and uid/gid that we will use for the newly-created file. 00694 */ 00695 00696 if (stat (real_filename, &st) != 0) { 00697 #if defined(HAVE_GETUID) && defined(HAVE_GETGID) 00698 struct stat dir_st; 00699 int result; 00700 #endif 00701 00702 /* Use default permissions */ 00703 saved_umask = umask(0); 00704 st.st_mode = 0666 & ~saved_umask; 00705 umask(saved_umask); 00706 #if defined(HAVE_GETUID) && defined(HAVE_GETGID) 00707 st.st_uid = getuid (); 00708 00709 result = stat (dirname, &dir_st); 00710 00711 if (result == 0 && (dir_st.st_mode & S_ISGID)) 00712 st.st_gid = dir_st.st_gid; 00713 else 00714 st.st_gid = getgid (); 00715 #endif 00716 } 00717 g_free (dirname); 00718 g_free (only_filename); 00719 g_free (real_filename); 00720 00721 /* Make the backup file writable before saving a new one */ 00722 if ( g_file_test (backup_filename, G_FILE_TEST_EXISTS) && 00723 (! g_file_test (backup_filename, G_FILE_TEST_IS_DIR))) { 00724 saved_umask = umask(0); 00725 if (chmod(backup_filename, (S_IWRITE|S_IWGRP|S_IWOTH) & 00726 ((~saved_umask) & 0777)) != 0) { 00727 s_log_message (_("Could NOT set previous backup file [%s] read-write\n"), 00728 backup_filename); 00729 } 00730 umask(saved_umask); 00731 } 00732 00733 if (o_save (toplevel, 00734 s_page_objects (toplevel->page_current), 00735 backup_filename, NULL)) { 00736 00737 p_current->ops_since_last_backup = 0; 00738 p_current->do_autosave_backup = 0; 00739 00740 /* Make the backup file readonly so a 'rm *' command will ask 00741 the user before deleting it */ 00742 saved_umask = umask(0); 00743 mask = (S_IWRITE|S_IWGRP|S_IEXEC|S_IXGRP|S_IXOTH); 00744 mask = (~mask)&0777; 00745 mask &= ((~saved_umask) & 0777); 00746 if (chmod(backup_filename,mask) != 0) { 00747 s_log_message (_("Could NOT set backup file [%s] readonly\n"), 00748 backup_filename); 00749 } 00750 umask(saved_umask); 00751 } else { 00752 s_log_message (_("Could NOT save backup file [%s]\n"), 00753 backup_filename); 00754 } 00755 g_free (backup_filename); 00756 } 00757 } 00758 } 00759 /* restore current page */ 00760 s_page_goto (toplevel, p_save); 00761 }