Use GtkMenuTracker for Quartz backend.
https://bugzilla.gnome.org/show_bug.cgi?id=710351
This commit is contained in:
parent
2a109250d5
commit
649ff84d91
@ -966,8 +966,8 @@ gtk_use_win32_c_sources = \
|
||||
gtk_use_quartz_c_sources = \
|
||||
gtksearchenginequartz.c \
|
||||
gtkmountoperation-stub.c \
|
||||
gtkmodelmenu-quartz.c \
|
||||
gtkapplication-quartz.c \
|
||||
gtkapplication-quartz-menu.c \
|
||||
gtkquartz.c
|
||||
gtk_use_stub_c_sources = \
|
||||
gtkmountoperation-stub.c
|
||||
@ -1005,7 +1005,6 @@ endif
|
||||
|
||||
gtk_use_quartz_private_h_sources = \
|
||||
gtksearchenginequartz.h \
|
||||
gtkmodelmenu-quartz.h \
|
||||
gtkquartz.h
|
||||
if USE_QUARTZ
|
||||
gtk_c_sources += $(gtk_use_quartz_c_sources)
|
||||
|
377
gtk/gtkapplication-quartz-menu.c
Normal file
377
gtk/gtkapplication-quartz-menu.c
Normal file
@ -0,0 +1,377 @@
|
||||
/*
|
||||
* Copyright © 2011 William Hua, Ryan Lortie
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the licence, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: William Hua <william@attente.ca>
|
||||
* Ryan Lortie <desrt@desrt.ca>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gtkapplicationprivate.h"
|
||||
#include "gtkmenutracker.h"
|
||||
#include "gtkicontheme.h"
|
||||
#include "gtktoolbar.h"
|
||||
#include "gtkquartz.h"
|
||||
|
||||
#include <gdk/quartz/gdkquartz.h>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#define ICON_SIZE 16
|
||||
|
||||
#define BLACK "#000000"
|
||||
#define TANGO_CHAMELEON_3 "#4e9a06"
|
||||
#define TANGO_ORANGE_2 "#f57900"
|
||||
#define TANGO_SCARLET_RED_2 "#cc0000"
|
||||
|
||||
@interface GNSMenu : NSMenu
|
||||
{
|
||||
GtkMenuTracker *tracker;
|
||||
}
|
||||
|
||||
- (id)initWithTitle:(NSString *)title model:(GMenuModel *)model observable:(GtkActionObservable *)observable;
|
||||
|
||||
- (id)initWithTitle:(NSString *)title trackerItem:(GtkMenuTrackerItem *)trackerItem;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSMenuItem (GtkMenuTrackerItem)
|
||||
|
||||
+ (id)menuItemForTrackerItem:(GtkMenuTrackerItem *)trackerItem;
|
||||
|
||||
@end
|
||||
|
||||
@interface GNSMenuItem : NSMenuItem
|
||||
{
|
||||
GtkMenuTrackerItem *trackerItem;
|
||||
gulong trackerItemChangedHandler;
|
||||
GCancellable *cancellable;
|
||||
}
|
||||
|
||||
- (id)initWithTrackerItem:(GtkMenuTrackerItem *)aTrackerItem;
|
||||
|
||||
- (void)didChangeLabel;
|
||||
- (void)didChangeIcon;
|
||||
- (void)didChangeSensitive;
|
||||
- (void)didChangeVisible;
|
||||
- (void)didChangeToggled;
|
||||
- (void)didChangeAccel;
|
||||
|
||||
- (void)didSelectItem:(id)sender;
|
||||
|
||||
@end
|
||||
|
||||
static void
|
||||
tracker_item_changed (GObject *object,
|
||||
GParamSpec *pspec,
|
||||
gpointer user_data)
|
||||
{
|
||||
GNSMenuItem *item = user_data;
|
||||
const gchar *name = g_param_spec_get_name (pspec);
|
||||
|
||||
if (name != NULL)
|
||||
{
|
||||
if (g_str_equal (name, "label"))
|
||||
[item didChangeLabel];
|
||||
else if (g_str_equal (name, "icon"))
|
||||
[item didChangeIcon];
|
||||
else if (g_str_equal (name, "visible"))
|
||||
[item didChangeVisible];
|
||||
else if (g_str_equal (name, "toggled"))
|
||||
[item didChangeToggled];
|
||||
else if (g_str_equal (name, "accel"))
|
||||
[item didChangeAccel];
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
icon_loaded (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkIconInfo *info = GTK_ICON_INFO (object);
|
||||
GNSMenuItem *item = user_data;
|
||||
GError *error = NULL;
|
||||
GdkPixbuf *pixbuf;
|
||||
|
||||
pixbuf = gtk_icon_info_load_symbolic_finish (info, result, NULL, &error);
|
||||
|
||||
if (pixbuf != NULL)
|
||||
{
|
||||
[item setImage:_gtk_quartz_create_image_from_pixbuf (pixbuf)];
|
||||
g_object_unref (pixbuf);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* on failure to load, clear the old icon */
|
||||
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
||||
[item setImage:nil];
|
||||
|
||||
g_error_free (error);
|
||||
}
|
||||
}
|
||||
|
||||
@implementation GNSMenuItem
|
||||
|
||||
- (id)initWithTrackerItem:(GtkMenuTrackerItem *)aTrackerItem
|
||||
{
|
||||
self = [super initWithTitle:@""
|
||||
action:@selector(didSelectItem:)
|
||||
keyEquivalent:@""];
|
||||
|
||||
if (self != nil)
|
||||
{
|
||||
[self setTarget:self];
|
||||
|
||||
trackerItem = g_object_ref (aTrackerItem);
|
||||
trackerItemChangedHandler = g_signal_connect (trackerItem, "notify", G_CALLBACK (tracker_item_changed), self);
|
||||
|
||||
[self didChangeLabel];
|
||||
[self didChangeIcon];
|
||||
[self didChangeSensitive];
|
||||
[self didChangeVisible];
|
||||
[self didChangeToggled];
|
||||
[self didChangeAccel];
|
||||
|
||||
if (gtk_menu_tracker_item_get_has_submenu (trackerItem))
|
||||
[self setSubmenu:[[[GNSMenu alloc] initWithTitle:[self title] trackerItem:trackerItem] autorelease]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (cancellable != NULL)
|
||||
{
|
||||
g_cancellable_cancel (cancellable);
|
||||
g_clear_object (&cancellable);
|
||||
}
|
||||
|
||||
g_signal_handler_disconnect (trackerItem, trackerItemChangedHandler);
|
||||
g_object_unref (trackerItem);
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)didChangeLabel
|
||||
{
|
||||
gchar *label = _gtk_toolbar_elide_underscores (gtk_menu_tracker_item_get_label (trackerItem));
|
||||
|
||||
[self setTitle:[NSString stringWithUTF8String:label ? : ""]];
|
||||
|
||||
g_free (label);
|
||||
}
|
||||
|
||||
- (void)didChangeIcon
|
||||
{
|
||||
GIcon *icon = gtk_menu_tracker_item_get_icon (trackerItem);
|
||||
|
||||
if (cancellable != NULL)
|
||||
{
|
||||
g_cancellable_cancel (cancellable);
|
||||
g_clear_object (&cancellable);
|
||||
}
|
||||
|
||||
if (icon != NULL)
|
||||
{
|
||||
static gboolean parsed;
|
||||
|
||||
static GdkRGBA foreground;
|
||||
static GdkRGBA success;
|
||||
static GdkRGBA warning;
|
||||
static GdkRGBA error;
|
||||
|
||||
GtkIconTheme *theme;
|
||||
GtkIconInfo *info;
|
||||
gint scale;
|
||||
|
||||
if (!parsed)
|
||||
{
|
||||
gdk_rgba_parse (&foreground, BLACK);
|
||||
gdk_rgba_parse (&success, TANGO_CHAMELEON_3);
|
||||
gdk_rgba_parse (&warning, TANGO_ORANGE_2);
|
||||
gdk_rgba_parse (&error, TANGO_SCARLET_RED_2);
|
||||
|
||||
parsed = TRUE;
|
||||
}
|
||||
|
||||
theme = gtk_icon_theme_get_default ();
|
||||
scale = roundf ([[NSScreen mainScreen] backingScaleFactor]);
|
||||
info = gtk_icon_theme_lookup_by_gicon_for_scale (theme, icon, ICON_SIZE, scale, GTK_ICON_LOOKUP_USE_BUILTIN);
|
||||
|
||||
if (info != NULL)
|
||||
{
|
||||
cancellable = g_cancellable_new ();
|
||||
gtk_icon_info_load_symbolic_async (info, &foreground, &success, &warning, &error,
|
||||
cancellable, icon_loaded, self);
|
||||
g_object_unref (info);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[self setImage:nil];
|
||||
}
|
||||
|
||||
- (void)didChangeSensitive
|
||||
{
|
||||
[self setEnabled:gtk_menu_tracker_item_get_sensitive (trackerItem) ? YES : NO];
|
||||
}
|
||||
|
||||
- (void)didChangeVisible
|
||||
{
|
||||
[self setHidden:gtk_menu_tracker_item_get_visible (trackerItem) ? NO : YES];
|
||||
}
|
||||
|
||||
- (void)didChangeToggled
|
||||
{
|
||||
[self setState:gtk_menu_tracker_item_get_toggled (trackerItem) ? NSOnState : NSOffState];
|
||||
}
|
||||
|
||||
- (void)didChangeAccel
|
||||
{
|
||||
const gchar *accel = gtk_menu_tracker_item_get_accel (trackerItem);
|
||||
|
||||
if (accel != NULL)
|
||||
{
|
||||
guint key;
|
||||
GdkModifierType mask;
|
||||
unichar character;
|
||||
NSUInteger modifiers;
|
||||
|
||||
gtk_accelerator_parse (accel, &key, &mask);
|
||||
|
||||
character = gdk_quartz_get_key_equivalent (key);
|
||||
[self setKeyEquivalent:[NSString stringWithCharacters:&character length:1]];
|
||||
|
||||
modifiers = 0;
|
||||
if (mask & GDK_SHIFT_MASK)
|
||||
modifiers |= NSShiftKeyMask;
|
||||
if (mask & GDK_CONTROL_MASK)
|
||||
modifiers |= NSControlKeyMask;
|
||||
if (mask & GDK_MOD1_MASK)
|
||||
modifiers |= NSAlternateKeyMask;
|
||||
if (mask & GDK_META_MASK)
|
||||
modifiers |= NSCommandKeyMask;
|
||||
[self setKeyEquivalentModifierMask:modifiers];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self setKeyEquivalent:@""];
|
||||
[self setKeyEquivalentModifierMask:0];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didSelectItem:(id)sender
|
||||
{
|
||||
gtk_menu_tracker_item_activated (trackerItem);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSMenuItem (GtkMenuTrackerItem)
|
||||
|
||||
+ (id)menuItemForTrackerItem:(GtkMenuTrackerItem *)trackerItem
|
||||
{
|
||||
if (gtk_menu_tracker_item_get_is_separator (trackerItem))
|
||||
return [NSMenuItem separatorItem];
|
||||
|
||||
return [[[GNSMenuItem alloc] initWithTrackerItem:trackerItem] autorelease];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void
|
||||
menu_item_inserted (GtkMenuTrackerItem *item,
|
||||
gint position,
|
||||
gpointer user_data)
|
||||
{
|
||||
GNSMenu *menu = user_data;
|
||||
|
||||
[menu insertItem:[NSMenuItem menuItemForTrackerItem:item] atIndex:position];
|
||||
}
|
||||
|
||||
static void
|
||||
menu_item_removed (gint position,
|
||||
gpointer user_data)
|
||||
{
|
||||
GNSMenu *menu = user_data;
|
||||
|
||||
[menu removeItemAtIndex:position];
|
||||
}
|
||||
|
||||
@implementation GNSMenu
|
||||
|
||||
- (id)initWithTitle:(NSString *)title model:(GMenuModel *)model observable:(GtkActionObservable *)observable
|
||||
{
|
||||
self = [super initWithTitle:title];
|
||||
|
||||
if (self != nil)
|
||||
{
|
||||
[self setAutoenablesItems:NO];
|
||||
|
||||
tracker = gtk_menu_tracker_new (observable,
|
||||
model,
|
||||
NO,
|
||||
NULL,
|
||||
menu_item_inserted,
|
||||
menu_item_removed,
|
||||
self);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithTitle:(NSString *)title trackerItem:(GtkMenuTrackerItem *)trackerItem
|
||||
{
|
||||
self = [super initWithTitle:title];
|
||||
|
||||
if (self != nil)
|
||||
{
|
||||
[self setAutoenablesItems:NO];
|
||||
|
||||
tracker = gtk_menu_tracker_new_for_item_submenu (trackerItem,
|
||||
menu_item_inserted,
|
||||
menu_item_removed,
|
||||
self);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
gtk_menu_tracker_free (tracker);
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
void
|
||||
gtk_application_impl_quartz_setup_menu (GMenuModel *model,
|
||||
GtkActionMuxer *muxer)
|
||||
{
|
||||
NSMenu *menu;
|
||||
|
||||
if (model != NULL)
|
||||
menu = [[GNSMenu alloc] initWithTitle:@"Main Menu" model:model observable:GTK_ACTION_OBSERVABLE (muxer)];
|
||||
else
|
||||
menu = [[NSMenu alloc] init];
|
||||
|
||||
[NSApp setMainMenu:menu];
|
||||
[menu release];
|
||||
}
|
@ -21,7 +21,6 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "gtkapplicationprivate.h"
|
||||
#include "gtkmodelmenu-quartz.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
typedef struct
|
||||
@ -46,6 +45,9 @@ typedef struct
|
||||
{
|
||||
GtkApplicationImpl impl;
|
||||
|
||||
GtkActionMuxer *muxer;
|
||||
GMenu *combined;
|
||||
|
||||
GSList *inhibitors;
|
||||
gint quit_inhibit;
|
||||
guint next_cookie;
|
||||
@ -83,20 +85,6 @@ G_DEFINE_TYPE (GtkApplicationImplQuartz, gtk_application_impl_quartz, GTK_TYPE_A
|
||||
}
|
||||
@end
|
||||
|
||||
static void
|
||||
gtk_application_impl_quartz_menu_changed (GtkApplicationImplQuartz *quartz)
|
||||
{
|
||||
GMenu *combined;
|
||||
|
||||
combined = g_menu_new ();
|
||||
g_menu_append_submenu (combined, "Application", gtk_application_get_app_menu (quartz->impl.application));
|
||||
g_menu_append_section (combined, NULL, gtk_application_get_menubar (quartz->impl.application));
|
||||
|
||||
gtk_quartz_set_main_menu (G_MENU_MODEL (combined), quartz->impl.application);
|
||||
|
||||
g_object_unref (combined);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_application_impl_quartz_startup (GtkApplicationImpl *impl,
|
||||
gboolean register_session)
|
||||
@ -109,7 +97,17 @@ gtk_application_impl_quartz_startup (GtkApplicationImpl *impl,
|
||||
[NSApp setDelegate: quartz->delegate];
|
||||
}
|
||||
|
||||
gtk_application_impl_quartz_menu_changed (quartz);
|
||||
quartz->muxer = gtk_action_muxer_new ();
|
||||
gtk_action_muxer_set_parent (quartz->muxer, gtk_application_get_action_muxer (impl->application));
|
||||
|
||||
/* app menu must come first so that we always see index '0' in
|
||||
* 'combined' as being the app menu.
|
||||
*/
|
||||
gtk_application_impl_set_app_menu (impl, gtk_application_get_app_menu (impl->application));
|
||||
gtk_application_impl_set_menubar (impl, gtk_application_get_menubar (impl->application));
|
||||
|
||||
/* OK. Now put it in the menu. */
|
||||
gtk_application_impl_quartz_setup_menu (G_MENU_MODEL (quartz->combined), quartz->muxer);
|
||||
|
||||
[NSApp finishLaunching];
|
||||
}
|
||||
@ -119,7 +117,8 @@ gtk_application_impl_quartz_shutdown (GtkApplicationImpl *impl)
|
||||
{
|
||||
GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) impl;
|
||||
|
||||
gtk_quartz_clear_main_menu ();
|
||||
/* destroy our custom menubar */
|
||||
[NSApp setMainMenu:[[[NSMenu alloc] init] autorelease]];
|
||||
|
||||
if (quartz->delegate)
|
||||
{
|
||||
@ -131,13 +130,39 @@ gtk_application_impl_quartz_shutdown (GtkApplicationImpl *impl)
|
||||
quartz->inhibitors = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_application_impl_quartz_active_window_changed (GtkApplicationImpl *impl,
|
||||
GtkWindow *window)
|
||||
{
|
||||
GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) impl;
|
||||
|
||||
gtk_action_muxer_remove (quartz->muxer, "win");
|
||||
|
||||
if (G_IS_ACTION_GROUP (window))
|
||||
gtk_action_muxer_insert (quartz->muxer, "win", G_ACTION_GROUP (window));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_application_impl_quartz_set_app_menu (GtkApplicationImpl *impl,
|
||||
GMenuModel *app_menu)
|
||||
{
|
||||
GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) impl;
|
||||
|
||||
gtk_application_impl_quartz_menu_changed (quartz);
|
||||
/* If there are any items at all, then the first one is the app menu */
|
||||
if (g_menu_model_get_n_items (G_MENU_MODEL (quartz->combined)))
|
||||
g_menu_remove (quartz->combined, 0);
|
||||
|
||||
if (app_menu)
|
||||
g_menu_prepend_submenu (quartz->combined, "Application", app_menu);
|
||||
else
|
||||
{
|
||||
GMenu *empty;
|
||||
|
||||
/* We must preserve the rule that index 0 is the app menu */
|
||||
empty = g_menu_new ();
|
||||
g_menu_prepend_submenu (quartz->combined, "Application", G_MENU_MODEL (empty));
|
||||
g_object_unref (empty);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
@ -146,7 +171,12 @@ gtk_application_impl_quartz_set_menubar (GtkApplicationImpl *impl,
|
||||
{
|
||||
GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) impl;
|
||||
|
||||
gtk_application_impl_quartz_menu_changed (quartz);
|
||||
/* If we have the menubar, it is a section at index '1' */
|
||||
if (g_menu_model_get_n_items (G_MENU_MODEL (quartz->combined)) > 1)
|
||||
g_menu_remove (quartz->combined, 1);
|
||||
|
||||
if (menubar)
|
||||
g_menu_append_section (quartz->combined, NULL, menubar);
|
||||
}
|
||||
|
||||
static guint
|
||||
@ -211,6 +241,7 @@ gtk_application_impl_quartz_is_inhibited (GtkApplicationImpl *impl,
|
||||
static void
|
||||
gtk_application_impl_quartz_init (GtkApplicationImplQuartz *quartz)
|
||||
{
|
||||
quartz->combined = g_menu_new ();
|
||||
}
|
||||
|
||||
static void
|
||||
@ -218,7 +249,7 @@ gtk_application_impl_quartz_finalize (GObject *object)
|
||||
{
|
||||
GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) object;
|
||||
|
||||
g_slist_free_full (quartz->inhibitors, (GDestroyNotify) gtk_application_quartz_inhibitor_free);
|
||||
g_clear_object (&quartz->combined);
|
||||
|
||||
G_OBJECT_CLASS (gtk_application_impl_quartz_parent_class)->finalize (object);
|
||||
}
|
||||
@ -230,6 +261,7 @@ gtk_application_impl_quartz_class_init (GtkApplicationImplClass *class)
|
||||
|
||||
class->startup = gtk_application_impl_quartz_startup;
|
||||
class->shutdown = gtk_application_impl_quartz_shutdown;
|
||||
class->active_window_changed = gtk_application_impl_quartz_active_window_changed;
|
||||
class->set_app_menu = gtk_application_impl_quartz_set_app_menu;
|
||||
class->set_menubar = gtk_application_impl_quartz_set_menubar;
|
||||
class->inhibit = gtk_application_impl_quartz_inhibit;
|
||||
|
@ -208,4 +208,8 @@ G_GNUC_INTERNAL
|
||||
gchar * gtk_application_impl_dbus_get_window_path (GtkApplicationImplDBus *dbus,
|
||||
GtkWindow *window);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void gtk_application_impl_quartz_setup_menu (GMenuModel *model,
|
||||
GtkActionMuxer *muxer);
|
||||
|
||||
#endif /* __GTK_APPLICATION_PRIVATE_H__ */
|
||||
|
@ -1,372 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2011 William Hua, Ryan Lortie
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the licence, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: William Hua <william@attente.ca>
|
||||
* Ryan Lortie <desrt@desrt.ca>
|
||||
*/
|
||||
|
||||
#include "gtkmodelmenu-quartz.h"
|
||||
|
||||
#include <gdk/gdkkeysyms.h>
|
||||
#include "gtkaccelmapprivate.h"
|
||||
#include "gtkactionhelper.h"
|
||||
#include "../gdk/quartz/gdkquartz.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
|
||||
|
||||
@interface GNSMenu : NSMenu
|
||||
{
|
||||
GtkApplication *application;
|
||||
GMenuModel *model;
|
||||
guint update_idle;
|
||||
GSList *connected;
|
||||
gboolean with_separators;
|
||||
}
|
||||
|
||||
- (id)initWithTitle:(NSString *)title model:(GMenuModel *)aModel application:(GtkApplication *)application hasSeparators:(BOOL)hasSeparators;
|
||||
|
||||
- (void)model:(GMenuModel *)model didChangeAtPosition:(NSInteger)position removed:(NSInteger)removed added:(NSInteger)added;
|
||||
|
||||
- (gboolean)handleChanges;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@interface GNSMenuItem : NSMenuItem
|
||||
{
|
||||
GtkActionHelper *helper;
|
||||
}
|
||||
|
||||
- (id)initWithModel:(GMenuModel *)model index:(NSInteger)index application:(GtkApplication *)application;
|
||||
|
||||
- (void)didSelectItem:(id)sender;
|
||||
|
||||
- (void)helperChanged;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
static gboolean
|
||||
gtk_quartz_model_menu_handle_changes (gpointer user_data)
|
||||
{
|
||||
GNSMenu *menu = user_data;
|
||||
|
||||
return [menu handleChanges];
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_quartz_model_menu_items_changed (GMenuModel *model,
|
||||
gint position,
|
||||
gint removed,
|
||||
gint added,
|
||||
gpointer user_data)
|
||||
{
|
||||
GNSMenu *menu = user_data;
|
||||
|
||||
[menu model:model didChangeAtPosition:position removed:removed added:added];
|
||||
}
|
||||
|
||||
void
|
||||
gtk_quartz_set_main_menu (GMenuModel *model,
|
||||
GtkApplication *application)
|
||||
{
|
||||
[NSApp setMainMenu:[[[GNSMenu alloc] initWithTitle:@"Main Menu" model:model application:application hasSeparators:NO] autorelease]];
|
||||
}
|
||||
|
||||
void
|
||||
gtk_quartz_clear_main_menu (void)
|
||||
{
|
||||
// ensure that we drop all GNSMenuItem (to ensure 'application' has no extra references)
|
||||
[NSApp setMainMenu:[[[NSMenu alloc] init] autorelease]];
|
||||
}
|
||||
|
||||
@interface GNSMenu ()
|
||||
|
||||
- (void)appendFromModel:(GMenuModel *)aModel withSeparators:(BOOL)withSeparators;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation GNSMenu
|
||||
|
||||
- (void)model:(GMenuModel *)model didChangeAtPosition:(NSInteger)position removed:(NSInteger)removed added:(NSInteger)added
|
||||
{
|
||||
if (update_idle == 0)
|
||||
update_idle = gdk_threads_add_idle (gtk_quartz_model_menu_handle_changes, self);
|
||||
}
|
||||
|
||||
- (void)appendItemFromModel:(GMenuModel *)aModel atIndex:(gint)index withHeading:(gchar **)heading
|
||||
{
|
||||
GMenuModel *section;
|
||||
|
||||
if ((section = g_menu_model_get_item_link (aModel, index, G_MENU_LINK_SECTION)))
|
||||
{
|
||||
g_menu_model_get_item_attribute (aModel, index, G_MENU_ATTRIBUTE_LABEL, "s", heading);
|
||||
[self appendFromModel:section withSeparators:NO];
|
||||
g_object_unref (section);
|
||||
}
|
||||
else
|
||||
[self addItem:[[[GNSMenuItem alloc] initWithModel:aModel index:index application:application] autorelease]];
|
||||
}
|
||||
|
||||
- (void)appendFromModel:(GMenuModel *)aModel withSeparators:(BOOL)withSeparators
|
||||
{
|
||||
gint n, i;
|
||||
|
||||
g_signal_connect (aModel, "items-changed", G_CALLBACK (gtk_quartz_model_menu_items_changed), self);
|
||||
connected = g_slist_prepend (connected, g_object_ref (aModel));
|
||||
|
||||
n = g_menu_model_get_n_items (aModel);
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
NSInteger ourPosition = [self numberOfItems];
|
||||
gchar *heading = NULL;
|
||||
|
||||
[self appendItemFromModel:aModel atIndex:i withHeading:&heading];
|
||||
|
||||
if (withSeparators && ourPosition < [self numberOfItems])
|
||||
{
|
||||
NSMenuItem *separator = nil;
|
||||
|
||||
if (heading)
|
||||
{
|
||||
separator = [[[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:heading] action:NULL keyEquivalent:@""] autorelease];
|
||||
|
||||
[separator setEnabled:NO];
|
||||
}
|
||||
else if (ourPosition > 0)
|
||||
separator = [NSMenuItem separatorItem];
|
||||
|
||||
if (separator != nil)
|
||||
[self insertItem:separator atIndex:ourPosition];
|
||||
}
|
||||
|
||||
g_free (heading);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)populate
|
||||
{
|
||||
/* removeAllItems is available only in 10.6 and later, but it's more
|
||||
efficient than iterating over the array of
|
||||
NSMenuItems. performSelector: suppresses a compiler warning when
|
||||
building on earlier OSX versions. */
|
||||
if ([self respondsToSelector: @selector (removeAllItems)])
|
||||
[self performSelector: @selector (removeAllItems)];
|
||||
else
|
||||
{
|
||||
/* Iterate from the bottom up to save reindexing the NSArray. */
|
||||
int i;
|
||||
for (i = [self numberOfItems]; i > 0; i--)
|
||||
[self removeItemAtIndex: i];
|
||||
}
|
||||
|
||||
[self appendFromModel:model withSeparators:with_separators];
|
||||
}
|
||||
|
||||
- (gboolean)handleChanges
|
||||
{
|
||||
while (connected)
|
||||
{
|
||||
g_signal_handlers_disconnect_by_func (connected->data, gtk_quartz_model_menu_items_changed, self);
|
||||
g_object_unref (connected->data);
|
||||
|
||||
connected = g_slist_delete_link (connected, connected);
|
||||
}
|
||||
|
||||
[self populate];
|
||||
|
||||
update_idle = 0;
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
- (id)initWithTitle:(NSString *)title model:(GMenuModel *)aModel application:(GtkApplication *)anApplication hasSeparators:(BOOL)hasSeparators
|
||||
{
|
||||
if((self = [super initWithTitle:title]) != nil)
|
||||
{
|
||||
[self setAutoenablesItems:NO];
|
||||
|
||||
model = g_object_ref (aModel);
|
||||
application = g_object_ref (anApplication);
|
||||
with_separators = hasSeparators;
|
||||
|
||||
[self populate];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
while (connected)
|
||||
{
|
||||
g_signal_handlers_disconnect_by_func (connected->data, gtk_quartz_model_menu_items_changed, self);
|
||||
g_object_unref (connected->data);
|
||||
|
||||
connected = g_slist_delete_link (connected, connected);
|
||||
}
|
||||
|
||||
g_object_unref (application);
|
||||
g_object_unref (model);
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
static void
|
||||
gtk_quartz_action_helper_changed (GObject *object,
|
||||
GParamSpec *pspec,
|
||||
gpointer user_data)
|
||||
{
|
||||
GNSMenuItem *item = user_data;
|
||||
|
||||
[item helperChanged];
|
||||
}
|
||||
|
||||
@implementation GNSMenuItem
|
||||
|
||||
- (id)initWithModel:(GMenuModel *)model index:(NSInteger)index application:(GtkApplication *)application
|
||||
{
|
||||
gchar *title = NULL;
|
||||
|
||||
if (g_menu_model_get_item_attribute (model, index, G_MENU_ATTRIBUTE_LABEL, "s", &title))
|
||||
{
|
||||
gchar *from, *to;
|
||||
|
||||
to = from = title;
|
||||
|
||||
while (*from)
|
||||
{
|
||||
if (*from == '_' && from[1])
|
||||
from++;
|
||||
|
||||
*to++ = *from++;
|
||||
}
|
||||
|
||||
*to = '\0';
|
||||
}
|
||||
|
||||
if ((self = [super initWithTitle:[NSString stringWithUTF8String:title ? : ""] action:@selector(didSelectItem:) keyEquivalent:@""]) != nil)
|
||||
{
|
||||
GMenuModel *submenu;
|
||||
gchar *action;
|
||||
GVariant *target;
|
||||
|
||||
action = NULL;
|
||||
g_menu_model_get_item_attribute (model, index, G_MENU_ATTRIBUTE_ACTION, "s", &action);
|
||||
target = g_menu_model_get_item_attribute_value (model, index, G_MENU_ATTRIBUTE_TARGET, NULL);
|
||||
|
||||
if ((submenu = g_menu_model_get_item_link (model, index, G_MENU_LINK_SUBMENU)))
|
||||
{
|
||||
[self setSubmenu:[[[GNSMenu alloc] initWithTitle:[NSString stringWithUTF8String:title] model:submenu application:application hasSeparators:YES] autorelease]];
|
||||
g_object_unref (submenu);
|
||||
}
|
||||
|
||||
else if (action != NULL)
|
||||
{
|
||||
GtkAccelKey key;
|
||||
gchar *path;
|
||||
|
||||
helper = gtk_action_helper_new_with_application (application);
|
||||
gtk_action_helper_set_action_name (helper, action);
|
||||
gtk_action_helper_set_action_target_value (helper, target);
|
||||
|
||||
g_signal_connect (helper, "notify", G_CALLBACK (gtk_quartz_action_helper_changed), self);
|
||||
|
||||
[self helperChanged];
|
||||
|
||||
path = _gtk_accel_path_for_action (action, target);
|
||||
if (gtk_accel_map_lookup_entry (path, &key))
|
||||
{
|
||||
unichar character = gdk_quartz_get_key_equivalent (key.accel_key);
|
||||
|
||||
if (character)
|
||||
{
|
||||
NSUInteger modifiers = 0;
|
||||
|
||||
if (key.accel_mods & GDK_SHIFT_MASK)
|
||||
modifiers |= NSShiftKeyMask;
|
||||
|
||||
if (key.accel_mods & GDK_MOD1_MASK)
|
||||
modifiers |= NSAlternateKeyMask;
|
||||
|
||||
if (key.accel_mods & GDK_CONTROL_MASK)
|
||||
modifiers |= NSControlKeyMask;
|
||||
|
||||
if (key.accel_mods & GDK_META_MASK)
|
||||
modifiers |= NSCommandKeyMask;
|
||||
|
||||
[self setKeyEquivalent:[NSString stringWithCharacters:&character length:1]];
|
||||
[self setKeyEquivalentModifierMask:modifiers];
|
||||
}
|
||||
}
|
||||
|
||||
g_free (path);
|
||||
|
||||
[self setTarget:self];
|
||||
}
|
||||
}
|
||||
|
||||
g_free (title);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (helper != NULL)
|
||||
g_object_unref (helper);
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)didSelectItem:(id)sender
|
||||
{
|
||||
gtk_action_helper_activate (helper);
|
||||
}
|
||||
|
||||
- (void)helperChanged
|
||||
{
|
||||
[self setEnabled:gtk_action_helper_get_enabled (helper)];
|
||||
[self setState:gtk_action_helper_get_active (helper)];
|
||||
|
||||
switch (gtk_action_helper_get_role (helper))
|
||||
{
|
||||
case GTK_ACTION_HELPER_ROLE_NORMAL:
|
||||
[self setOnStateImage:nil];
|
||||
break;
|
||||
case GTK_ACTION_HELPER_ROLE_TOGGLE:
|
||||
[self setOnStateImage:[NSImage imageNamed:@"NSMenuCheckmark"]];
|
||||
break;
|
||||
case GTK_ACTION_HELPER_ROLE_RADIO:
|
||||
[self setOnStateImage:[NSImage imageNamed:@"NSMenuRadio"]];
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2011 William Hua
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the licence, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: William Hua <william@attente.ca>
|
||||
*/
|
||||
|
||||
#ifndef __GTK_MODELMENU_QUARTZ_H__
|
||||
#define __GTK_MODELMENU_QUARTZ_H__
|
||||
|
||||
#include "gtkapplication.h"
|
||||
|
||||
void gtk_quartz_set_main_menu (GMenuModel *model,
|
||||
GtkApplication *application);
|
||||
|
||||
void gtk_quartz_clear_main_menu (void);
|
||||
|
||||
#endif /* __GTK_MODELMENU_QUARTZ_H__ */
|
Loading…
Reference in New Issue
Block a user