Enhancement: Fixes #445, taskband - Start menu - All Programs needs to handle added/removed desktop entries

This commit is contained in:
Rory Fewell
2025-04-17 12:40:58 +01:00
parent 8bbfb9abc5
commit 6c22327529
22 changed files with 2650 additions and 324 deletions

2
.gitignore vendored
View File

@@ -19,6 +19,8 @@ mui-stuff/
[Bb]uild/ [Bb]uild/
config.h config.h
libapi.h libapi.h
marshal.c
marshal.h
meta.h meta.h
resources.c resources.c
*.deb *.deb

View File

@@ -53,3 +53,45 @@ function(wintc_gdbus_codegen XML_FILE OUT_FILE_NOEXT CP_PUBLIC)
) )
endif() endif()
endfunction() endfunction()
# Define function for glib-genmashal
#
function(wintc_glib_genmarshal)
find_program(GLIB_GENMARSHAL glib-genmarshal REQUIRED)
add_custom_command(
OUTPUT
${CMAKE_CURRENT_SOURCE_DIR}/src/marshal.h
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src
COMMAND ${GLIB_GENMARSHAL}
ARGS
--header
--prefix wintc_cclosure_marshal
--output marshal.h
marshals.list
VERBATIM
DEPENDS
src/marshals.list
)
add_custom_command(
OUTPUT
${CMAKE_CURRENT_SOURCE_DIR}/src/marshal.c
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src
COMMAND ${GLIB_GENMARSHAL}
ARGS
--body
--prefix wintc_cclosure_marshal
--output marshal.c
marshals.list
VERBATIM
DEPENDS
src/marshals.list
)
add_custom_target(
marshal-codegen
DEPENDS
src/marshal.c
src/marshal.h
)
endfunction()

View File

@@ -8,7 +8,7 @@
// Change this to use either our binding or GTK // Change this to use either our binding or GTK
// //
#define TEST_USE_CTL_BINDING 1 #define TEST_USE_CTL_BINDING 0
// //
// FORWARD DECLARATIONS // FORWARD DECLARATIONS
@@ -17,6 +17,20 @@ static void wintc_menu_test_window_dispose(
GObject* object GObject* object
); );
static gint wintc_menu_test_window_find_item_index(
GMenuModel* model,
GMenuModel** target_model
);
static void on_button_add_clicked(
GtkButton* button,
gpointer user_data
);
static void on_button_del_clicked(
GtkButton* button,
gpointer user_data
);
// //
// GTK OOP CLASS/INSTANCE DEFINITIONS // GTK OOP CLASS/INSTANCE DEFINITIONS
// //
@@ -100,6 +114,43 @@ static void wintc_menu_test_window_init(
gtk_container_add(GTK_CONTAINER(box_container), menu_bar); gtk_container_add(GTK_CONTAINER(box_container), menu_bar);
// Add a strip for buttons for doing random additions/deletions
//
GtkWidget* box_buttons = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
GtkWidget* button_add = gtk_button_new_with_label("Random add");
GtkWidget* button_del = gtk_button_new_with_label("Random del");
gtk_box_pack_start(
GTK_BOX(box_buttons),
button_add,
FALSE,
FALSE,
0
);
gtk_box_pack_start(
GTK_BOX(box_buttons),
button_del,
FALSE,
FALSE,
0
);
gtk_container_add(GTK_CONTAINER(box_container), box_buttons);
g_signal_connect(
button_add,
"clicked",
G_CALLBACK(on_button_add_clicked),
model
);
g_signal_connect(
button_del,
"clicked",
G_CALLBACK(on_button_del_clicked),
model
);
// Show! // Show!
// //
gtk_container_add(GTK_CONTAINER(self), box_container); gtk_container_add(GTK_CONTAINER(self), box_container);
@@ -140,3 +191,150 @@ GtkWidget* wintc_menu_test_window_new(
) )
); );
} }
//
// PRIVATE FUNCTIONS
//
static gint wintc_menu_test_window_find_item_index(
GMenuModel* model,
GMenuModel** target_model
)
{
// We receive a top level model (the menu bar) - pick a menu to work on
//
gint n_items = g_menu_model_get_n_items(model);
GMenuModel* menu =
g_menu_model_get_item_link(
model,
g_random_int_range(0, n_items),
G_MENU_LINK_SUBMENU
);
// Find an item
//
gint idx_rand;
GMenuModel* model_current = menu;
while (TRUE)
{
n_items = g_menu_model_get_n_items(model_current);
// 0 items? It's over
//
if (!n_items)
{
*target_model = model_current;
return 0;
}
// Wahey we have items, let's pick one
//
idx_rand = g_random_int_range(0, n_items);
// If we hit a submenu, enter it
//
GMenuModel* model_submenu =
g_menu_model_get_item_link(
model_current,
idx_rand,
G_MENU_LINK_SUBMENU
);
if (model_submenu)
{
model_current = model_submenu;
continue;
}
// Otherwise return this index
//
*target_model = model_current;
return idx_rand;
}
}
//
// CALLBACKS
//
static void on_button_add_clicked(
WINTC_UNUSED(GtkButton* button),
gpointer user_data
)
{
GMenuModel* model = G_MENU_MODEL(user_data);
gint target_idx;
GMenuModel* target_model = NULL;
target_idx =
wintc_menu_test_window_find_item_index(model, &target_model);
// Create and add the menu item
//
GMenuItem* menu_item = g_menu_item_new("A random item.", NULL);
g_menu_item_set_icon(
menu_item,
g_themed_icon_new("emblem-favorite")
);
g_menu_insert_item(G_MENU(target_model), target_idx, menu_item);
g_object_unref(menu_item);
}
static void on_button_del_clicked(
WINTC_UNUSED(GtkButton* button),
gpointer user_data
)
{
GMenuModel* model = G_MENU_MODEL(user_data);
gint target_idx;
GMenuModel* target_model = NULL;
// We shouldn't delete section items... this is really rubbish but I'm also
// REALLY lazy so just try a few times to find a normal item otherwise give
// up
//
gint attempts = 0;
while (attempts < 3)
{
attempts++;
target_idx =
wintc_menu_test_window_find_item_index(model, &target_model);
// Skip if this item is a section
//
if (
g_menu_model_get_item_link(
target_model,
target_idx,
G_MENU_LINK_SECTION
)
)
{
continue;
}
// Don't delete if there's actually no items in this model
//
if (!g_menu_model_get_n_items(target_model))
{
continue;
}
// All good, bin the item
//
g_menu_remove(G_MENU(target_model), target_idx);
return;
}
g_message(
"%s",
"Didn't land on a normal item to bin, better luck next time."
);
}

