On Dec 21, 2007, at 5:35 PM, Jeffrey Ratcliffe wrote:

On 20/12/2007, muppet <[EMAIL PROTECTED]> wrote:
You'll also want to hush the "unused variable `class'" warning by not
including the line "SV * class" in the argument listing of
gtk_image_view_new().

Thanks.

No problem.


I hope I have now finished the bulk of the work. The remaining
bindings compile without error. However, the only constructor that is
passing a correctly blessed object is the main Gtk2::ImageView one,
all the others pass 'Glib::Object::_Unregistered::Gtk*', so in testing
isa, I get failures like:

#   Failed test 'The object isa Gtk2::AnimView'
#   in t/animview.t at line 23.
#     The object isn't a 'Gtk2::AnimView' it's a
'Glib::Object::_Unregistered::GtkAnimView'

I assume I am missing something fundamental, and would be grateful for a hint.


Hrm. It looks like gtkimageview.h doesn't follow the convention of one-stop header for everything in the gtkimageview library; to get GTK_TYPE_ANIM_VIEW #defined , you have to #include gtkanimview.h separately. The code generated by Glib::CodeGen in register.xsh wraps the registration of the object type for GtkAnimView in #ifdef GTK_TYPE_ANIM_VIEW. ImageView.xs, which #includes register.xsh, doesn't #include gtkanimview.h, so that macro isn't defined, and we simply don't register the GType to package mapping.

Simple solution:  #include gtkanimview.h in ImageView.xs.

More proactive: #include gtkanimview.h in gtkimageviewperl.h, and make sure you use gtkimageviewperl.h everywhere instead of duplicating the #include lines for all the various headers. The attached patch does this. One reason to do this is that anybody wanting to create an XS extension that builds atop yours (as yours builds atop gtk2perl.h) will expect everything it needs to come through one header, gtkimageviewperl.h. More immediately, this ensures that every file inside your project sees the same set of preprocessor definitions at build time. (Repeat after me: the C preprocessor is Evil.)


Oh, and check your license comment blocks; you've attributed your binding work to the gtk2-perl team as of four years ago, and have a CVS $Header:$ tag showing me as the most recent author, in June 2003. ;-)


A bigger issue: namespacing. The classes you define are Gtk2::ImageView, Gtk2::ImageToolSelector, etc. That is, they are right in the Gtk2:: namespace, waiting for potential name clashes if and when gtk+ adds something called GtkImageView. Now, of course, the same problem exists with the upstream library, but instead of taking up a bunch of slots in the Gtk2:: namespace, how about doing something like this, instead?

    GtkImageView -> Gtk2::ImageView
    GtkImageTransp -> Gtk2::ImageView::Transp
    GtkImageToolPainter -> Gtk2::ImageView::Tool::Painter



So, attached:

fix-includes.patch: make everything include gtkimageviewperl.h instead of various files.

requirements.patch: in Makefile.PL, look for gtkimageview 1.5.0, since these bindings won't compile against older. You can play with ExtUtils::PkgConfig and look at other modules for hints on how to make your bindings compile against multiple versions of the upstream library.

transp-map.patch: GtkImageTransp was listed incorrectly in the maps file as mapping to the package name Gtk2::ImageView. Fix that.

interactive.pl: a quick transliteration of gtkimageview/tests/ interactive.c. Note that the way this program uses the GtkImageTransp values in the UI manager actions doesn't really play nicely with perl syntax for enums, so there's a terrible, evil, horrible hack in its place. If you golf this down to more perlish code, using closures and such, you can sidestep the problem.


#!/usr/bin/perl -w
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; coding: utf-8 -*-
# vim: set expandtab softtabstop=4 shiftwidth=4 :

use strict;

# Library under test.
use Gtk2::ImageView;

use Glib ':constants';

# //////////////////////////////////////////////////////////////////////
# ///// Global data ////////////////////////////////////////////////////
# //////////////////////////////////////////////////////////////////////
my $open_dialog;
my $view;
my $main_window;
my $default_group;
my $image_group;
my $transform_group;
my $is_fullscreen;
my $statusbar;

# Label that displays the active selection.
my $sel_info_label;

# Tools
my $dragger;
my $selector;
my $painter;

# Context ID:s for the Statusbar
my $help_msg_cid;
my $image_info_cid;

