Compare commits

...

9 Commits

Author SHA1 Message Date
Ryan Lortie
f68f2900fa quartz: add a default application menu
When running on quartz, it is no longer expected for applications to
provide their own application menu.  Instead, they should simply ensure
that they provide "app.about", "app.preferences" and "app.quit" actions
(which many apps are already doing).

A default menu will be shown that looks like the one presented by all
other Mac OS applications, containing menu items for the above actions,
as well as the typical "Hide app", "Hide Others and "Show All" items and
the "Services" submenu.

If an application does explicitly set an application menu (via
gtk_application_set_app_menu()) then it will be respected, as before.
2013-12-17 17:17:09 -05:00
Ryan Lortie
255d3a1826 quartz menu: add special items
Add support for the "Hide app", "Hide Others" and "Show All" special
items and for the "Services" submenu.
2013-12-17 17:17:09 -05:00
Ryan Lortie
d8b8da4ad3 GtkMenuTracker: add 'special' items
Allow the possibility for items to be marked with a special attribute and
expose this via GtkTrackerMenuItem.  For internal use only.

We will use this to implement the special 'Hide', 'Hide Others' and 'Show All'
items and the 'Services' submenu in the Mac OS application menu.
2013-12-17 17:17:09 -05:00
Ryan Lortie
4894dccd9c quartz menu: add a hack for application name
Add a private hack to allow the insertion of the name of the application
into the label of menu items.

If it appears in the label of any menu item, the string
"`gtk-private-appname`" will be replaced with the name of the
application.

We will use this for the "Hide myapp", "Quit myapp" and "About myapp"
labels typically found on Mac OS programs.
2013-12-17 17:17:09 -05:00
Ryan Lortie
2d7110a2cd quartz menu: change sensitivity approach
By default, Mac OS scans menus as they are opened, updating the
sensitivity of each item in the menu.

The current code in gtkapplication-menu-quartz disables this behaviour,
preferring to manually control the sensitivity of each item in the menu
(when told by the tracker that it has changed internally).

Change the way that this works to more closely follow the usual Mac OS
regime.

This will allow us to construct a typical "application menu" on Mac OS
containing the items that are typically found there ("Hide", "Hide
Others", "Show All", "Services") and have the OS automatically update
their sensitivity.
2013-12-17 17:17:09 -05:00
William Hua
0c03793b63 Use GtkMenuTracker for Quartz backend 2013-12-17 17:17:09 -05:00
William Hua
3396c66346 GtkIconInfo: add gtk_icon_info_is_symbol() 2013-12-17 17:17:09 -05:00
Ryan Lortie
130ff2a3fc Fix GtkApplicationWindow action group implementation
GtkApplicationWindow frees its internal action group on dispose for the
usual reasons: to avoid the possibility of reference cycles caused by
actions referring back to the window again.

Unfortunately, if it happens to be inside of a GtkActionMuxer at the
time that it is disposed, it will (eventually) be removed from the muxer
after it has been disposed.  Removing an action group from a muxer
involves a call to g_action_group_list_actions() which will crash
because the internal action group to which we normally delegate the call
has been freed.

A future patch that reworks the quartz menu code will introduce a use of
GtkActionMuxer in a way that causes exactly this problem.

We can guard against the problem in a number of ways.

First, we can avoid the entire situation by ensuring that we are removed
from the muxer before we destroy the action group.  To this end, we
delay destruction of the action group until after the chain-up to the
dispose of GtkWindow (which is where the window is removed from the
GtkApplication).

Secondly, we can add checks to each of our GActionGroup and GActionMap
implementation functions to check that the internal action group is
still alive before we attempt to delegate to it.

We have to be careful, though: because our _list_actions() call will
suddenly be returning an empty list, people watching the group from
outside will have expected to see "action-removed" calls for the
now-missing items.  Make sure we send those. but only if someone is
watching.
2013-12-17 17:17:09 -05:00
Ryan Lortie
e8998c0883 gtkapplication-quartz: clean up inhibit code
When testing with bloatpad, the existing inhibit code seems not to be
working at all.  Replace it with a cleaner and simpler version that
works.
2013-12-17 17:17:09 -05:00
14 changed files with 1002 additions and 658 deletions

View File

@@ -6677,6 +6677,7 @@ gtk_icon_info_set_raw_coordinates
gtk_icon_info_get_embedded_rect
gtk_icon_info_get_attach_points
gtk_icon_info_get_display_name
gtk_icon_info_is_symbolic
<SUBSECTION Standard>
GtkIconThemeClass
GTK_ICON_THEME

View File

@@ -979,8 +979,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
@@ -1018,7 +1018,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)
@@ -1209,7 +1208,7 @@ gtktypebuiltins.c: $(gtk_public_h_sources) $(deprecated_h_sources) gtktypebuilti
gtkresources.h: gtk.gresource.xml
$(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) $(srcdir)/gtk.gresource.xml \
--target=$@ --sourcedir=$(srcdir) --c-name _gtk --generate-header --manual-register
gtkresources.c: gtk.gresource.xml gtk-default.css gtk-win32.css gtk-win32-xp.css gtk-win32-base.css gtk-win32-classic.css $(DND_CURSORS) $(COMPOSITE_TEMPLATES) $(template_headers)
gtkresources.c: gtk.gresource.xml gtk-default.css gtk-win32.css gtk-win32-xp.css gtk-win32-base.css gtk-win32-classic.css $(DND_CURSORS) $(COMPOSITE_TEMPLATES) $(template_headers) gtkapplication-quartz.ui
$(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) $(srcdir)/gtk.gresource.xml \
--target=$@ --sourcedir=$(srcdir) --c-name _gtk --generate-source --manual-register

