1 /* gEDA - GPL Electronic Design Automation
2  * gschem - gEDA Schematic Capture
3  * Copyright (C) 1998-2010 Ales Hvezda
4  * Copyright (C) 1998-2011 gEDA Contributors (see ChangeLog for details)
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 #include <config.h>
21 
22 #include <stdio.h>
23 #include <ctype.h>
24 #ifdef HAVE_STRING_H
25 #include <string.h>
26 #endif
27 #include <libgen.h>
28 
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 
33 #include "gschem.h"
34 
35 #ifdef HAVE_LIBDMALLOC
36 #include <dmalloc.h>
37 #endif
38 
39 /* break with the tradition here and input a list */
40 /*! \todo probably should go back and do the same for o_copy o_move
41  *  o_delete...
42  */
43 /*! \todo Finish function documentation!!!
44  *  \brief
45  *  \par Function Description
46  *
47  */
o_edit(GSCHEM_TOPLEVEL * w_current,GList * list)48 void o_edit(GSCHEM_TOPLEVEL *w_current, GList *list)
49 {
50   OBJECT *o_current;
51   const gchar *str = NULL;
52 
53   if (list == NULL) {
54     w_current->inside_action = 0;
55     i_set_state(w_current, SELECT);
56     return;
57   }
58 
59   o_current = (OBJECT *) list->data;
60   if (o_current == NULL) {
61     fprintf(stderr, _("Got an unexpected NULL in o_edit\n"));
62     exit(-1);
63   }
64 
65   /* for now deal with only the first item */
66   switch(o_current->type) {
67 
68     /* also add the ability to multi attrib edit: nets, busses, pins */
69     case(OBJ_COMPLEX):
70     case(OBJ_PLACEHOLDER):
71     case(OBJ_NET):
72     case(OBJ_PIN):
73     case(OBJ_BUS):
74     x_multiattrib_open (w_current);
75     break;
76 
77     case(OBJ_PICTURE):
78     picture_change_filename_dialog(w_current);
79     break;
80     case(OBJ_ARC):
81     arc_angle_dialog(w_current, o_current);
82     break;
83     case(OBJ_TEXT):
84       str = o_text_get_string (w_current->toplevel, o_current);
85       if (o_attrib_get_name_value (o_current, NULL, NULL) &&
86         /* attribute editor only accept 1-line values for attribute */
87         o_text_num_lines (str) == 1) {
88         attrib_edit_dialog(w_current,o_current, FROM_MENU);
89     } else {
90       o_text_edit(w_current, o_current);
91     }
92     break;
93   }
94 
95   /* has to be more extensive in the future */
96   /* some sort of redrawing? */
97 }
98 
99 /*! \todo Finish function documentation!!!
100  *  \brief
101  *  \par Function Description
102  *
103  */
104 /* This locks the entire selected list.  It does lock components, but does NOT
105  * change the color (of primatives of the components) though
106  * this cannot be called recursively */
o_lock(GSCHEM_TOPLEVEL * w_current)107 void o_lock(GSCHEM_TOPLEVEL *w_current)
108 {
109   OBJECT *object = NULL;
110   GList *s_current = NULL;
111 
112   /* skip over head */
113   s_current = geda_list_get_glist( w_current->toplevel->page_current->selection_list );
114 
115   while(s_current != NULL) {
116     object = (OBJECT *) s_current->data;
117     if (object) {
118       /* check to see if locked_color is already being used */
119       if (object->locked_color == -1) {
120         object->selectable = FALSE;
121         object->locked_color = object->color;
122         object->color = LOCK_COLOR;
123         w_current->toplevel->page_current->CHANGED=1;
124       } else {
125         s_log_message(_("Object already locked\n"));
126       }
127     }
128 
129     s_current = g_list_next(s_current);
130   }
131 
132   if (!w_current->SHIFTKEY) o_select_unselect_all(w_current);
133   o_undo_savestate(w_current, UNDO_ALL);
134   i_update_menus(w_current);
135 }
136 
137 /*! \todo Finish function documentation!!!
138  *  \brief
139  *  \par Function Description
140  *
141  */
142 /* You can unlock something by selecting it with a bounding box... */
143 /* this will probably change in the future, but for now it's a
144    something.. :-) */
145 /* this cannot be called recursively */
o_unlock(GSCHEM_TOPLEVEL * w_current)146 void o_unlock(GSCHEM_TOPLEVEL *w_current)
147 {
148   OBJECT *object = NULL;
149   GList *s_current = NULL;
150 
151   s_current = geda_list_get_glist( w_current->toplevel->page_current->selection_list );
152 
153   while(s_current != NULL) {
154     object = (OBJECT *) s_current->data;
155     if (object) {
156       /* only unlock if the object is locked */
157       if (object->selectable == FALSE) {
158         object->selectable = TRUE;
159         object->color = object->locked_color;
160         object->locked_color = -1;
161         w_current->toplevel->page_current->CHANGED = 1;
162       } else {
163         s_log_message(_("Object already unlocked\n"));
164       }
165     }
166 
167     s_current = g_list_next(s_current);
168   }
169   o_undo_savestate(w_current, UNDO_ALL);
170 }
171 
172 /*! \brief Rotate all objects in list.
173  *  \par Function Description
174  *  Given an object <B>list</B>, and the center of rotation
175  *  (<B>centerx</B>,<B>centery</B>, this function traverses all the selection
176  *  list, rotating each object through angle <B>angle</B>.
177  *  The list contains a given object and all its attributes
178  *  (refdes, pinname, pinlabel, ...).
179  *  There is a second pass to run the rotate hooks of non-simple objects,
180  *  like pin or complex objects, for example.
181  *
182  *  \param [in] w_current  The GSCHEM_TOPLEVEL object.
183  *  \param [in] centerx    Center x coordinate of rotation.
184  *  \param [in] centery    Center y coordinate of rotation.
185  *  \param [in] angle      Angle to rotate the objects through.
186  *  \param [in] list       The list of objects to rotate.
187  */
o_rotate_world_update(GSCHEM_TOPLEVEL * w_current,int centerx,int centery,int angle,GList * list)188 void o_rotate_world_update(GSCHEM_TOPLEVEL *w_current,
189                            int centerx, int centery, int angle, GList *list)
190 {
191   TOPLEVEL *toplevel = w_current->toplevel;
192   OBJECT *o_current;
193   GList *o_iter;
194 
195   /* this is okay if you just hit rotate and have nothing selected */
196   if (list == NULL) {
197     w_current->inside_action = 0;
198     i_set_state(w_current, SELECT);
199     return;
200   }
201 
202   o_invalidate_glist (w_current, list);
203 
204   /* Find connected objects, removing each object in turn from the
205    * connection list. We only _really_ want those objects connected
206    * to the selection, not those within in it.
207    */
208   for (o_iter = list; o_iter != NULL; o_iter = g_list_next (o_iter)) {
209     o_current = o_iter->data;
210 
211     s_conn_remove_object (toplevel, o_current);
212   }
213 
214   o_glist_rotate_world( toplevel, centerx, centery, angle, list );
215 
216   /* Find connected objects, adding each object in turn back to the
217    * connection list. We only _really_ want those objects connected
218    * to the selection, not those within in it.
219    */
220   for (o_iter = list; o_iter != NULL; o_iter = g_list_next (o_iter)) {
221     o_current = o_iter->data;
222 
223     s_conn_update_object (toplevel, o_current);
224   }
225 
226   o_invalidate_glist (w_current, list);
227 
228   /* Run rotate-objects-hook */
229   g_run_hook_object_list (w_current, "%rotate-objects-hook", list);
230 
231   /* Don't save the undo state if we are inside an action */
232   /* This is useful when rotating the selection while moving, for example */
233   toplevel->page_current->CHANGED = 1;
234   if (!w_current->inside_action) {
235     o_undo_savestate(w_current, UNDO_ALL);
236   }
237 }
238 
239 /*! \todo Finish function documentation!!!
240  *  \brief
241  *  \par Function Description
242  *
243  */
o_mirror_world_update(GSCHEM_TOPLEVEL * w_current,int centerx,int centery,GList * list)244 void o_mirror_world_update(GSCHEM_TOPLEVEL *w_current, int centerx, int centery, GList *list)
245 {
246   TOPLEVEL *toplevel = w_current->toplevel;
247   OBJECT *o_current;
248   GList *o_iter;
249 
250   if (list == NULL) {
251     w_current->inside_action = 0;
252     i_set_state(w_current, SELECT);
253     return;
254   }
255 
256   o_invalidate_glist (w_current, list);
257 
258   /* Find connected objects, removing each object in turn from the
259    * connection list. We only _really_ want those objects connected
260    * to the selection, not those within in it.
261    */
262   for (o_iter = list; o_iter != NULL; o_iter = g_list_next (o_iter)) {
263     o_current = o_iter->data;
264 
265     s_conn_remove_object (toplevel, o_current);
266   }
267 
268   o_glist_mirror_world( toplevel, centerx, centery, list );
269 
270   /* Find connected objects, adding each object in turn back to the
271    * connection list. We only _really_ want those objects connected
272    * to the selection, not those within in it.
273    */
274   for (o_iter = list; o_iter != NULL; o_iter = g_list_next (o_iter)) {
275     o_current = o_iter->data;
276 
277     s_conn_update_object (toplevel, o_current);
278   }
279 
280   o_invalidate_glist (w_current, list);
281 
282   /* Run mirror-objects-hook */
283   g_run_hook_object_list (w_current, "%mirror-objects-hook", list);
284 
285   toplevel->page_current->CHANGED=1;
286   o_undo_savestate(w_current, UNDO_ALL);
287 }
288 
289 /*! \todo Finish function documentation!!!
290  *  \brief
291  *  \par Function Description
292  *
293  */
o_edit_show_hidden_lowlevel(GSCHEM_TOPLEVEL * w_current,const GList * o_list)294 void o_edit_show_hidden_lowlevel (GSCHEM_TOPLEVEL *w_current,
295                                   const GList *o_list)
296 {
297   TOPLEVEL *toplevel = w_current->toplevel;
298   OBJECT *o_current;
299   const GList *iter;
300 
301   iter = o_list;
302   while (iter != NULL) {
303     o_current = (OBJECT *)iter->data;
304     if (o_current->type == OBJ_TEXT && !o_is_visible (toplevel, o_current)) {
305 
306       /* don't toggle the visibility flag */
307       o_text_recreate (toplevel, o_current);
308     }
309 
310     if (o_current->type == OBJ_COMPLEX || o_current->type == OBJ_PLACEHOLDER) {
311       o_edit_show_hidden_lowlevel(w_current, o_current->complex->prim_objs);
312       o_recalc_single_object(toplevel, o_current);
313     }
314 
315     iter = g_list_next (iter);
316   }
317 }
318 
319 /*! \todo Finish function documentation!!!
320  *  \brief
321  *  \par Function Description
322  *
323  */
o_edit_show_hidden(GSCHEM_TOPLEVEL * w_current,const GList * o_list)324 void o_edit_show_hidden (GSCHEM_TOPLEVEL *w_current, const GList *o_list)
325 {
326   /* this function just shows the hidden text, but doesn't toggle it */
327   /* this function does not change the CHANGED bit, no real changes are */
328   /* made to the schematic */
329 
330   /* toggle show_hidden_text variable, which when it is true */
331   /* means that hidden text IS drawn */
332   w_current->toplevel->show_hidden_text = !w_current->toplevel->show_hidden_text;
333   i_show_state(w_current, NULL); /* update screen status */
334 
335   o_edit_show_hidden_lowlevel(w_current, o_list);
336   o_invalidate_all (w_current);
337 
338   if (w_current->toplevel->show_hidden_text) {
339     s_log_message(_("Hidden text is now visible\n"));
340   } else {
341     s_log_message(_("Hidden text is now invisible\n"));
342   }
343 }
344 
345 #define FIND_WINDOW_HALF_SIZE (5000)
346 
347 OBJECT *last_o = NULL;
348 int skiplast;
349 
350 /*! \todo Finish function documentation!!!
351  *  \brief
352  *  \par Function Description
353  *
354  *  \todo Only descends into the first source schematic
355  *
356  */
o_edit_find_text(GSCHEM_TOPLEVEL * w_current,const GList * o_list,char * stext,int descend,int skip)357 int o_edit_find_text (GSCHEM_TOPLEVEL *w_current, const GList *o_list,
358                       char *stext, int descend, int skip)
359 {
360   TOPLEVEL *toplevel = w_current->toplevel;
361   char *attrib = NULL;
362   int count = 0;
363   PAGE *parent = NULL;
364   char *current_filename = NULL;
365   int page_control = 0;
366   int pcount = 0;
367   int rv;
368   int text_screen_height;
369   const GList *iter;
370 
371   OBJECT *o_current;
372 
373   skiplast = skip;
374 
375   iter = o_list;
376   while (iter != NULL) {
377     o_current = (OBJECT *)iter->data;
378 
379     if (descend) {
380       if (o_current->type == OBJ_COMPLEX) {
381         parent = toplevel->page_current;
382         attrib = o_attrib_search_attached_attribs_by_name (o_current,
383                                                            "source", count);
384 
385         /* if above is null, then look inside symbol */
386         if (attrib == NULL) {
387           attrib = o_attrib_search_inherited_attribs_by_name (o_current,
388                                                               "source", count);
389           /*          looking_inside = TRUE; */
390         }
391 
392         if (attrib) {
393           pcount = 0;
394           current_filename = u_basic_breakup_string(attrib, ',', pcount);
395           if (current_filename != NULL) {
396             PAGE *child_page =
397               s_hierarchy_down_schematic_single(toplevel,
398                                                 current_filename,
399                                                 parent,
400                                                 page_control,
401                                                 HIERARCHY_NORMAL_LOAD);
402 
403             if (child_page != NULL) {
404               page_control = child_page->page_control;
405               rv = o_edit_find_text (w_current,
406                                      s_page_objects (child_page),
407                                      stext, descend, skiplast);
408               if (!rv) {
409                 s_page_goto( toplevel, child_page );
410                 return 0;
411               }
412             }
413           }
414         }
415       }
416     }
417 
418     if (o_current->type == OBJ_TEXT &&
419         (o_is_visible (toplevel, o_current) || toplevel->show_hidden_text)) {
420 
421       const gchar *str = o_text_get_string (toplevel, o_current);
422      /* replaced strcmp with strstr to simplify the search */
423       if (strstr (str,stext)) {
424         if (!skiplast) {
425           int x1, y1, x2, y2;
426 
427           a_zoom(w_current, ZOOM_FULL, DONTCARE, A_PAN_DONT_REDRAW);
428           g_assert( world_get_single_object_bounds (toplevel, o_current, &x1, &y1, &x2, &y2) );
429           text_screen_height = SCREENabs (w_current, y2 - y1);
430           /* this code will zoom/pan till the text screen height is about */
431           /* 50 pixels high, perhaps a future enhancement will be to make */
432           /* this number configurable */
433           while (text_screen_height < 50) {
434             a_zoom(w_current, ZOOM_IN, DONTCARE, A_PAN_DONT_REDRAW);
435             text_screen_height = SCREENabs (w_current, y2 - y1);
436           }
437           a_pan_general(w_current,
438                         o_current->text->x, o_current->text->y,
439                         1, 0);
440 
441 	  /* Make sure the titlebar and scrollbars are up-to-date */
442 	  x_window_set_current_page(w_current,
443                                     w_current->toplevel->page_current );
444 
445           last_o = o_current;
446           break;
447         }
448         if (last_o == o_current) {
449           skiplast = 0;
450         }
451 
452       } /* if (strstr(o_current->text->string,stext)) */
453     } /* if (o_current->type == OBJ_TEXT) */
454     iter = g_list_next (iter);
455 
456     if (iter == NULL) {
457       return 1;
458     }
459   }
460   return (iter == NULL);
461 }
462 
463 
464 /*! \todo Finish function documentation!!!
465  *  \brief
466  *  \par Function Description
467  *
468  */
o_edit_hide_specific_text(GSCHEM_TOPLEVEL * w_current,const GList * o_list,char * stext)469 void o_edit_hide_specific_text (GSCHEM_TOPLEVEL *w_current,
470                                 const GList *o_list,
471                                 char *stext)
472 {
473   TOPLEVEL *toplevel = w_current->toplevel;
474   OBJECT *o_current;
475   const GList *iter;
476 
477   iter = o_list;
478   while (iter != NULL) {
479     o_current = (OBJECT *)iter->data;
480 
481     if (o_current->type == OBJ_TEXT) {
482       const gchar *str = o_text_get_string (w_current->toplevel, o_current);
483       if (!strncmp (stext, str, strlen (stext))) {
484         if (o_is_visible (toplevel, o_current)) {
485           o_set_visibility (toplevel, o_current, INVISIBLE);
486           o_text_recreate(toplevel, o_current);
487 
488           toplevel->page_current->CHANGED = 1;
489         }
490       }
491     }
492     iter = g_list_next (iter);
493   }
494   o_undo_savestate(w_current, UNDO_ALL);
495   o_invalidate_all (w_current);
496 }
497 
498 /*! \todo Finish function documentation!!!
499  *  \brief
500  *  \par Function Description
501  *
502  */
o_edit_show_specific_text(GSCHEM_TOPLEVEL * w_current,const GList * o_list,char * stext)503 void o_edit_show_specific_text (GSCHEM_TOPLEVEL *w_current,
504                                 const GList *o_list,
505                                 char *stext)
506 {
507   TOPLEVEL *toplevel = w_current->toplevel;
508   OBJECT *o_current;
509   const GList *iter;
510 
511   iter = o_list;
512   while (iter != NULL) {
513     o_current = (OBJECT *)iter->data;
514 
515     if (o_current->type == OBJ_TEXT) {
516       const gchar *str = o_text_get_string (w_current->toplevel, o_current);
517       if (!strncmp (stext, str, strlen (stext))) {
518         if (!o_is_visible (toplevel, o_current)) {
519           o_set_visibility (toplevel, o_current, VISIBLE);
520           o_text_recreate(toplevel, o_current);
521 
522           toplevel->page_current->CHANGED = 1;
523         }
524       }
525     }
526     iter = g_list_next (iter);
527   }
528   o_undo_savestate(w_current, UNDO_ALL);
529 }
530 
531 
532 /*! \brief Update a component.
533  *
534  * \par Function Description
535  * Updates \a o_current to the latest version of the symbol available
536  * in the symbol library, while preserving any attributes set in the
537  * current schematic. On success, returns the new OBJECT which
538  * replaces \a o_current on the page; \a o_current is deleted. On
539  * failure, returns NULL, and \a o_current is left unchanged.
540  *
541  * \param [in]     w_current The GSCHEM_TOPLEVEL object.
542  * \param [in,out] o_current The OBJECT to be updated.
543  *
544  * \return the new OBJECT that replaces \a o_current.
545  */
546 OBJECT *
o_update_component(GSCHEM_TOPLEVEL * w_current,OBJECT * o_current)547 o_update_component (GSCHEM_TOPLEVEL *w_current, OBJECT *o_current)
548 {
549   TOPLEVEL *toplevel = w_current->toplevel;
550   OBJECT *o_new;
551   PAGE *page;
552   GList *new_attribs;
553   GList *old_attribs;
554   GList *iter;
555   const CLibSymbol *clib;
556 
557   g_return_val_if_fail (o_current != NULL, NULL);
558   g_return_val_if_fail (o_current->type == OBJ_COMPLEX, NULL);
559   g_return_val_if_fail (o_current->complex_basename != NULL, NULL);
560 
561   page = o_get_page (toplevel, o_current);
562 
563   /* Force symbol data to be reloaded from source */
564   clib = s_clib_get_symbol_by_name (o_current->complex_basename);
565   s_clib_symbol_invalidate_data (clib);
566 
567   if (clib == NULL) {
568     s_log_message (_("Could not find symbol [%s] in library. Update failed.\n"),
569                    o_current->complex_basename);
570     return NULL;
571   }
572 
573   /* Unselect the old object. */
574   o_selection_remove (toplevel, page->selection_list, o_current);
575 
576   /* Create new object and set embedded */
577   o_new = o_complex_new (toplevel, OBJ_COMPLEX, DEFAULT_COLOR,
578                          o_current->complex->x,
579                          o_current->complex->y,
580                          o_current->complex->angle,
581                          o_current->complex->mirror,
582                          clib, o_current->complex_basename,
583                          1);
584   if (o_complex_is_embedded (o_current)) {
585     o_embed (toplevel, o_new);
586   }
587 
588   new_attribs = o_complex_promote_attribs (toplevel, o_new);
589 
590   /* Cull any attributes from new COMPLEX that are already attached to
591    * old COMPLEX. Note that the new_attribs list is kept consistent by
592    * setting GList data pointers to NULL if their OBJECTs are
593    * culled. At the end, the new_attribs list is updated by removing
594    * all list items with NULL data. This is slightly magic, but
595    * works. */
596   for (iter = new_attribs; iter != NULL; iter = g_list_next (iter)) {
597     OBJECT *attr_new = iter->data;
598     gchar *name;
599     gchar *value;
600 
601     g_assert (attr_new->type == OBJ_TEXT);
602 
603     o_attrib_get_name_value (attr_new, &name, NULL);
604 
605     value = o_attrib_search_attached_attribs_by_name (o_current, name, 0);
606     if (value != NULL) {
607       o_attrib_remove (toplevel, &o_new->attribs, attr_new);
608       s_delete_object (toplevel, attr_new);
609       iter->data = NULL;
610     }
611 
612     g_free (name);
613     g_free (value);
614   }
615   new_attribs = g_list_remove_all (new_attribs, NULL);
616 
617   /* Detach attributes from old OBJECT and attach to new OBJECT */
618   old_attribs = g_list_copy (o_current->attribs);
619   o_attrib_detach_all (toplevel, o_current);
620   o_attrib_attach_list (toplevel, old_attribs, o_new, 1);
621   g_list_free (old_attribs);
622 
623   /* Add new attributes to page */
624   s_page_append_list (toplevel, page, new_attribs);
625 
626   /* Update pinnumbers for current slot */
627   s_slot_update_object (toplevel, o_new);
628 
629   /* Replace old OBJECT with new OBJECT */
630   s_page_replace (toplevel, page, o_current, o_new);
631   s_delete_object (toplevel, o_current);
632 
633   /* Select new OBJECT */
634   o_selection_add (toplevel, page->selection_list, o_new);
635 
636   /* mark the page as modified */
637   toplevel->page_current->CHANGED = 1;
638   o_undo_savestate (w_current, UNDO_ALL);
639 
640   return o_new;
641 }
642 
643 /*! \brief Do autosave on all pages that are marked.
644  *  \par Function Description
645  *  Looks for pages with the do_autosave_backup flag activated and
646  *  autosaves them.
647  *
648  *  \param [in] w_current  The GSCHEM_TOPLEVEL object to search for autosave's.
649  */
o_autosave_backups(GSCHEM_TOPLEVEL * w_current)650 void o_autosave_backups(GSCHEM_TOPLEVEL *w_current)
651 {
652   TOPLEVEL *toplevel = w_current->toplevel;
653   GList *iter;
654   PAGE *p_save, *p_current;
655   gchar *backup_filename;
656   gchar *real_filename;
657   gchar *only_filename;
658   gchar *dirname;
659   mode_t saved_umask;
660   mode_t mask;
661   struct stat st;
662 
663   /* save current page */
664   p_save = toplevel->page_current;
665 
666   for ( iter = geda_list_get_glist( toplevel->pages );
667         iter != NULL;
668         iter = g_list_next( iter ) ) {
669 
670     p_current = (PAGE *)iter->data;
671 
672     if (p_current->do_autosave_backup == 0) {
673       continue;
674     }
675     if (p_current->ops_since_last_backup != 0) {
676       /* make p_current the current page of toplevel */
677       s_page_goto (toplevel, p_current);
678 
679       /* Get the real filename and file permissions */
680       real_filename = follow_symlinks (p_current->page_filename, NULL);
681 
682       if (real_filename == NULL) {
683         s_log_message (_("o_autosave_backups: Can't get the real filename of %s."), p_current->page_filename);
684       } else {
685         /* Get the directory in which the real filename lives */
686         dirname = g_path_get_dirname (real_filename);
687         only_filename = g_path_get_basename(real_filename);
688 
689         backup_filename = g_strdup_printf("%s%c"AUTOSAVE_BACKUP_FILENAME_STRING,
690                                           dirname, G_DIR_SEPARATOR, only_filename);
691 
692         /* If there is not an existing file with that name, compute the
693          * permissions and uid/gid that we will use for the newly-created file.
694          */
695 
696         if (stat (real_filename, &st) != 0) {
697 #if defined(HAVE_GETUID) && defined(HAVE_GETGID)
698             struct stat dir_st;
699             int result;
700 #endif
701 
702             /* Use default permissions */
703             saved_umask = umask(0);
704             st.st_mode = 0666 & ~saved_umask;
705             umask(saved_umask);
706 #if defined(HAVE_GETUID) && defined(HAVE_GETGID)
707             st.st_uid = getuid ();
708 
709             result = stat (dirname, &dir_st);
710 
711             if (result == 0 && (dir_st.st_mode & S_ISGID))
712               st.st_gid = dir_st.st_gid;
713             else
714               st.st_gid = getgid ();
715 #endif
716           }
717         g_free (dirname);
718         g_free (only_filename);
719         g_free (real_filename);
720 
721         /* Make the backup file writable before saving a new one */
722         if ( g_file_test (backup_filename, G_FILE_TEST_EXISTS) &&
723              (! g_file_test (backup_filename, G_FILE_TEST_IS_DIR))) {
724           saved_umask = umask(0);
725           if (chmod(backup_filename, (S_IWRITE|S_IWGRP|S_IWOTH) &
726                     ((~saved_umask) & 0777)) != 0) {
727             s_log_message (_("Could NOT set previous backup file [%s] read-write\n"),
728                            backup_filename);
729           }
730           umask(saved_umask);
731         }
732 
733         if (o_save (toplevel,
734                     s_page_objects (toplevel->page_current),
735                     backup_filename, NULL)) {
736 
737           p_current->ops_since_last_backup = 0;
738                 p_current->do_autosave_backup = 0;
739 
740           /* Make the backup file readonly so a 'rm *' command will ask
741              the user before deleting it */
742           saved_umask = umask(0);
743           mask = (S_IWRITE|S_IWGRP|S_IEXEC|S_IXGRP|S_IXOTH);
744           mask = (~mask)&0777;
745           mask &= ((~saved_umask) & 0777);
746           if (chmod(backup_filename,mask) != 0) {
747             s_log_message (_("Could NOT set backup file [%s] readonly\n"),
748                            backup_filename);
749           }
750           umask(saved_umask);
751         } else {
752           s_log_message (_("Could NOT save backup file [%s]\n"),
753                          backup_filename);
754         }
755         g_free (backup_filename);
756       }
757     }
758   }
759   /* restore current page */
760   s_page_goto (toplevel, p_save);
761 }
762