def InitNetwork(self): """ Launch network event loop. """ self.network_loop = NetworkLoop(self.reactor, self) BaseNavigatorApp.InitNetwork(self) self.network_loop.start() self.network.StartURLListener(self.params.url_port_min, self.params.url_port_max) SetSolipsisURLHandlers()
class NavigatorApp(BaseNavigatorApp, wx.App, XRCLoader): """ Main application class. Derived from wxPython "wx.App". """ def __init__(self, params, *args, **kargs): """available kargs: port""" BaseNavigatorApp.__init__(self, params, *args, **kargs) self.config_data = ConfigData(self.params) self.redraw_pending = False self.dialogs = None self.windows = None self.menubars = None # fields initialised by InitResources self.main_window = None self.main_menubar = None self.progress_dialog = None # Caution : wx.App.__init__ automatically calls OnInit(), # thus all data must be initialized before wx.App.__init__(self, *args, **kargs) def OnInit(self): """ Main initialization handler. """ self.InitWx() self.InitValidators() BaseNavigatorApp.OnInit(self) bookmarks_menu = self.main_menubar.GetMenu( self.main_menubar.FindMenu(_("&Bookmarks"))) assert bookmarks_menu is not None # Hack: we store the bookmarks dialog persistently because it # also interacts with the menubar (ssshh..) self.bookmarks_dialog = BookmarksDialog(app=self, world=self.world, config_data=self.config_data, menu=bookmarks_menu, parent=self.main_window) self.bookmarks_dialog.UpdateUI() # UI events in main window wx.EVT_MENU(self, XRCID("menu_about"), self._OnAbout) wx.EVT_MENU(self, XRCID("menu_connect"), self._OnConnect) wx.EVT_MENU(self, XRCID("menu_disconnect"), self._OnDisconnect) wx.EVT_MENU(self, XRCID("menu_kill"), self._OnKill) wx.EVT_MENU(self, XRCID("menu_jumpnear"), self._OnJumpNear) wx.EVT_MENU(self, XRCID("menu_jumppos"), self._OnJumpPos) wx.EVT_MENU(self, XRCID("menu_preferences"), self._OnPreferences) wx.EVT_MENU(self, XRCID("menu_quit"), self._OnQuit) wx.EVT_MENU(self, XRCID("menu_autorotate"), self._OnToggleAutoRotate) wx.EVT_MENU(self, XRCID("menu_nodeaddr"), self._OnDisplayAddress) wx.EVT_MENU(self, XRCID("menu_edit_bookmarks"), self._OnEditBookmarks) wx.EVT_CLOSE(self.main_window, self._OnQuit) # UI events in world viewport wx.EVT_IDLE(self.viewport_panel, self.OnIdle) wx.EVT_PAINT(self.viewport_panel, self.OnPaint) wx.EVT_SIZE(self.viewport_panel, self.OnResize) wx.EVT_LEFT_DOWN(self.viewport_panel, self._OnLeftClickViewport) wx.EVT_RIGHT_DOWN(self.viewport_panel, self._OnRightClickViewport) wx.EVT_MOTION(self.viewport_panel, self._OnHoverViewport) # For portability we need both wx.EVT_CHAR(self.main_window, self._OnKeyPressViewport) wx.EVT_CHAR(self.viewport_panel, self._OnKeyPressViewport) # Let's go... # 1. Show UI on screen self.main_window.Show() self.SetTopWindow(self.main_window) # 2. Launch main GUI loop if os.name == 'posix' and wx.Platform == '__WXGTK__': self.x11 = True else: self.x11 = False # 3. Other tasks are launched after ip found out & window is drawn wx.CallAfter(self.InitTwisted) wx.CallAfter(self.InitNetwork) return True def InitResources(self): """ Load UI layout from XML file(s). """ self.dialogs = [] self.windows = ["main_window"] self.menubars = ["main_menubar"] objects = self.dialogs + self.windows + self.menubars self.LoadResource("resources/navigator.xrc") for obj_name in objects: self.__setattr__(obj_name, self.Resource(obj_name)) # 2D viewport for the world view self.viewport_panel = XRCCTRL(self.main_window, "viewport_panel") self.viewport = Viewport(self.viewport_panel) self.world = World(self.viewport) # Putting objects together self.main_window.SetMenuBar(self.main_menubar) self.statusbar = StatusBar(self.main_window, _("Not connected")) # Nicer sizing for obj_name in objects: attr = self.__getattribute__(obj_name) # Avoid crash on MacOS X if isinstance(attr, wx.MenuBar): continue attr.SetSizeHintsSz(attr.GetBestVirtualSize()) def InitNetwork(self): """ Launch network event loop. """ self.network_loop = NetworkLoop(self.reactor, self) BaseNavigatorApp.InitNetwork(self) self.network_loop.start() self.network.StartURLListener(self.params.url_port_min, self.params.url_port_max) SetSolipsisURLHandlers() def InitServices(self): """ Initialize all services. """ self.services = WxServiceCollector(self.params, self.local_ip, self, self.reactor) BaseNavigatorApp.InitServices(self) # Service-specific menus in the menubar: We will insert # service menus just before the last menu, which is the "Help" # menu self.service_menus = [] self.service_menu_pos = self.main_menubar.GetMenuCount() - 1 # If the menu has become too wide because of the entries added # by services, resize main window so that the menu fits BUG: # this doesn't work :( self.main_menubar.Layout() self.main_window.Layout() self.main_window.SetSize(self.main_window.GetBestVirtualSize()) def InitWx(self): """ Initialize some basic wxWidgets stuff, including localization. """ import locale as system_locale wx.InitAllImageHandlers() self.locale = wx.Locale() if not self.locale.Init2(): print "Warning: your system language is not supported " \ "by wxWidgets, trying English instead" # Try English as default lang_info = wx.Locale.FindLanguageInfo('en') if lang_info is None or not self.locale.Init2(lang_info.Language): print "Error: failed to initialize wx.Locale!" if wx.Platform not in ('__WXMSW__', '__WXMAC__'): print "Please check the LC_MESSAGES " \ "or LANG environment variable is properly set:" env_vars = os.environ.items() env_vars.sort() for name, value in env_vars: if name.startswith('LC_') or name.startswith('LANG'): print "%s = %s" % (name, value) sys.exit(1) try: translation_dir = self.params.translation_dir except AttributeError: print "No translation dir specified in configuration file." pass else: self.locale.AddCatalogLookupPathPrefix(translation_dir) self.locale.AddCatalog("solipsis") # Workaround for buggy Python behaviour with floats system_locale.setlocale(system_locale.LC_NUMERIC, "C") # Override languages in config lang_code = self.locale.GetCanonicalName() if lang_code: self.config_data.languages = [str(lang_code.split('_')[0])] def InitValidators(self): """ Setup validators for various form controls. Validators have two purposes : 1. Validate proper data is entered in forms 2. Transfer validated data to their storage location (an instance variable of a ManagedData subclass instance). """ validators = [ # [ Containing window, control name, validator class, # data object, data attribute ] #~ [ self.prefs_dialog, "proxymode_auto", BooleanValidator, #~ c, "proxymode_auto" ], ] for v in validators: window, control_name, validator_class, data_obj, data_attr = v valid = validator_class(data_obj.Ref(data_attr)) XRCCTRL(window, control_name).SetValidator(valid) def AskRedraw(self): """ This method tries hard to schedule redraws of the world view in a smart way. """ if self.viewport.NeedsFurtherRedraw(): if not self.viewport.PendingRedraw() and not self.redraw_pending: # This is a hack so that we don't take too much CPU time # and give some timeslices to the graphics subsystem self.redraw_pending = True if self.x11: t = 5.0 + 5 * 1000 * self.viewport.LastRedrawDuration() else: t = 5.0 def _redraw(): """clean viewport""" self.redraw_pending = False self.Redraw() self.future_call(t, _redraw) return True return False # # Helpers # def future_call(self, delay, function): """call function after delay (milli sec)""" wx.FutureCall(delay, function) def display_message(self, msg, title="Solipsis"): """Way of communicta with user""" dialog = wx.MessageDialog(None, msg, caption=title, style=wx.OK | wx.ICON_EXCLAMATION) dialog.ShowModal() def display_warning(self, msg, title="Solipsis"): """Way of communicta with user""" dialog = wx.MessageDialog(None, msg, caption=title, style=wx.OK | wx.ICON_WARNING) dialog.ShowModal() def display_error(self, msg, title="Solipsis", error=None, trace=None): """Way of communicta with user""" dialog = wx.MessageDialog(None, msg, caption=title, style=wx.OK | wx.ICON_ERROR) dialog.ShowModal() def display_status(self, msg): """report a status""" self.statusbar.SetText(msg) def _CallAfter(self, fun, *args, **kargs): """ Call function asynchronously with args. """ wx.CallAfter(fun, *args, **kargs) def _LaunchFirstDialog(self): """ Display first UI dialog after everything has been initialized properly. """ BaseNavigatorApp._LaunchFirstDialog(self) self._CallAfter(self._OpenConnectDialog) def _DestroyProgress(self): """ Destroy progress dialog if necessary. """ if self.progress_dialog is not None: self.progress_dialog.Destroy() self.progress_dialog = None def _SetWaiting(self, waiting): """ Set "waiting" state of the interface. """ if waiting: cursor = wx.StockCursor(wx.CURSOR_ARROWWAIT) else: cursor = wx.StockCursor(wx.CURSOR_DEFAULT) self.viewport_panel.SetCursor(cursor) def _OpenConnectDialog(self): """get parameters of connection & connect""" connect_dialog = ConnectDialog(config_data=self.config_data, parent=self.main_window) if connect_dialog.ShowModal() != wx.ID_OK: return self.config_data.Compute() BaseNavigatorApp._OnConnect(self) def _HandleMouseMovement(self, evt): """ Handle the mouse position part of a mouse event. """ x, y = evt.GetPositionTuple() changed, id_ = self.viewport.Hover((x, y)) if changed and id_: self.statusbar.SetTemp(self.world.GetPeer(id_).pseudo) elif changed and not id_: self.statusbar.Reset() #===-----------------------------------------------------------------===# # Event handlers for the main window # (in alphabetical order) # def _OnConnect(self, evt): """ Called on "connect" event (menu -> File -> Connect). """ self._OpenConnectDialog() def _OnDisplayAddress(self, evt=None): """ Called on "node address" event (menu -> Actions -> Jump Near). """ if self._CheckNodeProxy(): address_str = self.world.GetNode().address.GetURL().ToString() clipboard = wx.TheClipboard clipboard.Open() clipboard.SetData(wx.TextDataObject(address_str)) clipboard.Close() msg = _("Your address has been copied to the clipboard. \n" "If you paste it and send it to your friends, \n" "they will be able to jump near you in the Solipsis world.") msg += "\n" + _("For reminder, here is your address:") + " " msg += address_str dialog = wx.MessageDialog(self.main_window, message=msg, caption=_("Your Solipsis address"), style=wx.OK|wx.CENTRE|wx.ICON_INFORMATION ) dialog.ShowModal() def _OnEditBookmarks(self, evt): """ Called on "edit bookmarks" event (menu -> Bookmarks -> Edit bookmarks). """ self.bookmarks_dialog.Show() self._SaveConfig() def OnIdle(self, event): """ Idle event handler. Used to smoothly redraw some things. """ if self.alive: if not self.AskRedraw(): event.Skip() else: self.ProcessPendingEvents() else: event.Skip() def _OnJumpNear(self, evt): """ Called on "jump near" event (menu -> Actions -> Jump near). """ if self._CheckNodeProxy(): dialog = wx.TextEntryDialog(self.main_window, message=_("Please enter the address to jump to"), caption=_("Jump near an entity"), defaultValue='slp://192.33.178.29:5010/' ) if dialog.ShowModal() == wx.ID_OK: v = dialog.GetValue() BaseNavigatorApp._OnJumpNear(self, v) def _OnJumpPos(self, evt): """ Called on "jump to position" event (menu -> Actions -> Jump to position). """ if not self._CheckNodeProxy(): return jump_dialog = PositionJumpDialog(config_data=self.config_data, parent=self.main_window) if jump_dialog.ShowModal() == wx.ID_OK: x, y = jump_dialog.GetPosition() BaseNavigatorApp._OnJumpPos(self, (x, y)) def OnPaint(self, event): """ Called on repaint request. """ self.viewport.Draw(onPaint=True) event.Skip() def _OnPreferences(self, evt): """ Called on "preferences" event (menu -> File -> Preferences). """ self.config_data.Compute() prefs_dialog = PreferencesDialog(config_data=self.config_data, parent=self.main_window) prefs_dialog.ShowModal() self._SaveConfig() def _Quit2(self): """ The end of the quit procedure ;-) """ BaseNavigatorApp._Quit2(self) # Process the last pending events self.ProcessPendingEvents() for obj_name in self.dialogs + self.windows: try: win = getattr(self, obj_name) win.Destroy() except: pass def OnResize(self, event): """ Called on repaint request. """ self.Redraw() def _OnToggleAutoRotate(self, evt): """ Called on autorotate event (menu -> View -> Autorotate). """ self.viewport.AutoRotate(evt.IsChecked()) #===-----------------------------------------------------------------===# # Event handlers for the about dialog # def _CloseAbout(self, evt): """ Called on close "about dialog" event (Ok button, window close box). """ self.about_dialog.Hide() #===-----------------------------------------------------------------===# # Event handlers for the world viewport # def _OnKeyPressViewport(self, evt): """ Called when a key is pressed. """ if self._CheckNodeProxy(False): dx = 0.0 dy = 0.0 key = evt.GetKeyCode() if key == wx.WXK_UP: dy -= 0.3 elif key == wx.WXK_DOWN: dy += 0.3 elif key == wx.WXK_LEFT: dx -= 0.3 elif key == wx.WXK_RIGHT: dx += 0.3 else: evt.Skip() if dx or dy: x, y = self.viewport.MoveToRelative((dx, dy)) self._MoveNode((x, y)) else: evt.Skip() def _OnLeftClickViewport(self, evt): """ Called on left click event. """ if self._CheckNodeProxy(False): # First update our position (due to buggy EVT_MOTION handling) self._HandleMouseMovement(evt) # Then handle mouse click x, y = self.viewport.MoveToPixels(evt.GetPositionTuple()) self._MoveNode((x, y)) evt.Skip() def _OnRightClickViewport(self, evt): """ Called on right click event. """ # We display a contextual menu menu = wx.Menu() if self._CheckNodeProxy(False): # First update our position (due to buggy EVT_MOTION handling) self._HandleMouseMovement(evt) # Then get ID of the hovered peer, if any id_ = self.viewport.HoveredItem() # MenuItem #1: bookmark peer if id_ is not None: item_id = wx.NewId() peer = self.world.GetPeer(id_) menu.Append(item_id, _('Bookmark peer "%s"') % peer.pseudo) menu.AppendSeparator() # TODO: model-view-controller ? def _clicked(evt): """action to perfome on right click""" self.config_data.bookmarks.AddPeer(peer) self.bookmarks_dialog.UpdateUI() wx.EVT_MENU(self.main_window, item_id, _clicked) # Following MenuItems are filled by service plugins l = self.services.GetPopupMenuItems(menu, id_) if len(l) > 0: for item in l: menu.AppendItem(item) menu.AppendSeparator() menu.Append(XRCID("menu_disconnect"), _("Disconnect")) else: menu.Append(XRCID("menu_connect"), _("Connect to node")) menu.Append(XRCID("menu_about"), _("About Solipsis")) self.viewport_panel.PopupMenu(menu) evt.Skip() def _OnHoverViewport(self, evt): """ Called on mouse movement in the viewport. """ if self._CheckNodeProxy(False): self._HandleMouseMovement(evt) evt.Skip() #===-----------------------------------------------------------------===# # Actions from the network thread(s) # def NodeConnectionFailed(self, error): """ Failed connecting to the node. """ # Allow for some leeway in certain cases if self.connection_trials > 0: if not self.progress_dialog: self.progress_max = self.connection_trials self.progress_dialog = wx.ProgressDialog( title=_('Connection progress'), message=_('Connecting to the node...'), maximum=self.progress_max + 1, style=wx.GA_SMOOTH) self.progress_dialog.SetSizeHintsSz( self.progress_dialog.GetBestVirtualSize()) else: self.connection_trials -= 1 self.progress_dialog.Update( self.progress_max - self.connection_trials) self.future_call(3000, self._TryConnect) else: # Connection failed BaseNavigatorApp.NodeConnectionFailed(self, error) #===-----------------------------------------------------------------===# # Actions from the services # def SetServiceMenu(self, service_id, title, menu): """ Allow a service to change the title of its entry in the main menu bar. """ val = (title, service_id) pos = bisect.bisect_right(self.service_menus, val) if pos == len(self.service_menus) \ or self.service_menus[pos][1] != service_id: self.main_menubar.Insert(pos + self.service_menu_pos, menu, title) self.service_menus.insert(pos, val) else: self.main_menubar.Replace(pos + self.service_menu_pos, menu, title) self.service_menus[pos] = val
class NavigatorApp(BaseNavigatorApp, wx.App, XRCLoader): """ Main application class. Derived from wxPython "wx.App". """ def __init__(self, params, *args, **kargs): """available kargs: port""" BaseNavigatorApp.__init__(self, params, *args, **kargs) self.config_data = ConfigData(self.params) self.redraw_pending = False self.dialogs = None self.windows = None self.menubars = None # fields initialised by InitResources self.main_window = None self.main_menubar = None self.progress_dialog = None # Caution : wx.App.__init__ automatically calls OnInit(), # thus all data must be initialized before wx.App.__init__(self, *args, **kargs) def OnInit(self): """ Main initialization handler. """ self.InitWx() self.InitValidators() BaseNavigatorApp.OnInit(self) bookmarks_menu = self.main_menubar.GetMenu( self.main_menubar.FindMenu(_("&Bookmarks"))) assert bookmarks_menu is not None # Hack: we store the bookmarks dialog persistently because it # also interacts with the menubar (ssshh..) self.bookmarks_dialog = BookmarksDialog(app=self, world=self.world, config_data=self.config_data, menu=bookmarks_menu, parent=self.main_window) self.bookmarks_dialog.UpdateUI() # UI events in main window wx.EVT_MENU(self, XRCID("menu_about"), self._OnAbout) wx.EVT_MENU(self, XRCID("menu_connect"), self._OnConnect) wx.EVT_MENU(self, XRCID("menu_disconnect"), self._OnDisconnect) wx.EVT_MENU(self, XRCID("menu_kill"), self._OnKill) wx.EVT_MENU(self, XRCID("menu_jumpnear"), self._OnJumpNear) wx.EVT_MENU(self, XRCID("menu_jumppos"), self._OnJumpPos) wx.EVT_MENU(self, XRCID("menu_preferences"), self._OnPreferences) wx.EVT_MENU(self, XRCID("menu_quit"), self._OnQuit) wx.EVT_MENU(self, XRCID("menu_autorotate"), self._OnToggleAutoRotate) wx.EVT_MENU(self, XRCID("menu_nodeaddr"), self._OnDisplayAddress) wx.EVT_MENU(self, XRCID("menu_edit_bookmarks"), self._OnEditBookmarks) wx.EVT_CLOSE(self.main_window, self._OnQuit) # UI events in world viewport wx.EVT_IDLE(self.viewport_panel, self.OnIdle) wx.EVT_PAINT(self.viewport_panel, self.OnPaint) wx.EVT_SIZE(self.viewport_panel, self.OnResize) wx.EVT_LEFT_DOWN(self.viewport_panel, self._OnLeftClickViewport) wx.EVT_RIGHT_DOWN(self.viewport_panel, self._OnRightClickViewport) wx.EVT_MOTION(self.viewport_panel, self._OnHoverViewport) # For portability we need both wx.EVT_CHAR(self.main_window, self._OnKeyPressViewport) wx.EVT_CHAR(self.viewport_panel, self._OnKeyPressViewport) # Let's go... # 1. Show UI on screen self._UpdateWindowTitle() self.main_window.Show() self.SetTopWindow(self.main_window) # 2. Launch main GUI loop if os.name == 'posix' and wx.Platform == '__WXGTK__': self.x11 = True else: self.x11 = False # 3. Other tasks are launched after ip found out & window is drawn wx.CallAfter(self.InitTwisted) wx.CallAfter(self.InitNetwork) return True def InitResources(self): """ Load UI layout from XML file(s). """ self.dialogs = [] self.windows = ["main_window"] self.menubars = ["main_menubar"] objects = self.dialogs + self.windows + self.menubars self.LoadResource("resources/navigator.xrc") for obj_name in objects: self.__setattr__(obj_name, self.Resource(obj_name)) # 2D viewport for the world view self.viewport_panel = XRCCTRL(self.main_window, "viewport_panel") self.viewport = Viewport(self.viewport_panel) self.world = World(self.viewport) # Putting objects together self.main_window.SetMenuBar(self.main_menubar) self.statusbar = StatusBar(self.main_window, _("Not connected")) # Nicer sizing for obj_name in objects: attr = self.__getattribute__(obj_name) # Avoid crash on MacOS X if isinstance(attr, wx.MenuBar): continue attr.SetSizeHintsSz(attr.GetBestVirtualSize()) def InitNetwork(self): """ Launch network event loop. """ self.network_loop = NetworkLoop(self.reactor, self) BaseNavigatorApp.InitNetwork(self) self.network_loop.start() self.network.StartURLListener(self.params.url_port_min, self.params.url_port_max) SetSolipsisURLHandlers() def InitServices(self): """ Initialize all services. """ self.services = WxServiceCollector(self.params, self.local_ip, self, self.reactor) BaseNavigatorApp.InitServices(self) # Service-specific menus in the menubar: We will insert # service menus just before the last menu, which is the "Help" # menu self.service_menus = [] self.service_menu_pos = self.main_menubar.GetMenuCount() - 1 # If the menu has become too wide because of the entries added # by services, resize main window so that the menu fits BUG: # this doesn't work :( self.main_menubar.Layout() self.main_window.Layout() self.main_window.SetSize(self.main_window.GetBestVirtualSize()) def InitWx(self): """ Initialize some basic wxWidgets stuff, including localization. """ import locale as system_locale wx.InitAllImageHandlers() self.locale = wx.Locale() if not self.locale.Init2(): print "Warning: your system language is not supported " \ "by wxWidgets, trying English instead" # Try English as default lang_info = wx.Locale.FindLanguageInfo('en') try: err = lang_info is None or not self.locale.Init2(lang_info.Language) except Exception, e: # Catch wx bullshit under Windows w/ py2exe pass else: if err: print "Error: failed to initialize wx.Locale!" if wx.Platform not in ('__WXMSW__', '__WXMAC__'): print "Please check the LC_MESSAGES " \ "or LANG environment variable is properly set:" env_vars = os.environ.items() env_vars.sort() for name, value in env_vars: if name.startswith('LC_') or name.startswith('LANG'): print "%s = %s" % (name, value) sys.exit(1) try: translation_dir = self.params.translation_dir except AttributeError: print "No translation dir specified in configuration file." pass else: self.locale.AddCatalogLookupPathPrefix(translation_dir) self.locale.AddCatalog("solipsis") # Workaround for buggy Python behaviour with floats system_locale.setlocale(system_locale.LC_NUMERIC, "C") # Override languages in config lang_code = self.locale.GetCanonicalName() if lang_code: self.config_data.languages = [str(lang_code.split('_')[0])]