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
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.
50 * This function is only used in this file, there are other create_menus...
52 static void create_size_menu (GtkComboBox
*combo
)
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) {
72 /* Set the default menu */
73 gtk_combo_box_set_active(GTK_COMBO_BOX (combo
), default_index
);
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.
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 ();
90 int i
=0, default_index
=0;
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) {
104 i
++; /* this is the count of items added to the combo box */
105 /* not the total number of pixbuf formats */
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
);
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.
127 x_image_get_type_from_description (const char *description
)
131 if (strcmp (description
, "Portable Document Format") == 0) {
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
);
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.
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 */
196 new_image_filename
= g_strdup_printf("%s.%s", file_name
,
199 new_image_filename
= g_strdup_printf("%s.%s", file_basename
,
203 /* Set the new filename */
205 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(file_chooser
),
208 s_log_message("x_image_update_dialog_filename: No parent file chooser found!.\n");
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.
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
;
234 GError
*gerror
= NULL
;
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
);
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
);
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
);
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"),
278 s_log_message("%s", gerror
->message
);
281 dialog
= gtk_message_dialog_new (GTK_WINDOW(w_current
->main_window
),
283 | GTK_DIALOG_DESTROY_WITH_PARENT
,
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
);
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); */
304 if (toplevel
->image_color
== TRUE
) {
305 s_log_message(_("Wrote color image to [%s] [%d x %d]\n"), filename
, width
, height
);
307 s_log_message(_("Wrote black and white image to [%s] [%d x %d]\n"), filename
, width
, height
);
312 g_object_unref(pixbuf
);
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.
335 void x_image_setup (GschemToplevel
*w_current
)
341 GtkWidget
*size_combo
;
344 GtkWidget
*type_combo
;
345 char *image_type_descr
;
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
),
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
,
399 /* Set the alternative button order (ok, cancel, help) for other systems */
400 gtk_dialog_set_alternative_button_order(GTK_DIALOG(dialog
),
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
,
413 "select-multiple", FALSE
,
414 /* only in GTK 2.8 */
415 "do-overwrite-confirmation", TRUE
,
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
),
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!!!
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
;
459 n_channels
= gdk_pixbuf_get_n_channels (pixbuf
);
466 if (gdk_pixbuf_get_colorspace (pixbuf
) != GDK_COLORSPACE_RGB
)
471 if (gdk_pixbuf_get_bits_per_sample (pixbuf
) != 8)
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];
496 /*! \todo Finish function documentation!!!
498 * \par Function Description
502 *x_image_get_pixbuf (GschemToplevel
*w_current
, int width
, int height
)
505 GschemPageView
*page_view
;
506 int origin_x
, origin_y
, bottom
, right
;
507 GschemToplevel new_w_current
;
508 GschemOptions options
;
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;
544 rect
.width
= right
- origin_x
;
545 rect
.height
= bottom
- origin_y
;
547 new_geometry
= gschem_page_geometry_new_with_values (width
,
549 old_geometry
->viewport_left
,
550 old_geometry
->viewport_top
,
551 old_geometry
->viewport_right
,
552 old_geometry
->viewport_bottom
,
556 toplevel
.init_bottom
);
558 o_redraw_rect (&new_w_current
,
560 toplevel
.page_current
,
564 gschem_page_geometry_free (new_geometry
);
567 pixbuf
= gdk_pixbuf_get_from_drawable (NULL
, window
, NULL
,
568 origin_x
, origin_y
, 0, 0,
572 if (toplevel
.image_color
== FALSE
)
574 x_image_convert_to_greyscale(pixbuf
);
577 if (window
!= NULL
) {
578 g_object_unref(window
);