1
/* -*- mode: ObjC; c-basic-offset: 4; indent-tabs-mode: nil; -*-
3
* Copyright 2008 Owen Taylor
5
* This file is part of Reinteract and distributed under the terms
6
* of the BSD license. See the file COPYING in the Reinteract
7
* distribution for full details.
9
************************************************************************/
13
#include "ThunkPython.h"
15
#include <gdk/gdkquartz.h>
16
#include <pygobject.h>
19
#import "MenuController.h"
21
/* This file implements the reinteract.native_main_menu object, which is
22
* glue between the menu and the PyGTK code. The interface here is:
24
* class NativeMainMenu:
25
* def do_action(self, action_name):
26
* """An Action was selected"""
27
* pass # Override in subclass
29
* def enable_action(self, action_name):
30
* """Enable the action with the specified name"""
33
* def disable_action(self, action_name):
34
* """Disable the action with the specified name"""
37
* def get_action_names(self):
38
* """Return a list of all action names in the menu"""
41
* def handle_key_press(self, event):
42
* """Check key equivalents and activate a menu item if appropriate
44
* @returns: True if a menu item was activated
48
* You use this class by deriving from it and instantiating a singleton
49
* copy. Instantiating more than one copy of a derived class will produce
50
* undefined and probably bad results.
55
MenuController *controller;
59
pyNativeMenu_actionCallback(NSString *actionName, void *data)
61
pyNativeMainMenu *slf = data;
63
PyGILState_STATE gstate;
64
gstate = PyGILState_Ensure();
66
PyObject *result = PyObject_CallMethod((PyObject *)slf, "do_action", "s", [actionName UTF8String]);
72
PyGILState_Release(gstate);
76
pyNativeMainMenu_init(pyNativeMainMenu *slf, PyObject *args, PyObject *kwds)
78
if (!PyArg_ParseTuple(args, ""))
81
slf->controller = [[MenuController alloc] init];
82
[slf->controller setActionCallback:pyNativeMenu_actionCallback callbackData:slf];
84
// Create the MainMenu object from the NIB file and set our controller
85
// object as the delegate
86
NSNib *nib = [[NSNib alloc] initWithNibNamed: @"MainMenu" bundle:nil];
87
if (![nib instantiateNibWithOwner:slf->controller topLevelObjects:nil]){
88
PyErr_SetString(PyExc_RuntimeError, "Can't instantiate MainMenu.nib");
92
// Once the nib has been loaded and the objects created (the menu
93
// is automatically added to the global NSApp), we don't need the data
94
// of the NIB any more
97
[slf->controller addActionsFromMenu:[NSApp mainMenu]];
99
// finishLaunching actually shows the menu. There might be some
100
// justification for splitting calling this out into a separate menu
101
[NSApp finishLaunching];
107
pyNativeMainMenu_enable_action(pyNativeMainMenu *slf, PyObject *args)
109
const char *action_name;
111
if (!PyArg_ParseTuple(args, "s", &action_name))
114
[slf->controller enableAction:[NSString stringWithUTF8String:action_name]];
120
pyNativeMainMenu_disable_action(pyNativeMainMenu *slf, PyObject *args)
122
const char *action_name;
124
if (!PyArg_ParseTuple(args, "s", &action_name))
127
[slf->controller disableAction:[NSString stringWithUTF8String:action_name]];
133
pyNativeMainMenu_get_action_names(pyNativeMainMenu *slf)
135
NSArray *names = [slf->controller actionNames];
136
PyObject *result = PyList_New([names count]);
142
for (i = 0; i < [names count]; i++) {
143
const char *name = [[names objectAtIndex:i] UTF8String];
144
PyObject *py_name = PyString_FromString(name);
150
PyList_SetItem(result, i, py_name); // Steals reference to py_name
157
event_get_nsevent(GdkEvent *event)
159
static NSEvent *(*ptr_gdk_quartz_event_get_nsevent) (GdkEvent*);
160
if (!ptr_gdk_quartz_event_get_nsevent) {
161
ptr_gdk_quartz_event_get_nsevent = dlsym(RTLD_DEFAULT, "gdk_quartz_event_get_nsevent");
162
if (!ptr_gdk_quartz_event_get_nsevent) {
163
fprintf(stderr, "Can't get pointer to gdk_quartz_event_get_nsevent()");
168
return (*ptr_gdk_quartz_event_get_nsevent) (event);
171
/* All key events get intercepted by the gtk-quartz event loop before
172
* normal processing by NSApplication can deliver them to the menu bar.
173
* For this reason, we have to pass key events received on our toplevel
174
* windows to the menu bar ourselves.
176
* Luckily the original NSEvent can be retrieved from the GdkEvent so
177
* we don't have to synthesize a new event ourselves.
180
pyNativeMainMenu_handle_key_press(pyNativeMainMenu *slf, PyObject *args)
184
if (!PyArg_ParseTuple(args, "O", &py_event))
188
/* Doing it this way would require us to call pygobject_init() first
189
* which is possible but requires us to thunk more Python API to make
190
* that (inline) functin happy. pyg_boxed_get() is just casting
191
* and structure access, so doesn't require pygobject_init()
193
if (!pyg_boxed_check(py_event, GDK_TYPE_EVENT)) {
194
PyErr_SetString(PyExc_TypeError, "Argument must be a GdkEvent");
198
PyObject *module = PyImport_ImportModule("gtk.gdk");
199
PyObject *attribute = PyObject_GetAttrString(module, "Event");
200
if (!PyObject_TypeCheck(py_event, (PyTypeObject *)attribute)) {
201
PyErr_SetString(PyExc_TypeError, "Argument must be a GdkEvent");
206
NSEvent *event = event_get_nsevent(pyg_boxed_get(py_event, GdkEvent));
208
if ([[NSApp mainMenu] performKeyEquivalent:event])
214
static PyMethodDef pyNativeMainMenu_methods[] = {
215
{"enable_action", (PyCFunction)pyNativeMainMenu_enable_action, METH_VARARGS,
216
"Enable the menu item with the specified action name"
218
{"disable_action", (PyCFunction)pyNativeMainMenu_disable_action, METH_VARARGS,
219
"Disable the menu item with the specified action name"
221
{"get_action_names", (PyCFunction)pyNativeMainMenu_get_action_names, METH_NOARGS,
222
"Return a list of all action names"
224
{"handle_key_press", (PyCFunction)pyNativeMainMenu_handle_key_press, METH_VARARGS,
225
"Perform any key equivalents for the given key event"
227
{NULL} /* Sentinel */
230
static PyTypeObject pyNativeMainMenuType = {
231
PyObject_HEAD_INIT(NULL)
233
"reinteract.native_main_menu.NativeMainMenu", /*tp_name*/
234
sizeof(pyNativeMainMenu), /*tp_basicsize*/
243
0, /*tp_as_sequence*/
251
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
252
"Native Interface to the main menu", /* tp_doc */
255
0, /* tp_richcompare */
256
0, /* tp_weaklistoffset */
259
pyNativeMainMenu_methods, /* tp_methods */
264
0, /* tp_descr_get */
265
0, /* tp_descr_set */
266
0, /* tp_dictoffset */
267
(initproc)pyNativeMainMenu_init, /* tp_init */
272
static PyMethodDef native_main_menu_methods[] = {
273
{ NULL } /* No module level methods */
277
init_py_native_main_menu(void)
281
if (PyType_Ready(&pyNativeMainMenuType) < 0)
284
// We need to load the reinteract package so we have the namespace
285
// for reinteract.native_main_menu. We don't actually need anything
286
// from reinteract.__init__.py, which is empty.
288
PyObject *tmp = PyImport_ImportModule("reinteract");
296
m = Py_InitModule("reinteract.native_main_menu", native_main_menu_methods);
298
pyNativeMainMenuType.tp_new = PyType_GenericNew;
300
Py_INCREF(&pyNativeMainMenuType);
301
PyModule_AddObject(m, "NativeMainMenu", (PyObject *)&pyNativeMainMenuType);