/GUI/Cocoa/Applications.py
Python | 403 lines | 359 code | 12 blank | 32 comment | 1 complexity | a92d134962343b896822d85cf3fbb27f MD5 | raw file
1#------------------------------------------------------------------------------
2#
3# Python GUI - Application class - PyObjC
4#
5#------------------------------------------------------------------------------
6
7import os, sys, traceback
8import objc
9#from ExceptionHandling import \
10# NSExceptionHandler, NSLogUncaughtExceptionMask, NSLogAndHandleEveryExceptionMask
11from Foundation import NSObject, NSBundle, NSDefaultRunLoopMode
12import AppKit
13from AppKit import NSApplication, NSResponder, NSScreen, NSMenu, NSMenuItem, \
14 NSKeyDown, NSKeyUp, NSMouseMoved, NSLeftMouseDown, NSSystemDefined, \
15 NSCommandKeyMask, NSPasteboard, NSStringPboardType, NSModalPanelRunLoopMode, \
16 NSAnyEventMask
17import Globals
18import GApplications
19from Globals import application
20from GApplications import Application as GApplication
21from Events import Event
22#from Exceptions import Cancel, Quit
23from Menus import _ns_standard_actions
24
25#------------------------------------------------------------------------------
26
27Globals.ns_screen_height = None
28Globals.ns_last_mouse_moved_event = None
29ns_application = None
30
31#------------------------------------------------------------------------------
32
33#def test_menus(app):
34# print "Testing menus"
35# foomenu = NSMenu.alloc().initWithTitle_("Foo")
36# foomenu.addItemWithTitle_action_keyEquivalent_("Blarg", None, "B")
37# feemenu = NSMenu.alloc().initWithTitle_("Fee")
38# feemenu.addItemWithTitle_action_keyEquivalent_("Blork", None, "K")
39# menubar = NSMenu.alloc().initWithTitle_("Main")
40# fooitem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_("Foo", "", "")
41# menubar.addItem_(fooitem)
42# menubar.setSubmenu_forItem_(foomenu, fooitem)
43# feeitem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_("Fee", "", "")
44# menubar.addItem_(feeitem)
45# menubar.setSubmenu_forItem_(feemenu, feeitem)
46# app.setMainMenu_(menubar)
47# app.setAppleMenu_(foomenu)
48# #app.run()
49# print "Finished testing menus"
50
51#------------------------------------------------------------------------------
52
53class Application(GApplication):
54 # _ns_app _PyGui_NSApplication
55 # _ns_pasteboard NSPasteboard
56 # _ns_key_window Window
57
58 _ns_menubar_update_pending = False
59
60 def __init__(self, **kwds):
61 #print "Application.__init__: argv =", sys.argv ###
62 create_ns_application()
63 self._ns_app = ns_application
64 self._ns_app.pygui_app = self
65 self._ns_init_standard_menu_items()
66 self._ns_pasteboard = NSPasteboard.generalPasteboard()
67 self._ns_key_window = None
68 GApplication.__init__(self, **kwds)
69 ns_application.init_application_name()
70
71 def destroy(self):
72 del self.menus[:]
73 import Windows
74 Windows._ns_zombie_window = None
75 self._ns_app.pygui_app = None
76 self._ns_app = None
77 self._ns_pasteboard = None
78 GApplication.destroy(self)
79
80 def set_menus(self, menu_list):
81 GApplication.set_menus(self, menu_list)
82 self._update_menubar()
83
84 def _update_menubar(self):
85 ns_app = self._ns_app
86 ns_menubar = NSMenu.alloc().initWithTitle_("")
87 menu_list = self._effective_menus()
88 for menu in menu_list:
89 ns_item = NSMenuItem.alloc()
90 ns_item.initWithTitle_action_keyEquivalent_(menu.title, '', "")
91 ns_menubar.addItem_(ns_item)
92 ns_menu = menu._ns_menu
93 # An NSMenu can only be a submenu of one menu at a time, so
94 # remove it from the old menubar if necessary.
95 old_supermenu = ns_menu.supermenu()
96 if old_supermenu:
97 i = old_supermenu.indexOfItemWithSubmenu_(ns_menu)
98 old_supermenu.removeItemAtIndex_(i)
99 ns_menubar.setSubmenu_forItem_(ns_menu, ns_item)
100 # The menu you pass to setAppleMenu_ must *also* be a member of the
101 # main menu.
102 ns_app.setMainMenu_(ns_menubar)
103 ns_app_menu = menu_list[0]._ns_menu
104 ns_app.setAppleMenu_(ns_app_menu)
105
106# def handle_events(self):
107# #print "Application.handle_events: entering NS run loop" ###
108# #try:
109# self._ns_app.run()
110# #finally:
111# #print "Application.handle_events: exiting NS run loop" ###
112
113 def handle_next_event(self, modal_window = None):
114 #print "Application.handle_next_event" ###
115 ns_app = self._ns_app
116 if modal_window:
117 ns_mode = NSModalPanelRunLoopMode
118 ns_modal_window = modal_window._ns_window
119 else:
120 ns_mode = NSDefaultRunLoopMode
121 ns_modal_window = None
122 ns_event = ns_app.nextEventMatchingMask_untilDate_inMode_dequeue_(
123 NSAnyEventMask, None, ns_mode, True)
124 if ns_event:
125 ns_window = ns_event.window()
126 if not ns_window or not ns_modal_window or ns_window == ns_modal_window:
127 ns_app.sendEvent_(ns_event)
128
129 def get_target_window(self):
130 # NSApplication.keyWindow() isn't reliable enough. We keep track
131 # of the key window ourselves.
132 return self._ns_key_window
133
134 def zero_windows_allowed(self):
135 return 1
136
137 def query_clipboard(self):
138 pb = self._ns_pasteboard
139 pb_types = pb.types()
140 return NSStringPboardType in pb_types
141
142 def get_clipboard(self):
143 raise NotImplementedError("TODO: Application.get_clipboard")
144
145 def set_clipboard(self, data):
146 raise NotImplementedError("TODO: Application.set_clipboard")
147
148 def _ns_init_standard_menu_items(self):
149 self._ns_standard_menu_items = {}
150 for (cmd_name, ns_selector) in _ns_standard_actions.iteritems():
151 self._ns_standard_menu_items[cmd_name] = NSMenuItem.alloc().\
152 initWithTitle_action_keyEquivalent_("", ns_selector, "")
153
154 def _dispatch_menu_setup(self, m):
155 for (cmd_name, ns_menu_item) in self._ns_standard_menu_items.iteritems():
156 ns_selector = ns_menu_item.action()
157 target = self._ns_app.targetForAction_(ns_selector)
158 if target and target.respondsToSelector_('validateMenuItem:'):
159 valid = target.validateMenuItem_(ns_menu_item)
160 m[cmd_name].enabled = valid
161 GApplication._dispatch_menu_setup(self, m)
162
163 def setup_menus(self, m):
164 if not self._ns_app.modalWindow():
165 GApplication.setup_menus(self, m)
166
167 def process_args(self, args):
168 # Note: When using py2app, argv_emulation should be disabled.
169 if args and args[0].startswith("-psn"):
170 # Launched from MacOSX Finder -- wait for file open/app launch messages
171 pass
172 else:
173 # Not launched from Finder or using argv emulation
174 self._ns_app.using_clargs = True
175 GApplication.process_args(self, args)
176
177 def run(self, fast_exit = True):
178 #print "Application.run" ###
179 try:
180 GApplication.run(self)
181 except (KeyboardInterrupt, SystemExit):
182 pass
183 except:
184 traceback.print_exc()
185 # A py2app bundled application seems to crash on exit if we don't
186 # bail out really quickly here (Python 2.3, PyObjC 1.3.7, py2app 0.2.1,
187 # MacOSX 10.4.4)
188 if fast_exit:
189 os._exit(0)
190
191 def event_loop(self):
192 #print "Application.event_loop" ###
193 self._ns_app.run()
194 #print "Exit Application.event_loop" ###
195
196# def _exit_event_loop(self):
197# self._ns_app.stop_(self)
198
199 def _quit(self):
200 #print "Application._quit" ###
201 self._quit_flag = True
202 self._ns_app.stop_(self._ns_app)
203 #print "Exit Application._quit" ###
204
205#------------------------------------------------------------------------------
206
207_ns_key_event_mask = AppKit.NSKeyDownMask | AppKit.NSKeyUpMask
208
209#------------------------------------------------------------------------------
210
211class _PyGui_NSApplication(NSApplication):
212
213 pygui_app = None
214 files_opened = False
215 using_clargs = False
216 pygui_menus_updated = False
217
218 def sendEvent_(self, ns_event):
219 # Perform special processing of key events.
220 # Perform menu setup when menu bar is clicked.
221 # Remember the most recent mouse-moved event to use as the
222 # location of event types which do not have a location themselves.
223 if pending_exception:
224 raise_pending_exception()
225 ns_type = ns_event.type()
226 #print "sendEvent_:", ns_event ###
227 self.pygui_menus_updated = False
228 if (1 << ns_type) & _ns_key_event_mask:
229 self.process_key_event(ns_event)
230 else:
231 if ns_type == NSMouseMoved:
232 Globals.ns_last_mouse_moved_event = ns_event
233 ns_window = ns_event.window()
234 if ns_window:
235 ns_view = ns_window.contentView().hitTest_(ns_event.locationInWindow())
236 if ns_view:
237 ns_view.mouseMoved_(ns_event)
238 else:
239 NSApplication.sendEvent_(self, ns_event)
240
241 def process_key_event(self, ns_event):
242 # Perform menu setup before command-key events.
243 # Send non-command key events to associated window if any,
244 # otherwise pass them to the pygui application. This is necessary
245 # because otherwise there is no way of receiving key events when
246 # there are no windows.
247 #print "_PyGui_NSApplication.process_key_event:", ns_event ###
248 if ns_event.modifierFlags() & NSCommandKeyMask:
249 #self.perform_menu_setup()
250 NSApplication.sendEvent_(self, ns_event)
251 else:
252 ns_window = ns_event.window()
253 #print "_PyGui_NSApplication.process_key_event: ns_window =", ns_window ###
254 if ns_window:
255 ns_window.sendEvent_(ns_event)
256 else:
257 self.pass_event_to_application(ns_event)
258
259 def menuNeedsUpdate_(self, ns_menu):
260 #print "_PyGui_NSApplication.menuNeedsUpdate_:", object.__repr__(ns_menu) ###
261 if not self.pygui_menus_updated:
262 #print "...updating menus" ###
263 self.perform_menu_setup()
264 self.pygui_menus_updated = True
265
266 def perform_menu_setup(self):
267 #print "_PyGui_NSApplication.perform_menu_setup" ###
268 app = self.pygui_app
269 if app:
270 app._perform_menu_setup()
271
272 def pass_event_to_application(self, ns_event):
273 app = self.pygui_app
274 if app:
275 event = Event(ns_event)
276 app.handle(event.kind, event)
277
278# def sendAction_to_from_(self, action, target, sender):
279# print "_PyGui_NSApplication.sendAction_to_from_", action, target, sender ###
280# return NSApplication.sendAction_to_from_(self, action, target, sender)
281
282 def menuSelection_(self, ns_menu_item):
283 command = ns_menu_item.representedObject()
284 index = ns_menu_item.tag()
285 if index >= 0:
286 self.dispatch_to_app(command, index)
287 else:
288 self.dispatch_to_app(command)
289
290 def dispatch_to_app(self, *args):
291 app = self.pygui_app
292 if app:
293 app.dispatch(*args)
294
295 def validateMenuItem_(self, item):
296 return False
297
298 def undo_(self, sender):
299 self.dispatch_to_app('undo_cmd')
300
301 def redo_(self, sender):
302 self.dispatch_to_app('redo_cmd')
303
304 def cut_(self, sender):
305 self.dispatch_to_app('cut_cmd')
306
307 def copy_(self, sender):
308 self.dispatch_to_app('copy_cmd')
309
310 def paste_(self, sender):
311 self.dispatch_to_app('paste_cmd')
312
313 def clear_(self, sender):
314 self.dispatch_to_app('clear_cmd')
315
316 def select_all_(self, sender):
317 self.dispatch_to_app('select_all_cmd')
318
319 def init_application_name(self):
320 # Arrange for the application name to be used as the title
321 # of the application menu.
322 ns_bundle = NSBundle.mainBundle()
323 if ns_bundle:
324 ns_info = ns_bundle.localizedInfoDictionary()
325 if ns_info:
326 if ns_info['CFBundleName'] == "Python":
327 ns_info['CFBundleName'] = Globals.application_name
328 return
329 #raise RuntimeError("No bundle information found. "
330 # "Perhaps the application was not run with pythonw?")
331
332 def application_openFile_(self, ns_app, path):
333 if self.using_clargs:
334 return True
335 #print "PyGUI_NSApplication.application_openFile_:", path ###
336 # Bizarrely, argv[0] gets passed to application_openFile_ under
337 # some circumstances. We don't want to try to open it!
338 if path == sys.argv[0]:
339 return True
340 self.files_opened = True
341 try:
342 self.pygui_app.open_path(path)
343 return True
344 except Exception, e:
345 self.pygui_app.report_error()
346 return False
347
348 def applicationDidFinishLaunching_(self, notification):
349 if self.using_clargs:
350 return
351 #print "PyGUI_NSApplication.applicationDidFinishLaunching_"
352 try:
353 if not self.files_opened:
354 self.pygui_app.open_app()
355 except Exception, e:
356 self.pygui_app.report_error()
357 return False
358
359#------------------------------------------------------------------------------
360
361pending_exception = None
362
363def raise_pending_exception():
364 global pending_exception
365 exc_type, exc_value, exc_tb = pending_exception
366 pending_exception = None
367 raise exc_type, exc_value, exc_tb
368
369def create_ns_application():
370 global ns_application
371 ns_application = _PyGui_NSApplication.sharedApplication()
372 ns_application.setDelegate_(ns_application)
373
374Globals.ns_screen_height = NSScreen.mainScreen().frame().size.height
375
376#------------------------------------------------------------------------------
377
378# Disable this for now, since MachSignals.signal segfaults. :-(
379#
380#def _install_sigint_handler():
381# print "_install_sigint_handler" ###
382# from Foundation import NSRunLoop
383# run_loop = NSRunLoop.currentRunLoop()
384# if not run_loop:
385# print "...No current run loop" ###
386# sys.exit(1) ###
387# MachSignals.signal(signal.SIGINT, _sigint_handler)
388# #from PyObjCTools.AppHelper import installMachInterrupt
389# #installMachInterrupt()
390# print "...done" ###
391#
392#def _sigint_handler(signum):
393# print "_sigint_handler" ###
394# raise KeyboardInterrupt
395
396#def _install_sigint_handler():
397# import signal
398# signal.signal(signal.SIGINT, _raise_keyboard_interrupt)
399#
400#def _raise_keyboard_interrupt(signum, frame):
401# raise KeyboardInterrupt
402
403#_install_sigint_handler()