# //////////////////////////////////////////////////////////////////////
# ///// Opener dialog //////////////////////////////////////////////////
# //////////////////////////////////////////////////////////////////////
sub init_open_dialog {
    $open_dialog = Gtk2::FileChooserDialog->new ("Open Image",
                                                 $main_window,
                                                 'open',
                                                 'gtk-cancel' => 'cancel',
                                                 'gtk-open'   => 'accept');
}

# //////////////////////////////////////////////////////////////////////
# ///// ImageViewerApp /////////////////////////////////////////////////
# //////////////////////////////////////////////////////////////////////
sub push_image_info {
    my ($basename, $anim) = @_;
    $statusbar->push ($image_info_cid,
                      sprintf ("%s, %d x %d pixels",
                               $basename, $anim->get_width, $anim->get_height));
}


sub load_filename {
    my ($path) = @_;

    eval {
        my $anim = Gtk2::Gdk::PixbufAnimation->new_from_file ($path);

        $view->set_anim ($anim);

        my $basename = Glib::filename_display_basename ($path);
        $main_window->set_title ($basename);
        push_image_info ($basename, $anim);

        $image_group->set_sensitive (TRUE);

        # Only active the transform_group if the loaded object is a single
        # image -- transformations cannot be applied to animations.
        $transform_group->set_sensitive ($anim->is_static_image ());
    };
    if ($@) {
        print "No anim!  [EMAIL PROTECTED]";
    }
}

sub gdk_rectangle_to_str {
    my $r = shift;
    sprintf "(%d, %d)-[%d, %d]", $r->x, $r->y, $r->width, $r->height;
}

sub get_enum_value {
    my ($package, $string) = @_;
    my @v = Glib::Type->list_values ($package);
    for (my $i = 0 ; $i < @v ; $i++) {
        use Data::Dumper;
        print Dumper($v[$i]);
        return $i if $v[$i]{name} eq $string or $v[$i]{nick} eq $string;
    }
}
#sub GTK_IMAGE_TRANSP_COLOR { get_enum_value ('Gtk2::ImageTransp', 'color') }
#sub GTK_IMAGE_TRANSP_BACKGROUND { get_enum_value 'Gtk2::ImageTransp', 'background' }
#sub GTK_IMAGE_TRANSP_GRID { get_enum_value 'Gtk2::ImageTransp', 'grid' }

sub GTK_IMAGE_TRANSP_COLOR { 0 }
sub GTK_IMAGE_TRANSP_BACKGROUND { 1 }
sub GTK_IMAGE_TRANSP_GRID { 2 }


# //////////////////////////////////////////////////////////////////////
# ///// Callbacks //////////////////////////////////////////////////////
# //////////////////////////////////////////////////////////////////////
sub sel_changed_cb {
    my ($selector, $label) = @_;
    my $sel = $selector->get_selection ();
    if (!$sel->width || !$sel->height) {
        $label->set_text ("");
    } else {
        $label->set_text (gdk_rectangle_to_str ($sel));
    }
}

sub change_image_tool_cb {
    my ($action, $current) = @_;
    my $value = $current->get_current_value ();
    my $tool = $selector;
    if ($value == 10) {
        $tool = $dragger;
    } elsif ($value == 30) {
        $tool = $painter;
    }
    $view->set_tool ($tool);
    if ($value == 20) {
        sel_changed_cb ($selector, $sel_info_label);
    } else {
        $sel_info_label->set_text ("");
    }
}

sub zoom_in_cb {
    $view->zoom_in ();
}

sub zoom_out_cb {
    $view->zoom_out ();
}

sub zoom_100_cb {
    $view->set_zoom (1.0);
}

sub zoom_to_fit_cb {
    $view->set_fitting (TRUE);
}

sub open_image_cb {
    my ($action) = @_;
    if (!$open_dialog) {
        init_open_dialog ();
    }
    if ($open_dialog->run () eq 'accept') {
        load_filename ($open_dialog->get_filename ());
    }
    $open_dialog->hide ();
}

sub fullscreen_cb {
    # I do not have the patience to implement all things you do to
    # fullscreen for real. This is a faked approximation.
    $is_fullscreen = !$is_fullscreen;
    if ($is_fullscreen) {
        $main_window->fullscreen ();
    } else {
        $main_window->unfullscreen ();
    }

    $view->set_show_cursor (!$is_fullscreen);
    $view->set_show_frame (!$is_fullscreen);
    $view->set_black_bg ($is_fullscreen);
}

