Trying to understand libhandy

hello,

I’m trying to recreate from scratch, in C, the hdyflap window in the C examples available
hdyflap

I didn’t go very far yet with :

  win = hdy_window_new();

  box = gtk_box_new( GTK_ORIENTATION_VERTICAL, 1 );
  gtk_container_add( GTK_CONTAINER(win), box );

  row = hdy_action_row_new();
  hdy_action_row_set_subtitle( row, "Use the swaper to swap the swaping thing!" );
  gtk_container_add( GTK_CONTAINER(box), row );

  item = gtk_switch_new();
  gtk_container_add( GTK_CONTAINER(row), item );

from_scratch

With the help of thoses documentations :

But it feels like this window, like the other ones, is made mostly by an XML document

Is the XML document the way how you are suppose to make interfaces with libhandy ?
or Is it just acting like some kind of objects templates ?

Did someone found some examples closer to the usual C approach ?
or a tutorial to better understand the maniplutaion of the library ?

3 Likes

The xml document can be use to create your interface, it is not a mandatory use, it is an available fonctionnality

It can be used to create object templates, than can be used later in the XML document to create an UI

As I am adapting one of my GTK2 software to be available with GTK3 & libhandy
I will use this post to make some mini-tutorials, and how I solved encountered problems

1 Like

If your switch widget is too big like this
libhandy_big_switch

You juste have to set the property valign of the switch widget to GTK_ALIGN_CENTER, 2 choices :
gtk_widget_set_valign( item, GTK_ALIGN_CENTER );
or if you are a g_object_set() user :
g_object_set( item, "valign", GTK_ALIGN_CENTER, NULL );

So if you want this kind of visual for your option:libhandy_row_switch
it is done with :

GtkWidget *page = hdy_preferences_page_new();
gtk_container_add( GTK_CONTAINER(parent), page );

GtkWidget *group = hdy_preferences_group_new();
gtk_container_add( GTK_CONTAINER(page), group );

GtkWidget *row = hdy_action_row_new();
hdy_preferences_row_set_title( (HdyPreferencesRow*) row, "Modal" );
hdy_action_row_set_subtitle( (HdyActionRow*) row, "Clicking outside the sidebar or pressing Esc will close it when folded" );
g_object_set( row, "subtitle-lines", 2, NULL );
gtk_container_add( GTK_CONTAINER(group), row );

GtkWidget *item = gtk_switch_new();
gtk_widget_set_valign( item,  GTK_ALIGN_CENTER );
hdy_action_row_set_activatable_widget( (HdyActionRow*) row, item );
gtk_container_add( GTK_CONTAINER(row), item );

Side notes :

  • hdy_preferences_page_new() is use to stack multiple preferences group
  • hdy_preferences_group_new() permits to group multiple rows in one block
  • The property ‘subtitle-lines’ of the row will allow to auto-cut the subtitle in 2 lines if it’s too long to fit in one line, else the subtitle is truncated with ‘…’
  • hdy_action_row_set_activatable_widget() permits the user to activate the switch widget by clicking anywhere inside the row
3 Likes

If you want to make this kind of exclusive choice option
libhandy_radio_button

it can be done with :

static void         Callback_always_on_XXX(GtkWidget *widget, gpointer data);

static void         Create_exclusive_choice_option(GtkWidget *parent)
{
  GtkWidget         *row, *box, *item;

  row = hdy_action_row_new();
  hdy_preferences_row_set_title( (HdyPreferencesRow*) row, label );
  hdy_action_row_set_subtitle( (HdyActionRow*) row, "allows to keep main window on top of or behind other windows" );
  g_object_set( row, "subtitle-lines", 2, NULL );
  gtk_container_add( GTK_CONTAINER(parent), row );

  box = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 0 );
  gtk_widget_set_valign( box, GTK_ALIGN_CENTER );
  gtk_container_add( GTK_CONTAINER(row), box );

  item = gtk_radio_button_new_with_label_from_widget( NULL, "top" );
  g_object_set( item, "draw-indicator", FALSE, NULL );
  g_signal_connect( G_OBJECT(item), "clicked", G_CALLBACK(Callback_always_on_XXX), 0 );
  gtk_container_add( GTK_CONTAINER(box), item );

  item = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(item), "bottom" );
  g_object_set( item, "draw-indicator", FALSE, NULL );
  g_signal_connect( G_OBJECT(item), "clicked", G_CALLBACK(Callback_always_on_XXX), 0 );
  gtk_container_add( GTK_CONTAINER(box), item );
}

