Thoughts on the ComboBox framework



Sorry for having taken so long to put something out. I am not a full
time hacker and also getting this done was hard. The most important
reason for that is that nobody really had an idea which direction the
new ComboBox had to take. What I have now is a composition of some
generic ideas people already had, discussion with people on IRC and lots
of thinking/trying things out.

In this mail I will present what I came up with. It will be a rather
long mail, sorry for that. Short table of contents:
 - EggComboBox,
 - EggCellView and EggCellViewMenuItem,
 - EggComboBoxPicker,
 - EggComboBoxGrid,
 - completion and history for GtkEntry (EggEntry),
 - EggComboBoxText.

I hope people like and agree with my thoughts outlined below. Of course
I am open for suggestions, flames, etc.


thanks,


	-Kris
EggComboBox
-----------

* Outline/purpose

EggComboBox is the generic base class of the framework. It contains a
chunk of code shared between the different ComboBoxes. Having a base
class for the ComboBoxes is of course good for consistency if everybody
bases their custom ComboBoxes on this base class. Though the problem I
encountered later is that the individual implementations of the Combos
need a lot of hacks outside of the shared code to get the feeling and
behaviour right. This includes evil hacks to get around the inflexibility
of EggComboBox, which basically renders the idea of having the base class
useless.

Because of that I am seriously considering dropping the baseclass and move
the shared code into, for example, eggcomboboxutils.c. But I am not sure
of that yet, because the base class still has some small advantages.

* API discussion

  struct _EggComboBoxClass
  {
    GtkHBoxClass parent_class;
                                                                                
    void     (* changed)     (EggComboBox *combobox);
    void     (* popped_up)   (EggComboBox *combobox);
    void     (* popped_down) (EggComboBox *combobox);
                                                                                
    /* Key Binding signals */
    gboolean (* popup)       (EggComboBox *combobox);
  };

Where "popped_up" means that the popup window came up, and "popped_down"
that the popup window disappeared. There seems to be confusion in the
use of popup and popdown, so it might be nice get that cleared up.

  GType      egg_combo_box_get_type (void);
  GtkWidget *egg_combo_box_new      (void);

Ditto.

  void       egg_combo_box_set_sample_widget (EggComboBox *combobox,
                                              GtkWidget   *sample);
  GtkWidget *egg_combo_box_get_sample_widget (EggComboBox *combobox);
  void       egg_combo_box_set_popup_widget  (EggComboBox *combobox,
                                              GtkWidget   *popup);
  GtkWidget *egg_combo_box_get_popup_widget  (EggComboBox *combobox);
                                                                                
Those are functions to set the sample widget, the widget displayed left
of the arrow, and the popup widget, the widget which will be in the
popup window.

  void       egg_combo_box_popup             (EggComboBox *combobox);
  void       egg_combo_box_popdown           (EggComboBox *combobox);

Also speak for themselves.

* Questions and discussion

 - As mentioned above, is having a base class like this useful?

 - We should clear up the popped_up/popped_down meanings.

 - Should the changed signal stay here? Or move it to the individual
   combo implementations and provide more arguments (ie the new item).



EggCellView/EggCellViewMenuItem
-------------------------------

* Outline/purpose

EggCellView is an utility widget used by the picker and grid Combos (more
on them later). You can think of it as a single GtkTreeViewColumn which
renderers a single row on it's window. EggCellViewMenuItem is a GtkMenuItem
containing a EggCellView.

* API discussion

  struct _EggCellViewClass
  {
    GtkWidgetClass parent_class;
  };

No surprises here.

  GType        egg_cell_view_get_type            (void);
  GtkWidget   *egg_cell_view_new                 (void);
  GtkWidget   *egg_cell_view_new_with_text       (const gchar   *text);
  GtkWidget   *egg_cell_view_new_with_markup     (const gchar   *markup);
  GtkWidget   *egg_cell_view_new_with_pixbuf     (GdkPixbuf     *pixbuf);

The default constructor _new, constructing an empty EggCellView. The 3
utility constructors pack a default renderer and set the data provided by
default.

  void  egg_cell_view_pack_start  (EggCellView     *cellview,
                                   GtkCellRenderer *renderer,
                                   gboolean         expand);
  void  egg_cell_view_pack_end    (EggCellView     *cellview,
                                   GtkCellRenderer *renderer,
                                   gboolean         expand);

