Problem with popup menu implemented using popover

Hi!
I am trying to implement simple popup (context) menu using Gtk4 (Ubuntu 22.04). My code (compiled with: g++ pkg-config --cflags gtk4 -o test main.cpp pkg-config --libs gtk4):

#include <gtk/gtk.h>

GtkApplication * app = NULL;
GtkWidget * window = NULL;
GtkWidget * area = NULL;

void item_1_activated( GSimpleAction * action, GVariant * parameter, gpointer user_data )
{
	printf( "Item 1\n" );
}

void item_2_activated( GSimpleAction * action, GVariant * parameter, gpointer user_data )
{
	printf( "Item 2\n" );
}

void popover_closed_callback( GtkPopover * popover, gpointer user_data )
{
	gtk_widget_unparent( GTK_WIDGET( popover ));
}

void PopUpMenu( int x, int y )
{
	GMenu * menu = g_menu_new();

	const GActionEntry entries[] =
	{
		{ "item_1_action", item_1_activated, NULL, NULL, NULL, { 0, 0, 0 }},
		{ "item_2_action", item_2_activated, NULL, NULL, NULL, { 0, 0, 0 }}
	};

	g_action_map_add_action_entries( G_ACTION_MAP( window ), entries, G_N_ELEMENTS ( entries ), NULL );

	g_menu_insert( menu, 0, "item 1", "win.item_1_action" );
	g_menu_insert( menu, 1, "item 2", "win.item_2_action" );

	GtkWidget * popover = gtk_popover_menu_new_from_model_full( G_MENU_MODEL( menu ), GTK_POPOVER_MENU_NESTED );

	g_object_unref( menu );

	gtk_widget_set_parent( popover, area );

	gtk_popover_set_has_arrow( GTK_POPOVER( popover ), FALSE );

	GdkRectangle rectangle = { x, y, 1, 1 };

	gtk_popover_set_pointing_to( GTK_POPOVER( popover ), &rectangle );

	g_signal_connect( popover, "closed", G_CALLBACK( popover_closed_callback ), NULL );

	gtk_popover_popup( GTK_POPOVER( popover ));
}

void mouse_button_pressed_callback( GtkGestureClick * gesture, int, double x, double y, gpointer user_data )
{
	if ( gtk_gesture_single_get_current_button(( GtkGestureSingle * ) gesture ) == GDK_BUTTON_SECONDARY )
		PopUpMenu( x, y );
}

void activate( GtkApplication *, gpointer user_data )
{
	window = gtk_application_window_new( app );

	gtk_window_set_title( GTK_WINDOW( window ), "Popover example" );

	gtk_window_set_default_size( GTK_WINDOW( window ), 400, 400 );

	area = gtk_drawing_area_new();

	gtk_window_set_child( GTK_WINDOW( window ), area );

	GtkGesture * gesture = gtk_gesture_click_new();

	gtk_gesture_single_set_button( GTK_GESTURE_SINGLE( gesture ), 0 );

	g_signal_connect( gesture, "pressed", G_CALLBACK( mouse_button_pressed_callback ), NULL );

	gtk_widget_add_controller( area, GTK_EVENT_CONTROLLER( gesture ));

	gtk_widget_show( window );
}

int main( int argc, char ** argv )
{
	app = gtk_application_new( NULL, G_APPLICATION_FLAGS_NONE );

	g_signal_connect( app, "activate", G_CALLBACK( activate ), NULL );

	int status = g_application_run( G_APPLICATION( app ), 0, NULL );

	g_object_unref( app );

	return status;
}

The problem: if I keep call to “gtk_widget_unparent” then I am not receive activate signal from actions; If I comment the call to “gtk_widget_unparent”, then I receive the signals, but on exit I get:

(test:6653): Gtk-WARNING **: 13:08:01.640: Finalizing GtkDrawingArea 0x55964e60a4b0, but it still has children left:
(test:6653): Gtk-WARNING **: 13:08:01.640:    - GtkPopoverMenu 0x559650ca6200

And, if I run this code in WSL then after receiving the signal the UI is hang.

Any ideas what I did wrong?

Thank you in advance

You cannot add random popovers to random widgets.

You will need to create your own widget derived type that owns a popover and:

A typical custom widget would also own the DrawingArea itself as a child; or, more likely, override GtkWidgetClass.snapshot() and render things directly.

Thank you for the answer! Maybe, there is some example I can start with?

There are various demos in gtk4-demo that include a popover. For instance demo3widget.c has a popover and a paintable; there are custom widgets with snapshot virtual function implementations as well.

Hi! I created the example with custom widget and popover usage inside, still similar problems: I don’t receive “dispose” event, and I get “Gtk-WARNING **: 10:16:13.551: Broken accounting of active state for widget 0x55ea4c5067b0(PopupWidget)” once during first time activation.

#include <gtk/gtk.h>

G_DECLARE_FINAL_TYPE( PopupWidget, popup_widget, POPUP, WIDGET, GtkWidget )

struct _PopupWidget
{
	GtkWidget parent_instance;

	GtkWidget * popover;

	GMenu * menu;

	int x;
	int y;
};

