Enhancement: Fixes #435, comctl - alternative GMenu binding object

This commit is contained in:
Rory Fewell
2025-03-03 22:11:08 +00:00
parent 7a68cd7f13
commit 7b269d373b
17 changed files with 1257 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
cmake_minimum_required(VERSION 3.5)
project(
wintc-menutest
VERSION 1.0
DESCRIPTION "Windows Total Conversion menu binding test application."
LANGUAGES C
)
set(PROJECT_ANYARCH false)
set(PROJECT_FREESTATUS true)
set(PROJECT_MAINTAINER "Rory Fewell <roryf@oddmatics.uk>")
set(PROJECT_ROOT ${CMAKE_CURRENT_LIST_DIR})
include(GNUInstallDirs)
include(../../../packaging/cmake-inc/common/CMakeLists.txt)
include(../../../packaging/cmake-inc/linking/CMakeLists.txt)
include(../../../packaging/cmake-inc/packaging/CMakeLists.txt)
include(../../../packaging/cmake-inc/resources/CMakeLists.txt)
wintc_resolve_library(glib-2.0 GLIB)
wintc_resolve_library(gtk+-3.0 GTK3)
wintc_resolve_library(wintc-comctl WINTC_COMCTL)
wintc_resolve_library(wintc-comgtk WINTC_COMGTK)
wintc_compile_resources()
add_executable(
wintc-menutest
src/application.c
src/application.h
src/main.c
src/resources.c
src/window.c
src/window.h
)
target_compile_options(
wintc-menutest
PRIVATE ${WINTC_COMPILE_OPTIONS}
)
target_include_directories(
wintc-menutest
SYSTEM
PRIVATE ${GLIB_INCLUDE_DIRS}
PRIVATE ${GTK3_INCLUDE_DIRS}
PRIVATE ${WINTC_COMCTL_INCLUDE_DIRS}
PRIVATE ${WINTC_COMGTK_INCLUDE_DIRS}
)
target_link_directories(
wintc-menutest
PRIVATE ${GLIB_LIBRARY_DIRS}
PRIVATE ${GTK3_LIBRARY_DIRS}
PRIVATE ${WINTC_COMCTL_LIBRARY_DIRS}
PRIVATE ${WINTC_COMGTK_LIBRARY_DIRS}
)
target_link_libraries(
wintc-menutest
PRIVATE ${GLIB_LIBRARIES}
PRIVATE ${GTK3_LIBRARIES}
PRIVATE ${WINTC_COMCTL_LIBRARIES}
PRIVATE ${WINTC_COMGTK_LIBRARIES}
)
# Installation
#
wintc_configure_and_install_packaging()
wintc_install_icons_into_package()
install(
FILES wintc-menutest.desktop
DESTINATION share/applications
)
install(
TARGETS wintc-menutest
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

View File

@@ -0,0 +1,4 @@
# hello
This directory contains the source code for the *Hello* sample program.
![image](https://github.com/rozniak/xfce-winxp-tc/assets/13258281/b78ba08f-c18d-4331-9d7f-072eb0090cca)

View File

@@ -0,0 +1,4 @@
bt,rt:glib2
bt,rt:gtk3
bt,rt:wintc-comctl
bt,rt:wintc-comgtk

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

View File

@@ -0,0 +1,75 @@
#include <glib.h>
#include <gtk/gtk.h>
#include <wintc/comgtk.h>
#include "application.h"
#include "window.h"
//
// GTK OOP CLASS/INSTANCE DEFINITIONS
//
struct _WinTCMenuTestApplicationClass
{
GtkApplicationClass __parent__;
};
struct _WinTCMenuTestApplication
{
GtkApplication __parent__;
};
//
// FORWARD DECLARATIONS
//
static void wintc_menu_test_application_activate(
GApplication* application
);
//
// GTK TYPE DEFINITIONS & CTORS
//
G_DEFINE_TYPE(
WinTCMenuTestApplication,
wintc_menu_test_application,
GTK_TYPE_APPLICATION
)
static void wintc_menu_test_application_class_init(
WinTCMenuTestApplicationClass* klass
)
{
GApplicationClass* application_class = G_APPLICATION_CLASS(klass);
application_class->activate = wintc_menu_test_application_activate;
}
static void wintc_menu_test_application_init(
WINTC_UNUSED(WinTCMenuTestApplication* self)
) { }
//
// CLASS VIRTUAL METHODS
//
static void wintc_menu_test_application_activate(
GApplication* application
)
{
GtkWidget* new_window =
wintc_menu_test_window_new(WINTC_MENU_TEST_APPLICATION(application));
gtk_widget_show_all(new_window);
}
//
// PUBLIC FUNCTIONS
//
WinTCMenuTestApplication* wintc_menu_test_application_new(void)
{
return WINTC_MENU_TEST_APPLICATION(
g_object_new(
wintc_menu_test_application_get_type(),
"application-id", "uk.co.oddmatics.wintc.play.menu-test",
NULL
)
);
}

View File

@@ -0,0 +1,27 @@
#ifndef __APPLICATION_H__
#define __APPLICATION_H__
#include <glib.h>
#include <gtk/gtk.h>
//
// GTK OOP BOILERPLATE
//
typedef struct _WinTCMenuTestApplicationClass WinTCMenuTestApplicationClass;
typedef struct _WinTCMenuTestApplication WinTCMenuTestApplication;
#define WINTC_TYPE_MENU_TEST_APPLICATION (wintc_menu_test_application_get_type())
#define WINTC_MENU_TEST_APPLICATION(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), WINTC_TYPE_MENU_TEST_APPLICATION, WinTCMenuTestApplication))
#define WINTC_MENU_TEST_APPLICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), WINTC_TYPE_MENU_TEST_APPLICATION, WinTCMenuTestApplicationClass))
#define IS_WINTC_MENU_TEST_APPLICATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), WINTC_TYPE_MENU_TEST_APPLICATION))
#define IS_WINTC_MENU_TEST_APPLICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), WINTC_TYPE_MENU_TEST_APPLICATION))
#define WINTC_MENU_TEST_APPLICATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), WINTC_TYPE_MENU_TEST_APPLICATION, WinTCMenuTestApplicationClass))
GType wintc_menu_test_application_get_type(void) G_GNUC_CONST;
//
// PUBLIC FUNCTIONS
//
WinTCMenuTestApplication* wintc_menu_test_application_new(void);
#endif

View File

@@ -0,0 +1,20 @@
#include <glib.h>
#include "application.h"
int main(
int argc,
char* argv[]
)
{
WinTCMenuTestApplication* app = wintc_menu_test_application_new();
int status;
g_set_application_name("Menu Test");
status = g_application_run(G_APPLICATION(app), argc, argv);
g_object_unref(app);
return status;
}

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id="menu">
<submenu>
<attribute name="label">Example menu</attribute>
<section>
<item>
<attribute name="label">Item 1</attribute>
<attribute name="icon">add</attribute>
</item>
<item>
<attribute name="label">Item 2</attribute>
<attribute name="icon">find</attribute>
</item>
<item>
<attribute name="label">Item 3</attribute>
<attribute name="icon">paste</attribute>
</item>
</section>
<section>
<item>
<attribute name="label">Item 4</attribute>
<attribute name="icon">computer</attribute>
</item>
<item>
<attribute name="label">Item 5</attribute>
<attribute name="icon">printer</attribute>
</item>
<item>
<attribute name="label">Item 6</attribute>
<attribute name="icon">drive-optical</attribute>
</item>
</section>
</submenu>
<submenu>
<attribute name="label">Second menu</attribute>
<section>
<item>
<attribute name="label">Item 1</attribute>
<attribute name="icon">add</attribute>
</item>
<item>
<attribute name="label">Item 2</attribute>
<attribute name="icon">find</attribute>
</item>
<item>
<attribute name="label">Item 3</attribute>
<attribute name="icon">paste</attribute>
</item>
</section>
<submenu>
<attribute name="label">More</attribute>
<attribute name="icon">applications-other</attribute>
<section>
<item>
<attribute name="label">Item 1</attribute>
<attribute name="icon">add</attribute>
</item>
<item>
<attribute name="label">Item 2</attribute>
<attribute name="icon">copy</attribute>
</item>
</section>
</submenu>
</submenu>
</menu>
</interface>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/uk/oddmatics/wintc/samples/menutest">
<file>menubar.ui</file>
</gresource>
</gresources>

View File

@@ -0,0 +1,142 @@
#include <glib.h>
#include <gtk/gtk.h>
#include <wintc/comctl.h>
#include <wintc/comgtk.h>
#include "application.h"
#include "window.h"
// Change this to use either our binding or GTK
//
#define TEST_USE_CTL_BINDING 1
//
// FORWARD DECLARATIONS
//
static void wintc_menu_test_window_dispose(
GObject* object
);
//
// GTK OOP CLASS/INSTANCE DEFINITIONS
//
struct _WinTCMenuTestWindowClass
{
GtkApplicationWindowClass __parent__;
};
struct _WinTCMenuTestWindow
{
GtkApplicationWindow __parent__;
WinTCCtlMenuBinding* menu_binding;
};
//
// GTK TYPE DEFINITION & CTORS
//
G_DEFINE_TYPE(
WinTCMenuTestWindow,
wintc_menu_test_window,
GTK_TYPE_APPLICATION_WINDOW
)
static void wintc_menu_test_window_class_init(
WinTCMenuTestWindowClass* klass
)
{
GObjectClass* object_class = G_OBJECT_CLASS(klass);
object_class->dispose = wintc_menu_test_window_dispose;
}
static void wintc_menu_test_window_init(
WinTCMenuTestWindow* self
)
{
GtkWidget* box_container = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_window_set_default_size(
GTK_WINDOW(self),
320,
200
);
// Retrieve menu model
//
GtkBuilder* builder =
gtk_builder_new_from_resource(
"/uk/oddmatics/wintc/samples/menutest/menubar.ui"
);
GMenuModel* model =
G_MENU_MODEL(gtk_builder_get_object(builder, "menu"));
g_object_ref(model);
g_object_unref(builder);
// Create a boring menu strip
//
GtkWidget* menu_bar = gtk_menu_bar_new();
if (TEST_USE_CTL_BINDING)
{
self->menu_binding =
wintc_ctl_menu_binding_new(
GTK_MENU_SHELL(menu_bar),
model
);
}
else
{
gtk_menu_shell_bind_model(
GTK_MENU_SHELL(menu_bar),
model,
NULL,
FALSE
);
}
gtk_container_add(GTK_CONTAINER(box_container), menu_bar);
// Show!
//
gtk_container_add(GTK_CONTAINER(self), box_container);
gtk_widget_show_all(box_container);
}
//
// CLASS VIRTUAL METHODS
//
static void wintc_menu_test_window_dispose(
GObject* object
)
{
WinTCMenuTestWindow* wnd = WINTC_MENU_TEST_WINDOW(object);
if (wnd->menu_binding)
{
g_clear_object(&(wnd->menu_binding));
}
(G_OBJECT_CLASS(wintc_menu_test_window_parent_class))
->dispose(object);
}
//
// PUBLIC FUNCTIONS
//
GtkWidget* wintc_menu_test_window_new(
WinTCMenuTestApplication* app
)
{
return GTK_WIDGET(
g_object_new(
WINTC_TYPE_MENU_TEST_WINDOW,
"application", GTK_APPLICATION(app),
"title", "Menu Binding Test",
NULL
)
);
}

View File

@@ -0,0 +1,31 @@
#ifndef __WINDOW_H__
#define __WINDOW_H__
#include <glib.h>
#include <gtk/gtk.h>
#include "application.h"
//
// GTK OOP BOILERPLATE
//
typedef struct _WinTCMenuTestWindowClass WinTCMenuTestWindowClass;
typedef struct _WinTCMenuTestWindow WinTCMenuTestWindow;
#define WINTC_TYPE_MENU_TEST_WINDOW (wintc_menu_test_window_get_type())
#define WINTC_MENU_TEST_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), WINTC_TYPE_MENU_TEST_WINDOW, WinTCMenuTestWindow))
#define WINTC_MENU_TEST_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), WINTC_TYPE_MENU_TEST_WINDOW, WinTCMenuTestWindowClass))
#define IS_WINTC_MENU_TEST_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), WINTC_TYPE_MENU_TEST_WINDOW))
#define IS_WINTC_MENU_TEST_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), WINTC_TYPE_MENU_TEST_WINDOW))
#define WINTC_MENU_TEST_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), WINTC_TYPE_MENU_TEST_WINDOW, WinTCMenuTestWindowClass))
GType wintc_menu_test_window_get_type(void) G_GNUC_CONST;
//
// PUBLIC FUNCTIONS
//
GtkWidget* wintc_menu_test_window_new(
WinTCMenuTestApplication* app
);
#endif

View File

@@ -0,0 +1,9 @@
[Desktop Entry]
Name=Menu Test (WinTC Test)
Comment=WinTC menu binding test program.
Exec=wintc-menutest
Icon=wintc-menutest
Terminal=false
StartupNotify=false
Type=Application
Categories=Utility;GTK;

View File

@@ -37,6 +37,8 @@ add_library(
public/animctl.h
src/cpl.c
public/cpl.h
src/menubind.c
public/menubind.h
src/style.c
public/style.h
)

View File

@@ -3,6 +3,7 @@
#include "@LIB_HEADER_DIR@/animctl.h"
#include "@LIB_HEADER_DIR@/cpl.h"
#include "@LIB_HEADER_DIR@/menubind.h"
#include "@LIB_HEADER_DIR@/style.h"
#endif

View File

@@ -0,0 +1,28 @@
#ifndef __COMCTL_MENUBIND_H__
#define __COMCTL_MENUBIND_H__
#include <glib.h>
#include <gtk/gtk.h>
//
// GTK OOP BOILERPLATE
//
#define WINTC_TYPE_CTL_MENU_BINDING (wintc_ctl_menu_binding_get_type())
G_DECLARE_FINAL_TYPE(
WinTCCtlMenuBinding,
wintc_ctl_menu_binding,
WINTC,
CTL_MENU_BINDING,
GObject
)
//
// PUBLIC FUNCTIONS
//
WinTCCtlMenuBinding* wintc_ctl_menu_binding_new(
GtkMenuShell* menu_shell,
GMenuModel* menu_model
);
#endif

View File

@@ -0,0 +1,755 @@
#include <glib.h>
#include <gtk/gtk.h>
#include <wintc/comgtk.h>
#include "../public/menubind.h"
//
// PRIVATE ENUMS
//
enum
{
PROP_MENU_SHELL = 1,
PROP_MENU_MODEL
};
//
// PRIVATE STRUCTURES
//
typedef struct _WinTCCtlMenuBindingMenu
{
WinTCCtlMenuBinding* menu_binding;
GtkMenuShell* menu_shell;
GMenuModel* menu_model;
GList* sections;
} WinTCCtlMenuBindingMenu;
typedef struct _WinTCCtlMenuBindingSection
{
WinTCCtlMenuBindingMenu* parent_menu;
GMenuModel* menu_model;
gint item_count;
} WinTCCtlMenuBindingSection;
//
// FORWARD DECLARATIONS
//
static void wintc_ctl_menu_binding_constructed(
GObject* object
);
static void wintc_ctl_menu_binding_dispose(
GObject* object
);
static void wintc_ctl_menu_binding_finalize(
GObject* object
);
static void wintc_ctl_menu_binding_get_property(
GObject* object,
guint prop_id,
GValue* value,
GParamSpec* pspec
);
static void wintc_ctl_menu_binding_set_property(
GObject* object,
guint prop_id,
const GValue* value,
GParamSpec* pspec
);
static GList* wintc_ctl_menu_binding_find_section_pos(
WinTCCtlMenuBindingMenu* menu,
gint dst_pos,
gint* mid_pos
);
static void wintc_ctl_menu_binding_insert_item(
WinTCCtlMenuBindingMenu* menu,
GMenuModel* menu_model,
gint src_pos,
gint dst_pos
);
static void wintc_ctl_menu_binding_track_menu(
WinTCCtlMenuBinding* menu_binding,
GtkMenuShell* menu_shell,
GMenuModel* menu_model
);
static void wintc_ctl_menu_binding_menu_free(
WinTCCtlMenuBindingMenu* menu
);
//
// GTK OOP CLASS/INSTANCE DEFINITIONS
//
typedef struct _WinTCCtlMenuBinding
{
GObject __parent__;
GtkMenuShell* menu_shell;
GMenuModel* menu_model;
GSList* tracked_menus;
} WinTCCtlMenuBinding;
//
// GTK TYPE DEFINITIONS & CTORS
//
G_DEFINE_TYPE(
WinTCCtlMenuBinding,
wintc_ctl_menu_binding,
G_TYPE_OBJECT
)
static void wintc_ctl_menu_binding_class_init(
WinTCCtlMenuBindingClass* klass
)
{
GObjectClass* object_class = G_OBJECT_CLASS(klass);
object_class->constructed = wintc_ctl_menu_binding_constructed;
object_class->dispose = wintc_ctl_menu_binding_dispose;
object_class->finalize = wintc_ctl_menu_binding_finalize;
object_class->get_property = wintc_ctl_menu_binding_get_property;
object_class->set_property = wintc_ctl_menu_binding_set_property;
g_object_class_install_property(
object_class,
PROP_MENU_SHELL,
g_param_spec_object(
"menu-shell",
"MenuShell",
"The GTK menu shell to manage.",
GTK_TYPE_MENU_SHELL,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY
)
);
g_object_class_install_property(
object_class,
PROP_MENU_MODEL,
g_param_spec_object(
"menu-model",
"MenuModel",
"The menu model to bind to.",
G_TYPE_MENU_MODEL,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY
)
);
}
static void wintc_ctl_menu_binding_init(
WINTC_UNUSED(WinTCCtlMenuBinding* self)
) {}
//
// CLASS VIRTUAL METHODS
//
static void wintc_ctl_menu_binding_constructed(
GObject* object
)
{
WinTCCtlMenuBinding* menu_binding = WINTC_CTL_MENU_BINDING(object);
wintc_container_clear(
GTK_CONTAINER(menu_binding->menu_shell)
);
wintc_ctl_menu_binding_track_menu(
menu_binding,
menu_binding->menu_shell,
menu_binding->menu_model
);
(G_OBJECT_CLASS(wintc_ctl_menu_binding_parent_class))
->constructed(object);
}
static void wintc_ctl_menu_binding_dispose(
GObject* object
)
{
WinTCCtlMenuBinding* menu_binding = WINTC_CTL_MENU_BINDING(object);
g_clear_object(&(menu_binding->menu_shell));
g_clear_object(&(menu_binding->menu_model));
(G_OBJECT_CLASS(wintc_ctl_menu_binding_parent_class))
->dispose(object);
}
static void wintc_ctl_menu_binding_finalize(
GObject* object
)
{
WinTCCtlMenuBinding* menu_binding = WINTC_CTL_MENU_BINDING(object);
g_clear_slist(
&(menu_binding->tracked_menus),
(GDestroyNotify) wintc_ctl_menu_binding_menu_free
);
(G_OBJECT_CLASS(wintc_ctl_menu_binding_parent_class))
->finalize(object);
}
static void wintc_ctl_menu_binding_get_property(
GObject* object,
guint prop_id,
WINTC_UNUSED(GValue* value),
GParamSpec* pspec
)
{
switch (prop_id)
{
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void wintc_ctl_menu_binding_set_property(
GObject* object,
guint prop_id,
const GValue* value,
GParamSpec* pspec
)
{
WinTCCtlMenuBinding* menu_binding = WINTC_CTL_MENU_BINDING(object);
switch (prop_id)
{
case PROP_MENU_SHELL:
menu_binding->menu_shell =
g_value_dup_object(value);
break;
case PROP_MENU_MODEL:
menu_binding->menu_model =
g_value_dup_object(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
//
// PUBLIC FUNCTIONS
//
WinTCCtlMenuBinding* wintc_ctl_menu_binding_new(
GtkMenuShell* menu_shell,
GMenuModel* menu_model
)
{
return WINTC_CTL_MENU_BINDING(
g_object_new(
WINTC_TYPE_CTL_MENU_BINDING,
"menu-shell", menu_shell,
"menu-model", menu_model,
NULL
)
);
}
//
// PRIVATE FUNCTIONS
//
static GList* wintc_ctl_menu_binding_find_section_pos(
WinTCCtlMenuBindingMenu* menu,
gint dst_pos,
gint* mid_pos
)
{
gint i = 0;
GList* iter = menu->sections;
if (!iter)
{
*mid_pos = 0;
return NULL;
}
for (; iter; iter = iter->next)
{
WinTCCtlMenuBindingSection* section =
(WinTCCtlMenuBindingSection*) iter->data;
// Have we hit our target?
//
if (i == dst_pos)
{
*mid_pos = 0;
return iter;
}
if (!(section->menu_model))
{
// Check if section should be inserted in the middle of a fake
// section
//
if (i + section->item_count > dst_pos)
{
*mid_pos = dst_pos - i;
return iter;
}
// Progress num of items in the fake section
//
i += section->item_count;
}
else
{
// Progress only 1 as a real section is considered 1 item
i++;
}
}
*mid_pos = 0;
return NULL;
}
static void wintc_ctl_menu_binding_insert_item(
WinTCCtlMenuBindingMenu* menu,
GMenuModel* menu_model,
gint src_pos,
gint dst_pos
)
{
gboolean is_menubar = GTK_IS_MENU_BAR(menu->menu_shell);
// If this is a section, get it set up - find where to insert it in the
// list... it could be in the middle of normal menu items
//
GMenuModel* section =
g_menu_model_get_item_link(
menu_model,
src_pos,
G_MENU_LINK_SECTION
);
if (section)
{
// Create new section
//
WinTCCtlMenuBindingSection* new_section =
g_new(WinTCCtlMenuBindingSection, 1);
new_section->parent_menu = menu;
new_section->menu_model = section;
new_section->item_count = 0;
// Find where to insert
//
gint mid_pos = 0;
GList* target_li =
wintc_ctl_menu_binding_find_section_pos(menu, dst_pos, &mid_pos);
if (!target_li)
{
menu->sections =
g_list_append(menu->sections, new_section);
}
else
{
if (mid_pos)
{
WinTCCtlMenuBindingSection* target =
(WinTCCtlMenuBindingSection*) target_li->data;
// Need to split up 'fake' section
//
WinTCCtlMenuBindingSection* new_subsection =
g_new(WinTCCtlMenuBindingSection, 1);
new_subsection->parent_menu = menu;
new_subsection->menu_model = NULL;
new_subsection->item_count = target->item_count - mid_pos;
target->item_count -= new_subsection->item_count;
// Append the new section + the latter half of the split fake
// section
//
menu->sections =
g_list_insert_before(
menu->sections,
target_li->next,
new_section
);
menu->sections =
g_list_insert_before(
menu->sections,
target_li->next,
new_subsection
);
}
else
{
menu->sections =
g_list_insert_before(
menu->sections,
target_li,
new_section
);
}
}
// Insert items from the section
//
gint n_items = g_menu_model_get_n_items(section);
for (gint i = 0; i < n_items; i++)
{
wintc_ctl_menu_binding_insert_item(
menu,
section,
i,
i
);
}
return;
}
// Actually have an item - pull its data
//
GIcon* icon = NULL;
GVariant* icon_var;
gchar* label = NULL;
g_menu_model_get_item_attribute(
menu_model,
src_pos,
G_MENU_ATTRIBUTE_LABEL,
"s",
&label
);
icon_var =
g_menu_model_get_item_attribute_value(
menu_model,
src_pos,
G_MENU_ATTRIBUTE_ICON,
NULL
);
if (icon_var)
{
icon = g_icon_deserialize(icon_var);
g_variant_unref(icon_var);
}
// Get action information
//
gchar* action_name = NULL;
GVariant* action_target;
if (
g_menu_model_get_item_attribute(
menu_model,
src_pos,
G_MENU_ATTRIBUTE_ACTION,
"s",
&action_name
)
)
{
action_target =
g_menu_model_get_item_attribute_value(
menu_model,
src_pos,
G_MENU_ATTRIBUTE_TARGET,
NULL
);
}
// Create the item
//
GtkWidget* box_container = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
GtkWidget* menu_item = gtk_menu_item_new();
gtk_container_add(GTK_CONTAINER(menu_item), box_container);
if (!is_menubar) // Only add icons to submenus
{
GtkWidget* img_icon = gtk_image_new();
gtk_widget_set_margin_end(img_icon, 6); // Got this from Mousepad
gtk_widget_set_size_request(img_icon, 16, 16);
if (icon)
{
gtk_image_set_from_gicon(
GTK_IMAGE(img_icon),
icon,
GTK_ICON_SIZE_MENU
);
gtk_image_set_pixel_size(
GTK_IMAGE(img_icon),
16
);
g_object_unref(icon);
}
gtk_box_pack_start(
GTK_BOX(box_container),
img_icon,
FALSE,
FALSE,
0
);
}
if (action_name)
{
gtk_actionable_set_action_name(
GTK_ACTIONABLE(menu_item),
action_name
);
gtk_actionable_set_action_target_value(
GTK_ACTIONABLE(menu_item),
action_target
);
g_free(action_name);
g_variant_unref(action_target);
}
if (label)
{
GtkWidget* label_title = gtk_label_new(label);
gtk_label_set_xalign(GTK_LABEL(label_title), 0.0f);
gtk_box_pack_start(
GTK_BOX(box_container),
label_title,
TRUE,
TRUE,
0
);
g_free(label);
}
// Do we have a submenu for this item?
//
GMenuModel* submenu_model =
g_menu_model_get_item_link(
menu_model,
src_pos,
G_MENU_LINK_SUBMENU
);
if (submenu_model)
{
GtkWidget* submenu = gtk_menu_new();
gtk_menu_set_reserve_toggle_size(GTK_MENU(submenu), FALSE);
wintc_ctl_menu_binding_track_menu(
menu->menu_binding,
GTK_MENU_SHELL(submenu),
submenu_model
);
gtk_menu_item_set_submenu(
GTK_MENU_ITEM(menu_item),
submenu
);
}
//
// Insert the item
//
GList* iter = menu->sections;
gint real_pos = 0;
// menu_model could either be the menu itself or one of its child sections
//
gboolean is_subsection = menu_model != menu->menu_model;
// If its a subsection then locate it and append the item
//
if (is_subsection)
{
for (; iter; iter = iter->next)
{
WinTCCtlMenuBindingSection* check_section =
(WinTCCtlMenuBindingSection*) iter->data;
// Is this the correct section? If so, insert and move on
//
if (menu_model == check_section->menu_model)
{
check_section->item_count++;
real_pos += dst_pos; // Location within the section
gtk_menu_shell_insert(menu->menu_shell, menu_item, real_pos);
return;
}
// No? Increment real_pos as needed
//
real_pos += check_section->item_count;
}
// This should never happen - an attempt to add an item within a
// section into a menu which does not contain the section
//
g_critical(
"%s",
"ctl - menubind - section not found in menu for item?"
);
return;
}
// Continue - add the item directly into the menu
//
if (!iter)
{
// If there isn't yet any sections, then this must be the first item
//
if (dst_pos > 0)
{
g_warning(
"ctl - menubind - first item not at 0? (wants %d)",
dst_pos
);
}
// Create fake section
//
WinTCCtlMenuBindingSection* fake_section =
g_new(WinTCCtlMenuBindingSection, 1);
fake_section->parent_menu = menu;
fake_section->menu_model = NULL;
fake_section->item_count = 1;
menu->sections =
g_list_append(menu->sections, fake_section);
gtk_menu_shell_append(menu->menu_shell, menu_item);
return;
}
// Need to iterate through sections to find where to add this item
//
gint i = 0;
while (i < dst_pos)
{
WinTCCtlMenuBindingSection* subsection =
(WinTCCtlMenuBindingSection*) iter->data;
// Check we're not overshooting - could be within a 'fake' section...
//
if (
!(subsection->menu_model) &&
i + subsection->item_count >= dst_pos
)
{
real_pos += (dst_pos - i);
break;
}
// No where else to go?
//
if (!(iter->next))
{
break;
}
// Moving on...
//
iter = iter->next;
real_pos += subsection->item_count;
if (subsection->menu_model)
{
i++;
}
else
{
i += subsection->item_count;
}
}
// If we fell out, we need to add the item to the end of the menu
//
WinTCCtlMenuBindingSection* last_section =
(WinTCCtlMenuBindingSection*) iter->data;
if (last_section->menu_model)
{
// If the last section is not fake, then we need to append a
// fake section now with the item in it
//
last_section = g_new(WinTCCtlMenuBindingSection, 1);
last_section->parent_menu = menu;
last_section->menu_model = NULL;
last_section->item_count = 0; // Will be incremented in a moment
menu->sections =
g_list_append(menu->sections, last_section);
// FIXME: Probably append a separator here first
}
last_section->item_count++;
gtk_menu_shell_append(menu->menu_shell, menu_item);
}
static void wintc_ctl_menu_binding_track_menu(
WinTCCtlMenuBinding* menu_binding,
GtkMenuShell* menu_shell,
GMenuModel* menu_model
)
{
// Initialise new tracker
//
WinTCCtlMenuBindingMenu* tracker =
g_new(WinTCCtlMenuBindingMenu, 1);
tracker->menu_binding = menu_binding;
tracker->menu_shell = menu_shell;
tracker->menu_model = menu_model;
tracker->sections = NULL;
menu_binding->tracked_menus =
g_slist_append(
menu_binding->tracked_menus,
tracker
);
// Iterate over the menu model
//
gint n_menu_items = g_menu_model_get_n_items(menu_model);
for (gint i = 0; i < n_menu_items; i++)
{
wintc_ctl_menu_binding_insert_item(
tracker,
tracker->menu_model,
i,
i
);
}
}
static void wintc_ctl_menu_binding_menu_free(
WinTCCtlMenuBindingMenu* menu
)
{
g_clear_list(&(menu->sections), (GDestroyNotify) g_free);
g_free(menu);
}