sub transform_cb {
    my $pixbuf = $view->get_pixbuf ();
# Not doing this just now, as perl is very inefficient for such things
#    my $pixels = $pixbuf->get_pixels ();
#    my $rowstride = $pixbuf->get_rowstride ();
#    my $n_channels = $pixbuf->get_n_channels ();
#    for (int y = 0; y < $pixbuf->get_height (); y++)
#        for (int x = 0; x < $pixbuf->get_width (); x++)
#        {
#            guchar *p = pixels + y * rowstride + x * n_channels;
#            for (int n  = 0; n < 3; n++)
#                p[n] ^= 0xff;
#        }
    $view->set_pixbuf ($pixbuf, FALSE);
}

sub change_zoom_quality_cb {
    my ($action, $current) = @_;
    $view->set_interpolation ($current->get_current_value ()
                              ? 'bilinear'
                              : 'nearest');
}

sub change_transp_type_cb {
    my ($action, $current) = @_;
    my $color = 0;
    my $transp = $current->get_current_value ();
    if ($transp == GTK_IMAGE_TRANSP_COLOR) {
        $color = 0x000000;
    }
    $view->set_transp ($transp, $color);
}

sub menu_item_select_cb {
    my ($proxy) = @_;

#    GtkAction *action = g_object_get_data (G_OBJECT (proxy), "gtk-action");
    my $action =
        Glib::Object->new_from_pointer ($proxy->get_data ('gtk-action'));

    my $msg = $action->get ('tooltip');
    $statusbar->push ($help_msg_cid, $msg) if $msg;
}

sub menu_item_deselect_cb {
    my ($item) = @_;
    $statusbar->pop ($help_msg_cid);
}

sub connect_proxy_cb {
    my ($ui, $action, $proxy) = @_;
    return unless $proxy->isa ('Gtk2::MenuItem');
    $proxy->signal_connect (select => \&menu_item_select_cb);
    $proxy->signal_connect (deselect => \&menu_item_deselect_cb);
}

sub disconnect_proxy_cb {
    my ($ui, $action, $proxy) = @_;
    return unless $proxy->isa ('Gtk2::MenuItem');
    $proxy->signal_handlers_disconnect_by_func (\&menu_item_select_cb);
    $proxy->signal_handlers_disconnect_by_func (\&menu_item_deselect_cb);
}

sub zoom_changed_cb {
    my ($view, $label) = @_;
    $label->set_text (sprintf "%d%%", int ($view->get_zoom () * 100.0));
}

sub kill_app_cb {
    # Kill the widgets.
    $main_window->destroy ();
    $open_dialog->destroy () if $open_dialog;
    Gtk2->main_quit ();
}

# //////////////////////////////////////////////////////////////////////
# ///// MainWindow /////////////////////////////////////////////////////
# //////////////////////////////////////////////////////////////////////
sub main_window_new {
    my ($widget, $width, $height) = @_;
    my $window = Gtk2::Window->new ();
    $window->set_default_size ($width, $height);
    $window->add ($widget);
    $window->signal_connect (delete_event => \&kill_app_cb);
    return $window;
}

# //////////////////////////////////////////////////////////////////////
# ///// UI Setup ///////////////////////////////////////////////////////
# //////////////////////////////////////////////////////////////////////
my @default_actions = (
    [ "FileMenu", undef, "_File" ],
    [
        "Open",
        'gtk-open',
        "_Open image",
        undef,
        "Open an image",
        \&open_image_cb
    ],
    [
        "Quit",
        'gtk-quit',
        "_Quit me!",
        undef,
        "Quit the program",
        \&kill_app_cb
    ],
    ["EditMenu", undef, "_Edit"],
    ["ViewMenu", undef, "_View"],
    ["TranspMenu", undef, "_Transparency"]
);

my @quality_actions = (
    [
        "QualityHigh",
        undef,
        "_High Quality",
        undef,
        "Use high quality zoom",
        TRUE
    ],
    [
        "QualityLow",
        undef,
        "_Low Quality",
        undef,
        "Use low quality zoom",
        FALSE
    ]
);

my @transp_actions = (
    [
        "TranspGrid",
        undef,
        "Square _Grid",
        undef,
        "Draw a grid on transparent parts",
        GTK_IMAGE_TRANSP_GRID
    ],
    [
        "TranspBackground",
        undef,
        "_Background",
        undef,
        "Draw background color on transparent parts",
        GTK_IMAGE_TRANSP_BACKGROUND
    ],
    [
        "TranspBlack",
        undef,
        "_Black",
        undef,
        "Draw black color on transparent parts",
        GTK_IMAGE_TRANSP_COLOR
    ]
);