Side notes :

  • Setting the property ‘draw-indicator’ to false does the trick to replace radio button by label button
  • We need a horizontal box in the row to prevent a gap between the buttons

Ok this is not really about libhandy, more about GTK3 but it can be still interesting, if like me you started with GTK2

If you have a popup window (GTK_WINDOW_POPUP), and you get this kind of message from GTK:

Gdk-Message: HH:mm:ss.ms: Window 0x555a77151d40 is a temporary window without parent, application will not be able to position it on screen.

I’m not having this message in a computer with GTK 3.24.20, but I got it in a VM with GTK 3.24.24

I encoutered it in 2 cases :

  • When moving + mapping the window
    To solve this you have to set a parent window with

gtk_window_set_transient_for( GTK_WINDOW(popup_win), GTK_WINDOW(parent_win) );

  • Your popup window is now linked to a parent window but, still it happens when mapping the popup window before the parent window
    To solve this, make sure you mapped your parent window before your popup window

Side notes :

  • The mapping happens in gtk_widget_show_all() and gtk_widget_show()

If you want to make this kind of combo box list option
libhandy_combo_box

it can be done with :

static gchar        *Callback_list(HdyValueObject *value, gpointer row)
{
  gchar             *str = g_value_dup_string( &value->value );

  switch( hdy_combo_row_get_selected_index( row )) {
  case 0: printf("To bottom\n"); break;
  case 1: printf("To top\n"); break;
  case 2: printf("To left\n"); break;
  case 3: printf("To right\n"); break;
  }

  return( str );
}

static void           Create_combo_row(GtkWidget *parent)
{
  GtkWidget           *row;
  GListStore          *list_store;
  HdyValueObject      *obj;

  row = hdy_combo_row_new();
  hdy_preferences_row_set_title( (HdyPreferencesRow*) row, "Infos view expansion" );
  hdy_action_row_set_subtitle( (HdyActionRow*) row, "position of the information window, relative to the icon" );
  g_object_set( row, "subtitle-lines", 2, NULL );
  gtk_container_add( GTK_CONTAINER(parent), row );

  list_store = g_list_store_new (HDY_TYPE_VALUE_OBJECT);

  obj = hdy_value_object_new_string( "To bottom" );
  g_list_store_insert (list_store, 0, obj);
  g_clear_object (&obj);

  obj = hdy_value_object_new_string( "To top" );
  g_list_store_insert (list_store, 1, obj);
  g_clear_object (&obj);

  obj = hdy_value_object_new_string( "To left" );
  g_list_store_insert (list_store, 2, obj);
  g_clear_object (&obj);

  obj = hdy_value_object_new_string( "To right" );
  g_list_store_insert (list_store, 2, obj);
  g_clear_object (&obj);

  hdy_combo_row_bind_name_model( (HdyComboRow*) row, G_LIST_MODEL (list_store), (HdyComboRowGetNameFunc) Callback_list, row, NULL);
}
 

Side notes :

  • In Callback_list(), you have to return an allocated copy of the value, else it doesn’t show the name of the selected item in the GUI
  • During the call hdy_combo_row_bind_name_model(), Callback_list() is called N times, N being the number of elements you have in your list
  • The values in the list can also be set from an enumration, see hdy_combo_row_set_for_enum()
  • If you compile as is and you face the following error (from gcc):

dereferencing pointer to incomplete type ‘HdyValueObject’

It’s because the structure is not declared in the includes of the library, which is far from perfect so you will have to add this declaration in your code:

typedef struct _HdyValueObject
{
  GObject parent_instance;
  GValue value;
} HdyValueObject;

@adrien.plazas: Shouldn’t this structure be declared in the libhandy includes ?

Hello! I don’t have anything useful to add here right now other than to say that I appreciate you keeping up your monologue here, looks like it could be very useful to look at if/when I might try doing something with libhandy later. The “trying to understand” kind of forum threads are my favorite kind. :slight_smile:

1 Like

Thanks, but to be honest I’m running out of “things I did” to show
The others things I did were pretty straight forward to do, so not enough interesting to make some mini-tutorial about
I still have 1 or 2 things in mind to show, but first I have take time to try and figure out how to make them :thinking:

Might be of interest to the libhandy folks:

tl;dr; – ilbadwaita is eating up libhandy

1 Like

Thanks for the news

