Commit 649ff84d authored by William Hua's avatar William Hua Committed by Allison Karlitskaya

Use GtkMenuTracker for Quartz backend.

https://bugzilla.gnome.org/show_bug.cgi?id=710351
parent 2a109250
......@@ -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)
......
/*
* 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__ */
/*
* 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);
}