G_DEFINE_TYPE( PopupWidget, popup_widget, GTK_TYPE_WIDGET )

enum
{
	PROP_POPUP_MENU = 1
};

void popup_widget_init( PopupWidget * self ) {}

void popup_widget_size_allocate( GtkWidget * widget, int width, int height, int baseline )
{
	PopupWidget * self = POPUP_WIDGET( widget );

	gtk_popover_present( GTK_POPOVER( self -> popover ));
}

void popup_widget_map( GtkWidget * widget )
{
	PopupWidget * self = POPUP_WIDGET( widget );

	self -> popover = gtk_popover_menu_new_from_model_full( G_MENU_MODEL( self -> menu ), GTK_POPOVER_MENU_NESTED );

	gtk_widget_set_parent( self -> popover, GTK_WIDGET( self ));

	gtk_popover_set_has_arrow( GTK_POPOVER( self -> popover ), FALSE );

	GdkRectangle rectangle = { self -> x, self -> y, 1, 1 };

	gtk_popover_set_pointing_to( GTK_POPOVER( self -> popover ), &rectangle );

	gtk_popover_popup( GTK_POPOVER( self -> popover ));

	GTK_WIDGET_CLASS( popup_widget_parent_class ) -> map( widget );
}

void popup_widget_dispose( GObject * object )
{
	PopupWidget * self = POPUP_WIDGET( object );

	gtk_widget_unparent( self -> popover );

	g_object_unref( self -> menu );

	G_OBJECT_CLASS( popup_widget_parent_class ) -> dispose( object );
}

void popup_widget_set_property( GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec )
{
	PopupWidget * self = POPUP_WIDGET( object );

		switch ( prop_id )
		{

	case PROP_POPUP_MENU:
			self -> menu = ( GMenu * ) g_value_get_pointer( value );
			g_object_ref( self -> menu );
			break;

	default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec );
			break;
		}
}

void popup_widget_class_init( PopupWidgetClass * klass )
{
	GObjectClass * object_class = G_OBJECT_CLASS( klass );
	GtkWidgetClass * widget_class = GTK_WIDGET_CLASS( klass );

	object_class -> dispose = popup_widget_dispose;
	object_class -> set_property = popup_widget_set_property;

	widget_class -> size_allocate = popup_widget_size_allocate;
	widget_class -> map = popup_widget_map;

	g_object_class_install_property( object_class, PROP_POPUP_MENU, g_param_spec_pointer( "popup-menu", "Popup menu", "Popup menu", G_PARAM_WRITABLE ));
}

GtkWidget * popup_widget_new( GMenu * menu, int x, int y )
{
	PopupWidget * self = POPUP_WIDGET( g_object_new( popup_widget_get_type(), "popup-menu", menu, NULL ));

	self -> x = x;
	self -> y = y;

	return GTK_WIDGET( self );
}

void item_activated( GSimpleAction * action, GVariant * parameter, gpointer user_data )
{
	printf( "Item activated\n" );
}

GMenu * create_menu( GtkWindow * window )
{
	const GActionEntry entries[] =
	{
		{ "item_action", item_activated, NULL, NULL, NULL, { 0, 0, 0 }}
	};

	g_action_map_add_action_entries( G_ACTION_MAP( window ), entries, G_N_ELEMENTS ( entries ), NULL );

	GMenu * menu = g_menu_new();

	g_menu_insert( menu, 0, "item", "win.item_action" );

	return menu;
}

void mouse_button_pressed_callback( GtkGestureClick * gesture, int, double x, double y, gpointer user_data )
{
	if ( gtk_gesture_single_get_current_button( GTK_GESTURE_SINGLE( gesture )) == GDK_BUTTON_SECONDARY )
	{
		GtkWindow * window = GTK_WINDOW( gtk_event_controller_get_widget( GTK_EVENT_CONTROLLER( gesture )));

		GMenu * menu = create_menu( window );

		GtkWidget * popup = popup_widget_new( menu, x, y );

		g_object_unref( menu );

		gtk_window_set_child( window, popup );
	}
}

void activate( GtkApplication * app, gpointer user_data )
{
	GtkWidget * window = gtk_application_window_new( app );

	gtk_window_set_title( GTK_WINDOW( window ), "Popup example" );

	gtk_window_set_default_size( GTK_WINDOW( window ), 400, 400 );

	GtkGesture * gesture = gtk_gesture_click_new();
	gtk_gesture_single_set_button( GTK_GESTURE_SINGLE( gesture ), 0 );
	g_signal_connect( gesture, "pressed", G_CALLBACK( mouse_button_pressed_callback ), NULL );
	gtk_widget_add_controller( window, GTK_EVENT_CONTROLLER( gesture ));

	gtk_widget_show( window );
}

int main( int argc, char ** argv )
{
	GtkApplication * app = gtk_application_new( NULL, G_APPLICATION_FLAGS_NONE );

	g_signal_connect( app, "activate", G_CALLBACK( activate ), NULL );

	int status = g_application_run( G_APPLICATION( app ), 0, NULL );

	g_object_unref( app );

	return status;
}

What I am missing?
Thanks

Can anyone help, please!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.