Functions to pack cell renderers.

  void   egg_cell_view_add_attribute      (EggCellView     *cellview,
                                           GtkCellRenderer *renderer,
                                           const gchar     *attribute,
                                           gint             column);
  void   egg_cell_view_clear_attributes   (EggCellView     *cellview,
                                           GtkCellRenderer *attribute);
  void   egg_cell_view_set_attributes     (EggCellView     *cellview,
                                           GtkCellRenderer *renderer,
                                           ...);

Functions for attribute manipulation, just like in GtkTreeViewColumn. Use
this in conjunction with the dynamic data retrieval functions
discussed below.

  void   egg_cell_view_set_value   (EggCellView     *cellview,
                                    GtkCellRenderer *renderer,
                                    gchar           *property,
                                    GValue          *value);
  void   egg_cell_view_set_values  (EggCellView     *cellview,
                                    GtkCellRenderer *renderer,
                                    ...);

Using these functions you set values on the cell renderers directly. This
is useful for EggCellViews with static content.

  void         egg_cell_view_set_model          (EggCellView   *cellview,
                                                 GtkTreeModel  *model);
  void         egg_cell_view_set_displayed_row  (EggCellView   *cellview,
                                                 GtkTreePath   *path);
  GtkTreePath *egg_cell_view_get_displayed_row  (EggCellView   *cellview);

Those are useful for dynamic EggCellViews. You can set a GtkTreeModel
where you want to get your data from. When you set a displayed row, the
EggCellView will automatically retrieve the data from the model. A
prerequisite is that you have set the attributes right.

  void  egg_cell_view_set_background_color  (EggCellView     *cellview,
                                             GdkColor        *color);

Trivial.

* Questions and discussion

None from my side.



EggComboBoxPicker
-----------------

* Outline/purpose

