NAME
Gtk2::Ex::MenuView -- menu of items from a TreeModel
SYNOPSIS
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";
}
WIDGET HIERARCHY
Gtk2::Ex::MenuView is a subclass of 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.
DESCRIPTION
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.
+--------------+
| 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.
FUNCTIONS
-
Create and return a new
Gtk2::Ex::MenuViewobject. Optional key/value pairs set initial properties as perGlib::Object->new.
Item Access
-
Return the
Gtk2::MenuItemchild at the given path or coordinates. If it doesn't exist yet thenitem-create-or-updateis called to create it.The return is
undefif the requested path doesn't exist in the model, or path is empty, or ifitem-create-or-updatereturnsundeffor no item for that row.item_at_indicesis handy on alist-onlymodel to get an item just by number (0 for the first row), without going through aGtk2::TreePathobject. For example,$item = $menuview->item_at_indices (0); # first menu item
Item Location
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::TreePathwhich is the location of$itemin its MenuView. Returnundefif$itemhas been removed from the menuview (eg. if its row was deleted).The
$pathobject is newly created and can be modified or kept by the caller. -
Return a combination menuview, model, path and iter which is the item's MenuView and location. Return no values if
$itemhas been removed from the menuview.$pathand$iterare both newly created and can be modified or kept by the caller.$modelis the same as$menuview->get('model'), but returned since it's often wanted for fetching data (using$iter).
PROPERTIES
model(object implementingGtk2::TreeModel, default undef)-
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-updatecalls 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::WantVisibleenum, default 'show_all')-
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 createdThe default
show_allmakes each item and all its child widgets visible. This is usually what you want to see it on the screen.showornoallow items or some of their child parts to be invisible at times. See "Item Visibility" below for further notes.(The enum value is
show_allwith 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::WantActivateenum, default 'leaf')-
Whether to emit the MenuView
activatesignal (described below) for item activation. The possible values areno don't emit leaf emit for leaf items all emit for all itemsThe default
leafwill suit most applications.allemits 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.Setting
nodoesn't emit theactivatesignal. 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.
SIGNALS
-
Emitted as a callback to the application asking it for a menu item for the given model row.
$itemis the previously returned item for this row, orundefif none yet. The return should be aGtk2::MenuItemor subclass. It can be either newly created or simply the existing$itemwith updated settings. If no item is wanted at all for the row then returnundef.$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-updateand will$item->destroywhen no longer wanted. (destroylets items break any circular references and in particular is necessary for an item with aGtk2::AccelLabelchild, per notes in Gtk2::MenuItem.)The order
item-create-or-updatecalls 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-updatehandler can call$menuview->item_at_pathetc to get another row item. This will do a recursiveitem-create-or-updateif the item isn't already up-to-date. Of course the item currently updating or any higher one in progress cannot be obtained.An
item-create-or-updatemust 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_activeon CheckMenuItems).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-updateabove, except of course$itemis notundef.If you change row contents then bear in mind it might cause an
item-create-or-updatecall 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
activatesignals (with asignal_connectinitem-create-or-update). The unified MenuViewactivateis designed for the common case where most items do something similar based on the model data.An
activatehandler must not insert, delete or reorder rows in the model, since doing so may invalidate the$pathand$iter. Those objects are passed to each connected handler without tracking row changes by the handlers. This restriction doesn't apply to anactivatehandler on an individual item, as it doesn't have path/iter parameters, and as long as the itemactivatewon't be emitted by code within anitem-create-or-update(likeset_activeon a CheckMenuItem does) and which thus has path and iters in use.
DETAILS
Item Create and Update
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));
}
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 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.
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.
Check Items
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.
$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.
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.
Radio Button Items
Gtk2::RadioMenuItem is a subclass of Gtk2::CheckMenuItem so the above "Check Items" notes apply to it too.
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.
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.
CellView Items
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.
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.
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?)
Buildable
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
<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.
Subclassing
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.
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.
Menu Size
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.)
FUTURE
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.
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.
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.
SEE ALSO
Gtk2::Menu, Gtk2::MenuItem, Gtk2::Label
HOME PAGE
http://user42.tuxfamily.org/gtk2-ex-menuview/index.html
LICENSE
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/.