The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

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

$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.

Item Access

$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.

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.

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 = $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::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).

The $path object is newly created and can be modified or kept by the caller.

($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).

PROPERTIES

model (object implementing Gtk2::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-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 enum, 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 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.

(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 enum, default 'leaf')

Whether to emit the MenuView activate signal (described below) for item activation. The possible values are

    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.

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.

SIGNALS

item-create-or-update (parameters: menuview, item, model, path, iter)

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.)

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.

An item-create-or-update must not insert, delete or reorder the model rows.

activate (parameters: menuview, item, model, path, iter)

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).

    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.

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.

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.

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/.