Gtk2::Ex::MenuView -- menu of items from a TreeModel
use Gtk2::Ex::MenuView; my $menuview = Gtk2::Ex::MenuView->new (model => $my_model); $menuview->signal_connect (item_create_or_update => \&my_item_create); $menuview->signal_connect (activate => \&my_item_activate); sub my_item_create { my ($menuview, $item, $model, $path, $iter) = @_; # make item if not already done if (! $item) { $item = Gtk2::MenuItem->new_with_label (''); } # update its settings to display model data my $label = $item->get_child; my $str = $model->get ($iter, 0); # column 0 $label->set_text ($str); return $item; # created or updated item } sub my_item_activate { my ($menuview, $item, $model, $path, $iter) = @_; print "an item was activated ...\n"; }
Gtk2::Ex::MenuView is a subclass of Gtk2::Menu,
Gtk2::Ex::MenuView
Gtk2::Menu
Gtk2::Widget Gtk2::Container Gtk2::MenuShell Gtk2::Menu Gtk2::Ex::MenuView::Menu Gtk2::Ex::MenuView
The MenuView::Menu subclass is an implementation detail (things common to the toplevel menu and submenus), so don't rely on that.
MenuView::Menu
Gtk2::Ex::MenuView presents rows and sub-rows of a Gtk2::TreeModel as a menu and sub-menus. The items update with changes in the model.
Gtk2::TreeModel
+--------------+ | Item One | | Item Two => | +------------+ | Item Three | | Sub-item A | +--------------+ | Sub-item B | +------------+
The menu items are created by an item-create-or-update callback signal described below. It offers flexibility for item class and settings, but there's no default, so you must connect a handler or nothing is displayed. The code shown in the SYNOPSIS above is typical, creating an item if not already created, then updating the item display settings to show the row contents.
item-create-or-update
$menuview = Gtk2::Ex::MenuView->new (key=>value,...)
Create and return a new Gtk2::Ex::MenuView object. Optional key/value pairs set initial properties as per Glib::Object->new.
Glib::Object->new
$item = $menuview->item_at_path ($path)
$item = $menuview->item_at_indices ($i, $j, ...)
Return the Gtk2::MenuItem child at the given path or coordinates. If it doesn't exist yet then item-create-or-update is called to create it.
Gtk2::MenuItem
The return is undef if the requested path doesn't exist in the model, or path is empty, or if item-create-or-update returns undef for no item for that row.
undef
item_at_indices is handy on a list-only model to get an item just by number (0 for the first row), without going through a Gtk2::TreePath object. For example,
item_at_indices
list-only
Gtk2::TreePath
$item = $menuview->item_at_indices (0); # first menu item
The following can methods are good in an item signal handler for locating an item within its MenuView.
$path = Gtk2::Ex::MenuView->item_get_path ($item)
Return a Gtk2::TreePath which is the location of $item in its MenuView. Return undef if $item has been removed from the menuview (eg. if its row was deleted).
$item
The $path object is newly created and can be modified or kept by the caller.
$path
($menuview, $model, $path, $iter) = Gtk2::Ex::MenuView->item_get_mmpi ($item)
Return a combination menuview, model, path and iter which is the item's MenuView and location. Return no values if $item has been removed from the menuview.
$path and $iter are both newly created and can be modified or kept by the caller. $model is the same as $menuview->get('model'), but returned since it's often wanted for fetching data (using $iter).
$iter
$model
$menuview->get('model')
model
The TreeModel to display. Until this is set the menu is empty.
The menu is updated to the new model data by item-create-or-update calls as necessary. Any popped-up submenus which don't exist in the new model are popped-down, but those existing in both the old and new model remain up.
want-visible
Gtk2::Ex::MenuView::WantVisible
Whether to automatically make items visible. The possible values are
no don't touch items' visible property show use $item->show when first created show_all use $item->show_all when first created
The default show_all makes each item and all its child widgets visible. This is usually what you want to see it on the screen. show or no allow items or some of their child parts to be invisible at times. See "Item Visibility" below for further notes.
show_all
show
no
(The enum value is show_all with an underscore. It corresponds to the method name and C function name, though it's unlike other enum nicks which use hyphens.)
want-activate
Gtk2::Ex::MenuView::WantActivate
Whether to emit the MenuView activate signal (described below) for item activation. The possible values are
activate
no don't emit leaf emit for leaf items all emit for all items
The default leaf will suit most applications. all emits on non-leaf nodes too, such as when clicking to pop up a submenu, which isn't really an item selection and not usually of interest.
leaf
all
Setting no doesn't emit the activate signal. This saves some signal connections and lookups and can be used if you want different connections on different items, or perhaps only care about a few item activations, or have a MenuItem subclass with its own activate handler in the class.
Currently this setting only affects newly created items, not existing ones.
Emitted as a callback to the application asking it for a menu item for the given model row.
$item is the previously returned item for this row, or undef if none yet. The return should be a Gtk2::MenuItem or subclass. It can be either newly created or simply the existing $item with updated settings. If no item is wanted at all for the row then return undef.
$menuview->signal_connect (item_create_or_update => \&my_item_handler); sub my_item_handler { my ($menuview, $item, $model, $path, $iter, $userdata) = @_; if (! $item) { $item = ...; # create something } # ... apply item settings to display row data return $item; }
MenuView owns any item returned to it by item-create-or-update and will $item->destroy when no longer wanted. (destroy lets items break any circular references and in particular is necessary for an item with a Gtk2::AccelLabel child, per notes in Gtk2::MenuItem.)
$item->destroy
destroy
Gtk2::AccelLabel
The order item-create-or-update calls are made for rows is unspecified. They're also done on a "lazy" basis, so items are only created or updated when the menu is visible, or its size is requested, etc.
An item-create-or-update handler can call $menuview->item_at_path etc to get another row item. This will do a recursive item-create-or-update if the item isn't already up-to-date. Of course the item currently updating or any higher one in progress cannot be obtained.
$menuview->item_at_path
An item-create-or-update must not insert, delete or reorder the model rows.
Emitted when a menu item is activated, either by the user clicking it, or a programmatic $item->activate (including an $item->set_active on CheckMenuItems).
$item->activate
$item->set_active
sub my_activate { my ($menuview, $item, $model, $path, $iter, $userdata) = @_; print "Item activated ", $path->to_string, "\n"; }
The parameters happen to be the same as item-create-or-update above, except of course $item is not undef.
If you change row contents then bear in mind it might cause an item-create-or-update call updating $item. If that callback decides to return a brand new item then you'll be left with only the old one (now destroyed).
You can connect directly to the individual item activate signals (with a signal_connect in item-create-or-update). The unified MenuView activate is designed for the common case where most items do something similar based on the model data.
signal_connect
An activate handler must not insert, delete or reorder rows in the model, since doing so may invalidate the $path and $iter. Those objects are passed to each connected handler without tracking row changes by the handlers. This restriction doesn't apply to an activate handler on an individual item, as it doesn't have path/iter parameters, and as long as the item activate won't be emitted by code within an item-create-or-update (like set_active on a CheckMenuItem does) and which thus has path and iters in use.
set_active
The way item-create-or-update combines the create and update operations makes it easy to sometimes update an existing item or sometimes create a new one, probably sharing the code that applies display settings.
An update can return a new item if a different class is wanted for different row data, or if some settings on an item can be made only when constructing it, not updated later. Otherwise an update is usually just a matter of fetching row data and putting it in properties in the item or child.
A slack item-create-or-update can create a new item every time. If model rows don't change often then this is perfectly respectable and may save a line or two of code. For example,
sub my_item_create_or_update_handler { my ($menuview, $item, $model, $path, $iter, $userdata) = @_; return Gtk2::MenuItem->new_with_label ($model->get($iter,0)); }
Usually all items should be made visible and MenuView does that automatically by default. If you want to manage visibility yourself then set want-visiblity to no to make MenuView leave it alone completely, or show to have MenuView just $item->show the item itself, not recursing into its children.
want-visiblity
$item->show
An invisible item or a return of undef from 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 sensitive property (see Gtk2::Widget) from item-create-or-update according to row data, or link it up to something external, etc.
sensitive
One use for a Gtk2::CheckMenuItem is to have the active property display and control a column in the model. In item-create-or-update do $item->set_active to make the item show the model data, then in the activate signal handler do $model->set to put the item's new active state into the model. See examples/checkitem.pl for a complete sample program.
Gtk2::CheckMenuItem
active
$model->set
$model->set under activate will cause MenuView to call item-create-or-update again because the model row has changed, and $item->set_active there may emit the activate signal again. This would be an endless recursion except that set_activate notices when the item is already in the state requested and does nothing. Be sure to have a cutoff like that.
set_activate
Another possibility is to tie the check item active property to something external using signal handlers to keep them in sync. 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 active property as the "master" storage for a flag, because the row drag-and-drop in 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.
Gtk2::TreeView
Gtk2::RadioMenuItem is a subclass of Gtk2::CheckMenuItem so the above "Check Items" notes apply to it too.
Gtk2::RadioMenuItem
When creating or updating a 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 $menuview->get_children (the 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.
$menuview->get_children
Gtk2::Container
Calling $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.
Gtk2::ComboBox displays its model-based menus using a Gtk2::CellView child in each item with 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.
Gtk2::ComboBox
Gtk2::CellView
Gtk2::CellRenderer
The only thing to note is that as of Gtk 2.20 a CellView doesn't automatically redraw if the model row changes. item-create-or-update is called for a row change and from there you can force a redraw with $cellview->set_displayed_row with the same path already set in it. See examples/cellview.pl for a complete program.
$cellview->set_displayed_row
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?)
Gtk2::Ex::MenuView inherits the Gtk2::Buildable interface like any widget subclass and Gtk2::Builder can be used to construct a MenuView similar to a plain Gtk2::Menu. The class name is Gtk2__Ex__MenuView, so for example
Gtk2::Buildable
Gtk2::Builder
Gtk2__Ex__MenuView
<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 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 examples/builder.pl for a complete program.
If you make a sub-class of MenuView you can have a "class closure" handler for item-create-or-update and 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 signal_chain_from_overridden. A subclass might expect certain model columns to contain certain data, like text to display etc.
signal_chain_from_overridden
You can also make a subclass of Gtk2::MenuItem for the items in a MenuView. This can be a good place to hide code that might otherwise be a blob within item-create-or-update, perhaps doing things like creating or updating child widgets, etc. MenuView doesn't care what class the items are, as long as they're Gtk2::MenuItem or some subclass of Gtk2::MenuItem.
MenuView is intended for up to perhaps a few hundred items. Each item is a separate Gtk2::MenuItem, usually with a child widget to draw, so it's not particularly memory-efficient. You probably won't want to create huge menus anyway since as of Gtk 2.12 the user scrolling in a menu bigger than the screen is poor. (You have to wait while it scrolls, and if you've got a slow X server it gets badly bogged down by its own drawing.)
It mostly works to $menu->prepend extra fixed items for the menu (see Gtk2::Menu), not controlled from model rows. For example a Gtk2::TearoffMenuItem or equivalent of some other class. There's nothing yet to do that in sub-menus though.
$menu->prepend
Gtk2::TearoffMenuItem
It may be possible to append fixed items too. An append could mean an item after the model ones, and prepend or an insert near the start could mean before the model ones. The only tricky bit is when there's no model items yet an insert right between the prepends and appends would be ambiguous. Perhaps prepend there would be most likely, or have pack_start and pack_end style methods.
append
prepend
insert
pack_start
pack_end
There's some secret work-in-progress in the code for an optional separator item above each row. The idea is to have visible separators, usually like Gtk2::SeparatorMenuItem or similar, between sets of related items, perhaps at places where a particular model column value changes.
Gtk2::SeparatorMenuItem
Gtk2::Menu, Gtk2::MenuItem, Gtk2::Label
http://user42.tuxfamily.org/gtk2-ex-menuview/index.html
Copyright 2008, 2009, 2010, 2011, 2012, 2017, 2019 Kevin Ryde
Gtk2-Ex-MenuView is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version.
Gtk2-Ex-MenuView is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Gtk2-Ex-MenuView. If not, see http://www.gnu.org/licenses/.
To install Gtk2::Ex::MenuView, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Gtk2::Ex::MenuView
CPAN shell
perl -MCPAN -e shell install Gtk2::Ex::MenuView
For more information on module installation, please visit the detailed CPAN module installation guide.