my @image_actions = (
    [
        "ZoomIn",
        'gtk-zoom-in',
        "Zoom _In",
        "<control>plus",
        "Zoom in one step",
        \&zoom_in_cb
    ],
    [
        "ZoomOut",
        'gtk-zoom-out',
        "Zoom _Out",
        "<control>minus",
        "Zoom out one step",
        \&zoom_out_cb
    ],
    [
        "ZoomNormal",
        'gtk-zoom-100',
        "_Normal Size",
        "<control>0",
        "Set zoom to natural size of the image",
        \&zoom_100_cb
    ],
    [
        "ZoomFit",
        'gtk-zoom-fit',
        "Best _Fit",
        undef,
        "Adapt zoom to fit image",
        \&zoom_to_fit_cb
    ],
    [
        "Fullscreen",
        'gtk-fullscreen',
        "_Fullscreen Mode",
        "F11",
        "View image in fullscreen",
        \&fullscreen_cb
    ]
);

my @image_tools = (
    [
        "DraggerTool",
        'gtk-refresh',
        "_Drag",
        undef,
        "Use the hand tool",
        10
    ],
    [
        "SelectorTool",
        'gtk-media-pause',
        "_Select",
        undef,
        "Use the rectangular selection tool",
        20
    ],
    [
        "PainterTool",
        'gtk-media-play',
        "_Paint",
        undef,
        "Use the painter tool",
        30
    ]
);

my @transform_actions = (
    [
        "Transform",
        undef,
        "_Transform",
        "<control>T",
        "Apply an XOR transformation to the image",
        \&transform_cb
    ]
);

my $ui_info = "
<ui>
  <menubar name = 'MenuBar'>
    <menu action = 'FileMenu'>
      <menuitem action = 'Open'/>
      <menuitem action = 'Quit'/>
    </menu>
    <menu action = 'EditMenu'>
      <menuitem action = 'Transform'/>
      <separator/> 
      <menuitem action = 'DraggerTool'/>
      <menuitem action = 'SelectorTool'/>
      <menuitem action = 'PainterTool'/>
    </menu>
    <menu action = 'ViewMenu'>
      <menuitem action = 'Fullscreen'/>
      <separator/>
      <menuitem action = 'ZoomIn'/>
      <menuitem action = 'ZoomOut'/>
      <menuitem action = 'ZoomNormal'/>
      <menuitem action = 'ZoomFit'/>
      <separator/>
      <menu action = 'TranspMenu'>
        <menuitem action = 'TranspGrid'/>
        <menuitem action = 'TranspBackground'/>
        <menuitem action = 'TranspBlack'/>
      </menu>
      <separator/>
      <menuitem action = 'QualityHigh'/>
      <menuitem action = 'QualityLow'/>
    </menu>
  </menubar>
  <toolbar name = 'ToolBar'>
    <toolitem action='Quit'/>
    <toolitem action='Open'/>
    <separator/>
    <toolitem action='DraggerTool'/>
    <toolitem action='SelectorTool'/>
    <toolitem action='PainterTool'/>
    <separator/>
    <toolitem action='ZoomIn'/>
    <toolitem action='ZoomOut'/>
    <toolitem action='ZoomNormal'/>
    <toolitem action='ZoomFit'/>
  </toolbar>
</ui>";


sub parse_ui {
    my $uimanager = shift;

    eval { $uimanager->add_ui_from_string ($ui_info) };

    if ($@) {
        die "Unable to create menus: [EMAIL PROTECTED]";
    }
}

sub add_action_groups {
    my $uimanager = shift;

    # Setup the default group.
    $default_group = Gtk2::ActionGroup->new ("default");
    $default_group->add_actions ([EMAIL PROTECTED]);
    $default_group->add_radio_actions ([EMAIL PROTECTED],
                                       10,
                                       \&change_image_tool_cb);
    $uimanager->insert_action_group ($default_group, 0);

    # Setup the image group.
    $image_group = Gtk2::ActionGroup->new ("image");
    $image_group->add_actions ([EMAIL PROTECTED]);
    $image_group->add_radio_actions ([EMAIL PROTECTED],
                                     TRUE,
                                     \&change_zoom_quality_cb);
    $image_group->add_radio_actions ([EMAIL PROTECTED],
                                     GTK_IMAGE_TRANSP_GRID,
                                     \&change_transp_type_cb);
    $image_group->set_sensitive (FALSE);
    $uimanager->insert_action_group ($image_group, 0);

    # Transform group
    my $transform_group = Gtk2::ActionGroup->new ("transform");
    if ($transform_group) {
        $transform_group->add_actions ([EMAIL PROTECTED]);
        $transform_group->set_sensitive (FALSE);
        $uimanager->insert_action_group ($transform_group, 0);
    }
}

