13
0
livetrax/libs/gtkmm2ext/gtkapplication_quartz.mm

1550 lines
34 KiB
Plaintext

/* GTK+ application-level integration for the Mac OS X/Cocoa
*
* Copyright (C) 2007 Pioneer Research Center USA, Inc.
* Copyright (C) 2007 Imendio AB
* Copyright (C) 2009 Paul Davis
*
* This is a reimplementation in Cocoa of the sync-menu.c concept
* from Imendio, although without the "set quit menu" API since
* a Cocoa app needs to handle termination anyway.
*
* 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; version 2.1
* of the License.
*
* 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <sigc++/signal.h>
#include <sigc++/slot.h>
#include <string.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <gtkmm2ext/gtkapplication.h>
#include <gtkmm2ext/gtkapplication-private.h>
#import <AppKit/NSMenu.h>
#import <AppKit/NSMenuItem.h>
#import <AppKit/NSCell.h>
#import <AppKit/NSEvent.h>
#import <AppKit/NSApplication.h>
#import <Foundation/NSString.h>
#import <Foundation/NSNotification.h>
#include <vector>
#define UNUSED_PARAMETER(a) (void) (a)
//#define DEBUG(format, ...) g_printerr ("%s: " format, G_STRFUNC, ## __VA_ARGS__)
#define DEBUG(format, ...)
/* TODO
*
* - Sync adding/removing/reordering items
* - Create on demand? (can this be done with gtk+? ie fill in menu
items when the menu is opened)
* - Figure out what to do per app/window...
*
*/
static gint _exiting = 0;
static std::vector<GtkMenuItem*> global_menu_items;
static bool _modal_state = false;
static guint
gdk_quartz_keyval_to_ns_keyval (guint keyval)
{
switch (keyval) {
case GDK_BackSpace:
return NSBackspaceCharacter;
case GDK_Delete:
return NSDeleteFunctionKey;
case GDK_Pause:
return NSPauseFunctionKey;
case GDK_Scroll_Lock:
return NSScrollLockFunctionKey;
case GDK_Sys_Req:
return NSSysReqFunctionKey;
case GDK_Home:
return NSHomeFunctionKey;
case GDK_Left:
case GDK_leftarrow:
return NSLeftArrowFunctionKey;
case GDK_Up:
case GDK_uparrow:
return NSUpArrowFunctionKey;
case GDK_Right:
case GDK_rightarrow:
return NSRightArrowFunctionKey;
case GDK_Down:
case GDK_downarrow:
return NSDownArrowFunctionKey;
case GDK_Page_Up:
return NSPageUpFunctionKey;
case GDK_Page_Down:
return NSPageDownFunctionKey;
case GDK_End:
return NSEndFunctionKey;
case GDK_Begin:
return NSBeginFunctionKey;
case GDK_Select:
return NSSelectFunctionKey;
case GDK_Print:
return NSPrintFunctionKey;
case GDK_Execute:
return NSExecuteFunctionKey;
case GDK_Insert:
return NSInsertFunctionKey;
case GDK_Undo:
return NSUndoFunctionKey;
case GDK_Redo:
return NSRedoFunctionKey;
case GDK_Menu:
return NSMenuFunctionKey;
case GDK_Find:
return NSFindFunctionKey;
case GDK_Help:
return NSHelpFunctionKey;
case GDK_Break:
return NSBreakFunctionKey;
case GDK_Mode_switch:
return NSModeSwitchFunctionKey;
case GDK_F1:
return NSF1FunctionKey;
case GDK_F2:
return NSF2FunctionKey;
case GDK_F3:
return NSF3FunctionKey;
case GDK_F4:
return NSF4FunctionKey;
case GDK_F5:
return NSF5FunctionKey;
case GDK_F6:
return NSF6FunctionKey;
case GDK_F7:
return NSF7FunctionKey;
case GDK_F8:
return NSF8FunctionKey;
case GDK_F9:
return NSF9FunctionKey;
case GDK_F10:
return NSF10FunctionKey;
case GDK_F11:
return NSF11FunctionKey;
case GDK_F12:
return NSF12FunctionKey;
case GDK_F13:
return NSF13FunctionKey;
case GDK_F14:
return NSF14FunctionKey;
case GDK_F15:
return NSF15FunctionKey;
case GDK_F16:
return NSF16FunctionKey;
case GDK_F17:
return NSF17FunctionKey;
case GDK_F18:
return NSF18FunctionKey;
case GDK_F19:
return NSF19FunctionKey;
case GDK_F20:
return NSF20FunctionKey;
case GDK_F21:
return NSF21FunctionKey;
case GDK_F22:
return NSF22FunctionKey;
case GDK_F23:
return NSF23FunctionKey;
case GDK_F24:
return NSF24FunctionKey;
case GDK_F25:
return NSF25FunctionKey;
case GDK_F26:
return NSF26FunctionKey;
case GDK_F27:
return NSF27FunctionKey;
case GDK_F28:
return NSF28FunctionKey;
case GDK_F29:
return NSF29FunctionKey;
case GDK_F30:
return NSF30FunctionKey;
case GDK_F31:
return NSF31FunctionKey;
case GDK_F32:
return NSF32FunctionKey;
case GDK_F33:
return NSF33FunctionKey;
case GDK_F34:
return NSF34FunctionKey;
case GDK_F35:
return NSF35FunctionKey;
default:
break;
}
return 0;
}
static gboolean
keyval_is_keypad (guint keyval)
{
switch (keyval) {
case GDK_KP_F1:
case GDK_KP_F2:
case GDK_KP_F3:
case GDK_KP_F4:
case GDK_KP_Home:
case GDK_KP_Left:
case GDK_KP_Up:
case GDK_KP_Right:
case GDK_KP_Down:
case GDK_KP_Page_Up:
case GDK_KP_Page_Down:
case GDK_KP_End:
case GDK_KP_Begin:
case GDK_KP_Insert:
case GDK_KP_Delete:
case GDK_KP_Equal:
case GDK_KP_Multiply:
case GDK_KP_Add:
case GDK_KP_Separator:
case GDK_KP_Subtract:
case GDK_KP_Decimal:
case GDK_KP_Divide:
case GDK_KP_0:
case GDK_KP_1:
case GDK_KP_2:
case GDK_KP_3:
case GDK_KP_4:
case GDK_KP_5:
case GDK_KP_6:
case GDK_KP_7:
case GDK_KP_8:
case GDK_KP_9:
return TRUE;
break;
default:
break;
}
return FALSE;
}
static guint
keyval_keypad_nonkeypad_equivalent (guint keyval)
{
switch (keyval) {
case GDK_KP_F1:
return GDK_F1;
case GDK_KP_F2:
return GDK_F2;
case GDK_KP_F3:
return GDK_F3;
case GDK_KP_F4:
return GDK_F4;
case GDK_KP_Home:
return GDK_Home;
case GDK_KP_Left:
return GDK_Left;
case GDK_KP_Up:
return GDK_Up;
case GDK_KP_Right:
return GDK_Right;
case GDK_KP_Down:
return GDK_Down;
case GDK_KP_Page_Up:
return GDK_Page_Up;
case GDK_KP_Page_Down:
return GDK_Page_Down;
case GDK_KP_End:
return GDK_End;
case GDK_KP_Begin:
return GDK_Begin;
case GDK_KP_Insert:
return GDK_Insert;
case GDK_KP_Delete:
return GDK_Delete;
case GDK_KP_Equal:
return GDK_equal;
case GDK_KP_Multiply:
return GDK_asterisk;
case GDK_KP_Add:
return GDK_plus;
case GDK_KP_Subtract:
return GDK_minus;
case GDK_KP_Decimal:
return GDK_period;
case GDK_KP_Divide:
return GDK_slash;
case GDK_KP_0:
return GDK_0;
case GDK_KP_1:
return GDK_1;
case GDK_KP_2:
return GDK_2;
case GDK_KP_3:
return GDK_3;
case GDK_KP_4:
return GDK_4;
case GDK_KP_5:
return GDK_5;
case GDK_KP_6:
return GDK_6;
case GDK_KP_7:
return GDK_7;
case GDK_KP_8:
return GDK_8;
case GDK_KP_9:
return GDK_9;
default:
break;
}
return GDK_VoidSymbol;
}
static const gchar*
gdk_quartz_keyval_to_string (guint keyval)
{
switch (keyval) {
case GDK_space:
return " ";
case GDK_exclam:
return "!";
case GDK_quotedbl:
return "\"";
case GDK_numbersign:
return "#";
case GDK_dollar:
return "$";
case GDK_percent:
return "%";
case GDK_ampersand:
return "&";
case GDK_apostrophe:
return "'";
case GDK_parenleft:
return "(";
case GDK_parenright:
return ")";
case GDK_asterisk:
return "*";
case GDK_plus:
return "+";
case GDK_comma:
return ",";
case GDK_minus:
return "-";
case GDK_period:
return ".";
case GDK_slash:
return "/";
case GDK_0:
return "0";
case GDK_1:
return "1";
case GDK_2:
return "2";
case GDK_3:
return "3";
case GDK_4:
return "4";
case GDK_5:
return "5";
case GDK_6:
return "6";
case GDK_7:
return "7";
case GDK_8:
return "8";
case GDK_9:
return "9";
case GDK_colon:
return ":";
case GDK_semicolon:
return ";";
case GDK_less:
return "<";
case GDK_equal:
return "=";
case GDK_greater:
return ">";
case GDK_question:
return "?";
case GDK_at:
return "@";
case GDK_A:
case GDK_a:
return "a";
case GDK_B:
case GDK_b:
return "b";
case GDK_C:
case GDK_c:
return "c";
case GDK_D:
case GDK_d:
return "d";
case GDK_E:
case GDK_e:
return "e";
case GDK_F:
case GDK_f:
return "f";
case GDK_G:
case GDK_g:
return "g";
case GDK_H:
case GDK_h:
return "h";
case GDK_I:
case GDK_i:
return "i";
case GDK_J:
case GDK_j:
return "j";
case GDK_K:
case GDK_k:
return "k";
case GDK_L:
case GDK_l:
return "l";
case GDK_M:
case GDK_m:
return "m";
case GDK_N:
case GDK_n:
return "n";
case GDK_O:
case GDK_o:
return "o";
case GDK_P:
case GDK_p:
return "p";
case GDK_Q:
case GDK_q:
return "q";
case GDK_R:
case GDK_r:
return "r";
case GDK_S:
case GDK_s:
return "s";
case GDK_T:
case GDK_t:
return "t";
case GDK_U:
case GDK_u:
return "u";
case GDK_V:
case GDK_v:
return "v";
case GDK_W:
case GDK_w:
return "w";
case GDK_X:
case GDK_x:
return "x";
case GDK_Y:
case GDK_y:
return "y";
case GDK_Z:
case GDK_z:
return "z";
case GDK_bracketleft:
return "[";
case GDK_backslash:
return "\\";
case GDK_bracketright:
return "]";
case GDK_asciicircum:
return "^";
case GDK_underscore:
return "_";
case GDK_grave:
return "`";
case GDK_braceleft:
return "{";
case GDK_bar:
return "|";
case GDK_braceright:
return "}";
case GDK_asciitilde:
return "~";
default:
break;
}
return NULL;
};
static gboolean
keyval_is_uppercase (guint keyval)
{
switch (keyval) {
case GDK_A:
case GDK_B:
case GDK_C:
case GDK_D:
case GDK_E:
case GDK_F:
case GDK_G:
case GDK_H:
case GDK_I:
case GDK_J:
case GDK_K:
case GDK_L:
case GDK_M:
case GDK_N:
case GDK_O:
case GDK_P:
case GDK_Q:
case GDK_R:
case GDK_S:
case GDK_T:
case GDK_U:
case GDK_V:
case GDK_W:
case GDK_X:
case GDK_Y:
case GDK_Z:
return TRUE;
default:
return FALSE;
}
return FALSE;
}
/* gtk/osx has a problem in that mac main menu events
are handled using an "internal" event handling system that
doesn't pass things back to the glib/gtk main loop. if we call
gtk_main_iteration() block while in a menu event handler, then
glib gets confused and thinks there are two threads running
g_main_poll_func(). apps call call gdk_quartz_in_menu_event_handler()
if they need to check this.
*/
static int _in_menu_event_handler = 0;
int
gdk_quartz_in_menu_event_handler ()
{
return _in_menu_event_handler;
}
static gboolean
idle_call_activate (gpointer data)
{
gtk_menu_item_activate ((GtkMenuItem*) data);
return FALSE;
}
@interface GNSMenuItem : NSMenuItem <NSMenuItemValidation>
{
@public
GtkMenuItem* gtk_menu_item;
GClosure* accel_closure;
bool premodal;
}
- (id) initWithTitle:(NSString*) title andGtkWidget:(GtkMenuItem*) w;
- (void) activate:(id) sender;
- (BOOL) validateMenuItem:(NSMenuItem*) menuItem;
@end
@implementation GNSMenuItem
- (id) initWithTitle:(NSString*) title andGtkWidget:(GtkMenuItem*) w
{
/* All menu items have the action "activate", which will be handled by this child class
*/
self = [ super initWithTitle:title action:@selector(activate:) keyEquivalent:@"" ];
if (self) {
/* make this handle its own action */
[ self setTarget:self ];
gtk_menu_item = w;
accel_closure = 0;
}
return self;
}
- (void) activate:(id) sender
{
UNUSED_PARAMETER(sender);
// Hot Fix. Increase Priority.
g_idle_add_full (G_PRIORITY_HIGH_IDLE, idle_call_activate, gtk_menu_item, NULL);
// g_idle_add (idle_call_activate, gtk_menu_item);
}
- (BOOL) validateMenuItem:(NSMenuItem*) menuItem
{
if (_modal_state) {
return false;
}
GtkAction* act = gtk_activatable_get_related_action (GTK_ACTIVATABLE(gtk_menu_item));
if (act) {
return gtk_action_get_sensitive (act);
} else {
return true;
}
}
@end
static void push_menu_shell_to_nsmenu (GtkMenuShell *menu_shell,
NSMenu *menu,
gboolean toplevel,
gboolean debug);
/*
* utility functions
*/
static GtkWidget *
find_menu_label (GtkWidget *widget)
{
GtkWidget *label = NULL;
if (GTK_IS_LABEL (widget))
return widget;
if (GTK_IS_CONTAINER (widget))
{
GList *children;
GList *l;
children = gtk_container_get_children (GTK_CONTAINER (widget));
for (l = children; l; l = l->next)
{
label = find_menu_label ((GtkWidget*) l->data);
if (label)
break;
}
g_list_free (children);
}
return label;
}
static const gchar *
get_menu_label_text (GtkWidget *menu_item,
GtkWidget **label)
{
GtkWidget *my_label;
my_label = find_menu_label (menu_item);
if (label)
*label = my_label;
if (my_label)
return gtk_label_get_text (GTK_LABEL (my_label));
return NULL;
}
static gboolean
accel_find_func (GtkAccelKey * /*key*/,
GClosure *closure,
gpointer data)
{
return (GClosure *) data == closure;
}
/*
* CocoaMenu functions
*/
static GQuark cocoa_menu_quark = 0;
static NSMenu *
cocoa_menu_get (GtkWidget *widget)
{
return (NSMenu*) g_object_get_qdata (G_OBJECT (widget), cocoa_menu_quark);
}
static void
cocoa_menu_free (gpointer *ptr)
{
NSMenu* menu = (NSMenu*) ptr;
[menu release];
}
static void
cocoa_menu_connect (GtkWidget *menu,
NSMenu* cocoa_menu)
{
[cocoa_menu retain];
if (cocoa_menu_quark == 0)
cocoa_menu_quark = g_quark_from_static_string ("NSMenu");
g_object_set_qdata_full (G_OBJECT (menu), cocoa_menu_quark,
cocoa_menu,
(GDestroyNotify) cocoa_menu_free);
}
/*
* NSMenuItem functions
*/
static GQuark cocoa_menu_item_quark = 0;
static void cocoa_menu_item_connect (GtkWidget* menu_item,
GNSMenuItem* cocoa_menu_item,
GtkWidget *label);
static void
cocoa_menu_item_free (gpointer *ptr)
{
GNSMenuItem* item = (GNSMenuItem*) ptr;
[item release];
}
static GNSMenuItem *
cocoa_menu_item_get (GtkWidget *widget)
{
return (GNSMenuItem*) g_object_get_qdata (G_OBJECT (widget), cocoa_menu_item_quark);
}
static void
cocoa_menu_item_update_state (NSMenuItem* cocoa_item,
GtkWidget *widget)
{
gboolean sensitive;
gboolean visible;
g_object_get (widget,
"sensitive", &sensitive,
"visible", &visible,
NULL);
if (!sensitive) {
[cocoa_item setEnabled:NO];
} else {
[cocoa_item setEnabled:YES];
}
#if 0
// requires OS X 10.5 or later
if (!visible)
[cocoa_item setHidden:YES];
else
[cocoa_item setHidden:NO];
#endif
}
static void
cocoa_menu_item_update_active (NSMenuItem *cocoa_item,
GtkWidget *widget)
{
gboolean active;
g_object_get (widget, "active", &active, NULL);
if (active)
[cocoa_item setState:NSOnState];
else
[cocoa_item setState:NSOffState];
}
static void
cocoa_menu_item_update_submenu (NSMenuItem *cocoa_item,
GtkWidget *widget)
{
GtkWidget *submenu;
g_return_if_fail (cocoa_item != NULL);
g_return_if_fail (widget != NULL);
submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
if (submenu)
{
GtkWidget* label = NULL;
const gchar *label_text;
NSMenu* cocoa_submenu;
label_text = get_menu_label_text (widget, &label);
/* create a new nsmenu to hold the GTK menu */
if (label_text)
cocoa_submenu = [ [ NSMenu alloc ] initWithTitle:[ [ NSString alloc] initWithCString:label_text encoding:NSUTF8StringEncoding]];
else
cocoa_submenu = [ [ NSMenu alloc ] initWithTitle:@""];
[cocoa_submenu setAutoenablesItems:YES];
cocoa_menu_connect (submenu, cocoa_submenu);
/* connect the new nsmenu to the passed-in item (which lives in
the parent nsmenu)
(Note: this will release any pre-existing version of this submenu)
*/
[ cocoa_item setSubmenu:cocoa_submenu];
/* and push the GTK menu into the submenu */
push_menu_shell_to_nsmenu (GTK_MENU_SHELL (submenu), cocoa_submenu, FALSE, FALSE);
[ cocoa_submenu release ];
}
}
static void
cocoa_menu_item_update_label (NSMenuItem *cocoa_item,
GtkWidget *widget)
{
const gchar *label_text;
g_return_if_fail (cocoa_item != NULL);
g_return_if_fail (widget != NULL);
label_text = get_menu_label_text (widget, NULL);
if (label_text)
[cocoa_item setTitle:[ [ NSString alloc] initWithCString:label_text encoding:NSUTF8StringEncoding]];
else
[cocoa_item setTitle:@""];
}
static void
cocoa_menu_item_update_accelerator (NSMenuItem *cocoa_item,
GtkWidget *widget)
{
GtkWidget *label;
g_return_if_fail (cocoa_item != NULL);
g_return_if_fail (widget != NULL);
/* important note: this function doesn't do anything to actually change
key handling. Its goal is to get Cocoa to display the correct
accelerator as part of a menu item. Actual accelerator handling
is still done by GTK, so this is more cosmetic than it may
appear.
*/
get_menu_label_text (widget, &label);
if (GTK_IS_ACCEL_LABEL (label) &&
GTK_ACCEL_LABEL (label)->accel_closure)
{
GtkAccelKey *key;
key = gtk_accel_group_find (GTK_ACCEL_LABEL (label)->accel_group,
accel_find_func,
GTK_ACCEL_LABEL (label)->accel_closure);
if (key &&
key->accel_key &&
key->accel_flags & GTK_ACCEL_VISIBLE)
{
guint modifiers = 0;
const gchar* str = NULL;
guint actual_key = key->accel_key;
if (keyval_is_keypad (actual_key)) {
if ((actual_key = keyval_keypad_nonkeypad_equivalent (actual_key)) == GDK_VoidSymbol) {
/* GDK_KP_Separator */
[cocoa_item setKeyEquivalent:@""];
return;
}
modifiers |= NSNumericPadKeyMask;
}
/* if we somehow got here with GDK_A ... GDK_Z rather than GDK_a ... GDK_z, then take note
of that and make sure we use a shift modifier.
*/
if (keyval_is_uppercase (actual_key)) {
modifiers |= NSShiftKeyMask;
}
str = gdk_quartz_keyval_to_string (actual_key);
if (str) {
unichar ukey = str[0];
[cocoa_item setKeyEquivalent:[NSString stringWithCharacters:&ukey length:1]];
} else {
unichar ukey = gdk_quartz_keyval_to_ns_keyval (actual_key);
if (ukey != 0) {
[cocoa_item setKeyEquivalent:[NSString stringWithCharacters:&ukey length:1]];
} else {
/* cannot map this key to Cocoa key equivalent */
[cocoa_item setKeyEquivalent:@""];
return;
}
}
if (key->accel_mods || modifiers)
{
if (key->accel_mods & GDK_SHIFT_MASK) {
modifiers |= NSShiftKeyMask;
}
/* gdk/quartz maps Alt/Option to Mod1 */
if (key->accel_mods & (GDK_MOD1_MASK)) {
modifiers |= NSAlternateKeyMask;
}
if (key->accel_mods & GDK_CONTROL_MASK) {
modifiers |= NSControlKeyMask;
}
/* our modified gdk/quartz maps Command to Mod2 */
if (key->accel_mods & GDK_MOD2_MASK) {
modifiers |= NSCommandKeyMask;
}
}
[cocoa_item setKeyEquivalentModifierMask:modifiers];
return;
}
}
/* otherwise, clear the menu shortcut */
[cocoa_item setKeyEquivalent:@""];
}
static void
cocoa_menu_item_accel_changed (GtkAccelGroup* /*accel_group*/,
guint /*keyval*/,
GdkModifierType /*modifier*/,
GClosure *accel_closure,
GtkWidget *widget)
{
GNSMenuItem *cocoa_item;
GtkWidget *label;
if (_exiting)
return;
cocoa_item = cocoa_menu_item_get (widget);
get_menu_label_text (widget, &label);
if (GTK_IS_ACCEL_LABEL (label) &&
GTK_ACCEL_LABEL (label)->accel_closure == accel_closure)
cocoa_menu_item_update_accelerator (cocoa_item, widget);
}
static void
cocoa_menu_item_update_accel_closure (GNSMenuItem *cocoa_item,
GtkWidget *widget)
{
GtkAccelGroup *group;
GtkWidget *label;
get_menu_label_text (widget, &label);
if (cocoa_item->accel_closure)
{
group = gtk_accel_group_from_accel_closure (cocoa_item->accel_closure);
g_signal_handlers_disconnect_by_func (group,
(void*) cocoa_menu_item_accel_changed,
widget);
g_closure_unref (cocoa_item->accel_closure);
cocoa_item->accel_closure = NULL;
}
if (GTK_IS_ACCEL_LABEL (label)) {
cocoa_item->accel_closure = GTK_ACCEL_LABEL (label)->accel_closure;
}
if (cocoa_item->accel_closure)
{
g_closure_ref (cocoa_item->accel_closure);
group = gtk_accel_group_from_accel_closure (cocoa_item->accel_closure);
g_signal_connect_object (group, "accel-changed",
G_CALLBACK (cocoa_menu_item_accel_changed),
widget, (GConnectFlags) 0);
}
cocoa_menu_item_update_accelerator (cocoa_item, widget);
}
static void
cocoa_menu_item_notify_label (GObject *object,
GParamSpec *pspec,
gpointer)
{
GNSMenuItem *cocoa_item;
if (_exiting)
return;
cocoa_item = cocoa_menu_item_get (GTK_WIDGET (object));
if (!strcmp (pspec->name, "label"))
{
cocoa_menu_item_update_label (cocoa_item,
GTK_WIDGET (object));
}
else if (!strcmp (pspec->name, "accel-closure"))
{
cocoa_menu_item_update_accel_closure (cocoa_item,
GTK_WIDGET (object));
}
}
static void
cocoa_menu_item_notify (GObject *object,
GParamSpec *pspec,
NSMenuItem *cocoa_item)
{
if (_exiting)
return;
if (!strcmp (pspec->name, "sensitive") ||
!strcmp (pspec->name, "visible"))
{
cocoa_menu_item_update_state (cocoa_item, GTK_WIDGET (object));
}
else if (!strcmp (pspec->name, "active"))
{
cocoa_menu_item_update_active (cocoa_item, GTK_WIDGET (object));
}
else if (!strcmp (pspec->name, "submenu"))
{
cocoa_menu_item_update_submenu (cocoa_item, GTK_WIDGET (object));
}
}
static void
cocoa_menu_item_connect (GtkWidget* menu_item,
GNSMenuItem* cocoa_item,
GtkWidget *label)
{
GNSMenuItem* old_item = cocoa_menu_item_get (menu_item);
[cocoa_item retain];
if (cocoa_menu_item_quark == 0)
cocoa_menu_item_quark = g_quark_from_static_string ("NSMenuItem");
g_object_set_qdata_full (G_OBJECT (menu_item), cocoa_menu_item_quark,
cocoa_item,
(GDestroyNotify) cocoa_menu_item_free);
if (!old_item) {
g_signal_connect (menu_item, "notify",
G_CALLBACK (cocoa_menu_item_notify),
cocoa_item);
if (label)
g_signal_connect_swapped (label, "notify::label",
G_CALLBACK (cocoa_menu_item_notify_label),
menu_item);
}
}
static void
add_menu_item (NSMenu* cocoa_menu, GtkWidget* menu_item, int index)
{
GtkWidget* label = NULL;
GNSMenuItem *cocoa_item;
DEBUG ("add %s to menu %s separator ? %d\n", get_menu_label_text (menu_item, NULL),
[[cocoa_menu title] cStringUsingEncoding:NSUTF8StringEncoding],
GTK_IS_SEPARATOR_MENU_ITEM(menu_item));
cocoa_item = cocoa_menu_item_get (menu_item);
if (cocoa_item)
return;
if (GTK_IS_SEPARATOR_MENU_ITEM (menu_item)) {
cocoa_item = [NSMenuItem separatorItem];
DEBUG ("\ta separator\n");
} else {
if (!GTK_WIDGET_VISIBLE (menu_item)) {
DEBUG ("\tnot visible\n");
return;
}
const gchar* label_text = get_menu_label_text (menu_item, &label);
if (label_text)
cocoa_item = [ [ GNSMenuItem alloc] initWithTitle:[ [ NSString alloc] initWithCString:label_text encoding:NSUTF8StringEncoding]
andGtkWidget:(GtkMenuItem*)menu_item];
else
cocoa_item = [ [ GNSMenuItem alloc] initWithTitle:@"" andGtkWidget:(GtkMenuItem*)menu_item];
DEBUG ("\tan item\n");
}
/* connect GtkMenuItem and NSMenuItem so that we can notice changes to accel/label/submenu etc. */
cocoa_menu_item_connect (menu_item, (GNSMenuItem*) cocoa_item, label);
cocoa_menu_item_update_state (cocoa_item, menu_item);
if (index >= 0)
[ cocoa_menu insertItem:cocoa_item atIndex:index];
else
[ cocoa_menu addItem:cocoa_item];
if (!GTK_WIDGET_IS_SENSITIVE (menu_item))
[cocoa_item setState:NSOffState];
#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_4
if (!GTK_WIDGET_VISIBLE (menu_item))
[cocoa_item setHidden:YES];
#endif
if (GTK_IS_CHECK_MENU_ITEM (menu_item))
cocoa_menu_item_update_active (cocoa_item, menu_item);
if (!GTK_IS_SEPARATOR_MENU_ITEM (menu_item))
cocoa_menu_item_update_accel_closure (cocoa_item, menu_item);
if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu_item)))
cocoa_menu_item_update_submenu (cocoa_item, menu_item);
[ cocoa_item release];
if (GTK_IS_CHECK_MENU_ITEM (menu_item)) {
GtkMenuItem* mitem = GTK_MENU_ITEM(menu_item);
global_menu_items.push_back (mitem);
}
}
static void
push_menu_shell_to_nsmenu (GtkMenuShell *menu_shell,
NSMenu* cocoa_menu,
gboolean /*toplevel*/,
gboolean /*debug*/)
{
GList *children;
GList *l;
children = gtk_container_get_children (GTK_CONTAINER (menu_shell));
for (l = children; l; l = l->next)
{
GtkWidget *menu_item = (GtkWidget*) l->data;
if (GTK_IS_TEAROFF_MENU_ITEM (menu_item))
continue;
if (g_object_get_data (G_OBJECT (menu_item), "gtk-empty-menu-item"))
continue;
add_menu_item (cocoa_menu, menu_item, -1);
}
g_list_free (children);
}
static gulong emission_hook_id = 0;
static gboolean
parent_set_emission_hook (GSignalInvocationHint* /*ihint*/,
guint /*n_param_values*/,
const GValue* param_values,
gpointer data)
{
GtkWidget *instance = (GtkWidget*) g_value_get_object (param_values);
if (GTK_IS_MENU_ITEM (instance))
{
GtkWidget *previous_parent = (GtkWidget*) g_value_get_object (param_values + 1);
GtkWidget *menu_shell = NULL;
if (GTK_IS_MENU_SHELL (previous_parent))
{
menu_shell = previous_parent;
}
else if (GTK_IS_MENU_SHELL (instance->parent))
{
menu_shell = instance->parent;
}
if (menu_shell)
{
NSMenu *cocoa_menu = cocoa_menu_get (menu_shell);
if (cocoa_menu)
{
push_menu_shell_to_nsmenu (GTK_MENU_SHELL (menu_shell),
cocoa_menu,
cocoa_menu == (NSMenu*) data,
FALSE);
}
}
}
return TRUE;
}
static void
parent_set_emission_hook_remove (GtkWidget*, gpointer)
{
g_signal_remove_emission_hook (g_signal_lookup ("parent-set", GTK_TYPE_WIDGET),
emission_hook_id);
}
/* Building "standard" Cocoa/OS X menus */
#warning You can safely ignore the next warning about a duplicate interface definition
@interface NSApplication(NSWindowsMenu)
- (void)setAppleMenu:(NSMenu *)aMenu;
@end
static NSMenu* _main_menubar = 0;
static NSMenu* _window_menu = 0;
static NSMenu* _app_menu = 0;
static int
add_to_menubar (NSMenu *menu)
{
NSMenuItem *dummyItem = [[NSMenuItem alloc] initWithTitle:@""
action:nil keyEquivalent:@""];
[dummyItem setSubmenu:menu];
[_main_menubar addItem:dummyItem];
[dummyItem release];
return 0;
}
#if 0
static int
add_to_app_menu (NSMenu *menu)
{
NSMenuItem *dummyItem = [[NSMenuItem alloc] initWithTitle:@""
action:nil keyEquivalent:@""];
[dummyItem setSubmenu:menu];
[_app_menu addItem:dummyItem];
[dummyItem release];
return 0;
}
#endif
static int
create_apple_menu ()
{
NSMenuItem *menuitem;
// Create the application (Apple) menu.
_app_menu = [[NSMenu alloc] initWithTitle: @"Apple Menu"];
NSMenu *menuServices = [[NSMenu alloc] initWithTitle: @"Services"];
[NSApp setServicesMenu:menuServices];
[_app_menu addItem: [NSMenuItem separatorItem]];
menuitem = [[NSMenuItem alloc] initWithTitle: @"Services"
action:nil keyEquivalent:@""];
[menuitem setSubmenu:menuServices];
[_app_menu addItem: menuitem];
[menuitem release];
[_app_menu addItem: [NSMenuItem separatorItem]];
menuitem = [[NSMenuItem alloc] initWithTitle:@"Hide"
action:@selector(hide:) keyEquivalent:@""];
[menuitem setTarget: NSApp];
[_app_menu addItem: menuitem];
[menuitem release];
menuitem = [[NSMenuItem alloc] initWithTitle:@"Hide Others"
action:@selector(hideOtherApplications:) keyEquivalent:@""];
[menuitem setTarget: NSApp];
[_app_menu addItem: menuitem];
[menuitem release];
menuitem = [[NSMenuItem alloc] initWithTitle:@"Show All"
action:@selector(unhideAllApplications:) keyEquivalent:@""];
[menuitem setTarget: NSApp];
[_app_menu addItem: menuitem];
[menuitem release];
[_app_menu addItem: [NSMenuItem separatorItem]];
menuitem = [[NSMenuItem alloc] initWithTitle:@"Quit"
action:@selector(terminate:) keyEquivalent:@"q"];
[menuitem setTarget: NSApp];
[_app_menu addItem: menuitem];
[menuitem release];
[NSApp setAppleMenu:_app_menu];
add_to_menubar (_app_menu);
return 0;
}
#if 0
static int
add_to_window_menu (NSMenu *menu)
{
NSMenuItem *dummyItem = [[NSMenuItem alloc] initWithTitle:@""
action:nil keyEquivalent:@""];
[dummyItem setSubmenu:menu];
[_window_menu addItem:dummyItem];
[dummyItem release];
return 0;
}
static int
create_window_menu ()
{
_window_menu = [[NSMenu alloc] initWithTitle: @"Window"];
[_window_menu addItemWithTitle:@"Minimize"
action:@selector(performMiniaturize:) keyEquivalent:@""];
[_window_menu addItem: [NSMenuItem separatorItem]];
[_window_menu addItemWithTitle:@"Bring All to Front"
action:@selector(arrangeInFront:) keyEquivalent:@""];
[NSApp setWindowsMenu:_window_menu];
add_to_menubar(_window_menu);
return 0;
}
#endif
/*
* public functions
*/
extern "C" void
gtk_application_set_menu_bar (GtkMenuShell *menu_shell)
{
NSMenu* cocoa_menubar;
g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
if (cocoa_menu_quark == 0)
cocoa_menu_quark = g_quark_from_static_string ("NSMenu");
if (cocoa_menu_item_quark == 0)
cocoa_menu_item_quark = g_quark_from_static_string ("NSMenuItem");
cocoa_menubar = [ [ NSApplication sharedApplication] mainMenu];
/* turn off auto-enabling for the menu - its silly and slow and
doesn't really make sense for a Gtk/Cocoa hybrid menu.
*/
[cocoa_menubar setAutoenablesItems:YES];
emission_hook_id =
g_signal_add_emission_hook (g_signal_lookup ("parent-set",
GTK_TYPE_WIDGET),
0,
parent_set_emission_hook,
cocoa_menubar, NULL);
g_signal_connect (menu_shell, "destroy",
G_CALLBACK (parent_set_emission_hook_remove),
NULL);
push_menu_shell_to_nsmenu (menu_shell, cocoa_menubar, TRUE, FALSE);
}
extern "C" void
gtk_application_add_app_menu_item (GtkApplicationMenuGroup *group,
GtkMenuItem *menu_item)
{
// we know that the application menu is always the submenu of the first item in the main menu
NSMenu* mainMenu;
NSMenu *appMenu;
NSMenuItem *firstItem;
GList *list;
gint index = 0;
mainMenu = [NSApp mainMenu];
firstItem = [ mainMenu itemAtIndex:0];
appMenu = [ firstItem submenu ];
g_return_if_fail (group != NULL);
g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
for (list = _gtk_application_menu_groups; list; list = g_list_next (list))
{
GtkApplicationMenuGroup *list_group = (GtkApplicationMenuGroup*) list->data;
index += g_list_length (list_group->items);
/* adjust index for the separator between groups, but not
* before the first group
*/
if (list_group->items && list->prev)
index++;
if (group == list_group)
{
/* add a separator before adding the first item, but not
* for the first group
*/
if (!group->items && list->prev)
{
[appMenu insertItem:[NSMenuItem separatorItem] atIndex:index+1];
index++;
}
DEBUG ("Add to APP menu bar %s\n", get_menu_label_text (GTK_WIDGET(menu_item), NULL));
add_menu_item (appMenu, GTK_WIDGET(menu_item), index+1);
group->items = g_list_append (group->items, menu_item);
gtk_widget_hide (GTK_WIDGET (menu_item));
return;
}
}
if (!list)
g_warning ("%s: app menu group %p does not exist",
G_STRFUNC, group);
}
/* application delegate, currently in C++ */
#include <gtkmm2ext/application.h>
#include <glibmm/ustring.h>
namespace Gtk {
namespace Application {
sigc::signal<void,bool> ActivationChanged;
sigc::signal<void,const Glib::ustring&> ShouldLoad;
sigc::signal<void> ShouldQuit;
}
}
@interface GtkApplicationNotificationObject : NSObject {}
- (GtkApplicationNotificationObject*) init;
@end
@implementation GtkApplicationNotificationObject
- (GtkApplicationNotificationObject*) init
{
self = [ super init ];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appDidBecomeActive:)
name:NSApplicationDidBecomeActiveNotification
object:[NSApplication sharedApplication]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appDidBecomeInactive:)
name:NSApplicationWillResignActiveNotification
object:[NSApplication sharedApplication]];
}
return self;
}
- (void)appDidBecomeActive:(NSNotification *) notification
{
UNUSED_PARAMETER(notification);
Gtkmm2ext::Application::instance()->ActivationChanged (true);
}
- (void)appDidBecomeInactive:(NSNotification *) notification
{
UNUSED_PARAMETER(notification);
Gtkmm2ext::Application::instance()->ActivationChanged (false);
}
@end
@interface GtkApplicationDelegate : NSObject
-(BOOL) application:(NSApplication*) app openFile:(NSString*) file;
- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *) app;
@end
@implementation GtkApplicationDelegate
-(BOOL) application:(NSApplication*) app openFile:(NSString*) file
{
UNUSED_PARAMETER(app);
Glib::ustring utf8_path ([file UTF8String]);
Gtkmm2ext::Application::instance()->ShouldLoad (utf8_path);
return 1;
}
- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *) app
{
UNUSED_PARAMETER(app);
if (_modal_state) {
return NSTerminateCancel;
}
Gtkmm2ext::Application::instance()->ShouldQuit ();
return NSTerminateCancel;
}
@end
static void
gdk_quartz_modal_notify (GdkWindow*, gboolean modal)
{
/* this global will control sensitivity of our app menu items, via validateMenuItem */
_modal_state = modal;
/* Need to notify GTK that actions are insensitive where necessary */
for (auto & mitem : global_menu_items) {
GtkAction* act = gtk_activatable_get_related_action (GTK_ACTIVATABLE(mitem));
if (act) {
gtk_action_set_sensitive (act, !modal);
}
}
}
/* Basic setup */
extern "C" int
gtk_application_init ()
{
gdk_window_set_modal_notify (gdk_quartz_modal_notify);
_main_menubar = [[NSMenu alloc] initWithTitle: @""];
if (!_main_menubar)
return -1;
[NSApp setMainMenu: _main_menubar];
create_apple_menu ();
// create_window_menu ();
/* this will stick around for ever ... is that OK ? */
[ [GtkApplicationNotificationObject alloc] init];
[ NSApp setDelegate: [GtkApplicationDelegate new]];
return 0;
}
extern "C" void
gtk_application_ready ()
{
[ NSApp finishLaunching ];
[[NSApplication sharedApplication] activateIgnoringOtherApps : YES];
}
extern "C" void
gtk_application_hide ()
{
[NSApp performSelector:@selector(hide:)];
}
extern "C" void
gtk_application_cleanup()
{
_exiting = 1;
if (_window_menu) {
[ _window_menu release ];
_window_menu = 0;
}
if (_app_menu) {
[ _app_menu release ];
_app_menu = 0;
}
if (_main_menubar) {
[ _main_menubar release ];
_main_menubar = 0;
}
}