View File

@@ -35,5 +35,6 @@
<file compressed="true">gtkscalebutton.ui</file>
<file compressed="true">gtkstatusbar.ui</file>
<file compressed="true">gtkvolumebutton.ui</file>
<file compressed="true">gtkapplication-quartz.ui</file>
</gresource>
</gresources>

View File

@@ -0,0 +1,718 @@
/*
* 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 <gdk/quartz/gdkquartz.h>
#include <gdk/gdkkeysyms.h>
#include "gtkmenutracker.h"
#include "gtkicontheme.h"
#import <Cocoa/Cocoa.h>
#define BUFFER_SIZE 1024
#define ICON_SIZE 16
@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;
NSMutableData *iconData;
gpointer iconBuffer;
GCancellable *cancellable;
}
- (id)initWithTrackerItem:(GtkMenuTrackerItem *)aTrackerItem;
- (void)didChangeLabel;
- (void)didChangeIcon;
- (void)didChangeVisible;
- (void)didChangeToggled;
- (void)didChangeAccel;
- (void)didSelectItem:(id)sender;
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem;
@end
@interface GNSMenuItem ()
- (void)setImageFromLoadableIcon:(GLoadableIcon *)icon;
- (void)readDataFromStream:(GInputStream *)stream;
- (void)readDataFromStream:(GInputStream *)stream count:(gssize)count;
- (void)reset;
@end
/*
* Code for key code conversion
*
* Copyright (C) 2009 Paul Davis
*/
static unichar
get_key_equivalent (guint key)
{
if (key >= GDK_KEY_A && key <= GDK_KEY_Z)
return key + (GDK_KEY_a - GDK_KEY_A);
if (key >= GDK_KEY_space && key <= GDK_KEY_asciitilde)
return key;
switch (key)
{
case GDK_KEY_BackSpace:
return NSBackspaceCharacter;
case GDK_KEY_Delete:
return NSDeleteFunctionKey;
case GDK_KEY_Pause:
return NSPauseFunctionKey;
case GDK_KEY_Scroll_Lock:
return NSScrollLockFunctionKey;
case GDK_KEY_Sys_Req:
return NSSysReqFunctionKey;
case GDK_KEY_Home:
return NSHomeFunctionKey;
case GDK_KEY_Left:
case GDK_KEY_leftarrow:
return NSLeftArrowFunctionKey;
case GDK_KEY_Up:
case GDK_KEY_uparrow:
return NSUpArrowFunctionKey;
case GDK_KEY_Right:
case GDK_KEY_rightarrow:
return NSRightArrowFunctionKey;
case GDK_KEY_Down:
case GDK_KEY_downarrow:
return NSDownArrowFunctionKey;
case GDK_KEY_Page_Up:
return NSPageUpFunctionKey;
case GDK_KEY_Page_Down:
return NSPageDownFunctionKey;
case GDK_KEY_End:
return NSEndFunctionKey;
case GDK_KEY_Begin:
return NSBeginFunctionKey;
case GDK_KEY_Select:
return NSSelectFunctionKey;
case GDK_KEY_Print:
return NSPrintFunctionKey;
case GDK_KEY_Execute:
return NSExecuteFunctionKey;
case GDK_KEY_Insert:
return NSInsertFunctionKey;
case GDK_KEY_Undo:
return NSUndoFunctionKey;
case GDK_KEY_Redo:
return NSRedoFunctionKey;
case GDK_KEY_Menu:
return NSMenuFunctionKey;
case GDK_KEY_Find:
return NSFindFunctionKey;
case GDK_KEY_Help:
return NSHelpFunctionKey;
case GDK_KEY_Break:
return NSBreakFunctionKey;
case GDK_KEY_Mode_switch:
return NSModeSwitchFunctionKey;
case GDK_KEY_F1:
return NSF1FunctionKey;
case GDK_KEY_F2:
return NSF2FunctionKey;
case GDK_KEY_F3:
return NSF3FunctionKey;
case GDK_KEY_F4:
return NSF4FunctionKey;
case GDK_KEY_F5:
return NSF5FunctionKey;
case GDK_KEY_F6:
return NSF6FunctionKey;
case GDK_KEY_F7:
return NSF7FunctionKey;
case GDK_KEY_F8:
return NSF8FunctionKey;
case GDK_KEY_F9:
return NSF9FunctionKey;
case GDK_KEY_F10:
return NSF10FunctionKey;
case GDK_KEY_F11:
return NSF11FunctionKey;
case GDK_KEY_F12:
return NSF12FunctionKey;
case GDK_KEY_F13:
return NSF13FunctionKey;
case GDK_KEY_F14:
return NSF14FunctionKey;
case GDK_KEY_F15:
return NSF15FunctionKey;
case GDK_KEY_F16:
return NSF16FunctionKey;
case GDK_KEY_F17:
return NSF17FunctionKey;
case GDK_KEY_F18:
return NSF18FunctionKey;
case GDK_KEY_F19:
return NSF19FunctionKey;
case GDK_KEY_F20:
return NSF20FunctionKey;
case GDK_KEY_F21:
return NSF21FunctionKey;
case GDK_KEY_F22:
return NSF22FunctionKey;
case GDK_KEY_F23:
return NSF23FunctionKey;
case GDK_KEY_F24:
return NSF24FunctionKey;
case GDK_KEY_F25:
return NSF25FunctionKey;
case GDK_KEY_F26:
return NSF26FunctionKey;
case GDK_KEY_F27:
return NSF27FunctionKey;
case GDK_KEY_F28:
return NSF28FunctionKey;
case GDK_KEY_F29:
return NSF29FunctionKey;
case GDK_KEY_F30:
return NSF30FunctionKey;
case GDK_KEY_F31:
return NSF31FunctionKey;
case GDK_KEY_F32:
return NSF32FunctionKey;
case GDK_KEY_F33:
return NSF33FunctionKey;
case GDK_KEY_F34:
return NSF34FunctionKey;
case GDK_KEY_F35:
return NSF35FunctionKey;
default:
break;
}
return '\0';
}
static void
pixbuf_loaded (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GtkIconInfo *info = GTK_ICON_INFO (object);
GNSMenuItem *item = user_data;
GError *error = NULL;
GdkPixbuf *pixbuf = gtk_icon_info_load_symbolic_finish (info, result, NULL, &error);
if (pixbuf != NULL)
{
if (G_IS_LOADABLE_ICON (pixbuf))
[item setImageFromLoadableIcon:G_LOADABLE_ICON (pixbuf)];
g_object_unref (pixbuf);
}
if (error != NULL)
g_error_free (error);
}
static void
input_stream_created (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GLoadableIcon *icon = G_LOADABLE_ICON (object);
GNSMenuItem *item = user_data;
GError *error = NULL;
GInputStream *stream = g_loadable_icon_load_finish (icon, result, NULL, &error);
if (stream != NULL)
{
[item readDataFromStream:stream];
g_object_unref (stream);
}
else
[item reset];
if (error != NULL)
g_error_free (error);
}
static void
input_stream_read (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GInputStream *stream = G_INPUT_STREAM (object);
GNSMenuItem *item = user_data;
GError *error = NULL;
gssize count = g_input_stream_read_finish (stream, result, &error);
if (count >= 0)
[item readDataFromStream:stream count:count];
else
[item reset];
if (error != NULL)
g_error_free (error);
}
static void
tracker_item_changed (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
static const gchar *label = NULL;
static const gchar *icon = NULL;
static const gchar *visible = NULL;
static const gchar *toggled = NULL;
static const gchar *accel = NULL;
GNSMenuItem *item = user_data;
const gchar *name = g_param_spec_get_name (pspec);
if (G_UNLIKELY (label == NULL))
label = g_intern_static_string ("label");
if (G_UNLIKELY (icon == NULL))
icon = g_intern_static_string ("icon");
if (G_UNLIKELY (visible == NULL))
visible = g_intern_static_string ("visible");
if (G_UNLIKELY (toggled == NULL))
toggled = g_intern_static_string ("toggled");
if (G_UNLIKELY (accel == NULL))
accel = g_intern_static_string ("accel");
if (name == label)
[item didChangeLabel];
else if (name == icon)
[item didChangeIcon];
else if (name == visible)
[item didChangeVisible];
else if (name == toggled)
[item didChangeToggled];
else if (name == accel)
[item didChangeAccel];
}
@implementation GNSMenuItem
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
return gtk_menu_tracker_item_get_sensitive (trackerItem) ? YES : NO;
}
- (id)initWithTrackerItem:(GtkMenuTrackerItem *)aTrackerItem
{
if ((self = [super initWithTitle:@""
action:@selector(didSelectItem:)
keyEquivalent:@""]) != nil)
{
const gchar *special = gtk_menu_tracker_item_get_special (aTrackerItem);
if (special && g_str_equal (special, "hide-this"))
{
[self setAction:@selector(hide:)];
[self setTarget:NSApp];
}
else if (special && g_str_equal (special, "hide-others"))
{
[self setAction:@selector(hideOtherApplications:)];
[self setTarget:NSApp];
}
else if (special && g_str_equal (special, "show-all"))
{
[self setAction:@selector(unhideAllApplications:)];
[self setTarget:NSApp];
}
else
[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 didChangeVisible];
[self didChangeToggled];
[self didChangeAccel];
if (gtk_menu_tracker_item_get_has_submenu (trackerItem))
{
[self setSubmenu:[[[GNSMenu alloc] initWithTitle:[self title] trackerItem:trackerItem] autorelease]];
if (special && g_str_equal (special, "services-submenu"))
[NSApp setServicesMenu:[self submenu]];
}
}
return self;
}
- (void)dealloc
{
if (cancellable != NULL)
{
g_cancellable_cancel (cancellable);
[self reset];
}
g_signal_handler_disconnect (trackerItem, trackerItemChangedHandler);
g_object_unref (trackerItem);
[super dealloc];
}
- (void)reset
{
g_clear_object (&cancellable);
g_free (iconBuffer);
iconBuffer = NULL;
[iconData release];
iconData = nil;
}
- (void)readDataFromStream:(GInputStream *)stream
{
g_return_if_fail (G_IS_INPUT_STREAM (stream));
g_input_stream_read_async (stream,
iconBuffer,
BUFFER_SIZE,
G_PRIORITY_LOW,
cancellable,
input_stream_read,
self);
}
- (void)readDataFromStream:(GInputStream *)stream count:(gssize)count
{
g_return_if_fail (G_IS_INPUT_STREAM (stream));
g_return_if_fail (count >= 0);
if (count > 0)
{
[iconData appendBytes:iconBuffer length:count];
[self readDataFromStream:stream];
}
else if (count == 0)
{
NSImage *image = [[[NSImage alloc] initWithData:iconData] autorelease];
[image setSize:NSMakeSize(16, 16)];
[self setImage:image];
[self reset];
}
}
- (void)setImageFromLoadableIcon:(GLoadableIcon *)icon
{
if (GDK_IS_PIXBUF (icon))
{
NSImage *image = gdk_quartz_pixbuf_to_ns_image_libgtk_only (GDK_PIXBUF (icon));
[image setSize:NSMakeSize(16, 16)];
[self setImage:image];
}
else
{
iconData = [[NSMutableData alloc] init];
iconBuffer = g_malloc (BUFFER_SIZE);
cancellable = g_cancellable_new ();
g_loadable_icon_load_async (icon, ICON_SIZE, cancellable, input_stream_created, self);
}
}
- (void)didChangeLabel
{
NSString *label = [NSString stringWithUTF8String:gtk_menu_tracker_item_get_label (trackerItem) ? : ""];
NSMutableString *title = [NSMutableString stringWithCapacity:[label length]];
NSRange range;
int i;
range = [label rangeOfString:@"`gtk-private-appname`"];
if (range.location != NSNotFound)
label = [label stringByReplacingCharactersInRange:range withString:[[NSProcessInfo processInfo] processName]];
for (i = 0; i < [label length]; i++)
{
unichar c = [label characterAtIndex:i];
if (c == '_')
{
i++;
if (i >= [label length])
break;
c = [label characterAtIndex:i];
}
[title appendString:[NSString stringWithCharacters:&c length:1]];
}
[self setTitle:title];
}
- (void)didChangeIcon
{
GIcon *icon = gtk_menu_tracker_item_get_icon (trackerItem);
if (cancellable != NULL)
{
g_cancellable_cancel (cancellable);
[self reset];
}
while (G_IS_EMBLEM (icon) || G_IS_EMBLEMED_ICON (icon))
{
if (G_IS_EMBLEM (icon))
icon = g_emblem_get_icon (G_EMBLEM (icon));
else
icon = g_emblemed_icon_get_icon (G_EMBLEMED_ICON (icon));
}
if (G_IS_FILE_ICON (icon))
{
NSImage *image = nil;
GFile *file = g_file_icon_get_file (G_FILE_ICON (icon));
gchar *path = g_file_get_path (file);
if (path != NULL)
{
image = [[[NSImage alloc] initByReferencingFile:[NSString stringWithUTF8String:path]] autorelease];
g_free (path);
}
else
{
path = g_file_get_uri (file);
if (path != NULL)
{
image = [[[NSImage alloc] initByReferencingURL:[NSURL URLWithString:[NSString stringWithUTF8String:path]]] autorelease];
g_free (path);
}
}
[image setSize:NSMakeSize(16, 16)];
[self setImage:image];
}
else if (G_IS_THEMED_ICON (icon))
{
GtkIconTheme *theme = gtk_icon_theme_get_default ();
if (theme != NULL)
{
GtkIconInfo *info = gtk_icon_theme_lookup_by_gicon (theme, icon, ICON_SIZE, GTK_ICON_LOOKUP_USE_BUILTIN);
if (info != NULL)
{
GdkRGBA foreground = { 0.0, 0.0, 0.0, 1.0 };
GdkRGBA success = { 0.0, 1.0, 0.0, 1.0 };
GdkRGBA warning = { 1.0, 1.0, 0.0, 1.0 };
GdkRGBA error = { 1.0, 0.0, 0.0, 1.0 };
if (gtk_icon_info_is_symbolic (info))
{
cancellable = g_cancellable_new ();
gtk_icon_info_load_symbolic_async (info, &foreground, &success, &warning, &error, cancellable, pixbuf_loaded, self);
}
else
{
const gchar *path = gtk_icon_info_get_filename (info);
if (path != NULL)
{
NSImage *image = [[[NSImage alloc] initByReferencingFile:[NSString stringWithUTF8String:path]] autorelease];
[image setSize:NSMakeSize(16, 16)];
[self setImage:image];
}
else
{
GdkPixbuf *pixbuf = gtk_icon_info_get_builtin_pixbuf (info);
if (G_IS_LOADABLE_ICON (pixbuf))
[self setImageFromLoadableIcon:G_LOADABLE_ICON (pixbuf)];
else
{
cancellable = g_cancellable_new ();
gtk_icon_info_load_symbolic_async (info, &foreground, &success, &warning, &error, cancellable, pixbuf_loaded, self);
}
}
}
g_object_unref (info);
}
}
}
else if (G_IS_LOADABLE_ICON (icon))
[self setImageFromLoadableIcon:G_LOADABLE_ICON (icon)];
else
[self setImage:nil];
}
- (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 = 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
{
if ((self = [super initWithTitle:title]) != nil)
{
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
{
if ((self = [super initWithTitle:title]) != nil)
{
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];
}

View File

@@ -21,9 +21,7 @@
#include "config.h"
#include "gtkapplicationprivate.h"
#include "gtkmodelmenu-quartz.h"
#include "gtkmessagedialog.h"
#include <glib/gi18n-lib.h>
#include "gtkbuilder.h"
#import <Cocoa/Cocoa.h>
typedef struct
@@ -48,6 +46,9 @@ typedef struct
{
GtkApplicationImpl impl;
GtkActionMuxer *muxer;
GMenu *combined;
GSList *inhibitors;
gint quit_inhibit;
guint next_cookie;
@@ -55,89 +56,76 @@ typedef struct
G_DEFINE_TYPE (GtkApplicationImplQuartz, gtk_application_impl_quartz, GTK_TYPE_APPLICATION_IMPL)
/* OS X implementation copied from EggSMClient, but simplified since
* it doesn't need to interact with the user.
*/
static gboolean
idle_will_quit (gpointer user_data)
@interface GtkApplicationQuartzDelegate : NSObject
{
GtkApplicationImplQuartz *quartz = user_data;
if (quartz->quit_inhibit == 0)
g_application_quit (G_APPLICATION (quartz->impl.application));
else
{
GtkApplicationQuartzInhibitor *inhibitor;
GSList *iter;
GtkWidget *dialog;
for (iter = quartz->inhibitors; iter; iter = iter->next)
{
inhibitor = iter->data;
if (inhibitor->flags & GTK_APPLICATION_INHIBIT_LOGOUT)
break;
}
g_assert (inhibitor != NULL);
dialog = gtk_message_dialog_new (inhibitor->window,
GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
_("%s cannot quit at this time:\n\n%s"),
g_get_application_name (),
inhibitor->reason);
g_signal_connect_swapped (dialog,
"response",
G_CALLBACK (gtk_widget_destroy),
dialog);
gtk_widget_show_all (dialog);
}
return G_SOURCE_REMOVE;
GtkApplicationImplQuartz *quartz;
}
static pascal OSErr
quit_requested (const AppleEvent *aevt,
AppleEvent *reply,
long refcon)
{
GtkApplicationImplQuartz *quartz = GSIZE_TO_POINTER ((gsize)refcon);
- (id)initWithImpl:(GtkApplicationImplQuartz*)impl;
- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender;
@end
/* Don't emit the "quit" signal immediately, since we're
* called from a weird point in the guts of gdkeventloop-quartz.c
@implementation GtkApplicationQuartzDelegate
-(id)initWithImpl:(GtkApplicationImplQuartz*)impl
{
quartz = impl;
return self;
}
-(NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
{
/* We have no way to give our message other than to pop up a dialog
* ourselves, which we should not do since the OS will already show
* one when we return NSTerminateNow.
*
* Just let the OS show the generic message...
*/
g_idle_add_full (G_PRIORITY_DEFAULT, idle_will_quit, quartz, NULL);
return quartz->quit_inhibit == 0 ? noErr : userCanceledErr;
}
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);
return quartz->quit_inhibit == 0 ? NSTerminateNow : NSTerminateCancel;
}
@end
static void
gtk_application_impl_quartz_startup (GtkApplicationImpl *impl,
gboolean register_session)
{
GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) impl;
GSimpleActionGroup *gtkinternal;
GMenuModel *app_menu;
if (register_session)
AEInstallEventHandler (kCoreEventClass, kAEQuitApplication,
NewAEEventHandlerUPP (quit_requested),
(long)GPOINTER_TO_SIZE (quartz), false);
[NSApp setDelegate: [[GtkApplicationQuartzDelegate alloc] initWithImpl:quartz]];
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));
/* Add the default accels */
gtk_application_add_accelerator (impl->application, "<Primary>comma", "app.preferences", NULL);
gtk_application_add_accelerator (impl->application, "<Primary><Alt>h", "gtkinternal.hide-others", NULL);
gtk_application_add_accelerator (impl->application, "<Primary>h", "gtkinternal.hide", NULL);
gtk_application_add_accelerator (impl->application, "<Primary>q", "app.quit", NULL);
app_menu = gtk_application_get_app_menu (impl->application);
if (app_menu == NULL)
{
GtkBuilder *builder;
/* If the user didn't fill in their own menu yet, add ours.
*
* The fact that we do this here ensures that we will always have the
* app menu at index 0 in 'combined'.
*/
builder = gtk_builder_new_from_resource ("/org/gtk/libgtk/gtkapplication-quartz.ui");
gtk_application_set_app_menu (impl->application, G_MENU_MODEL (gtk_builder_get_object (builder, "app-menu")));
g_object_unref (builder);
}
else
gtk_application_impl_set_app_menu (impl, app_menu);
/* This may or may not add an item to 'combined' */
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];
}
@@ -147,19 +135,58 @@ 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]];
g_slist_free_full (quartz->inhibitors, (GDestroyNotify) gtk_application_quartz_inhibitor_free);
quartz->inhibitors = NULL;
}
static void
gtk_application_impl_quartz_window_added (GtkApplicationImpl *impl,
GtkWindow *window)
{
}
static void
gtk_application_impl_quartz_window_removed (GtkApplicationImpl *impl,
GtkWindow *window)
{
}
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, "`gtk-private-appname`", 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, "`gtk-private-appname`", G_MENU_MODEL (empty));
g_object_unref (empty);
}
}
static void
@@ -168,7 +195,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
@@ -233,6 +265,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
@@ -240,7 +273,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);
}
@@ -252,6 +285,9 @@ gtk_application_impl_quartz_class_init (GtkApplicationImplClass *class)
class->startup = gtk_application_impl_quartz_startup;
class->shutdown = gtk_application_impl_quartz_shutdown;
class->window_added = gtk_application_impl_quartz_window_added;
class->window_removed = gtk_application_impl_quartz_window_removed;
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;

