Gtk2-Ex-MenuView
view release on metacpan or search on metacpan
lib/Gtk2/Ex/MenuView.pm view on Meta::CPAN
properties => [ Glib::ParamSpec->object
('model',
(do {
my $str = 'Model';
eval { require Locale::Messages;
Locale::Messages::dgettext('gtk20-properties',$str)
} || $str }),
'TreeModel to display.',
'Gtk2::TreeModel',
Glib::G_PARAM_READWRITE),
Glib::ParamSpec->enum
('want-activate',
'Want activate',
'Whether to connect and generate a unified activate signal.',
'Gtk2::Ex::MenuView::WantActivate',
'leaf',
Glib::G_PARAM_READWRITE),
Glib::ParamSpec->enum
('want-visible',
'Want visible',
'Whether to automatically make items visible.',
'Gtk2::Ex::MenuView::WantVisible',
'show_all',
Glib::G_PARAM_READWRITE),
];
# TODO:
#
# dirty 0, 1=item, 2=separator
#
# current_item_at_indices ...
# $menu->get_model_items
# $menu->model_items_array
# $menuview->menu_at_path
# $menuview->model_items
# $menuview->foreach_model_item
# item_get_indices
# mnemonics
# accel key from model example
# circular protection pay attention to model changes ?
#------------------------------------------------------------------------------
# sub INIT_INSTANCE {
# my ($self) = @_;
# }
sub SET_PROPERTY {
my ($self, $pspec, $newval) = @_;
my $pname = $pspec->get_name;
$self->{$pname} = $newval; # per default GET_PROPERTY
if ($pname eq 'model') {
my $model = $newval;
Scalar::Util::weaken (my $weak_self = $self);
my $ref_weak_self = \$weak_self;
$self->{'model_ids'} = $model && Glib::Ex::SignalIds->new
($model,
$model->signal_connect (row_changed => \&_do_row_changed,
$ref_weak_self),
$model->signal_connect (row_deleted => \&_do_row_deleted,
$ref_weak_self),
$model->signal_connect (row_inserted => \&_do_row_inserted,
$ref_weak_self),
$model->signal_connect (rows_reordered => \&_do_rows_reordered,
$ref_weak_self),
$model->signal_connect
(row_has_child_toggled => \&_do_row_has_child_toggled, $ref_weak_self));
_dirty_all_menus ($self);
}
}
sub _freshen_item {
my ($self, $menu, $menu_path, $i) = @_;
### _freshen_item() number: $i
my $model = $self->{'model'} || return;
if (delete $menu->{'all_dirty'}) {
### all_dirty, make dirty array
my $menu_iter = ($menu_path->get_depth
? $model->get_iter($menu_path) || do {
### no iter for menu_path
return;
}
: undef);
my $len = $model->iter_n_children ($menu_iter);
$menu->{'dirty'} ||= [];
@{$menu->{'dirty'}} = ((Gtk2::Ex::MenuView::Menu::_DIRTY_ITEM()
| Gtk2::Ex::MenuView::Menu::_DIRTY_SEPARATOR())
x $len);
}
### dirty_bits: $menu->{'dirty'}->[$i]
my $dirty_bits = delete $menu->{'dirty'}->[$i] || do {
### not dirty, no freshen needed
return;
};
# still dirty if recursive freshens such as item_at_indices() look, but
# cleared when this _freshen_item() returns
local $menu->{'dirty'}->[$i] = $dirty_bits;
my $item_path = $menu_path->copy;
$item_path->append_index ($i);
my $item_iter = $model->get_iter($item_path) || do {
### no iter for item_path
return;
};
my $key = $item_path->to_string;
### in progress: $self->{'item_update_in_progress'}
my $in_progress = $self->{'item_update_in_progress'} || {};
if ($in_progress->{$key}) {
### croak for recursion
croak "Recursive item create or update for path=$key";
lib/Gtk2/Ex/MenuView.pm view on Meta::CPAN
}
$menuview->signal_emit ('activate', $item, $model, $path, $iter);
}
sub _item_index_to_menu_pos {
my ($menu, $i) = @_;
### _item_index_to_menu_pos(): $i
### menu: "@{[$menu->get_children]}"
my $children = $menu->{'children'} || return -1;
### children: "@{[grep {defined} @$children]}"
my $pos = -1;
OUTER: for ( ; $i < @$children; $i++) {
if (my $after = $children->[$i]) {
$after = $after->{'Gtk2::Ex::MenuView.separator'} || $after;
foreach my $child ($menu->get_children) {
$pos++;
if ($child == $after) { last OUTER; }
}
### oops, not found in menu: "$after"
### assert: 0
return -1;
}
}
### _item_index_to_menu_pos(): "$i -> $pos"
return $pos;
}
sub _dirty_all_menus {
my ($self) = @_;
### _dirty_all_menus
my $menu = $self;
my @pending;
do {
_dirty_menu($menu);
push @pending, map { my $submenu = $_->get_submenu;
($submenu ? ($submenu) : ()) }
@{$menu->{'children'}};
} while ($menu = pop @pending);
}
# sub _menushellbits_menu_and_submenus {
# my ($menu) = @_;
# my @pending;
# my @ret;
# do {
# push @ret, $menu;
# push @pending, grep {defined} map {$_->get_submenu} $menu->get_children;
# } while ($menu = pop @pending);
# return @ret;
# }
#------------------------------------------------------------------------------
# dirtiness etc
sub _idle_freshen {
my ($menu) = @_;
if ($menu->mapped) {
my $self = $menu->_get_menuview;
Scalar::Util::weaken (my $weak_self = $self);
$self->{'idle'} ||= Glib::Ex::SourceIds->new
(Glib::Idle->add (\&_do_idle, \$weak_self,
Gtk2::GTK_PRIORITY_RESIZE - 1)); # just before resize
}
}
sub _do_idle {
my ($ref_weak_self) = @_;
### _do_idle
my $self = $$ref_weak_self || return;
delete $self->{'idle'};
my $menu = $self;
my @pending;
do {
if ($menu->mapped) {
$menu->_freshen;
}
push @pending, map {$_->get_submenu || ()} @{$menu->{'children'}};
} while ($menu = pop @pending);
return 0; # Glib::SOURCE_REMOVE
}
# mark $menu as all dirty
sub _dirty_menu {
my ($menu) = @_;
delete $menu->{'dirty'};
$menu->{'all_dirty'} ||= do {
_idle_freshen ($menu);
1;
}
}
# mark $menu item number $i as dirty
sub _dirty_add {
my ($self, $menu, $i, $dirty_bits) = @_;
### _dirty_add(): $i, "$menu", $dirty_bits
if (! $menu->{'all_dirty'}) {
my $dirty = ($menu->{'dirty'} ||= []);
$dirty->[$i] |= do {
_idle_freshen ($self);
$dirty_bits; # item
};
}
}
sub _dirty_item_and_following_separator {
my ($self, $menu, $path) = @_;
my $i = ($path->get_indices)[-1];
_dirty_add ($self, $menu, $i,
Gtk2::Ex::MenuView::Menu::_DIRTY_ITEM());
if (my $model = $self->{'model'}) {
$path = $path->copy;
$path->next;
if ($model->get_iter($path)) {
_dirty_add ($self, $menu, $i+1,
Gtk2::Ex::MenuView::Menu::_DIRTY_SEPARATOR());
lib/Gtk2/Ex/MenuView.pm view on Meta::CPAN
my ($menuview, $item, $model, $path, $iter, $userdata) = @_;
return Gtk2::MenuItem->new_with_label ($model->get($iter,0));
}
=head2 Item Visibility
Usually all items should be made visible and MenuView does that
automatically by default. If you want to manage visibility yourself then
set C<want-visiblity> to C<no> to make MenuView leave it alone completely,
or C<show> to have MenuView just C<< $item->show >> the item itself, not
recursing into its children.
An invisible item or a return of C<undef> from C<item-create-or-update> both
result in nothing displayed. If items are slow to create you might keep
them in the menu but invisible when unwanted (trading memory against
slowness of creation). Visibility could be controlled from something
external too.
From a user interface point of view it's often better to make items
insensitive (greyed out) when not applicable etc. You can set the item
C<sensitive> property (see L<Gtk2::Widget>) from C<item-create-or-update>
according to row data, or link it up to something external, etc.
=head2 Check Items
One use for a C<Gtk2::CheckMenuItem> is to have the C<active> property
display and control a column in the model. In C<item-create-or-update> do
C<< $item->set_active >> to make the item show the model data, then in the
C<activate> signal handler do C<< $model->set >> to put the item's new
C<active> state into the model. See F<examples/checkitem.pl> for a complete
sample program.
C<< $model->set >> under C<activate> will cause MenuView to call
C<item-create-or-update> again because the model row has changed, and
C<< $item->set_active >> there may emit the C<activate> signal again. This
would be an endless recursion except that C<set_activate> notices when the
item is already in the state requested and does nothing. Be sure to have a
cutoff like that.
Another possibility is to tie the check item C<active> property to something
external using signal handlers to keep them in sync.
L<Glib::Ex::ConnectProperties> is a handy way to link properties between
widgets or objects.
It's usually not a good idea to treat a check item's C<active> property as
the "master" storage for a flag, because the row drag-and-drop in
C<Gtk2::TreeView> and similar doesn't work by reordering rows but instead by
inserting a copy then deleting the old. MenuView can't tell when that's
happening and creates a new item shortly followed by deleting the old, which
loses the flag value in the old item.
=head2 Radio Button Items
C<Gtk2::RadioMenuItem> is a subclass of C<Gtk2::CheckMenuItem> so the above
L</Check Items> notes apply to it too.
When creating or updating a C<Gtk2::RadioMenuItem> the "group" is set by
passing another radio item widget to group with. Currently there's not much
in MenuView to help you find a widget to group with.
Keeping group members in a weakened bucket is one possibility. For
top-level rows another is C<< $menuview->get_children >> (the
C<Gtk2::Container> method) to find a suitable existing group item. If radio
items are all you ever have in the menu then just the first (if any) will be
enough.
Calling C<< $menuview->item_at_path >> to get another row is no use because
you don't want to create new items, only find an existing one. In the
future there'll probably be some sort of get current item at path if exists,
or get existing items and paths, or get current items in submenu, or get
submenu widget, etc.
=head2 CellView Items
C<Gtk2::ComboBox> displays its model-based menus using a C<Gtk2::CellView>
child in each item with C<Gtk2::CellRenderer> objects for the drawing. Alas
it doesn't make this available for general use (only with the selector box,
launching from there). You can make a similar thing with MenuView by
creating items with a CellView child each.
The only thing to note is that as of Gtk 2.20 a CellView doesn't
automatically redraw if the model row changes. C<item-create-or-update> is
called for a row change and from there you can force a redraw with
C<< $cellview->set_displayed_row >> with the same path already set in it.
See F<examples/cellview.pl> for a complete program.
Often a single CellRenderer can be shared among all the items created.
Drawing is done one cell at a time so different attribute values applied for
different rows don't clash, as long as every CellView sets all attributes
which matter. (Is that a documented CellView feature though?)
=head2 Buildable
C<Gtk2::Ex::MenuView> inherits the C<Gtk2::Buildable> interface like any
widget subclass and C<Gtk2::Builder> can be used to construct a MenuView
similar to a plain C<Gtk2::Menu>. The class name is C<Gtk2__Ex__MenuView>,
so for example
<object class="Gtk2__Ex__MenuView" id="my-menu">
<property name="model">my-model</property>
<signal name="item-create-or-update" handler="my_create"/>
<signal name="activate" handler="my_activate"/>
</object>
Like a plain C<Gtk2::Menu>, a MenuView will be a top-level object in the
builder and then either connected up as the submenu of a menuitem somewhere
(in another menu or menubar), or just created ready to be popped up
explicitly by event handler code. See F<examples/builder.pl> for a complete
program.
=head2 Subclassing
If you make a sub-class of MenuView you can have a "class closure" handler
for C<item-create-or-update> and C<activate>. This is a good way to hide
item creation and setups. There's no base class handlers for those signals,
so no need to C<signal_chain_from_overridden>. A subclass might expect
certain model columns to contain certain data, like text to display etc.
You can also make a subclass of C<Gtk2::MenuItem> for the items in a
MenuView. This can be a good place to hide code that might otherwise be a
blob within C<item-create-or-update>, perhaps doing things like creating or
( run in 1.386 second using v1.01-cache-2.11-cpan-39bf76dae61 )