missing NULL terminator in set_config_x
[geda-gaf.git] / gschem / src / x_image.c
blob26871126d76a9e99b8f6003b89445d4966f2b8f5
1 /* gEDA - GPL Electronic Design Automation
2 * gschem - gEDA Schematic Capture
3 * Copyright (C) 1998-2010 Ales Hvezda
4 * Copyright (C) 1998-2020 gEDA Contributors (see ChangeLog for details)
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.
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.
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
20 #include <config.h>
22 #include <stdio.h>
23 #include <unistd.h>
24 #ifdef HAVE_STDLIB_H
25 #include <stdlib.h>
26 #endif
27 #ifdef HAVE_STRING_H
28 #include <string.h>
29 #endif
31 #include <glib.h>
33 #include "gschem.h"
35 #define X_IMAGE_DEFAULT_SIZE "800x600"
37 #define X_IMAGE_SIZE_MENU_NAME "image_size_menu"
38 #define X_IMAGE_TYPE_MENU_NAME "image_type_menu"
40 #define X_IMAGE_DEFAULT_TYPE "PNG"
42 static char *x_image_sizes[] = {"320x240", "640x480", "800x600", "1200x768",
43 "1280x960", "1600x1200", "3200x2400", NULL};
45 /*! \brief Create the options of the image size combobox
46 * \par This function adds the options of the image size to the given combobox.
47 * \param combo [in] the combobox to add the options to.
48 * \return nothing
49 * \note
50 * This function is only used in this file, there are other create_menus...
52 static void create_size_menu (GtkComboBox *combo)
54 char *buf;
55 char *default_size;
56 int i, default_index = 0;
58 default_size = g_strdup (X_IMAGE_DEFAULT_SIZE);
59 for (i=0; x_image_sizes[i] != NULL;i++) {
60 /* Create a new string and add it as an option*/
61 buf = g_strdup (x_image_sizes[i]);
62 gtk_combo_box_append_text (GTK_COMBO_BOX (combo), buf);
64 /* Compare with the default size, to get the default index */
65 if (strcasecmp(buf, default_size ) == 0) {
66 default_index = i;
68 g_free(buf);
70 g_free(default_size);
72 /* Set the default menu */
73 gtk_combo_box_set_active(GTK_COMBO_BOX (combo), default_index);
75 return;
78 /*! \brief Create the options of the image type combobox
79 * \par This function adds the options of the image type to the given combobox.
80 * \param combo [in] the combobox to add the options to.
81 * \return nothing
82 * \note
83 * This function is only used in this file, there are other create_menus...
85 static void create_type_menu(GtkComboBox *combo)
87 GSList *formats = gdk_pixbuf_get_formats ();
88 GSList *ptr;
89 char *buf;
90 int i=0, default_index=0;
92 ptr = formats;
93 while (ptr) {
94 if (gdk_pixbuf_format_is_writable (ptr->data)) {
95 /* Get the format description and add it to the menu */
96 buf = g_strdup (gdk_pixbuf_format_get_description(ptr->data));
97 gtk_combo_box_append_text (GTK_COMBO_BOX (combo), buf);
99 /* Compare the name with "png" and store the index */
100 buf = g_strdup (gdk_pixbuf_format_get_name(ptr->data));
101 if (strcasecmp(buf, X_IMAGE_DEFAULT_TYPE) == 0) {
102 default_index = i;
104 i++; /* this is the count of items added to the combo box */
105 /* not the total number of pixbuf formats */
106 g_free(buf);
108 ptr = ptr->next;
110 g_slist_free (formats);
111 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), "Portable Document Format");
113 /* Set the default menu */
114 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), default_index);
115 return;
118 /*! \brief Given a gdk-pixbuf image type description, it returns the type,
119 * or extension of the image.
120 * \par Return the gdk-pixbuf image type, or extension, which has the
121 * given gdk-pixbuf description.
122 * \param description The gdk-pixbuf image type description.
123 * \return The gdk-pixbuf type, or extension, of the image.
124 * \note This function is only used in this file.
126 static char *
127 x_image_get_type_from_description (const char *description)
129 GSList *ptr;
131 if (strcmp (description, "Portable Document Format") == 0) {
132 return "pdf";
135 ptr = gdk_pixbuf_get_formats ();
137 while (ptr != NULL) {
138 gchar *ptr_descr = gdk_pixbuf_format_get_description (ptr->data);
140 if (ptr_descr && (strcasecmp (ptr_descr, description) == 0)) {
141 return gdk_pixbuf_format_get_name (ptr->data);
144 ptr = g_slist_next (ptr);
147 return NULL;
150 /*! \brief Update the filename of a file dialog, when the image type has changed.
151 * \par Given a combobox inside a file chooser dialog, this function updates
152 * the filename displayed by the dialog, removing the current extension, and
153 * adding the extension of the image type selected.
154 * \param combo [in] A combobox inside a file chooser dialog, with gdk-pixbuf image type descriptions.
155 * \param w_current [in] the GschemToplevel structure.
156 * \return nothing.
159 static void x_image_update_dialog_filename(GtkComboBox *combo,
160 GschemToplevel *w_current) {
161 TOPLEVEL *toplevel = gschem_toplevel_get_toplevel (w_current);
162 char* image_type_descr = NULL;
163 char *image_type = NULL;
164 char *old_image_filename = NULL;
165 char *file_basename = NULL;
166 char *file_name = NULL ;
167 char *new_image_filename = NULL;
168 GtkWidget *file_chooser;
170 /* Get the current image type */
171 image_type_descr = gtk_combo_box_get_active_text(GTK_COMBO_BOX(combo));
172 image_type = x_image_get_type_from_description(image_type_descr);
174 /* Get the parent dialog */
175 file_chooser = gtk_widget_get_ancestor(GTK_WIDGET(combo),
176 GTK_TYPE_FILE_CHOOSER);
178 /* Get the previous file name. If none, revert to the page filename */
179 old_image_filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(file_chooser));
180 if (!old_image_filename) {
181 old_image_filename = toplevel->page_current->page_filename;
184 /* Get the file name, without extension */
185 if (old_image_filename) {
186 file_basename = g_path_get_basename(old_image_filename);
188 if (g_strrstr(file_basename, ".") != NULL) {
189 file_name = g_strndup(file_basename,
190 g_strrstr(file_basename, ".") - file_basename);
194 /* Add the extension */
195 if (file_name) {
196 new_image_filename = g_strdup_printf("%s.%s", file_name,
197 image_type);
198 } else {
199 new_image_filename = g_strdup_printf("%s.%s", file_basename,
200 image_type);
203 /* Set the new filename */
204 if (file_chooser) {
205 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(file_chooser),
206 new_image_filename);
207 } else {
208 s_log_message("x_image_update_dialog_filename: No parent file chooser found!.\n");
211 g_free(file_name);
212 g_free(file_basename);
213 g_free(new_image_filename);
216 /*! \brief Write the image file, with the desired options.
217 * \par This function writes the image file, with the options set in the
218 * dialog by the user.
219 * \param w_current [in] the GschemToplevel structure.
220 * \param filename [in] the image filename.
221 * \param width [in] the image width chosen by the user.
222 * \param height [in] the image height chosen by the user.
223 * \param filetype [in] image filetype.
224 * \return nothing
227 void x_image_lowlevel(GschemToplevel *w_current, const char* filename,
228 int width, int height, char *filetype)
230 TOPLEVEL *toplevel = gschem_toplevel_get_toplevel (w_current);
231 int save_page_left, save_page_right, save_page_top, save_page_bottom;
232 int page_width, page_height, page_center_left, page_center_top;
233 GdkPixbuf *pixbuf;
234 GError *gerror = NULL;
235 GtkWidget *dialog;
236 float prop;
237 GschemPageView *view = gschem_toplevel_get_current_page_view (w_current);
239 GschemPageGeometry *geometry = gschem_page_view_get_page_geometry (view);
240 g_return_if_fail (geometry != NULL);
242 /* Save geometry */
243 save_page_left = geometry->viewport_left;
244 save_page_right = geometry->viewport_right;
245 save_page_top = geometry->viewport_top;
246 save_page_bottom = geometry->viewport_bottom;
248 page_width = geometry->viewport_right - geometry->viewport_left;
249 page_height = geometry->viewport_bottom - geometry->viewport_top;
251 page_center_left = geometry->viewport_left + (page_width / 2);
252 page_center_top = geometry->viewport_top + (page_height / 2);
254 /* Preserve proportions */
255 prop = (float)width / height;
256 if (((float)page_width / page_height) > prop) {
257 page_height = (page_width / prop);
258 }else{
259 page_width = (page_height * prop);
262 gschem_page_geometry_set_viewport_left (geometry, page_center_left - (page_width / 2));
263 gschem_page_geometry_set_viewport_right (geometry, page_center_left + (page_width / 2));
264 gschem_page_geometry_set_viewport_top (geometry, page_center_top - (page_height / 2));
265 gschem_page_geometry_set_viewport_bottom (geometry, page_center_top + (page_height / 2));
267 /* de select everything first */
268 o_select_unselect_all( w_current );
270 if (strcmp(filetype, "pdf") == 0)
271 x_print_export_pdf (w_current, filename);
272 else {
273 pixbuf = x_image_get_pixbuf(w_current, width, height);
274 if (pixbuf != NULL) {
275 if (!gdk_pixbuf_save(pixbuf, filename, filetype, &gerror, NULL)) {
276 s_log_message(_("x_image_lowlevel: Unable to write %s file %s.\n"),
277 filetype, filename);
278 s_log_message("%s", gerror->message);
280 /* Warn the user */
281 dialog = gtk_message_dialog_new (GTK_WINDOW(w_current->main_window),
282 GTK_DIALOG_MODAL
283 | GTK_DIALOG_DESTROY_WITH_PARENT,
284 GTK_MESSAGE_ERROR,
285 GTK_BUTTONS_OK,
286 _("There was the following error when saving image with type %s to filename:\n%s\n\n%s.\n"),
287 filetype, filename, gerror->message
290 gtk_dialog_run (GTK_DIALOG (dialog));
291 gtk_widget_destroy (dialog);
293 /* Free the gerror */
294 g_error_free(gerror);
295 gerror = NULL;
297 /* Unlink the output file */
298 /* It's not safe to unlink the file if there was an error.
299 For example: if the operation was not allowed due to permissions,
300 the _previous existing_ file will be removed */
301 /* unlink(filename); */
303 else {
304 if (toplevel->image_color == TRUE) {
305 s_log_message(_("Wrote color image to [%s] [%d x %d]\n"), filename, width, height);
306 } else {
307 s_log_message(_("Wrote black and white image to [%s] [%d x %d]\n"), filename, width, height);
310 g_free(filetype);
311 if (pixbuf != NULL)
312 g_object_unref(pixbuf);
314 else {
315 s_log_message(_("x_image_lowlevel: Unable to get pixbuf from gschem's window.\n"));
319 /* Restore geometry */
320 gschem_page_geometry_set_viewport_left (geometry, save_page_left );
321 gschem_page_geometry_set_viewport_right (geometry, save_page_right );
322 gschem_page_geometry_set_viewport_top (geometry, save_page_top );
323 gschem_page_geometry_set_viewport_bottom (geometry, save_page_bottom);
325 gschem_page_view_invalidate_all (view);
328 /*! \brief Display the image file selection dialog.
329 * \par Display the image file selection dialog, allowing the user to
330 * set several options, like image size and image type.
331 * When the user hits "ok", then it writes the image file.
332 * \param w_current [in] the GschemToplevel structure.
333 * \return nothing
335 void x_image_setup (GschemToplevel *w_current)
337 GtkWidget *dialog;
338 GtkWidget *vbox1;
339 GtkWidget *hbox;
340 GtkWidget *label1;
341 GtkWidget *size_combo;
342 GtkWidget *vbox2;
343 GtkWidget *label2;
344 GtkWidget *type_combo;
345 char *image_type_descr;
346 char *filename;
347 char *image_size;
348 char *image_type;
349 int width, height;
351 hbox = gtk_hbox_new(FALSE, 0);
353 /* Image size selection */
354 vbox1 = gtk_vbox_new(TRUE, 0);
355 label1 = gtk_label_new (_("Width x Height"));
356 gtk_widget_show (label1);
357 gtk_misc_set_alignment( GTK_MISC (label1), 0, 0);
358 gtk_misc_set_padding (GTK_MISC (label1), 0, 0);
359 gtk_box_pack_start (GTK_BOX (vbox1),
360 label1, FALSE, FALSE, 0);
362 size_combo = gtk_combo_box_new_text ();
363 create_size_menu (GTK_COMBO_BOX(size_combo));
365 gtk_widget_show (size_combo);
366 gtk_box_pack_start (GTK_BOX (vbox1), size_combo, TRUE, TRUE, 0);
367 gtk_widget_show(vbox1);
369 /* Image type selection */
370 vbox2 = gtk_vbox_new(TRUE, 0);
371 label2 = gtk_label_new (_("Image type"));
372 gtk_widget_show (label2);
373 gtk_misc_set_alignment( GTK_MISC (label2), 0, 0);
374 gtk_misc_set_padding (GTK_MISC (label2), 0, 0);
375 gtk_box_pack_start (GTK_BOX (vbox2),
376 label2, FALSE, FALSE, 0);
378 type_combo = gtk_combo_box_new_text ();
379 gtk_box_pack_start (GTK_BOX (vbox2), type_combo, TRUE, TRUE, 0);
380 create_type_menu (GTK_COMBO_BOX(type_combo));
382 /* Connect the changed signal to the callback, so the filename
383 gets updated every time the image type is changed */
384 g_signal_connect (type_combo, "changed",
385 G_CALLBACK(x_image_update_dialog_filename),
386 w_current);
388 gtk_widget_show (type_combo);
389 gtk_widget_show(vbox2);
391 /* Create the dialog */
392 dialog = gtk_file_chooser_dialog_new (_("Write image..."),
393 GTK_WINDOW(w_current->main_window),
394 GTK_FILE_CHOOSER_ACTION_SAVE,
395 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
396 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
397 NULL);
399 /* Set the alternative button order (ok, cancel, help) for other systems */
400 gtk_dialog_set_alternative_button_order(GTK_DIALOG(dialog),
401 GTK_RESPONSE_ACCEPT,
402 GTK_RESPONSE_CANCEL,
403 -1);
405 /* Add the extra widgets to the dialog*/
406 gtk_box_pack_start(GTK_BOX(hbox), vbox1, FALSE, FALSE, 10);
407 gtk_box_pack_start(GTK_BOX(hbox), vbox2, FALSE, FALSE, 10);
409 gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER(dialog), hbox);
411 g_object_set (dialog,
412 /* GtkFileChooser */
413 "select-multiple", FALSE,
414 /* only in GTK 2.8 */
415 "do-overwrite-confirmation", TRUE,
416 NULL);
418 /* Update the filename */
419 x_image_update_dialog_filename(GTK_COMBO_BOX(type_combo), w_current);
421 gtk_dialog_set_default_response(GTK_DIALOG(dialog),
422 GTK_RESPONSE_ACCEPT);
424 gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
426 gtk_container_set_border_width(GTK_CONTAINER(dialog),
427 DIALOG_BORDER_SPACING);
428 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox),
429 DIALOG_V_SPACING);
431 gtk_widget_show (dialog);
433 if (gtk_dialog_run((GTK_DIALOG(dialog))) == GTK_RESPONSE_ACCEPT) {
434 image_size = gtk_combo_box_get_active_text(GTK_COMBO_BOX(size_combo));
436 image_type_descr = gtk_combo_box_get_active_text(GTK_COMBO_BOX(type_combo));
438 image_type = x_image_get_type_from_description(image_type_descr);
439 sscanf(image_size, "%ix%i", &width, &height);
440 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
442 x_image_lowlevel(w_current, filename, width, height, image_type);
445 gtk_widget_destroy (dialog);
448 /*! \todo Finish function documentation!!!
449 * \brief
450 * \par Function Description
453 static void x_image_convert_to_greyscale(GdkPixbuf *pixbuf)
455 int width, height, rowstride, n_channels;
456 guchar *pixels, *p, new_value;
457 int i, j;
459 n_channels = gdk_pixbuf_get_n_channels (pixbuf);
461 if (n_channels != 3)
463 return;
466 if (gdk_pixbuf_get_colorspace (pixbuf) != GDK_COLORSPACE_RGB)
468 return;
471 if (gdk_pixbuf_get_bits_per_sample (pixbuf) != 8)
473 return;
476 width = gdk_pixbuf_get_width (pixbuf);
477 height = gdk_pixbuf_get_height (pixbuf);
479 rowstride = gdk_pixbuf_get_rowstride (pixbuf);
480 pixels = gdk_pixbuf_get_pixels (pixbuf);
482 for (j = 0; j < height; j++)
484 for (i = 0; i < width; i++)
486 p = pixels + j * rowstride + i * n_channels;
488 new_value = 0.3 * p[0] + 0.59 * p[1] + 0.11 * p[2];
489 p[0] = new_value;
490 p[1] = new_value;
491 p[2] = new_value;
496 /*! \todo Finish function documentation!!!
497 * \brief
498 * \par Function Description
501 GdkPixbuf
502 *x_image_get_pixbuf (GschemToplevel *w_current, int width, int height)
504 GdkPixbuf *pixbuf;
505 GschemPageView *page_view;
506 int origin_x, origin_y, bottom, right;
507 GschemToplevel new_w_current;
508 GschemOptions options;
509 TOPLEVEL toplevel;
510 GdkRectangle rect;
511 GschemPageGeometry *old_geometry, *new_geometry;
512 GdkPixmap *window = NULL;
514 page_view = gschem_toplevel_get_current_page_view (w_current);
516 old_geometry = gschem_page_view_get_page_geometry (page_view);
518 /* Do a copy of the w_current struct and work with it */
519 memcpy(&new_w_current, w_current, sizeof(GschemToplevel));
520 /* Do a copy of the options struct and work with it */
521 memcpy(&options, w_current->options, sizeof(GschemOptions));
522 /* Do a copy of the toplevel struct and work with it */
523 memcpy(&toplevel, w_current->toplevel, sizeof(TOPLEVEL));
525 new_w_current.toplevel = &toplevel;
526 new_w_current.options = &options;
528 window = gdk_pixmap_new (gtk_widget_get_window (GTK_WIDGET(page_view)), width, height, -1);
530 gschem_options_set_grid_mode (new_w_current.options, GRID_MODE_NONE);
532 if (toplevel.image_color == FALSE)
534 /*! \bug Need to handle image color setting properly. See
535 * Launchpad bug 1086530. */
538 origin_x = origin_y = 0;
539 right = width;
540 bottom = height;
542 rect.x = origin_x;
543 rect.y = origin_y;
544 rect.width = right - origin_x;
545 rect.height = bottom - origin_y;
547 new_geometry = gschem_page_geometry_new_with_values (width,
548 height,
549 old_geometry->viewport_left,
550 old_geometry->viewport_top,
551 old_geometry->viewport_right,
552 old_geometry->viewport_bottom,
553 toplevel.init_left,
554 toplevel.init_top,
555 toplevel.init_right,
556 toplevel.init_bottom);
558 o_redraw_rect (&new_w_current,
559 window,
560 toplevel.page_current,
561 new_geometry,
562 &rect);
564 gschem_page_geometry_free (new_geometry);
566 /* Get the pixbuf */
567 pixbuf = gdk_pixbuf_get_from_drawable (NULL, window, NULL,
568 origin_x, origin_y, 0, 0,
569 right-origin_x,
570 bottom-origin_y);
572 if (toplevel.image_color == FALSE)
574 x_image_convert_to_greyscale(pixbuf);
577 if (window != NULL) {
578 g_object_unref(window);
581 return(pixbuf);