note description: "EiffelVision pixmap, GTK implementation." legal: "See notice at end of class." status: "See notice at end of class." keywords: "drawable, primitives, figures, buffer, bitmap, picture" date: "$Date$" revision: "$Revision$" class EV_PIXMAP_IMP inherit EV_PIXMAP_I redefine end_drawing_session, interface, flush, save_to_named_path end EV_DRAWABLE_IMP redefine interface, end_drawing_session, pixbuf_from_drawable_at_position end EV_PRIMITIVE_IMP undefine foreground_color_internal, background_color_internal, set_foreground_color, set_background_color redefine interface, width, height, destroy, c_object_dispose, make, process_draw_event end create make feature {NONE} -- Initialization old_make (an_interface: attached like interface) -- Create a gtk pixmap of size (1 * 1) with no mask. do assign_interface (an_interface) end make -- Initialize `Current' local l_app_imp: like app_implementation l_expose_actions: like expose_actions do set_c_object ({GTK}.gtk_drawing_area_new) Precursor {EV_PRIMITIVE_IMP} l_app_imp := app_implementation -- Connect Draw event (replacing previous expose-event) real_signal_connect_after (visual_widget, {EV_GTK_EVENT_STRINGS}.draw_event_name, agent (l_app_imp.gtk_marshal).draw_actions_event (c_object, ?)) set_size (1, 1) -- Initialize the Graphical Context init_default_values clear_rectangle (0, 0, 1, 1) -- Initialize expose actions so blitting may occur. l_expose_actions := expose_actions end init_from_pointer_style (a_pointer_style: EV_POINTER_STYLE) -- Initialize from `a_pointer_style' local l_pixbuf, l_xpm: POINTER do check attached {EV_POINTER_STYLE_IMP} a_pointer_style.implementation as a_pointer_style_imp then if a_pointer_style_imp.predefined_cursor_code > 0 then -- We are building from a stock cursor. inspect a_pointer_style_imp.predefined_cursor_code when {EV_POINTER_STYLE_CONSTANTS}.busy_cursor then l_xpm := {EV_STOCK_PIXMAPS_IMP}.busy_cursor_xpm when {EV_POINTER_STYLE_CONSTANTS}.wait_cursor then l_xpm := {EV_STOCK_PIXMAPS_IMP}.wait_cursor_xpm when {EV_POINTER_STYLE_CONSTANTS}.crosshair_cursor then l_xpm := {EV_STOCK_PIXMAPS_IMP}.crosshair_cursor_xpm when {EV_POINTER_STYLE_CONSTANTS}.help_cursor then l_xpm := {EV_STOCK_PIXMAPS_IMP}.help_cursor_xpm when {EV_POINTER_STYLE_CONSTANTS}.ibeam_cursor then l_xpm := {EV_STOCK_PIXMAPS_IMP}.ibeam_cursor_xpm when {EV_POINTER_STYLE_CONSTANTS}.no_cursor then l_xpm := {EV_STOCK_PIXMAPS_IMP}.no_cursor_xpm when {EV_POINTER_STYLE_CONSTANTS}.sizeall_cursor then l_xpm := {EV_STOCK_PIXMAPS_IMP}.sizeall_cursor_xpm when {EV_POINTER_STYLE_CONSTANTS}.sizenesw_cursor then l_xpm := {EV_STOCK_PIXMAPS_IMP}.sizenesw_cursor_xpm when {EV_POINTER_STYLE_CONSTANTS}.sizens_cursor then l_xpm := {EV_STOCK_PIXMAPS_IMP}.sizens_cursor_xpm when {EV_POINTER_STYLE_CONSTANTS}.sizenwse_cursor then l_xpm := {EV_STOCK_PIXMAPS_IMP}.sizenwse_cursor_xpm when {EV_POINTER_STYLE_CONSTANTS}.sizewe_cursor then l_xpm := {EV_STOCK_PIXMAPS_IMP}.sizewe_cursor_xpm when {EV_POINTER_STYLE_CONSTANTS}.uparrow_cursor then l_xpm := {EV_STOCK_PIXMAPS_IMP}.uparrow_cursor_xpm when {EV_POINTER_STYLE_CONSTANTS}.standard_cursor then l_xpm := {EV_STOCK_PIXMAPS_IMP}.standard_cursor_xpm else l_xpm := default_pointer end if l_xpm.is_default_pointer then l_xpm := default_pointer set_size (a_pointer_style.width, a_pointer_style.height) clear else set_from_xpm_data (l_xpm) end else l_pixbuf := a_pointer_style_imp.gdk_pixbuf if not l_pixbuf.is_default_pointer then set_pixmap_from_pixbuf (a_pointer_style_imp.gdk_pixbuf) else set_size (a_pointer_style.width, a_pointer_style.height) clear end end end end init_from_pixel_buffer (a_pixel_buffer: EV_PIXEL_BUFFER) -- Initialize from `a_pixel_buffer' do check attached {EV_PIXEL_BUFFER_IMP} a_pixel_buffer.implementation as l_pixel_buffer_imp then set_pixmap_from_pixbuf (l_pixel_buffer_imp.gdk_pixbuf) end end feature -- Drawing operations update_if_needed -- Update `Current' if needed. do if not in_expose_actions then {GTK}.gtk_widget_queue_draw (visual_widget) end end redraw -- Force `Current' to redraw itself. do if is_displayed then {GTK}.gtk_widget_queue_draw (visual_widget) end end flush -- Ensure that the appearance of `Current' is updated on screen -- immediately. Any changes that have not yet been reflected will -- become visible. do refresh_now end disable_double_buffering -- Disable double buffering for exposed areas. note eis: "name=gtk_widget_set_double_buffered", "src=https://developer.gnome.org/gtk3/stable/GtkWidget.html#gtk-widget-set-double-buffered" obsolete "Disabling double buffering is mostly detrimental to the performance of an app. [2021-06-01]" -- In 3.10 GTK and GDK have been restructured for translucent drawing. -- Since then expose events for double-buffered widgets are culled into a single event to the toplevel GDK window. -- If you now unset double buffering, you will cause a separate rendering pass for every widget. -- This will likely cause rendering problems - in particular related to stacking - -- and usually increases rendering times significantly. do -- {GTK2}.gtk_widget_set_double_buffered (visual_widget, False) end feature -- Measurement width: INTEGER -- Width of the pixmap in pixels. do if cairo_surface /= default_pointer then Result := {CAIRO}.image_surface_get_width (cairo_surface) end end height: INTEGER -- height of the pixmap. do if cairo_surface /= default_pointer then Result := {CAIRO}.image_surface_get_height (cairo_surface) end end feature -- Element change read_from_named_path (file_path: PATH) -- Attempt to load pixmap data from a file specified by `file_name'. local a_cs: EV_GTK_C_STRING g_error: POINTER filepixbuf: POINTER e: EV_GLIB_ERROR m: READABLE_STRING_32 do create a_cs.make_from_path (file_path) filepixbuf := {GDK}.gdk_pixbuf_new_from_file (a_cs.item, $g_error) if g_error /= default_pointer then -- GDK cannot not load the image. Raise an exception. create e.make_from_pointer (g_error) m := e.message e.free {EXCEPTIONS}.raise (m) end set_pixmap_from_pixbuf (filepixbuf) -- Unreference pixbuf so that it may be collected. {GDK}.g_object_unref (filepixbuf) end pixbuf: POINTER -- Converts the Cairo surface to a GdkPixbuf do Result:= {GDK}.gdk_pixbuf_get_from_surface (cairo_surface, 0, 0, width, height) end set_with_default -- Initialize the pixmap with the default -- pixmap (Vision2 logo) do set_from_xpm_data (default_pixmap_xpm) end stretch (a_x, a_y: INTEGER) -- Stretch the image to fit in size `a_x' by `a_y'. local a_gdkpixbuf, scaled_pixbuf: POINTER a_scale_type: INTEGER l_width, l_height: INTEGER do l_width := width l_height := height if l_width /= a_x or else l_height /= a_y then a_gdkpixbuf := pixbuf_from_drawable if l_width <= 16 and then l_height <= 16 then -- For small images this method scales better a_scale_type := {GDK}.gdk_interp_nearest else -- For larger images this mode provides better scaling a_scale_type := {GDK}.gdk_interp_bilinear end scaled_pixbuf := {GDK}.gdk_pixbuf_scale_simple (a_gdkpixbuf, a_x, a_y, a_scale_type) {GDK}.g_object_unref (a_gdkpixbuf) set_pixmap_from_pixbuf (scaled_pixbuf) {GDK}.g_object_unref (scaled_pixbuf) end end set_size (a_width, a_height: INTEGER) -- Set the size of the pixmap to `a_width' by `a_height'. do release_previous_cairo_surface cairo_surface := {CAIRO}.image_surface_create ({CAIRO}.format_argb32, a_width, a_height) get_new_cairo_context init_default_values end reset_for_buffering (a_width, a_height: INTEGER) -- Resets the size of the pixmap without keeping original image or clearing background. do set_size (a_width, a_height) end set_mask (a_mask: EV_BITMAP) -- Set the GdkBitmap used for masking `Current'. do end feature {EV_GTK_DEPENDENT_APPLICATION_IMP, EV_ANY_I} -- Implementation pixbuf_from_drawable_at_position (src_x, src_y, dest_x, dest_y, a_width, a_height: INTEGER): POINTER -- Return a GdkPixbuf object from the current Gdkpixbuf structure do Result := {GDK}.gdk_pixbuf_get_from_surface (cairo_surface, src_x, src_y, a_width, a_height) end feature {EV_INTERMEDIARY_ROUTINES} -- Implementation in_expose_actions: BOOLEAN -- Is `Current' in an expose action? process_draw_event (a_cairo_context: POINTER): BOOLEAN -- Call the expose actions for the drawing area. local l_width, l_height: INTEGER do l_width := {GTK}.gtk_widget_get_allocated_width (c_object) l_height := {GTK}.gtk_widget_get_allocated_height (c_object) {CAIRO}.set_source_surface (a_cairo_context, cairo_surface, (l_width - width) / 2, (l_height - height) / 2) if attached expose_actions_internal as l_expose_actions then in_expose_actions := True -- Even if the callback on "draw" event provides a cairo_context, the implementation -- will use a new one using the expected cairo_surface. start_drawing_session l_expose_actions.call (app_implementation.gtk_marshal.dimension_tuple (0, 0, l_width, l_height)) end_drawing_session in_expose_actions := False end {CAIRO}.paint (a_cairo_context) end feature -- Access raw_image_data: EV_RAW_IMAGE_DATA local -- a_gdkimage, a_visual: POINTER -- a_visual_type, a_pixel: INTEGER -- a_color: POINTER -- a_color_map: POINTER -- a_width: INTEGER -- array_offset, array_size: INTEGER -- array_area: SPECIAL [NATURAL_8] -- color_struct_size: INTEGER do create Result.make_with_alpha_zero (width, height) Result.set_originating_pixmap (attached_interface) -- a_gdkimage := {GDK}.gdk_image_get (drawable, 0, 0, width, height) -- from -- a_width := width * 4 ---- a_color_map := {GDK}.gdk_screen_get_rgb_colormap ({GTK2}.gdk_screen_get_default) -- a_visual := {GDK}.gdk_colormap_get_visual (a_color_map) -- a_visual_type := {GDK}.gdk_visual_struct_type (a_visual) -- a_color := {GTK}.c_gdk_color_struct_allocate -- array_size := a_width * height -- array_area := Result.area -- color_struct_size := {GTK}.c_gdk_color_struct_size -- until -- array_offset = array_size -- loop ---- a_pixel := {GDK}.gdk_image_get_pixel ( ---- a_gdkimage, ---- (array_offset \\ (a_width) // 4), -- Zero based X coord ---- ((array_offset) // a_width) -- Zero based Y coord ---- ) -- {GDK}.gdk_colormap_query_color (a_color_map, a_pixel, a_color) -- -- RGB values of a_color are 16 bit. -- array_area.put (({GDK}.gdk_color_struct_red (a_color) // 256).to_natural_8, array_offset) -- array_area.put (({GDK}.gdk_color_struct_green (a_color) // 256).to_natural_8, array_offset + 1) -- array_area.put (({GDK}.gdk_color_struct_blue (a_color) // 256).to_natural_8, array_offset + 2) -- array_area.put (255, array_offset + 3) -- array_offset := array_offset + 4 -- end -- a_color.memory_free -- {GDK}.gdk_image_destroy (a_gdkimage) end feature -- Duplication copy_pixmap (other: EV_PIXMAP) -- Update `Current' to have same appearance as `other'. -- (So as to satisfy `is_equal'.) local l_width, l_height: INTEGER cr: POINTER do if attached {EV_PIXMAP_IMP} other.implementation as l_other_imp then l_width := l_other_imp.width l_height := l_other_imp.height release_previous_cairo_surface cairo_surface := {CAIRO}.image_surface_create ({CAIRO}.format_argb32, l_width, l_height) get_new_cairo_context cr := cairo_context init_default_values {CAIRO}.set_source_surface (cr, l_other_imp.cairo_surface, 0, 0) {CAIRO}.paint (cr) {CAIRO}.set_source_rgb (cr, 0, 0, 0) end end feature {EV_ANY_I, EV_GTK_DEPENDENT_APPLICATION_IMP} -- Implementation set_pixmap_from_pixbuf (a_pixbuf: POINTER) -- Attempt to load pixmap data from a file specified by `file_name'. local cr: POINTER do release_previous_cairo_surface cairo_surface := {CAIRO}.image_surface_create ( {CAIRO}.FORMAT_ARGB32, {GDK}.gdk_pixbuf_get_width (a_pixbuf), {GDK}.gdk_pixbuf_get_height (a_pixbuf) ) get_new_cairo_context cr := cairo_context set_drawing_mode (drawing_mode_copy) -- Temporarily set the source to the pixbuf so that we can draw it on to the drawable. {GDK_CAIRO}.set_source_pixbuf (cr, a_pixbuf, 0, 0) {CAIRO}.paint (cr) -- Reset the cairo context back to rgb source of black. {CAIRO}.set_source_rgb (cr, 0, 0, 0) end feature {EV_ANY_I} -- Drawing wrapper pre_drawing local cr: like cairo_context do -- If inside drawing session -- the cairo context is already created in `start_drawing_session` if not is_in_drawing_session then -- Reuse or get once a new cairo_context get_cairo_context cr := cairo_context if not cr.is_default_pointer then {CAIRO}.save (cr) end end end post_drawing local cr: like cairo_context do -- If inside drawing session -- keep the cairo context for the next draw operation, it will be releazed by `end_drawing_session` if not is_in_drawing_session then cr := cairo_context if not cr.is_default_pointer then {CAIRO}.restore (cr) end update_if_needed end end feature {NONE} -- Session implementation end_drawing_session do if is_in_top_drawing_session then update_if_needed end Precursor end feature {EV_ANY_I} -- cairo object access cairo_surface: POINTER -- Cairo drawable surface used for storing pixmap data in RGB format. get_cairo_context local l_surface: like cairo_surface do if cairo_context.is_default_pointer then l_surface := cairo_surface if not l_surface.is_default_pointer then cairo_context := {CAIRO}.create_context (l_surface) end end end feature {EV_ANY_I} -- cairo object release release_cairo_surface (a_surface: POINTER) do if not a_surface.is_default_pointer then {CAIRO}.surface_destroy (a_surface) end end release_previous_cairo_surface do clear_cairo_context release_cairo_surface (cairo_surface) cairo_surface := default_pointer end feature {EV_GTK_DEPENDENT_APPLICATION_IMP, EV_ANY_I} -- Implementation internal_xpm_data: POINTER -- Pointer to the appropriate XPM image used for the default stock cursor if any feature {EV_STOCK_PIXMAPS_IMP, EV_PIXMAPABLE_IMP, EV_PIXEL_BUFFER_IMP} -- Implementation set_from_xpm_data (a_xpm_data: POINTER) -- Pixmap symbolizing a piece of information. require xpm_data_not_null: a_xpm_data /= default_pointer local xpmpixbuf: POINTER do -- Store internal xpm data for default stock cursor handling. internal_xpm_data := a_xpm_data xpmpixbuf := {GDK}.gdk_pixbuf_new_from_xpm_data (a_xpm_data) set_pixmap_from_pixbuf (xpmpixbuf) -- Unreference pixbuf so that it may be collected. {GDK}.g_object_unref (xpmpixbuf) end set_from_stock_id (a_stock_id: POINTER) -- Pixmap symbolizing a piece of information require a_stock_id_not_null: a_stock_id /= default_pointer local stock_pixbuf: POINTER l_error: POINTER l_screen: POINTER do -- Check if the current widget realized as well -- Gtk-WARNING produces "Drawing a gadget with negative dimensions" if not {GTK2}.gtk_widget_get_realized (c_object) then {GTK}.gtk_widget_realize (c_object) end if {GTK2}.gtk_widget_has_screen(c_object) then l_screen:= {GTK2}.gtk_widget_get_screen(c_object) else l_screen:= {GDK}.gdk_screen_get_default end stock_pixbuf := {GTK2}.gtk_icon_theme_load_icon ({GTK2}.gtk_icon_theme_get_for_screen(l_screen), a_stock_id, 48, 0, $l_error) if stock_pixbuf /= default_pointer then -- If a stock pixmap can be found then set it, else do nothing. set_pixmap_from_pixbuf (stock_pixbuf) {GDK}.g_object_unref (stock_pixbuf) end end feature {NONE} -- Implementation save_to_named_path (a_format: EV_GRAPHICAL_FORMAT; a_file_path: PATH) -- Save `Current' in `a_format' to `a_file_path' local a_gdkpixbuf, stretched_pixbuf: POINTER a_handle, a_filetype: EV_GTK_C_STRING gerror: POINTER e: EV_GLIB_ERROR m: READABLE_STRING_32 do if app_implementation.writeable_pixbuf_formats.has (a_format.file_extension.as_upper) then -- Perform custom saving with GdkPixbuf a_gdkpixbuf := pixbuf_from_drawable create a_handle.make_from_path (a_file_path) a_filetype := a_format.file_extension if a_format.scale_width > 0 and then a_format.scale_height > 0 then stretched_pixbuf := {GDK}.gdk_pixbuf_scale_simple (a_gdkpixbuf, a_format.scale_width, a_format.scale_height, {GTK2}.gdk_interp_bilinear) -- Unref original pixbuf so it gets deleted from memory {GDK}.g_object_unref (a_gdkpixbuf) -- Set our scaled pixbuf to be the one that is saved a_gdkpixbuf := stretched_pixbuf end {GDK}.gdk_pixbuf_save (a_gdkpixbuf, a_handle.item, a_filetype.item, $gerror) {GDK}.g_object_unref (a_gdkpixbuf) if not gerror.is_default_pointer then -- GDK cannot save the image. Raise an exception. create e.make_from_pointer (gerror) m := e.message e.free {EXCEPTIONS}.raise (m) end else -- If Gtk cannot save the file then the default is called. Precursor {EV_PIXMAP_I} (a_format, a_file_path) end end destroy -- Destroy the pixmap and resources. do clear_cairo_context if not cairo_surface.is_default_pointer then release_cairo_surface (cairo_surface) cairo_surface := default_pointer end Precursor {EV_PRIMITIVE_IMP} end c_object_dispose -- Called when `c_object' is destroyed. -- Only called if `Current' is referenced from `c_object'. -- Render `Current' unusable. do if not cairo_context.is_default_pointer then {CAIRO}.destroy (cairo_context) cairo_context := default_pointer end if not cairo_surface.is_default_pointer then {CAIRO}.surface_destroy (cairo_surface) cairo_surface := default_pointer end Precursor end feature {NONE} -- Externals default_pixmap_xpm: POINTER external "C | %"ev_c_util.h%"" alias "default_pixmap_xpm" end feature {NONE} -- Constants Monochrome_color_depth: INTEGER = 1 -- Black and White color depth (for mask). feature {EV_ANY, EV_ANY_I} -- Implementation interface: detachable EV_PIXMAP note option: stable attribute end note ca_ignore: "CA011", "CA011: too many arguments" copyright: "Copyright (c) 1984-2023, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software 5949 Hollister Ave., Goleta, CA 93117 USA Telephone 805-685-1006, Fax 805-685-6869 Website http://www.eiffel.com Customer support http://support.eiffel.com ]" end