I took a small look to the API in the sources (https://gitlab.gnome.org/GNOME/libadwaita)
Seems like they mainly renamed all prefixes from hdy to adw

So my futur obsolete exemples will need a bit of renaming to work in libadwaita…

If you want to make this kind of expanding row with exclusive blocks of option
libhandy_expension_1
When you click on the row, it expand like this:
libhandy_expension_2
When you click on “Automatic method” radio button, it disables the ‘Manual method’ entry and expand like this this:
libhandy_expension_3
When you click back to the “Manual method” radio button, it enables the entry and disables+closes the “Automatic method” row

it can be done with :

static GtkWidget *auto_exprow = 0;

static void              Callback_manual(GtkWidget *radio, GtkWidget *manual_entry)
{
  gtk_widget_set_sensitive( manual_entry, 1 );
  hdy_expander_row_set_enable_expansion( (HdyExpanderRow*) auto_exprow, 0 );
}

static void              Callback_automatic(GtkWidget *radio, GtkWidget *manual_entry)
{
  gtk_widget_set_sensitive( manual_entry, 0 );
  hdy_expander_row_set_enable_expansion( (HdyExpanderRow*) auto_exprow, 1 );
}

static void              Create_exclusive_expension_row(GtkWidget *parent)
{
  GtkWidget              *group, *exprow, *row, *first_radio, *entry, *item;

  group = hdy_preferences_group_new();
  gtk_container_add( GTK_CONTAINER(parent), group );

  exprow = hdy_expander_row_new();
  hdy_preferences_row_set_title( (HdyPreferencesRow*) exprow, "Retrieve method" );
  hdy_expander_row_set_subtitle( (HdyExpanderRow*) exprow, "Configure automatic or manual" );
  gtk_container_add( GTK_CONTAINER(group), exprow );

  /* Manual method */
  row = hdy_action_row_new();
  gtk_container_add( GTK_CONTAINER(exprow), row );

  first_radio = gtk_radio_button_new_with_label_from_widget( NULL, "Manual method" );
  hdy_action_row_add_prefix( (HdyActionRow*) row, first_radio );

  entry = gtk_entry_new();
  g_signal_connect( G_OBJECT(first_radio), "clicked", G_CALLBACK(Callback_manual), entry );
  gtk_widget_set_valign( entry, GTK_ALIGN_CENTER );
  hdy_action_row_set_activatable_widget( (HdyActionRow*) row, entry );
  gtk_container_add( GTK_CONTAINER(row), entry );

  /* Automatic method */
  auto_exprow = hdy_expander_row_new();
  gtk_container_add( GTK_CONTAINER(exprow), auto_exprow );

  item = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(first_radio), "Automatic method" );
  g_signal_connect( G_OBJECT(item), "clicked", G_CALLBACK(Callback_automatic), entry );
  hdy_expander_row_add_prefix( (HdyExpanderRow*) auto_exprow, item );

  /* whatever option */
  row = hdy_action_row_new();
  hdy_preferences_row_set_title( (HdyPreferencesRow*) row, "Whatever option" );
  hdy_action_row_set_subtitle( (HdyActionRow*) row, "This row activate whatever..." );
  gtk_container_add( GTK_CONTAINER(auto_exprow), row );

  item = gtk_switch_new();
  gtk_widget_set_valign( item, GTK_ALIGN_CENTER );
  hdy_action_row_set_activatable_widget( (HdyActionRow*) row, item );
  gtk_container_add( GTK_CONTAINER(row), item );
}

Side notes:

  • So to make it work with libadwaita, you probably only have to replace the hdy prefixes with adw
  • To add a widget to the left of the row, you have to use the hdy_expander_row_add_prefix() function
  • Using hdy_expander_row_set_enable_expansion() will expand or close the row while it enables or disables it
  • We need 2 variables for the 2 widgets to be passed to the callback functions
    I used the parameter of the callback, and a global variable
    It’s just an out context exemple to make it work
    It would be probably better to pass a pointer to an allocated structure/table containing those 2 variables
    You could also use only one callback function and check the state of one of the radio button

Just to giving a overview of the migration from GTK2 to GTK3 to libhandy (aka libadwaita) I made
The theme used for GTK2 and GTK3 is ‘greybird’, and I think ‘adwaita’ for libhandy

Here my original GTK2 version
settings_gtk2

Then migrated to GTK3 (I could have made a better job to align the options in the ‘Main window’ frame)
settings_gtk3

Then migrated to GTK3/libhandy


It really gives an easy nice homogenous touch to the options

2 Likes