/GUI/Cocoa/Applications.py

https://bitbucket.org/alsh/pygui-mirror · Python · 403 lines · 230 code · 60 blank · 113 comment · 47 complexity · a92d134962343b896822d85cf3fbb27f MD5 · raw file

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