View File

@@ -0,0 +1,45 @@
<interface>
<menu id='app-menu'>
<section>
<item>
<attribute name='label' translatable='yes'>About `gtk-private-appname`</attribute>
<attribute name='action'>app.about</attribute>
</item>
</section>
<section>
<item>
<attribute name='label' translatable='yes'>Preferences</attribute>
<attribute name='action'>app.preferences</attribute>
</item>
</section>
<section>
<submenu>
<attribute name='label' translatable='yes'>Services</attribute>
<attribute name='gtk-private-special'>services-submenu</attribute>
</submenu>
</section>
<section>
<item>
<attribute name='label' translatable='yes'>Hide `gtk-private-appname`</attribute>
<attribute name='gtk-private-special'>hide-this</attribute>
<attribute name='action'>gtkinternal.hide</attribute>
</item>
<item>
<attribute name='label' translatable='yes'>Hide Others</attribute>
<attribute name='gtk-private-special'>hide-others</attribute>
<attribute name='action'>gtkinternal.hide-others</attribute>
</item>
<item>
<attribute name='label' translatable='yes'>Show All</attribute>
<attribute name='gtk-private-special'>show-all</attribute>
<attribute name='action'>gtkinternal.show-all</attribute>
</item>
</section>
<section>
<item>
<attribute name='label' translatable='yes'>Quit `gtk-private-appname`</attribute>
<attribute name='action'>app.quit</attribute>
</item>
</section>
</menu>
</interface>

