mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-05-01 13:53:57 +02:00
439 lines
13 KiB
PHP
439 lines
13 KiB
PHP
(* Images
|
|
*
|
|
* GtkImage is used to display an image; the image can be in a number of formats.
|
|
* Typically, you load an image into a GdkPixbuf, then display the pixbuf.
|
|
*
|
|
* This demo code shows some of the more obscure cases, in the simple
|
|
* case a call to gtk_image_new_from_file() is all you need.
|
|
*
|
|
* If you want to put image data in your program as a C variable,
|
|
* use the make-inline-pixbuf program that comes with GTK+.
|
|
* This way you won't need to depend on loading external files, your
|
|
* application binary can be self-contained.
|
|
*)
|
|
|
|
|
|
var
|
|
image_window : PGtkWidget;
|
|
image_pixbuf_loader : PGdkPixbufLoader;
|
|
image_load_timeout : guint;
|
|
image_stream : file;
|
|
|
|
type
|
|
TBuffer256 = array [0..255] of byte;
|
|
|
|
procedure progressive_prepared_callback (loader : PGdkPixbufLoader;
|
|
data : gpointer); cdecl;
|
|
var
|
|
pixbuf : PGdkPixbuf;
|
|
image : PGtkWidget;
|
|
|
|
begin
|
|
image := PGtkWidget (data);
|
|
|
|
pixbuf := gdk_pixbuf_loader_get_pixbuf (loader);
|
|
|
|
(* Avoid displaying random memory contents, since the pixbuf
|
|
* isn't filled in yet.
|
|
*)
|
|
gdk_pixbuf_fill (pixbuf, $aaaaaaff);
|
|
|
|
gtk_image_set_from_pixbuf (pGtkImage (image), pixbuf);
|
|
end;
|
|
|
|
procedure progressive_updated_callback (loader : PGdkPixbufLoader;
|
|
x, y,
|
|
width,
|
|
height : gint;
|
|
data : gpointer); cdecl;
|
|
var
|
|
image : PGtkWidget;
|
|
|
|
begin
|
|
image := PGtkWidget (data);
|
|
|
|
(* We know the pixbuf inside the GtkImage has changed, but the image
|
|
* itself doesn't know this; so queue a redraw. If we wanted to be
|
|
* really efficient, we could use a drawing area or something
|
|
* instead of a GtkImage, so we could control the exact position of
|
|
* the pixbuf on the display, then we could queue a draw for only
|
|
* the updated area of the image.
|
|
*)
|
|
|
|
gtk_widget_queue_draw (image);
|
|
end;
|
|
|
|
function progressive_timeout (data : gpointer): gboolean; cdecl;
|
|
var
|
|
image : PGtkWidget;
|
|
buf : TBuffer256;
|
|
bytes_read : integer;
|
|
error : PGError;
|
|
dialog : PGtkWidget;
|
|
error_msg,
|
|
filename : pgchar;
|
|
|
|
begin
|
|
image := PGtkWidget (data);
|
|
|
|
(* This shows off fully-paranoid error handling, so looks scary.
|
|
* You could factor out the error handling code into a nice separate
|
|
* function to make things nicer.
|
|
*)
|
|
|
|
if file_is_valid (image_stream) then // is there a better way???
|
|
begin
|
|
error := NULL;
|
|
|
|
blockread (image_stream, buf, sizeof(buf), bytes_read);
|
|
|
|
if not gdk_pixbuf_loader_write (image_pixbuf_loader,
|
|
@buf[0], bytes_read, @error) then
|
|
begin
|
|
dialog := gtk_message_dialog_new (GTK_WINDOW (image_window),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_ERROR,
|
|
GTK_BUTTONS_CLOSE,
|
|
'Failed to load image: %s',
|
|
[error^.message]);
|
|
|
|
g_error_free (error);
|
|
|
|
g_signal_connect (dialog, 'response',
|
|
G_CALLBACK (@gtk_widget_destroy), NULL);
|
|
|
|
close (image_stream);
|
|
|
|
gtk_widget_show (dialog);
|
|
|
|
image_load_timeout := 0;
|
|
|
|
exit (FALSE); (* uninstall the timeout *)
|
|
end; {of not gdk_pixbuf_loader_write}
|
|
|
|
if eof (image_stream) then
|
|
begin
|
|
close (image_stream);
|
|
|
|
(* Errors can happen on close, e.g. if the image
|
|
* file was truncated we'll know on close that
|
|
* it was incomplete.
|
|
*)
|
|
error := NULL;
|
|
if not gdk_pixbuf_loader_close (image_pixbuf_loader, @error) then
|
|
begin
|
|
dialog := gtk_message_dialog_new (GTK_WINDOW (image_window),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_ERROR,
|
|
GTK_BUTTONS_CLOSE,
|
|
'Failed to load image: %s',
|
|
[error^.message]);
|
|
|
|
g_error_free (error);
|
|
|
|
g_signal_connect (dialog, 'response',
|
|
G_CALLBACK (@gtk_widget_destroy), NULL);
|
|
|
|
gtk_widget_show (dialog);
|
|
|
|
g_object_unref (G_OBJECT (image_pixbuf_loader));
|
|
image_pixbuf_loader := NULL;
|
|
|
|
image_load_timeout := 0;
|
|
|
|
exit(FALSE); (* uninstall the timeout *)
|
|
end; {of not gdk_pixbuf_loader_close}
|
|
|
|
g_object_unref (G_OBJECT (image_pixbuf_loader));
|
|
image_pixbuf_loader := NULL;
|
|
end; {of eof}
|
|
end {of image_stream}
|
|
else begin
|
|
error_msg := NULL;
|
|
|
|
(* demo_find_file() looks in the the current directory first,
|
|
* so you can run gtk-demo without installing GTK, then looks
|
|
* in the location where the file is installed.
|
|
*)
|
|
|
|
filename := demo_find_file ('alphatest.png', @error);
|
|
if error <> NULL then
|
|
begin
|
|
error_msg := g_strdup (error^.message);
|
|
g_error_free (error);
|
|
end else
|
|
begin
|
|
{$push}{$I-}
|
|
assign (image_stream, filename);
|
|
reset (image_stream, 1);
|
|
{$pop}
|
|
|
|
if IOResult <> 0 then
|
|
error_msg := g_strdup_printf ('Error while opening file "%s"',
|
|
[filename]);
|
|
g_free (filename);
|
|
end;
|
|
|
|
if not file_is_valid (image_stream) then
|
|
begin
|
|
dialog := gtk_message_dialog_new (GTK_WINDOW (image_window),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_ERROR,
|
|
GTK_BUTTONS_CLOSE,
|
|
'%s', [error_msg]);
|
|
g_free (error_msg);
|
|
|
|
g_signal_connect (dialog, 'response',
|
|
G_CALLBACK (@gtk_widget_destroy), NULL);
|
|
|
|
gtk_widget_show (dialog);
|
|
|
|
image_load_timeout := 0;
|
|
|
|
exit (FALSE); (* uninstall the timeout *)
|
|
end;
|
|
|
|
if image_pixbuf_loader <> NULL then
|
|
begin
|
|
gdk_pixbuf_loader_close (image_pixbuf_loader, NULL);
|
|
g_object_unref (G_OBJECT (image_pixbuf_loader));
|
|
image_pixbuf_loader := NULL;
|
|
end;
|
|
|
|
image_pixbuf_loader := gdk_pixbuf_loader_new ();
|
|
|
|
g_signal_connect (G_OBJECT (image_pixbuf_loader), 'area_prepared',
|
|
G_CALLBACK (@progressive_prepared_callback), image);
|
|
|
|
g_signal_connect (G_OBJECT (image_pixbuf_loader), 'area_updated',
|
|
G_CALLBACK (@progressive_updated_callback), image);
|
|
end; {of else image_stream}
|
|
|
|
(* leave timeout installed *)
|
|
exit (TRUE);
|
|
end;
|
|
|
|
procedure start_progressive_loading (image : PGtkWidget); cdecl;
|
|
begin
|
|
(* This is obviously totally contrived (we slow down loading
|
|
* on purpose to show how incremental loading works).
|
|
* The real purpose of incremental loading is the case where
|
|
* you are reading data from a slow source such as the network.
|
|
* The timeout simply simulates a slow data source by inserting
|
|
* pauses in the reading process.
|
|
*)
|
|
image_load_timeout := g_timeout_add (150,
|
|
@progressive_timeout,
|
|
image);
|
|
end;
|
|
|
|
procedure images_cleanup_callback (theobject : PGtkObject;
|
|
data : gpointer); cdecl;
|
|
begin
|
|
if image_load_timeout <> 0 then
|
|
begin
|
|
g_source_remove (image_load_timeout);
|
|
image_load_timeout := 0;
|
|
end;
|
|
|
|
if image_pixbuf_loader <> NULL then
|
|
begin
|
|
gdk_pixbuf_loader_close (image_pixbuf_loader, NULL);
|
|
g_object_unref (G_OBJECT (image_pixbuf_loader));
|
|
image_pixbuf_loader := NULL;
|
|
end;
|
|
|
|
if file_is_valid (image_stream) then
|
|
close (image_stream);
|
|
end;
|
|
|
|
procedure toggle_sensitivity_callback (togglebutton : PGtkWidget;
|
|
user_data : gpointer);cdecl;
|
|
var
|
|
container : PGtkContainer;
|
|
list,
|
|
tmp : PGList;
|
|
|
|
begin
|
|
container := PGtkContainer (user_data);
|
|
|
|
list := gtk_container_get_children (container);
|
|
|
|
tmp := list;
|
|
|
|
while tmp <> NULL do
|
|
begin
|
|
(* don't disable our toggle *)
|
|
if pGtkWidget (tmp^.data) <> togglebutton then
|
|
gtk_widget_set_sensitive (pGtkWidget (tmp^.data),
|
|
not gtk_toggle_button_get_active (pGtkToggleButton (togglebutton)));
|
|
|
|
tmp := tmp^.next;
|
|
end;
|
|
|
|
g_list_free (list);
|
|
end;
|
|
|
|
|
|
function do_images : PGtkWidget;
|
|
var
|
|
frame,
|
|
vbox,
|
|
image,
|
|
thelabel,
|
|
align,
|
|
dialog,
|
|
button : PGtkWidget;
|
|
|
|
pixbuf : PGdkPixbuf;
|
|
error : PGError;
|
|
filename : pgchar;
|
|
|
|
begin
|
|
error := NULL;
|
|
|
|
if image_window = NULL then
|
|
begin
|
|
image_window := gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|
gtk_window_set_title (GTK_WINDOW (image_window), 'Images');
|
|
|
|
g_signal_connect (image_window, 'destroy',
|
|
G_CALLBACK (@gtk_widget_destroyed), @image_window);
|
|
g_signal_connect (image_window, 'destroy',
|
|
G_CALLBACK (@images_cleanup_callback), NULL);
|
|
|
|
gtk_container_set_border_width (GTK_CONTAINER (image_window), 8);
|
|
|
|
vbox := gtk_vbox_new (FALSE, 8);
|
|
gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
|
|
gtk_container_add (GTK_CONTAINER (image_window), vbox);
|
|
|
|
thelabel := gtk_label_new (NULL);
|
|
gtk_label_set_markup (GTK_LABEL (thelabel),
|
|
'<u>Image loaded from a file</u>');
|
|
|
|
gtk_box_pack_start (GTK_BOX (vbox), thelabel, FALSE, FALSE, 0);
|
|
|
|
frame := gtk_frame_new (NULL);
|
|
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
|
|
|
|
(* The alignment keeps the frame from growing when users resize
|
|
* the window
|
|
*)
|
|
align := gtk_alignment_new (0.5, 0.5, 0, 0);
|
|
gtk_container_add (GTK_CONTAINER (align), frame);
|
|
gtk_box_pack_start (GTK_BOX (vbox), align, FALSE, FALSE, 0);
|
|
|
|
(* demo_find_file() looks in the the current directory first,
|
|
* so you can run gtk-demo without installing GTK, then looks
|
|
* in the location where the file is installed.
|
|
*)
|
|
|
|
pixbuf := NULL;
|
|
filename := demo_find_file ('gtk-logo-rgb.gif', @error);
|
|
if filename <> NULL then
|
|
begin
|
|
pixbuf := gdk_pixbuf_new_from_file (filename, @error);
|
|
g_free (filename);
|
|
end;
|
|
|
|
if error <> NULL then
|
|
begin
|
|
(* This code shows off error handling. You can just use
|
|
* gtk_image_new_from_file() instead if you don't want to report
|
|
* errors to the user. If the file doesn't load when using
|
|
* gtk_image_new_from_file(), a "missing image" icon will
|
|
* be displayed instead.
|
|
*)
|
|
|
|
dialog := gtk_message_dialog_new (GTK_WINDOW (image_window),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_ERROR,
|
|
GTK_BUTTONS_CLOSE,
|
|
'Unable to open image file "gtk-logo-rgb.gif": %s',
|
|
[error^.message]);
|
|
g_error_free (error);
|
|
|
|
g_signal_connect (dialog, 'response',
|
|
G_CALLBACK (@gtk_widget_destroy), NULL);
|
|
|
|
gtk_widget_show (dialog);
|
|
end;
|
|
|
|
image := gtk_image_new_from_pixbuf (pixbuf);
|
|
|
|
gtk_container_add (GTK_CONTAINER (frame), image);
|
|
|
|
|
|
(* Animation *)
|
|
|
|
thelabel := gtk_label_new (NULL);
|
|
gtk_label_set_markup (GTK_LABEL (thelabel),
|
|
'<u>Animation loaded from a file</u>');
|
|
gtk_box_pack_start (GTK_BOX (vbox), thelabel, FALSE, FALSE, 0);
|
|
|
|
frame := gtk_frame_new (NULL);
|
|
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
|
|
|
|
(* The alignment keeps the frame from growing when users resize
|
|
* the window
|
|
*)
|
|
align := gtk_alignment_new (0.5, 0.5, 0, 0);
|
|
gtk_container_add (GTK_CONTAINER (align), frame);
|
|
gtk_box_pack_start (GTK_BOX (vbox), align, FALSE, FALSE, 0);
|
|
|
|
filename := demo_find_file ('floppybuddy.gif', NULL);
|
|
image := gtk_image_new_from_file (filename);
|
|
g_free (filename);
|
|
|
|
gtk_container_add (GTK_CONTAINER (frame), image);
|
|
|
|
|
|
(* Progressive *)
|
|
|
|
|
|
thelabel := gtk_label_new (NULL);
|
|
gtk_label_set_markup (GTK_LABEL (thelabel),
|
|
'<u>Progressive image loading</u>');
|
|
gtk_box_pack_start (GTK_BOX (vbox), thelabel, FALSE, FALSE, 0);
|
|
|
|
frame := gtk_frame_new (NULL);
|
|
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
|
|
|
|
(* The alignment keeps the frame from growing when users resize
|
|
* the window
|
|
*)
|
|
align := gtk_alignment_new (0.5, 0.5, 0, 0);
|
|
gtk_container_add (GTK_CONTAINER (align), frame);
|
|
gtk_box_pack_start (GTK_BOX (vbox), align, FALSE, FALSE, 0);
|
|
|
|
(* Create an empty image for now; the progressive loader
|
|
* will create the pixbuf and fill it in.
|
|
*)
|
|
|
|
image := gtk_image_new_from_pixbuf (NULL);
|
|
gtk_container_add (GTK_CONTAINER (frame), image);
|
|
|
|
start_progressive_loading (image);
|
|
|
|
(* Sensitivity control *)
|
|
button := gtk_toggle_button_new_with_mnemonic ('_Insensitive');
|
|
gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
|
|
|
|
g_signal_connect (G_OBJECT (button), 'toggled',
|
|
G_CALLBACK (@toggle_sensitivity_callback),
|
|
vbox);
|
|
end;
|
|
|
|
if not GTK_WIDGET_VISIBLE (image_window) then
|
|
gtk_widget_show_all (image_window)
|
|
else
|
|
begin
|
|
gtk_widget_destroy (image_window);
|
|
image_window := NULL;
|
|
end;
|
|
|
|
do_images := image_window;
|
|
end;
|
|
|