sub setup_layout {
    my $uimanager = shift;

    my $box = Gtk2::VBox->new (FALSE, 0);
    
    my $menu = $uimanager->get_widget ("/MenuBar");
    $box->pack_start ($menu, FALSE, FALSE, 0);

    my $toolbar = $uimanager->get_widget ("/ToolBar");
    $box->pack_start ($toolbar, FALSE, FALSE, 0);

    my $scroll_win = Gtk2::ImageScrollWin->new ($view);

    $box->pack_start ($scroll_win, TRUE, TRUE, 0); 

    $statusbar = Gtk2::Statusbar->new ();

    # A label in the statusbar that displays the current selection if
    # there is one.
    my $sel_info_frame = Gtk2::Frame->new ();
    $sel_info_frame->set_shadow_type ('in');

    $sel_info_label = Gtk2::Label->new ("");
    $sel_info_frame->add ($sel_info_label);

    $selector->signal_connect (selection_changed => \&sel_changed_cb,
                               $sel_info_label);
    
    $statusbar->pack_start ($sel_info_frame, FALSE, FALSE, 0);

    # A label in the statusbar that displays the current zoom. It
    # updates its text when the zoom-changed signal is fired from the
    # view.
    my $zoom_info_frame = Gtk2::Frame->new ();
    $zoom_info_frame->set_shadow_type ('in');

    my $zoom_info_label = Gtk2::Label->new ("100%");
    $zoom_info_frame->add ($zoom_info_label);

    $view->signal_connect (zoom_changed => \&zoom_changed_cb, $zoom_info_label);

    $statusbar->pack_start ($zoom_info_frame, FALSE, FALSE, 0);

    $box->pack_end ($statusbar, FALSE, FALSE, 0);
    return $box;
}

sub setup_main_window {
    my $uimanager = Gtk2::UIManager->new ();

    $uimanager->signal_connect (connect_proxy => \&connect_proxy_cb);
    $uimanager->signal_connect (disconnect_proxy => \&disconnect_proxy_cb);

    add_action_groups ($uimanager);
    parse_ui ($uimanager);

    my $accels = $uimanager->get_accel_group ();
    die "no accels!" unless $accels;

    my $vbox = setup_layout ($uimanager);
    $main_window = main_window_new ($vbox, 700, 500);
    $main_window->add_accel_group ($accels);

    $view->grab_focus ();

    # Setup context ID:s
    $help_msg_cid = $statusbar->get_context_id ("help_msg");
    $image_info_cid = $statusbar->get_context_id ("image_info");
}

#	char **filenames = NULL;
#	GOptionEntry options[] = {
#		{
#			G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY,
#			&filenames, NULL, "[FILE...]"
#		},
#		{NULL}
#	};
#	GOptionContext *ctx = g_option_context_new ("Sample image viewer");
#	g_option_context_add_main_entries (ctx, options, "example1");
#	g_option_context_parse (ctx, &argc, &argv, NULL);
#	g_option_context_free (ctx);
	
Gtk2->init;

$view = Gtk2::AnimView->new ();

$dragger = Gtk2::ImageToolDragger->new ($view);
$selector = Gtk2::ImageToolSelector->new ($view);
$painter = Gtk2::ImageToolPainter->new ($view);

setup_main_window ();

load_filename ($ARGV[0]) if @ARGV;

$main_window->show_all ();
Gtk2->main ();

Attachment: fix-includes.patch
Description: Binary data

Attachment: requirements.patch
Description: Binary data

Attachment: transp-map.patch
Description: Binary data




--
One, two, free, four, five, six, sebben, eight, nine, ten, elebben, twull, fourteen, sickteen, sebbenteen, eightteen, elebbenteen, fiffeen, elebbenteen!
  -- Zella, aged three, counting to twenty.


_______________________________________________
gtk-perl-list mailing list
gtk-perl-list@gnome.org
http://mail.gnome.org/mailman/listinfo/gtk-perl-list

Reply via email to