View File

@@ -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__ */

View File

@@ -389,6 +389,10 @@ gtk_application_window_list_actions (GActionGroup *group)
{
GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (group);
/* may be NULL after dispose has run */
if (!window->priv->actions)
return g_new0 (char *, 0 + 1);
return g_action_group_list_actions (G_ACTION_GROUP (window->priv->actions));
}
@@ -403,6 +407,9 @@ gtk_application_window_query_action (GActionGroup *group,
{
GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (group);
if (!window->priv->actions)
return FALSE;
return g_action_group_query_action (G_ACTION_GROUP (window->priv->actions),
action_name, enabled, parameter_type, state_type, state_hint, state);
}
@@ -414,7 +421,10 @@ gtk_application_window_activate_action (GActionGroup *group,
{
GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (group);
return g_action_group_activate_action (G_ACTION_GROUP (window->priv->actions), action_name, parameter);
if (!window->priv->actions)
return;
g_action_group_activate_action (G_ACTION_GROUP (window->priv->actions), action_name, parameter);
}
static void
@@ -424,7 +434,10 @@ gtk_application_window_change_action_state (GActionGroup *group,
{
GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (group);
return g_action_group_change_action_state (G_ACTION_GROUP (window->priv->actions), action_name, state);
if (!window->priv->actions)
return;
g_action_group_change_action_state (G_ACTION_GROUP (window->priv->actions), action_name, state);
}
static GAction *
@@ -433,6 +446,9 @@ gtk_application_window_lookup_action (GActionMap *action_map,
{
GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (action_map);
if (!window->priv->actions)
return NULL;
return g_action_map_lookup_action (G_ACTION_MAP (window->priv->actions), action_name);
}
@@ -442,6 +458,9 @@ gtk_application_window_add_action (GActionMap *action_map,
{
GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (action_map);
if (!window->priv->actions)
return;
g_action_map_add_action (G_ACTION_MAP (window->priv->actions), action);
}
@@ -451,6 +470,9 @@ gtk_application_window_remove_action (GActionMap *action_map,
{
GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (action_map);
if (!window->priv->actions)
return;
g_action_map_remove_action (G_ACTION_MAP (window->priv->actions), action_name);
}
@@ -740,10 +762,59 @@ gtk_application_window_dispose (GObject *object)
g_clear_object (&window->priv->app_menu_section);
g_clear_object (&window->priv->menubar_section);
g_clear_object (&window->priv->actions);
G_OBJECT_CLASS (gtk_application_window_parent_class)
->dispose (object);
/* We do this below the chain-up above to give us a chance to be
* removed from the GtkApplication (which is done in the dispose
* handler of GtkWindow).
*
* That reduces our chances of being watched as a GActionGroup from a
* muxer constructed by GtkApplication. Even still, it's
* theoretically possible that someone else could be watching us.
* Therefore, we have to take care to ensure that we don't violate our
* obligations under the interface of GActionGroup.
*
* The easiest thing is just for us to act as if all of the actions
* suddenly disappeared.
*/
if (window->priv->actions)
{
gchar **action_names;
guint signal;
gint i;
/* Only send the remove signals if someone is listening */
signal = g_signal_lookup ("action-removed", G_TYPE_ACTION_GROUP);
if (signal && g_signal_has_handler_pending (window, signal, 0, TRUE))
/* need to send a removed signal for each action */
action_names = g_action_group_list_actions (G_ACTION_GROUP (window->priv->actions));
else
/* don't need to send signals: nobody is watching */
action_names = NULL;
/* Free the group before sending the signals for two reasons:
*
* 1) we want any incoming calls to see an empty group
*
* 2) we don't want signal handlers that trigger in response to
* the action-removed signals that we're firing to attempt to
* modify the action group in a way that may cause it to fire
* additional signals (which we would then propagate)
*/
g_object_unref (window->priv->actions);
window->priv->actions = NULL;
/* It's safe to send the signals now, if we need to. */
if (action_names)
{
for (i = 0; action_names[i]; i++)
g_action_group_action_removed (G_ACTION_GROUP (window), action_names[i]);
g_strfreev (action_names);
}
}
}
static void

View File

@@ -3544,6 +3544,34 @@ gtk_icon_info_get_builtin_pixbuf (GtkIconInfo *icon_info)
return icon_info->cache_pixbuf;
}
/**
* gtk_icon_info_is_symbolic:
* @icon_info: a #GtkIconInfo structure
*
* Checks if the icon is symbolic or not.
*
* Return value: %TRUE if the icon is symbolic, %FALSE otherwise.
*
* Since: 3.12
**/
gboolean
gtk_icon_info_is_symbolic (GtkIconInfo *icon_info)
{
gchar *icon_uri;
gboolean is_symbolic;
g_return_val_if_fail (GTK_IS_ICON_INFO (icon_info), FALSE);
icon_uri = NULL;
if (icon_info->icon_file)
icon_uri = g_file_get_uri (icon_info->icon_file);
is_symbolic = (icon_uri != NULL) && (g_str_has_suffix (icon_uri, "-symbolic.svg"));
g_free (icon_uri);
return is_symbolic;
}
static gboolean icon_info_ensure_scale_and_pixbuf (GtkIconInfo*, gboolean);
/* Combine the icon with all emblems, the first emblem is placed
@@ -4329,18 +4357,12 @@ gtk_icon_info_load_symbolic (GtkIconInfo *icon_info,
gboolean *was_symbolic,
GError **error)
{
gchar *icon_uri;
gboolean is_symbolic;
g_return_val_if_fail (icon_info != NULL, NULL);
g_return_val_if_fail (fg != NULL, NULL);
icon_uri = NULL;
if (icon_info->icon_file)
icon_uri = g_file_get_uri (icon_info->icon_file);
is_symbolic = (icon_uri != NULL) && (g_str_has_suffix (icon_uri, "-symbolic.svg"));
g_free (icon_uri);
is_symbolic = gtk_icon_info_is_symbolic (icon_info);
if (was_symbolic)
*was_symbolic = is_symbolic;
@@ -4396,18 +4418,12 @@ gtk_icon_info_load_symbolic_for_context (GtkIconInfo *icon_info,
GdkRGBA error_color;
GdkRGBA *error_colorp;
GtkStateFlags state;
gchar *icon_uri;
gboolean is_symbolic;
g_return_val_if_fail (icon_info != NULL, NULL);
g_return_val_if_fail (context != NULL, NULL);
icon_uri = NULL;
if (icon_info->icon_file)
icon_uri = g_file_get_uri (icon_info->icon_file);
is_symbolic = (icon_uri != NULL) && (g_str_has_suffix (icon_uri, "-symbolic.svg"));
g_free (icon_uri);
is_symbolic = gtk_icon_info_is_symbolic (icon_info);
if (was_symbolic)
*was_symbolic = is_symbolic;
@@ -4542,7 +4558,6 @@ gtk_icon_info_load_symbolic_async (GtkIconInfo *icon_info,
{
GTask *task;
AsyncSymbolicData *data;
gchar *icon_uri;
SymbolicPixbufCache *symbolic_cache;
GdkPixbuf *pixbuf;
@@ -4554,12 +4569,7 @@ gtk_icon_info_load_symbolic_async (GtkIconInfo *icon_info,
data = g_slice_new0 (AsyncSymbolicData);
g_task_set_task_data (task, data, (GDestroyNotify) async_symbolic_data_free);
icon_uri = NULL;
if (icon_info->icon_file)
icon_uri = g_file_get_uri (icon_info->icon_file);
data->is_symbolic = (icon_uri != NULL) && (g_str_has_suffix (icon_uri, "-symbolic.svg"));
g_free (icon_uri);
data->is_symbolic = gtk_icon_info_is_symbolic (icon_info);
if (!data->is_symbolic)
{
@@ -4814,18 +4824,12 @@ gtk_icon_info_load_symbolic_for_style (GtkIconInfo *icon_info,
GdkRGBA *warning_colorp;
GdkRGBA error_color;
GdkRGBA *error_colorp;
gchar *icon_uri;
gboolean is_symbolic;
g_return_val_if_fail (icon_info != NULL, NULL);
g_return_val_if_fail (style != NULL, NULL);
icon_uri = NULL;
if (icon_info->icon_file)
icon_uri = g_file_get_uri (icon_info->icon_file);
is_symbolic = (icon_uri != NULL) && (g_str_has_suffix (icon_uri, "-symbolic.svg"));
g_free (icon_uri);
is_symbolic = gtk_icon_info_is_symbolic (icon_info);
if (was_symbolic)
*was_symbolic = is_symbolic;

View File

@@ -267,6 +267,8 @@ GDK_AVAILABLE_IN_ALL
const gchar * gtk_icon_info_get_filename (GtkIconInfo *icon_info);
GDK_AVAILABLE_IN_ALL
GdkPixbuf * gtk_icon_info_get_builtin_pixbuf (GtkIconInfo *icon_info);
GDK_AVAILABLE_IN_3_12
gboolean gtk_icon_info_is_symbolic (GtkIconInfo *icon_info);
GDK_AVAILABLE_IN_ALL
GdkPixbuf * gtk_icon_info_load_icon (GtkIconInfo *icon_info,
GError **error);

View File

@@ -575,6 +575,16 @@ gtk_menu_tracker_item_get_accel (GtkMenuTrackerItem *self)
return gtk_action_muxer_get_primary_accel (GTK_ACTION_MUXER (self->observable), self->action_and_target);
}
const gchar *
gtk_menu_tracker_item_get_special (GtkMenuTrackerItem *self)
{
const gchar *special = NULL;;
g_menu_item_get_attribute (self->item, "gtk-private-special", "&s", &special);
return special;
}
GMenuModel *
_gtk_menu_tracker_item_get_submenu (GtkMenuTrackerItem *self)
{

View File

@@ -48,6 +48,8 @@ GtkMenuTrackerItem * _gtk_menu_tracker_item_new (GtkActi
const gchar *action_namespace,
gboolean is_separator);
const gchar * gtk_menu_tracker_item_get_special (GtkMenuTrackerItem *self);
GtkActionObservable * _gtk_menu_tracker_item_get_observable (GtkMenuTrackerItem *self);
gboolean gtk_menu_tracker_item_get_is_separator (GtkMenuTrackerItem *self);

View File

@@ -1,519 +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>
/*
* Code for key code conversion
*
* Copyright (C) 2009 Paul Davis
*/
static unichar
gtk_quartz_model_menu_get_unichar (gint key)
{
if (key >= GDK_KEY_A && key <= GDK_KEY_Z)
return key + (GDK_KEY_a - GDK_KEY_A);
if (key >= GDK_KEY_space && key <= GDK_KEY_asciitilde)
return key;
switch (key)
{
case GDK_KEY_BackSpace:
return NSBackspaceCharacter;
case GDK_KEY_Delete:
return NSDeleteFunctionKey;
case GDK_KEY_Pause:
return NSPauseFunctionKey;
case GDK_KEY_Scroll_Lock:
return NSScrollLockFunctionKey;
case GDK_KEY_Sys_Req:
return NSSysReqFunctionKey;
case GDK_KEY_Home:
return NSHomeFunctionKey;
case GDK_KEY_Left:
case GDK_KEY_leftarrow:
return NSLeftArrowFunctionKey;
case GDK_KEY_Up:
case GDK_KEY_uparrow:
return NSUpArrowFunctionKey;
case GDK_KEY_Right:
case GDK_KEY_rightarrow:
return NSRightArrowFunctionKey;
case GDK_KEY_Down:
case GDK_KEY_downarrow:
return NSDownArrowFunctionKey;
case GDK_KEY_Page_Up:
return NSPageUpFunctionKey;
case GDK_KEY_Page_Down:
return NSPageDownFunctionKey;
case GDK_KEY_End:
return NSEndFunctionKey;
case GDK_KEY_Begin:
return NSBeginFunctionKey;
case GDK_KEY_Select:
return NSSelectFunctionKey;
case GDK_KEY_Print:
return NSPrintFunctionKey;
case GDK_KEY_Execute:
return NSExecuteFunctionKey;
case GDK_KEY_Insert:
return NSInsertFunctionKey;
case GDK_KEY_Undo:
return NSUndoFunctionKey;
case GDK_KEY_Redo:
return NSRedoFunctionKey;
case GDK_KEY_Menu:
return NSMenuFunctionKey;
case GDK_KEY_Find:
return NSFindFunctionKey;
case GDK_KEY_Help:
return NSHelpFunctionKey;
case GDK_KEY_Break:
return NSBreakFunctionKey;
case GDK_KEY_Mode_switch:
return NSModeSwitchFunctionKey;
case GDK_KEY_F1:
return NSF1FunctionKey;
case GDK_KEY_F2:
return NSF2FunctionKey;
case GDK_KEY_F3:
return NSF3FunctionKey;
case GDK_KEY_F4:
return NSF4FunctionKey;
case GDK_KEY_F5:
return NSF5FunctionKey;
case GDK_KEY_F6:
return NSF6FunctionKey;
case GDK_KEY_F7:
return NSF7FunctionKey;
case GDK_KEY_F8:
return NSF8FunctionKey;
case GDK_KEY_F9:
return NSF9FunctionKey;
case GDK_KEY_F10:
return NSF10FunctionKey;
case GDK_KEY_F11:
return NSF11FunctionKey;
case GDK_KEY_F12:
return NSF12FunctionKey;
case GDK_KEY_F13:
return NSF13FunctionKey;
case GDK_KEY_F14:
return NSF14FunctionKey;
case GDK_KEY_F15:
return NSF15FunctionKey;
case GDK_KEY_F16:
return NSF16FunctionKey;
case GDK_KEY_F17:
return NSF17FunctionKey;
case GDK_KEY_F18:
return NSF18FunctionKey;
case GDK_KEY_F19:
return NSF19FunctionKey;
case GDK_KEY_F20:
return NSF20FunctionKey;
case GDK_KEY_F21:
return NSF21FunctionKey;
case GDK_KEY_F22:
return NSF22FunctionKey;
case GDK_KEY_F23:
return NSF23FunctionKey;
case GDK_KEY_F24:
return NSF24FunctionKey;
case GDK_KEY_F25:
return NSF25FunctionKey;
case GDK_KEY_F26:
return NSF26FunctionKey;
case GDK_KEY_F27:
return NSF27FunctionKey;
case GDK_KEY_F28:
return NSF28FunctionKey;
case GDK_KEY_F29:
return NSF29FunctionKey;
case GDK_KEY_F30:
return NSF30FunctionKey;
case GDK_KEY_F31:
return NSF31FunctionKey;
case GDK_KEY_F32:
return NSF32FunctionKey;
case GDK_KEY_F33:
return NSF33FunctionKey;
case GDK_KEY_F34:
return NSF34FunctionKey;
case GDK_KEY_F35:
return NSF35FunctionKey;
default:
break;
}
return '\0';
}
@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 = gtk_quartz_model_menu_get_unichar (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

View File

@@ -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__ */