In my whole opinion this is the coolest combo (:. The basic purpose of this
combo is picking an item from the list (hence the suffix "Picker", though
maybe some people will like "Chooser" better). The combo uses a GtkListStore
as data storage internally, the user is presented a set of generic API
to add/remove items. As with the GtkTreeView you have to pack a bunch of 
cells and set the attributes to get items displayed.

Perhaps the coolest feature is that the EggComboBoxPicker is able to
switch between "Windows style" and "UNIX style" on the fly. This will be
a style property. The programmer has nothing the do with it, both styles
of course have the same API. A screenshot can be found at:

  http://www.math.leidenuniv.nl/~kris/blaat/picker-combos-2.png

What's still missing in the "Windows style" Combo is the "selection cursor
follows mouse" behaviour. I will implement this later as it requires the
new GtkTreeViews modes API.

* API discussion

  struct _EggComboBoxPickerClass
  {
    EggComboBoxClass parent_class;
  };

Nothing surprising.

  GType      egg_combo_box_picker_get_type         (void);
  GtkWidget *egg_combo_box_picker_new              (gint    n_columns,
                                                    ...);
  GtkWidget *egg_combo_box_picker_newv             (gint    n_columns,
                                                    GType *types);
  void       egg_combo_box_picker_set_column_types (EggComboBoxPicker *picker,
                                                    gint   n_columns,
                                                    GType *types);

The arguments are for initializing the internal GtkListStore. These
functions are basically the same as their GtkListStore "counterparts".

  void egg_combo_box_picker_pack_start     (EggComboBoxPicker *picker,
                                            GtkCellRenderer   *rend,
                                            gboolean           expand);
  void egg_combo_box_picker_pack_end       (EggComboBoxPicker *picker,
                                            GtkCellRenderer   *rend,
                                            gboolean           expand);
  void egg_combo_box_picker_set_attributes (EggComboBoxPicker *picker,
                                            GtkCellRenderer   *rend,
                                            ...);

Speak for themselves.

  void egg_combo_box_picker_insert     (EggComboBoxPicker *picker,
                                        gint index,
                                        ...);
  void egg_combo_box_picker_append     (EggComboBoxPicker *picker,
                                        ...);
  void egg_combo_box_picker_prepend    (EggComboBoxPicker *picker,
                                        ...);
  void egg_combo_box_picker_remove     (EggComboBoxPicker *picker,
                                        gint index);
  gint egg_combo_box_picker_get_index  (EggComboBoxPicker *picker,
                                        ...);
  void egg_combo_box_picker_clear      (EggComboBoxPicker *picker);

Missing here is a "_insertv" function which is required by language bindings.
The format of the ellipsis is a column number/value pair, closed by a
-1 column number.

_get_index takes a bunch of column number/value pairs, and returns the
index of the item matching those pairs.

  gint egg_combo_box_picker_get_sample (EggComboBoxPicker *picker);
  void egg_combo_box_picker_set_sample (EggComboBoxPicker *picker,
                                        gint index);

Functions to set/get the item displayed in the sample widget.

* Questions and discussion

 - We might want to change the name. EggComboBoxChooser maybe?



EggComboBoxGrid
---------------

* Outline/purpose

The EggComboBoxGrid is used for displaying items in a two dimensional
grid. This requires patching GtkMenu, I will fixup and send the patch to
the list later on (as there are still some little problems with it,
also keynav related). Basically the patch adds this function to GtkMenu:

  void  gtk_menu_attach   (GtkMenu             *menu,
                           GtkWidget           *child,
                           guint                left_attach,
                           guint                right_attach,
                           guint                top_attach,
                           guint                bottom_attach);

This should look familiar to users of GtkTable. Using the EggComboBoxGrid
is as easy as creating one and attaching GtkMenuItems to it. Of course
you can use the EggCellViewMenuItems here.

* API discussion

  struct _EggComboBoxGridClass
  {
    EggComboBoxClass parent_class;
  };

Nothing surprising again (:.

  GType      egg_combo_box_grid_get_type  (void);
  GtkWidget *egg_combo_box_grid_new       (void);

Just a basic constructor.

  void  egg_combo_box_grid_attach   (EggComboBoxGrid *grid,
                                     GtkMenuItem     *child,
                                     guint            left_attach,
                                     guint            right_attach,
                                     guint            top_attach,
                                     guint            bottom_attach);

Attaching items is as easy as that.

  void egg_combo_box_grid_detach  (EggComboBoxGrid *grid,
                                   GtkWidget       *child);
  void egg_combo_box_grid_clear   (EggComboBoxGrid *grid);

Getting rid of items is easy too.

  GtkWidget  *egg_combo_box_grid_get_sample  (EggComboBoxGrid *grid);
  void        egg_combo_box_grid_set_sample  (EggComboBoxGrid *grid,
                                              GtkMenuItem     *child);

And using these functions you can get/set the sample item.

* Questions and discussion

 - Do we need to be able to control row/column spacing?

 - Do we need autolayouting of items?



EggEntry: history and completion for GtkEntry
---------------------------------------------

* Outline/purpose

Completion and history for EggEntry are two other things on the 2.4 list.
Because completion and history have connections with text combos, I decided
to look at those too while working on the Combo framework. As a result
completion/history are tightly coupled with the EggComboBoxText (discussed
below).

For completion, the completion code from Galeon was discussed on the mailing
list earlier. In my opinion the Galeon code is too bloated and big to put
in GTK+ as a simple completion mechanism. Personally I see *much* more in
a small, well designed mechanism as I will present here. During the design
I got some help from Marco (from Epiphany fame). According to us, the
design presented here should be advanced/featureful enough for most uses
of completion.

Also the e-completion code was considered. And personally I think that's
too featureful too for usage in GTK+. Marco and I really think that the
small API discussed below is good enough.

For the EggEntry, history and completion are tightly coupled with eachother.
I think that enabled history without completion doesn't make any sense,
because there is no way you can get a list of items without having
completion enabled in an EggEntry. You can only get a list without having
completion is it was a combo box.

I think that completion on it's own in EggEntry (so without a combo or
history) does make sense. We give the user the option to put in a user
provided GtkListStore. As an extra you can enable history and in that case
you mostly always want to complete on the items in the history list. So
that's why I think that sharing the GtkListStore between completion and
history makes a lot of sense.

* API discussion

  typedef gboolean (* EggCompletionFunc) (const gchar *key,
                                          const gchar *item,
                                          GtkTreeIter *iter,
                                          gpointer     user_data);

The idea of function is passing the key and an item, provided as an iter
and as the string which is in the given list_column of the model. The user
should return a gboolean indicating whether item/iter is a match or not.

  void     egg_entry_enable_completion  (EggEntry          *entry,
                                         GtkListStore      *model,
                                         gint               list_column,
                                         gint               entry_column,
                                         EggCompletionFunc  func,
                                         gpointer           func_data,
                                         GtkDestroyNotify   func_destroy);
  gboolean egg_entry_completion_enabled (EggEntry          *entry);

The second function is pretty much trivial, the first one needs some more
explaination. @entry is the EggEntry we want to enable completion on and
@model is a GtkListStore providing a possible list of completions. This has
to be a list store because the history code needs write access to the
model. This might be inflexible, but I don't think this is a big problem.

Another solution might be to give to use the option to pass in a generic
GtkTreeModel here, and that history will only work is the given Model is a
GtkListStore. But personally I think such special cases are not a good idea.

The user also needs to specify two columns, @list_column would be the column
displayed in the list in the completion popup. @entry_column is a column
containing the text which will be put into the EggEntry on an accepted
completion.

And of courses there's a func/func_data/func_destroy for specifying the
completion func etc.

  GtkTreeViewColumn *egg_entry_completion_get_column (EggEntry      *entry);
  void               egg_entry_completion_get_model  (EggEntry      *entry,
                                                      GtkTreeModel **model,
                                                      gint *list_column);

Using the first column you can get the GtkTreeViewColumn which is used
in the completion popup. This option is there so that the user can put
in cells, so you can create multi-columned popups or put an icon in front
of a possible match. Example:

  http://www.math.leidenuniv.nl/~kris/blaat/completion.png

Second function is trivial.

  void   egg_entry_history_set_max  (EggEntry     *entry,
                                     gint          max);
  gint   egg_entry_history_get_max  (EggEntry     *entry);

Setting the max history value to -1 will disable history, everything > 0
will of course enabled it. History keeping will prepend "new" items to the
list (hance the required write access to the model).

* Questions and discussion

 - I think async data sources can be supported without any problems with
   this completion setup. The popup uses a GtkTreeModelFilter to filter out
   matches. So matches appearing later on can be included in that list
   without any problems.

 - We need to figure out a good behaviour for the completion. Some people
   are against using Tab for completion, some can't live without. If we
   can't figure something out, I guess we should default to a non-tab key
   setup, and make the tab key setup an option, perhaps via a style property.



EggComboBoxText
---------------

* Outline/purpose

In the current design the EggComboBoxText is just an extra layer around
the EggEntry, providing the user with an option to display the full list
which is kept in the background. This full list would contain all
possible completions/items in the history.

Because of this the EggComboBoxText class does not contain any functions
to add/remove items from the list. This should be done by using the
GtkListStore directly. But we *do* provide functions to add items in the
EggComboBoxPicker class, so this might be inconsistent.

* API discussion

  struct _EggComboBoxTextClass
  {
    EggComboBoxClass parent_class;
  };

No surprises.

  GType      egg_combo_box_text_get_type       (void);
  GtkWidget *egg_combo_box_text_new_with_entry (EggEntry      *entry);
  GtkWidget *egg_combo_box_text_new_with_model (GtkListStore  *model,
                                                gint           text_column,
                                                gint           history_max);

We give the user two options for creating an EggComboBoxText. Using the
first option the user provids a configured EggEntry himself (that means
that the user has set up completion/history on the EggEntry). And using
the second function the user provides the parameters so that the
EggComboBoxText can create the entry. We might need something to toggle
completion here.

  gint egg_combo_box_text_get_index   (EggComboBoxText *textcombo,
                                       const gchar     *text);
  gint egg_combo_box_text_get_length  (EggComboBoxText *textcombo);

These are two utility functions. They are easy to implement yourself by
accessing the GtkListStore, but I think they are still useful to have here.

  gint   egg_combo_box_text_get_sample_index (EggComboBoxText *textcombo);
  gchar *egg_combo_box_text_get_sample_text  (EggComboBoxText *textcombo);
  void   egg_combo_box_text_set_sample       (EggComboBoxText *textcombo,
                                              gint index);

Functions to get/set the sample. Trivial.

* Questions and discussion

 - Do we need something to toggle completion?



General questions and discussion
--------------------------------

 - EggComboBoxPicker has functions for adding/removing items. EggComboBoxText
   has not, and expects the user to do this via GtkListStore. Is this
   inconsistent?


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]