View File

@@ -24,6 +24,8 @@ typedef struct _WinTCCtlMenuBindingMenu
GMenuModel* menu_model; GMenuModel* menu_model;
GList* sections; GList* sections;
gulong sigid_items_changed;
} WinTCCtlMenuBindingMenu; } WinTCCtlMenuBindingMenu;
typedef struct _WinTCCtlMenuBindingSection typedef struct _WinTCCtlMenuBindingSection
@@ -70,6 +72,11 @@ static void wintc_ctl_menu_binding_insert_item(
gint src_pos, gint src_pos,
gint dst_pos gint dst_pos
); );
static void wintc_ctl_menu_binding_remove_item(
WinTCCtlMenuBindingMenu* menu,
GMenuModel* menu_model,
gint src_pos
);
static void wintc_ctl_menu_binding_track_menu( static void wintc_ctl_menu_binding_track_menu(
WinTCCtlMenuBinding* menu_binding, WinTCCtlMenuBinding* menu_binding,
GtkMenuShell* menu_shell, GtkMenuShell* menu_shell,
@@ -80,6 +87,14 @@ static void wintc_ctl_menu_binding_menu_free(
WinTCCtlMenuBindingMenu* menu WinTCCtlMenuBindingMenu* menu
); );
static void on_menu_model_menu_items_changed(
GMenuModel* model,
gint position,
gint removed,
gint added,
gpointer user_data
);
// //
// GTK OOP CLASS/INSTANCE DEFINITIONS // GTK OOP CLASS/INSTANCE DEFINITIONS
// //
@@ -481,6 +496,11 @@ static void wintc_ctl_menu_binding_insert_item(
gtk_widget_set_margin_end(img_icon, 6); // Got this from Mousepad gtk_widget_set_margin_end(img_icon, 6); // Got this from Mousepad
gtk_widget_set_size_request(img_icon, 16, 16); gtk_widget_set_size_request(img_icon, 16, 16);
gtk_menu_item_set_reserve_indicator(
GTK_MENU_ITEM(menu_item),
TRUE
);
if (icon) if (icon)
{ {
gtk_image_set_from_gicon( gtk_image_set_from_gicon(
@@ -537,6 +557,8 @@ static void wintc_ctl_menu_binding_insert_item(
g_free(label); g_free(label);
} }
gtk_widget_show_all(menu_item);
// Do we have a submenu for this item? // Do we have a submenu for this item?
// //
GMenuModel* submenu_model = GMenuModel* submenu_model =
@@ -645,7 +667,7 @@ static void wintc_ctl_menu_binding_insert_item(
// //
gint i = 0; gint i = 0;
while (i < dst_pos) while (i < dst_pos && iter)
{ {
WinTCCtlMenuBindingSection* subsection = WinTCCtlMenuBindingSection* subsection =
(WinTCCtlMenuBindingSection*) iter->data; (WinTCCtlMenuBindingSection*) iter->data;
@@ -661,13 +683,6 @@ static void wintc_ctl_menu_binding_insert_item(
break; break;
} }
// No where else to go?
//
if (!(iter->next))
{
break;
}
// Moving on... // Moving on...
// //
iter = iter->next; iter = iter->next;
@@ -683,10 +698,55 @@ static void wintc_ctl_menu_binding_insert_item(
} }
} }
// If we found where to insert, then get that sorted
//
if (iter)
{
WinTCCtlMenuBindingSection* found_section =
(WinTCCtlMenuBindingSection*) iter->data;
// If this is a real section, look for a fake section before it to
// insert into instead
//
if (found_section->menu_model && iter->prev)
{
WinTCCtlMenuBindingSection* prev_section =
(WinTCCtlMenuBindingSection*) iter->prev->data;
if (!(prev_section->menu_model))
{
found_section = prev_section;
}
}
// Do we have a fake section? If not, create a new one before the real
// section
//
if (found_section->menu_model)
{
found_section = g_new(WinTCCtlMenuBindingSection, 1);
found_section->parent_menu = menu;
found_section->menu_model = NULL;
found_section->item_count = 0; // Will be incremented in a moment
menu->sections =
g_list_insert_before(
menu->sections,
iter,
found_section
);
}
found_section->item_count++;
gtk_menu_shell_insert(menu->menu_shell, menu_item, real_pos);
return;
}
// If we fell out, we need to add the item to the end of the menu // If we fell out, we need to add the item to the end of the menu
// //
WinTCCtlMenuBindingSection* last_section = WinTCCtlMenuBindingSection* last_section =
(WinTCCtlMenuBindingSection*) iter->data; (WinTCCtlMenuBindingSection*) (g_list_last(menu->sections))->data;
if (last_section->menu_model) if (last_section->menu_model)
{ {
@@ -709,6 +769,158 @@ static void wintc_ctl_menu_binding_insert_item(
gtk_menu_shell_append(menu->menu_shell, menu_item); gtk_menu_shell_append(menu->menu_shell, menu_item);
} }
static void wintc_ctl_menu_binding_remove_item(
WinTCCtlMenuBindingMenu* menu,
GMenuModel* menu_model,
gint src_pos
)
{
gboolean is_subsection = menu_model != menu->menu_model;
// Let's fine the position of the item to bin!
//
gint real_pos = 0;
if (is_subsection)
{
for (GList* iter = menu->sections; iter; iter = iter->next)
{
WinTCCtlMenuBindingSection* check_section =
(WinTCCtlMenuBindingSection*) iter->data;
if (!(check_section->menu_model))
{
real_pos += check_section->item_count;
continue;
}
if (check_section->menu_model == menu_model)
{
real_pos += src_pos;
check_section->item_count--;
gtk_widget_destroy(
wintc_container_get_nth_child(
GTK_CONTAINER(menu->menu_shell),
real_pos
)
);
// Handle when we just destroyed the last item in a section
//
if (!(check_section->item_count))
{
menu->sections =
g_list_delete_link(
menu->sections,
iter
);
}
return;
}
}
}
else
{
gint i = 0;
for (GList* iter = menu->sections; iter; iter = iter->next)
{
WinTCCtlMenuBindingSection* check_section =
(WinTCCtlMenuBindingSection*) iter->data;
if (i == src_pos)
{
// If the item referred to is the section itself, we need to
// delete the entire section
//
if (check_section->menu_model)
{
for (gint j = 0; j < check_section->item_count; j++)
{
gtk_widget_destroy(
wintc_container_get_nth_child(
GTK_CONTAINER(menu->menu_shell),
real_pos
)
);
}
menu->sections =
g_list_delete_link(
menu->sections,
iter
);
return;
}
else
{
check_section->item_count--;
gtk_widget_destroy(
wintc_container_get_nth_child(
GTK_CONTAINER(menu->menu_shell),
real_pos
)
);
if (!(check_section->item_count))
{
menu->sections =
g_list_delete_link(
menu->sections,
iter
);
}
}
return;
}
// Keep looking...
//
// If this section is real, then no fancy stuff is required - if
// it's a 'fake' section then we need to check whether src_pos is
// inside it
//
if (check_section->menu_model)
{
i++;
real_pos += check_section->item_count;
}
else
{
if (i + check_section->item_count > src_pos)
{
real_pos += src_pos - i;
check_section->item_count--;
gtk_widget_destroy(
wintc_container_get_nth_child(
GTK_CONTAINER(menu->menu_shell),
real_pos
)
);
return;
}
i += check_section->item_count;
real_pos += check_section->item_count;
}
}
}
g_critical(
"%s",
"comctl - menu binding - somehow failed to find menu item to delete?"
);
}
static void wintc_ctl_menu_binding_track_menu( static void wintc_ctl_menu_binding_track_menu(
WinTCCtlMenuBinding* menu_binding, WinTCCtlMenuBinding* menu_binding,
GtkMenuShell* menu_shell, GtkMenuShell* menu_shell,
@@ -725,6 +937,19 @@ static void wintc_ctl_menu_binding_track_menu(
tracker->menu_model = menu_model; tracker->menu_model = menu_model;
tracker->sections = NULL; tracker->sections = NULL;
WINTC_LOG_DEBUG(
"comctl - menu binding - new menu tracker: %p",
(void*) tracker
);
tracker->sigid_items_changed =
g_signal_connect(
menu_model,
"items-changed",
G_CALLBACK(on_menu_model_menu_items_changed),
tracker
);
menu_binding->tracked_menus = menu_binding->tracked_menus =
g_slist_append( g_slist_append(
menu_binding->tracked_menus, menu_binding->tracked_menus,
@@ -753,3 +978,38 @@ static void wintc_ctl_menu_binding_menu_free(
g_clear_list(&(menu->sections), (GDestroyNotify) g_free); g_clear_list(&(menu->sections), (GDestroyNotify) g_free);
g_free(menu); g_free(menu);
} }
//
// CALLBACKS
//
static void on_menu_model_menu_items_changed(
GMenuModel* model,
gint position,
gint removed,
gint added,
gpointer user_data
)
{
WinTCCtlMenuBindingMenu* menu = (WinTCCtlMenuBindingMenu*) user_data;
WINTC_LOG_DEBUG(
"comctl - menubind - update pos %d, remove %d, add %d",
position,
removed,
added
);
// Removed items
//
for (gint i = position; i < position + removed; i++)
{
wintc_ctl_menu_binding_remove_item(menu, model, i);
}
// Added items
//
for (gint i = position; i < position + added; i++)
{
wintc_ctl_menu_binding_insert_item(menu, model, i, i);
}
}

View File

@@ -18,4 +18,16 @@ void wintc_container_clear(
GtkContainer* container GtkContainer* container
); );
/**
* Gets the nth child widget of a container.
*
* @param container The container.
* @param pos The index of the widget.
* @return The child widget at position N in the container.
*/
GtkWidget* wintc_container_get_nth_child(
GtkContainer* container,
gint pos
);
#endif #endif

View File

@@ -1,3 +1,5 @@
/** @file */
#ifndef __COMGTK_MEMORY_H__ #ifndef __COMGTK_MEMORY_H__
#define __COMGTK_MEMORY_H__ #define __COMGTK_MEMORY_H__
@@ -6,15 +8,47 @@
// //
// PUBLIC FUNCTIONS // PUBLIC FUNCTIONS
// //
/**
* Frees a null-terminated array - the provided function will be called to free
* each of the array elements.
*
* @param mem The array.
* @param destroy The function for freeing an individual element.
*/
void wintc_freev( void wintc_freev(
gpointer mem, gpointer mem,
GDestroyNotify destroy GDestroyNotify destroy
); );
/**
* Frees an array of the specified size - the provided function will be called
* to free each of the array elements.
*
* @param mem The array.
* @param n_elements The number of elements in the array.
* @param destroy The function for freeing an individual element.
*/
void wintc_freenv( void wintc_freenv(
gpointer mem, gpointer mem,
guint n_elements, guint n_elements,
GDestroyNotify destroy GDestroyNotify destroy
); );
/**
* Convenience function for using memcpy where potentially the destination
* buffer is NULL, in which case nothing will be done.
*
* @param dst_buf The reference to the buffer to copy into.
* @param offset The offset into the destination buffer to copy at.
* @param src_buf The source buffer.
* @param n The number of bytes to be copied.
*/
void wintc_memcpy_ref(
void* dst_buf,
glong offset,
const void* src_buf,
size_t n
);
#endif #endif

View File

@@ -69,6 +69,24 @@ gchar* wintc_str_set_suffix(
const gchar* suffix const gchar* suffix
); );
/**
* Duplicates part of a string from the start and ending at the first
* occurrence of the specified delimiter (exclusive, so the delimiter will not
* be included).
*
* @param str The string.
* @param len The maximum length of str to use, -1 for null terminated string.
* @param c The character to look for.
* @param pos If not NULL, a storage location for position after the delimiter.
* @return The new string, a complete copy of the string if c wasn't found.
*/
gchar* wintc_strdup_nextchr(
const gchar* str,
gssize len,
gunichar c,
const gchar** pos
);
/** /**
* Duplicates a string and replaces the string at the destination with it - if * Duplicates a string and replaces the string at the destination with it - if
* there was a string at the destination pointer, it will be freed. * there was a string at the destination pointer, it will be freed.
@@ -130,4 +148,16 @@ guint wintc_strv_length(
const gchar** str_array const gchar** str_array
); );
/**
* Extracts a substring from one string to create a new string.
*
* @param start A pointer to the start of the substring.
* @param end A pointer to the end of the substring.
* @return A copy of the substring.
*/
gchar* wintc_substr(
const gchar* start,
const gchar* end
);
#endif #endif

View File

@@ -22,3 +22,18 @@ void wintc_container_clear(
g_list_free(children); g_list_free(children);
} }
GtkWidget* wintc_container_get_nth_child(
GtkContainer* container,
gint pos
)
{
GList* children = gtk_container_get_children(container);
GtkWidget* nth_child =
GTK_WIDGET(g_list_nth_data(children, pos));
g_list_free(children);
return nth_child;
}

View File

@@ -35,3 +35,18 @@ void wintc_freenv(
g_free(mem); g_free(mem);
} }
void wintc_memcpy_ref(
void* dst_buf,
glong offset,
const void* src_buf,
size_t n
)
{
if (!dst_buf)
{
return;
}
memcpy(dst_buf + offset, src_buf, n);
}

View File

@@ -1,6 +1,7 @@
#include <glib.h> #include <glib.h>
#include <string.h> #include <string.h>
#include "../public/shorthand.h"
#include "../public/strings.h" #include "../public/strings.h"
// //
@@ -59,6 +60,43 @@ gchar* wintc_str_set_suffix(
} }
} }
gchar* wintc_strdup_nextchr(
const gchar* str,
gssize len,
gunichar c,
const gchar** pos
)
{
const gchar* end;
if (!str)
{
WINTC_SAFE_REF_SET(pos, NULL);
return NULL;
}
end = g_utf8_strchr(str, len, c);
if (pos)
{
WINTC_SAFE_REF_SET(pos, end ? end + 1 : NULL);
}
if (!end)
{
return g_strdup(str);
}
// Allocate new string
//
gint diff = end - str;
gchar* buf = g_malloc0(diff + 1);
memcpy(buf, str, diff);
return buf;
}
void wintc_strdup_replace( void wintc_strdup_replace(
gchar** dest, gchar** dest,
const gchar* src const gchar* src
@@ -180,3 +218,24 @@ guint wintc_strv_length(
return i; return i;
} }
gchar* wintc_substr(
const gchar* start,
const gchar* end
)
{
if (end < start)
{
g_critical("substr: invalid substring requested");
return NULL;
}
gchar* buf = g_malloc0(end - start + 1);
if (start != end)
{
memcpy(buf, start, end - start);
}
return buf;
}

View File

@@ -13,9 +13,12 @@ set(PROJECT_MAINTAINER "Rory Fewell <roryf@oddmatics.uk>")
set(PROJECT_ROOT ${CMAKE_CURRENT_LIST_DIR}) set(PROJECT_ROOT ${CMAKE_CURRENT_LIST_DIR})
set(WINTC_NO_PEDANTIC_COMPILE true) # Necessary for glib-genmarshal
include(GNUInstallDirs) include(GNUInstallDirs)
include(../../packaging/cmake-inc/common/CMakeLists.txt) include(../../packaging/cmake-inc/common/CMakeLists.txt)
include(../../packaging/cmake-inc/codegen/CMakeLists.txt)
include(../../packaging/cmake-inc/libraries/CMakeLists.txt) include(../../packaging/cmake-inc/libraries/CMakeLists.txt)
include(../../packaging/cmake-inc/linking/CMakeLists.txt) include(../../packaging/cmake-inc/linking/CMakeLists.txt)
include(../../packaging/cmake-inc/packaging/CMakeLists.txt) include(../../packaging/cmake-inc/packaging/CMakeLists.txt)
@@ -24,10 +27,16 @@ wintc_resolve_library(glib-2.0 GLIB)
wintc_resolve_library(gtk+-3.0 GTK3) wintc_resolve_library(gtk+-3.0 GTK3)
wintc_resolve_library(wintc-comgtk WINTC_COMGTK) wintc_resolve_library(wintc-comgtk WINTC_COMGTK)
wintc_glib_genmarshal()
add_library( add_library(
libwintc-shcommon libwintc-shcommon
src/fs.c src/fs.c
public/fs.h public/fs.h
src/marshal.c
src/marshal.h
src/monitor.c
public/monitor.h
src/path.c src/path.c
public/path.h public/path.h
src/places.c src/places.c

View File

@@ -1,6 +1,8 @@
#ifndef __SHCOMMON_FS_H__ #ifndef __SHCOMMON_FS_H__
#define __SHCOMMON_FS_H__ #define __SHCOMMON_FS_H__
#include <glib.h>
// //
// PUBLIC FUNCTIONS // PUBLIC FUNCTIONS
// //

View File

@@ -2,6 +2,7 @@
#define __WINTC_SHCOMMON_H__ #define __WINTC_SHCOMMON_H__
#include "@LIB_HEADER_DIR@/fs.h" #include "@LIB_HEADER_DIR@/fs.h"
#include "@LIB_HEADER_DIR@/monitor.h"
#include "@LIB_HEADER_DIR@/path.h" #include "@LIB_HEADER_DIR@/path.h"
#include "@LIB_HEADER_DIR@/places.h" #include "@LIB_HEADER_DIR@/places.h"

View File

@@ -0,0 +1,31 @@
#ifndef __SHCOMMON_MONITOR_H__
#define __SHCOMMON_MONITOR_H__
#include <gio/gio.h>
#include <glib.h>
//
// GTK OOP BOILERPLATE
//
#define WINTC_TYPE_SH_DIR_MONITOR_RECURSIVE (wintc_sh_dir_monitor_recursive_get_type())
G_DECLARE_FINAL_TYPE(
WinTCShDirMonitorRecursive,
wintc_sh_dir_monitor_recursive,
WINTC,
SH_DIR_MONITOR_RECURSIVE,
GObject
)
//
// PUBLIC FUNCTIONS
//
WinTCShDirMonitorRecursive* wintc_sh_fs_monitor_directory_recursive(
GFile* file,
GFileMonitorFlags flags,
GCancellable* cancellable,
GError** error
);
#endif

View File

@@ -0,0 +1 @@
VOID:OBJECT,OBJECT,INT

View File

@@ -0,0 +1,537 @@
#include <gio/gio.h>
#include <glib.h>
#include <wintc/comgtk.h>
#include "../public/fs.h"
#include "../public/monitor.h"
#include "marshal.h"
//
// PRIVATE ENUMS
//
enum
{
PROP_FILE = 1,
PROP_FILE_MONITOR
};
enum
{
SIGNAL_CHANGED = 0,
N_SIGNALS
};
//
// FORWARD DECLARATIONS
//
static void wintc_sh_dir_monitor_recursive_constructed(
GObject* object
);
static void wintc_sh_dir_monitor_recursive_dispose(
GObject* object
);
static void wintc_sh_dir_monitor_recursive_get_property(
GObject* object,
guint prop_id,
GValue* value,
GParamSpec* pspec
);
static void wintc_sh_dir_monitor_recursive_set_property(
GObject* object,
guint prop_id,
const GValue* value,
GParamSpec* pspec
);
static void wintc_sh_dir_monitor_recursive_delete_monitor(
WinTCShDirMonitorRecursive* monitor_recursive,
GFile* file
);
static void wintc_sh_dir_monitor_recursive_new_monitor(
WinTCShDirMonitorRecursive* monitor_recursive,
GFile* file
);
static void on_file_monitor_root_changed(
GFileMonitor* monitor,
GFile* file,
GFile* other_file,
GFileMonitorEvent event_type,
gpointer user_data
);
static void on_file_monitor_subdir_changed(
GFileMonitor* monitor,
GFile* file,
GFile* other_file,
GFileMonitorEvent event_type,
gpointer user_data
);
//
// STATIC DATA
//
static gint wintc_sh_dir_monitor_recursive_signals[N_SIGNALS] = { 0 };
//
// GTK OOP CLASS/INSTANCE DEFINITIONS
//
struct _WinTCShDirMonitorRecursive
{
GObject __parent__;
GFile* file_root;
GFileMonitor* monitor_root;
GHashTable* map_rel_path_to_monitor;
};
//
// GTK TYPE DEFINITIONS & CTORS
//
G_DEFINE_TYPE(
WinTCShDirMonitorRecursive,
wintc_sh_dir_monitor_recursive,
G_TYPE_OBJECT
)
static void wintc_sh_dir_monitor_recursive_class_init(
WinTCShDirMonitorRecursiveClass* klass
)
{
GObjectClass* object_class = G_OBJECT_CLASS(klass);
object_class->constructed = wintc_sh_dir_monitor_recursive_constructed;
object_class->dispose = wintc_sh_dir_monitor_recursive_dispose;
object_class->get_property = wintc_sh_dir_monitor_recursive_get_property;
object_class->set_property = wintc_sh_dir_monitor_recursive_set_property;
g_object_class_install_property(
object_class,
PROP_FILE,
g_param_spec_object(
"file",
"File",
"The root directory to monitor recursively.",
G_TYPE_FILE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
)
);
g_object_class_install_property(
object_class,
PROP_FILE_MONITOR,
g_param_spec_object(
"file-monitor",
"FileMonitor",
"The file monitor for the root directory.",
G_TYPE_FILE_MONITOR,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
)
);
wintc_sh_dir_monitor_recursive_signals[SIGNAL_CHANGED] =
g_signal_new(
"changed",
G_TYPE_FROM_CLASS(object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
wintc_cclosure_marshal_VOID__OBJECT_OBJECT_INT,
G_TYPE_NONE,
3,
G_TYPE_OBJECT,
G_TYPE_OBJECT,
G_TYPE_INT
);
}
static void wintc_sh_dir_monitor_recursive_init(
WINTC_UNUSED(WinTCShDirMonitorRecursive* self)
) {}
//
// CLASS VIRTUAL METHODS
//
static void wintc_sh_dir_monitor_recursive_constructed(
GObject* object
)
{
WinTCShDirMonitorRecursive* monitor_recursive =
WINTC_SH_DIR_MONITOR_RECURSIVE(object);
if (
!(monitor_recursive->file_root) ||
!(monitor_recursive->monitor_root)
)
{
g_critical("%s", "shcommon: invalid dir monitor created");
return;
}
// Set up signal for the root monitor
//
g_signal_connect(
monitor_recursive->monitor_root,
"changed",
G_CALLBACK(on_file_monitor_root_changed),
monitor_recursive
);
// Pull the root path details
//
const gchar* root_path = g_file_peek_path(monitor_recursive->file_root);
gint root_len = g_utf8_strlen(root_path, -1);
// Build monitors recursively in the map
//
GList* files =
wintc_sh_fs_get_names_as_list(
root_path,
TRUE,
G_FILE_TEST_IS_DIR,
TRUE,
NULL // FIXME: Error handling
);
monitor_recursive->map_rel_path_to_monitor =
g_hash_table_new_full(
g_str_hash,
g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_object_unref
);
for (GList* iter = files; iter; iter = iter->next)
{
GFile* file =
g_file_new_for_path(
(gchar*) iter->data
);
GFileMonitor* monitor =
g_file_monitor_directory(
file,
G_FILE_MONITOR_NONE,
NULL,
NULL // FIXME: Error handling
);
if (monitor)
{
g_signal_connect(
monitor,
"changed",
G_CALLBACK(on_file_monitor_subdir_changed),
monitor_recursive
);
g_hash_table_insert(
monitor_recursive->map_rel_path_to_monitor,
g_strdup(root_path + root_len),
monitor
);
}
g_object_unref(file);
}
g_list_free_full(files, (GDestroyNotify) g_free);
}
static void wintc_sh_dir_monitor_recursive_dispose(
GObject* object
)
{
WinTCShDirMonitorRecursive* monitor_recursive =
WINTC_SH_DIR_MONITOR_RECURSIVE(object);
g_clear_object(&(monitor_recursive->file_root));
g_clear_object(&(monitor_recursive->monitor_root));
g_hash_table_destroy(
g_steal_pointer(&(monitor_recursive->map_rel_path_to_monitor))
);
(G_OBJECT_CLASS(wintc_sh_dir_monitor_recursive_parent_class))
->dispose(object);
}
static void wintc_sh_dir_monitor_recursive_get_property(
GObject* object,
guint prop_id,
GValue* value,
GParamSpec* pspec
)
{
WinTCShDirMonitorRecursive* monitor_recursive =
WINTC_SH_DIR_MONITOR_RECURSIVE(object);
switch (prop_id)
{
case PROP_FILE:
g_value_set_object(value, monitor_recursive->file_root);
break;
case PROP_FILE_MONITOR:
g_value_set_object(value, monitor_recursive->monitor_root);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void wintc_sh_dir_monitor_recursive_set_property(
GObject* object,
guint prop_id,
const GValue* value,
GParamSpec* pspec
)
{
WinTCShDirMonitorRecursive* monitor_recursive =
WINTC_SH_DIR_MONITOR_RECURSIVE(object);
switch (prop_id)
{
case PROP_FILE:
monitor_recursive->file_root = g_value_dup_object(value);
break;
case PROP_FILE_MONITOR:
monitor_recursive->monitor_root = g_value_dup_object(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
//
// PUBLIC FUNCTIONS
//
WinTCShDirMonitorRecursive* wintc_sh_fs_monitor_directory_recursive(
GFile* file,
GFileMonitorFlags flags,
GCancellable* cancellable,
GError** error
)
{
GFileMonitor* monitor =
g_file_monitor_directory(
file,
flags,
cancellable,
error
);
if (!monitor)
{
return NULL;
}
return WINTC_SH_DIR_MONITOR_RECURSIVE(
g_object_new(
WINTC_TYPE_SH_DIR_MONITOR_RECURSIVE,
"file", file,
"file-monitor", monitor,
NULL
)
);
}
//
// PRIVATE FUNCTIONS
//
static void wintc_sh_dir_monitor_recursive_delete_monitor(
WinTCShDirMonitorRecursive* monitor_recursive,
GFile* file
)
{
const gchar* rel_path =
g_file_peek_path(file) +
g_utf8_strlen(
g_file_peek_path(monitor_recursive->file_root),
-1
);
if (
!g_hash_table_lookup(
monitor_recursive->map_rel_path_to_monitor,
rel_path
)
)
{
return;
}
g_hash_table_remove(
monitor_recursive->map_rel_path_to_monitor,
rel_path
);
}
static void wintc_sh_dir_monitor_recursive_new_monitor(
WinTCShDirMonitorRecursive* monitor_recursive,
GFile* file
)
{
const gchar* rel_path =
g_file_peek_path(file) +
g_utf8_strlen(
g_file_peek_path(monitor_recursive->file_root),
-1
);
if (
g_hash_table_lookup(
monitor_recursive->map_rel_path_to_monitor,
rel_path
)
)
{
return;
}
// Attempt to create a directory monitor
//
GFileMonitor* monitor =
g_file_monitor_directory(
file,
G_FILE_MONITOR_NONE,
NULL,
NULL // FIXME: Error handling
);
if (!monitor)
{
return;
}
g_hash_table_insert(
monitor_recursive->map_rel_path_to_monitor,
g_strdup(rel_path),
monitor
);
}
//
// CALLBACKS
//
static void on_file_monitor_root_changed(
WINTC_UNUSED(GFileMonitor* monitor),
GFile* file,
WINTC_UNUSED(GFile* other_file),
GFileMonitorEvent event_type,
gpointer user_data
)
{
WinTCShDirMonitorRecursive* monitor_recursive =
WINTC_SH_DIR_MONITOR_RECURSIVE(user_data);
switch (event_type)
{
case G_FILE_MONITOR_EVENT_CREATED:
WINTC_LOG_DEBUG(
"shcommon: new item in root: %s",
g_file_peek_path(file)
);
wintc_sh_dir_monitor_recursive_new_monitor(
monitor_recursive,
file
);
break;
case G_FILE_MONITOR_EVENT_DELETED:
WINTC_LOG_DEBUG(
"shcommon: deleted item in root: %s",
g_file_peek_path(file)
);
wintc_sh_dir_monitor_recursive_delete_monitor(
monitor_recursive,
file
);
break;
default:
WINTC_LOG_DEBUG(
"shcommon: unhandled event in root: %s (%d)",
g_file_peek_path(file),
event_type
);
break;
}
g_signal_emit(
monitor_recursive,
wintc_sh_dir_monitor_recursive_signals[SIGNAL_CHANGED],
0,
file,
other_file,
event_type
);
}
static void on_file_monitor_subdir_changed(
WINTC_UNUSED(GFileMonitor* monitor),
GFile* file,
WINTC_UNUSED(GFile* other_file),
GFileMonitorEvent event_type,
gpointer user_data
)
{
WinTCShDirMonitorRecursive* monitor_recursive =
WINTC_SH_DIR_MONITOR_RECURSIVE(user_data);
switch (event_type)
{
case G_FILE_MONITOR_EVENT_CREATED:
WINTC_LOG_DEBUG(
"shcommon: new item in subdir: %s",
g_file_peek_path(file)
);
wintc_sh_dir_monitor_recursive_new_monitor(
monitor_recursive,
file
);
break;
case G_FILE_MONITOR_EVENT_DELETED:
WINTC_LOG_DEBUG(
"shcommon: deleted item in subdir: %s",
g_file_peek_path(file)
);
wintc_sh_dir_monitor_recursive_delete_monitor(
monitor_recursive,
file
);
break;
default:
WINTC_LOG_DEBUG(
"shcommon: unhandled event in subdir: %s (%d)",
g_file_peek_path(file),
event_type
);
break;
}
g_signal_emit(
monitor_recursive,
wintc_sh_dir_monitor_recursive_signals[SIGNAL_CHANGED],
0,
file,
other_file,
event_type
);
}

View File

@@ -127,14 +127,6 @@ void create_personal_menu(
GtkBuilder* builder; GtkBuilder* builder;
WinTCTaskbandToolbar* toolbar = WINTC_TASKBAND_TOOLBAR(toolbar_start); WinTCTaskbandToolbar* toolbar = WINTC_TASKBAND_TOOLBAR(toolbar_start);
GError* error = NULL;
if (!wintc_toolbar_start_progmenu_init(&error))
{
wintc_display_error_and_clear(&error, NULL);
return;
}
// Set default states // Set default states
// //
toolbar_start->personal.sync_menu_refresh = TRUE; toolbar_start->personal.sync_menu_refresh = TRUE;
@@ -266,6 +258,7 @@ void create_personal_menu(
gtk_menu_item_set_submenu( gtk_menu_item_set_submenu(
GTK_MENU_ITEM(toolbar_start->personal.menuitem_all_programs), GTK_MENU_ITEM(toolbar_start->personal.menuitem_all_programs),
wintc_toolbar_start_progmenu_new_gtk_menu( wintc_toolbar_start_progmenu_new_gtk_menu(
toolbar_start->progmenu,
&(toolbar_start->personal.all_programs_binding) &(toolbar_start->personal.all_programs_binding)
) )
); );
@@ -858,6 +851,9 @@ static void refresh_personal_menu(
) )
); );
g_object_unref(entry_internet);
g_object_unref(entry_email);
// Add separator between defaults & MFU // Add separator between defaults & MFU
// //
gtk_menu_shell_append( gtk_menu_shell_append(

File diff suppressed because it is too large Load Diff

View File

@@ -5,15 +5,27 @@
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <wintc/comctl.h> #include <wintc/comctl.h>
//
// GTK OOP BOILERPLATE
//
#define WINTC_TYPE_TOOLBAR_START_PROGMENU (wintc_toolbar_start_progmenu_get_type())
G_DECLARE_FINAL_TYPE(
WinTCToolbarStartProgmenu,
wintc_toolbar_start_progmenu,
WINTC,
TOOLBAR_START_PROGMENU,
GObject
)
// //
// PUBLIC FUNCTIONS // PUBLIC FUNCTIONS
// //
gboolean wintc_toolbar_start_progmenu_init( WinTCToolbarStartProgmenu* wintc_toolbar_start_progmenu_new(void);
GError** error
);
GtkWidget* wintc_toolbar_start_progmenu_new_gtk_menu( GtkWidget* wintc_toolbar_start_progmenu_new_gtk_menu(
WinTCCtlMenuBinding** menu_binding WinTCToolbarStartProgmenu* progmenu,
WinTCCtlMenuBinding** menu_binding
); );
#endif #endif

View File

@@ -5,6 +5,8 @@
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <wintc/comctl.h> #include <wintc/comctl.h>
#include "progmenu.h"
// //
// INTERNAL STRUCTS // INTERNAL STRUCTS
// //
@@ -51,6 +53,8 @@ typedef struct _WinTCToolbarStart
// //
PersonalStartMenuData personal; PersonalStartMenuData personal;
WinTCToolbarStartProgmenu* progmenu;
// UI state // UI state
// //
gboolean sync_button; gboolean sync_button;

View File

@@ -6,6 +6,7 @@
#include "../toolbar.h" #include "../toolbar.h"
#include "personal.h" #include "personal.h"
#include "progmenu.h"
#include "shared.h" #include "shared.h"
#include "toolbar.h" #include "toolbar.h"
@@ -66,6 +67,10 @@ static void wintc_toolbar_start_init(
GTK_STYLE_PROVIDER_PRIORITY_FALLBACK GTK_STYLE_PROVIDER_PRIORITY_FALLBACK
); );
// Initialize progmenu
//
self->progmenu = wintc_toolbar_start_progmenu_new();
// Create root widget (Start button) // Create root widget (Start button)
// //
builder = builder =
@@ -120,6 +125,10 @@ static void wintc_toolbar_start_dispose(
// //
destroy_personal_menu(toolbar_start); destroy_personal_menu(toolbar_start);
// Destroy progmenu - ensures the data will be saved
//
g_object_unref(toolbar_start->progmenu);
(G_OBJECT_CLASS(wintc_toolbar_start_parent_class))->dispose(object); (G_OBJECT_CLASS(wintc_toolbar_start_parent_class))->dispose(object);
} }

View File

@@ -143,6 +143,8 @@ static void wintc_notification_power_constructed(
update_client_on_battery(power, power->up_client); update_client_on_battery(power, power->up_client);
g_ptr_array_unref(all_devices);
(G_OBJECT_CLASS( (G_OBJECT_CLASS(
wintc_notification_power_parent_class wintc_notification_power_parent_class
))->constructed(object); ))->constructed(object);