# -*- test-case-name: mv3d.test.util.test_application -*- # Copyright (C) 2010-2012 Mortal Coil Games # See LICENSE for details. """ Common code for starting up various types of applications. @author: mike """ import sys import os from ConfigParser import ConfigParser from twisted.python import log from twisted.internet import reactor from zope.interface import Interface, implements import mv3d from mv3d.util.iservice import IPlayerClient from mv3d.util.conductor import Conductor from twisted.python.log import deferr from mv3d.client.ui.irenderer import Ogre3D from mv3d.util.input import KeyEvent, MouseEvent, EventSource from mv3d.tools.guidewx import wxKeyMap class IApplication(Interface): """ This is the interface for applications. """ def __init__(config, conductor): #@NoSelf """ Perform any initialization of the application. """ def start(): #@NoSelf """ Start up the application. Read in configuration and run the reactor. This does not return until the application is finished. """ def onClose(obj, prop): #@NoSelf """ Called when the application is requesting to close. Usually triggered by a GUI callback. """ class WxApplication(object): """ A WxWindows application. """ implements(IApplication) configFile = None baseConfig = None configSection = None @classmethod def start(cls): """ Starts up the application. This method doesn't return until the app is finished. """ import wx cfg = ConfigParser() loaded = [] if cls.baseConfig is not None: basePath = os.path.join(os.path.dirname(mv3d.__file__), "..", "conf") loaded.extend(cfg.read(os.path.join(basePath, cls.baseConfig))) loaded.extend(cfg.read(os.path.join("conf", cls.baseConfig))) loaded.extend(cfg.read(cls.baseConfig)) if cls.configFile is not None: basePath = os.path.join(os.path.dirname(mv3d.__file__), "..", "conf") loaded.extend(cfg.read(os.path.join(basePath, cls.configFile))) loaded.extend(cfg.read(os.path.join("conf", cls.configFile))) loaded.extend(cfg.read(cls.configFile)) if not loaded: # probably best to bomb out early. raise RuntimeError("Could not read config file %s!" % cls.configFile) log.startLoggingWithObserver(log.FileLogObserver(sys.stdout).emit) cond = Conductor() configFiles = [] if cls.baseConfig is not None: configFiles.append(cls.baseConfig) if cls.configFile is not None: configFiles.append(cls.configFile) cond.configure(cls.configSection, cfg, configFiles) try: player = cond.getLocalService(IPlayerClient) player.renderer.initialize(openwindow=False) except KeyError: pass cond.startService() app = wx.PySimpleApp(0) wx.InitAllImageHandlers() instance = cls(config=cfg, conductor=cond) app.SetTopWindow(instance.window.window) reactor.registerWxApp(app) #@UndefinedVariable reactor.run() #@UndefinedVariable def onClose(self, _obj, _prop): """ The user hit the close button """ reactor.stop() #@UndefinedVariable class WebApplication(object): """ Uses Panda3D's web browser plugin to display an application inside of a browser. """ implements(IApplication) configFile = None configSection = None configPaths = None useWx = False log = None @classmethod def start(cls): """ Starts up the application. This method doesn't return until the app is finished. """ cfg = ConfigParser() if cls.configFile is not None: localConfig = os.path.expanduser(os.path.join("~/.mv3d/", cls.configFile)) realConfig = os.path.join(os.path.abspath(os.path.dirname( mv3d.__file__)), "..", cls.configFile) cfg.read([localConfig, realConfig]) if cls.configPaths: for path in cls.configPaths: cfg.read(os.path.join(path, cls.configFile)) log.startLoggingWithObserver(log.FileLogObserver(sys.stdout).emit) if cls.useWx: import wx app = wx.PySimpleApp(0) wx.InitAllImageHandlers() cond = Conductor() instance = cls(config=cfg, conductor=cond) instance.log = log cond.configure(cls.configSection, cfg, cls.configFile) dfrd = cond.startService() dfrd.addErrback(deferr) if cls.useWx: app.SetTopWindow(instance.window.window) reactor.registerWxApp(app) #@UndefinedVariable reactor.run() #@UndefinedVariable try: base.finalizeExit() #@UndefinedVariable except NameError: pass def stop(self): """ Shut down the application """ reactor.stop() #@UndefinedVariable def onClose(self, _obj, _prop): """ The user hit the close button """ self.stop() def run(self): try: taskMgr.step() #@UndefinedVariable except: self.log.deferr() class KeyCatcher(object): callback = None lastMods = None def __init__(self, renderer): self.downKeys = [] self.renderer = renderer import wx self.wx = wx def setEventCallback(self, callback): self.callback = callback def capture(self): pass def keyEvent(self, event): evt = KeyEvent.fromWxEvent(event, wxKeyMap, self.wx) if evt.keyDown: if evt.key not in self.downKeys: # disable key repeat! self.callback.keyPressed(evt.toOISEvent(self.renderer)) self.downKeys.append(evt.key) elif evt.keyUp: self.callback.keyReleased(evt.toOISEvent(self.renderer)) self.downKeys.remove(evt.key) self.lastMods = evt.mods event.Skip() def isModifierDown(self, mod): if self.lastMods is None: return False if str(mod) == "Ctrl": return self.lastMods.ctrl if str(mod) == "Alt": return self.lastMods.alt if str(mod) == "Shift": return self.lastMods.shift print repr(mod) class MouseCatcher(object): callback = None buttons = 0 lastMouse = None def __init__(self, width, height, renderer): self.width = width self.height = height self.renderer = renderer import wx self.wx = wx def setEventCallback(self, callback): self.callback = callback self.lastMouse = None def capture(self): pass def onMouseEvent(self, event): """ Accept a mouse event. """ if self.lastMouse is None: self.lastMouse = MouseEvent() self.lastMouse.source = EventSource.wx self.lastMouse.pos = (self.width / 2, self.height / 2) evt = MouseEvent.fromWxEvent(event, self.lastMouse, self.wx) evt.width = self.width evt.height = self.height self.lastMouse = evt if evt.leftDown: self.callback.mousePressed(evt.toOISEvent(self.renderer), 0) self.buttons ^= 1 if evt.rightDown: self.callback.mousePressed(evt.toOISEvent(self.renderer), 1) self.buttons ^= 2 if evt.middleDown: self.callback.mousePressed(evt.toOISEvent(self.renderer), 2) self.buttons ^= 4 if evt.leftUp: self.callback.mouseReleased(evt.toOISEvent(self.renderer), 0) self.buttons ^= 1 if evt.rightUp: self.callback.mouseReleased(evt.toOISEvent(self.renderer), 1) self.buttons ^= 2 if evt.middleUp: self.callback.mouseReleased(evt.toOISEvent(self.renderer), 2) self.buttons ^= 4 if self.callback is not None and evt.rel is not None and ( evt.rel[0] or evt.rel[1]): self.callback.mouseMoved(evt.toOISEvent(self.renderer)) event.Skip() def getMouseState(self): return self #exceptions.AttributeError: MouseCatcher instance has no attribute 'getMouseState' class ClientApplication(object): """ A standard client application that uses one of the supported rendering systems to create its window. """ implements(IApplication) configFile = None baseConfig = None configSection = None @classmethod def start(cls): """ Starts up the application. This method doesn't return until the app is finished. """ cfg = ConfigParser() loaded = [] if cls.baseConfig is not None: basePath = os.path.join(os.path.dirname(mv3d.__file__), "..", "conf") loaded.extend(cfg.read(os.path.join(basePath, cls.baseConfig))) loaded.extend(cfg.read(os.path.join("conf", cls.baseConfig))) loaded.extend(cfg.read(cls.baseConfig)) if cls.configFile is not None: basePath = os.path.join(os.path.dirname(mv3d.__file__), "..", "conf") loaded.extend(cfg.read(os.path.join(basePath, cls.configFile))) loaded.extend(cfg.read(os.path.join("conf", cls.configFile))) loaded.extend(cfg.read(cls.configFile)) if not loaded: # probably best to bomb out early. raise RuntimeError("Could not read config file %s!" % cls.configFile) log.startLoggingWithObserver(log.FileLogObserver(sys.stdout).emit) cond = Conductor() configFiles = [] if cls.baseConfig is not None: configFiles.append(cls.baseConfig) if cls.configFile is not None: configFiles.append(cls.configFile) cond.configure(cls.configSection, cfg, configFiles) psvc = cond.getLocalService(IPlayerClient, None) useWx = False if psvc is not None and cfg.has_section("Graphics"): # peek at the config renderer = psvc.renderer if renderer.getType() == Ogre3D: if cfg.has_option("Graphics", "fullScreen"): if not cfg.getboolean("Graphics", "fullScreen"): useWx = True def start(): cond.startService() instance = cls(config=cfg, conductor=cond) if useWx: window.listen("onClose", instance.onClose) if useWx: try: dims = cfg.get("Graphics", "resolution").split("x") dims = tuple([int(dim) for dim in dims]) import wx app = wx.PySimpleApp(0) wx.InitAllImageHandlers() psvc.renderer.initialize(openwindow=False) psvc.renderer.mouse = MouseCatcher(dims[0], dims[1], psvc.renderer) psvc.renderer.keyboard = KeyCatcher(psvc.renderer) psvc.renderer.mouse.setEventCallback(psvc.renderer) psvc.renderer.keyboard.setEventCallback(psvc.renderer) psvc.ui.renderer = psvc.renderer from mv3d.tools.guidewx import Window, RenderFrame window = Window(width=dims[0], height=dims[1], text="MV3D Client", parent=psvc.ui) rframe = RenderFrame(width=dims[0], height=dims[1], renderer=psvc.renderer, parent=window, ownScene=False, cameraController="mv3d.util.camera.ChaseCameraController") rframe.window.Bind(wx.EVT_KEY_DOWN, psvc.renderer.keyboard.keyEvent) rframe.window.Bind(wx.EVT_KEY_UP, psvc.renderer.keyboard.keyEvent) # rframe.Bind(wx.EVT_CHAR, psvc.renderer.keyboard.keyEvent) rframe.window.Bind(wx.EVT_MOUSE_EVENTS, psvc.renderer.mouse.onMouseEvent) window.initialize(psvc.ui) window.show() cursor = wx.StockCursor(wx.CURSOR_BLANK) rframe.window.SetCursor(cursor) rframe.window.callWhenInitialized(start) app.SetTopWindow(window.window) reactor.registerWxApp(app) #@UndefinedVariable except: useWx = False raise else: start() reactor.run() #@UndefinedVariable try: base.finalizeExit() #@UndefinedVariable except NameError: pass def onClose(self, _obj, _prop): """ The user hit the close button """ reactor.stop() #@UndefinedVariable