############################################################################### # Name: perspectives.py # # Purpose: Editra's view management service # # Author: Cody Precord # # Copyright: (c) 2007 Cody Precord # # License: wxWindows License # ############################################################################### """ Provides a perspective management class for saving and loading custom perspectives in the MainWindow. """ __author__ = "Cody Precord " __cvsid__ = "$Id: perspective.py 70229 2012-01-01 01:27:10Z CJP $" __revision__ = "$Revision: 70229 $" #--------------------------------------------------------------------------# # Dependencies import os import wx import wx.aui as aui # Editra Imports import util import ed_menu import ed_fmgr from profiler import Profile_Get, Profile_Set #--------------------------------------------------------------------------# # Globals AUTO_PERSPECTIVE = u'Automatic' DATA_FILE = u'perspectives' LAST_KEY = u'**LASTVIEW**' # ID's ID_SAVE_PERSPECTIVE = wx.NewId() ID_DELETE_PERSPECTIVE = wx.NewId() ID_AUTO_PERSPECTIVE = wx.NewId() # Aliases _ = wx.GetTranslation #--------------------------------------------------------------------------# class PerspectiveManager(object): """Creates a perspective manager for the given aui managed window. It supports saving and loading of on disk perspectives as created by calling SavePerspective from the AuiManager. Mixin class for a wx.Frame. """ def __init__(self, base): """Initializes the perspective manager. The auimgr parameter is a reference to the windows AuiManager instance, base is the base path to where perspectives should be loaded from and saved to. @param base: path to configuration cache """ super(PerspectiveManager, self).__init__() hint = aui.AUI_MGR_TRANSPARENT_HINT if wx.Platform == '__WXGTK__': # Use venetian blinds style as transparent can cause crashes # on linux when desktop compositing is used. hint = aui.AUI_MGR_VENETIAN_BLINDS_HINT self._mgr = ed_fmgr.EdFrameManager(flags=aui.AUI_MGR_DEFAULT | aui.AUI_MGR_TRANSPARENT_DRAG | hint | aui.AUI_MGR_ALLOW_ACTIVE_PANE) self._mgr.SetManagedWindow(self) # Attributes self._ids = list() # List of menu ids self._base = os.path.join(base, DATA_FILE) # Path to config self._viewset = dict() # Set of Views self.LoadPerspectives() self._menu = ed_menu.EdMenu() # Control menu self._currview = Profile_Get('DEFAULT_VIEW') # Currently used view # Setup Menu self._menu.Append(ID_SAVE_PERSPECTIVE, _("Save Current View"), _("Save the current window layout")) self._menu.Append(ID_DELETE_PERSPECTIVE, _("Delete Saved View")) self._menu.AppendSeparator() self._menu.Append(ID_AUTO_PERSPECTIVE, _("Automatic"), _("Automatically save/use window state from last session"), wx.ITEM_CHECK) self._menu.AppendSeparator() for name in self._viewset: self.AddPerspectiveMenuEntry(name) # Restore the managed windows previous position preference if available. pos = Profile_Get('WPOS', "size_tuple", False) if Profile_Get('SET_WPOS') and pos: # Ensure window is on screen if not self.IsPositionOnScreen(pos): pos = self.GetPrimaryDisplayOrigin() self.SetPosition(pos) # Event Handlers self.Bind(wx.EVT_MENU, self.OnPerspectiveMenu) #---- Properties ----# PanelMgr = property(lambda self: self._mgr) def AddPerspective(self, name, p_data=None): """Add a perspective to the view set. If the p_data parameter is not set then the current view will be added with the given name. @param name: name for new perspective @keyword p_data: perspective data from auimgr @return: bool (True == Added, False == Not Added) """ # Don't allow empty keys or ones that override the automatic # settings to be added name = name.strip() if not len(name) or name == AUTO_PERSPECTIVE: return False domenu = not self.HasPerspective(name) if p_data is None: self._viewset[name] = self._mgr.SavePerspective() else: self._viewset[name] = p_data self._currview = name if name != AUTO_PERSPECTIVE and domenu: self.AddPerspectiveMenuEntry(name) self.SavePerspectives() return True def AddPerspectiveMenuEntry(self, name): """Adds an entry to list of perspectives in the menu for this manager. @param name: name of perspective to add to menu @return: bool (added or not) """ name = name.strip() if not len(name) or name == AUTO_PERSPECTIVE: return False per_id = wx.NewId() self._ids.append(per_id) self._menu.InsertAlpha(per_id, name, _("Change view to \"%s\"") % name, kind=wx.ITEM_CHECK, after=ID_AUTO_PERSPECTIVE) return True def GetFrameManager(self): """Returns the manager for this frame @return: Reference to the AuiMgr of this window """ return self._mgr def GetPerspectiveControls(self): """Returns the control menu for the manager @return: menu of this manager """ return self._menu def GetPerspective(self): """Returns the name of the current perspective used @return: name of currently active perspective """ return self._currview def GetPerspectiveData(self, name): """Returns the given named perspectives data string @param name: name of perspective to fetch data from """ return self._viewset.get(name, None) def GetPersectiveHandlers(self): """Gets a list of ID to UIHandlers for the perspective Menu @return: list of [(ID, HandlerFunction)] """ handlers = [(m_id, self.OnUpdatePerspectiveMenu) for m_id in self._ids] return handlers + [(ID_AUTO_PERSPECTIVE, self.OnUpdatePerspectiveMenu)] def GetPerspectiveList(self): """Returns a list of all the loaded perspectives. The returned list only provides the names of the perspectives and not the actual data. @return: list of all managed perspectives """ return sorted(self._viewset.keys()) def GetPrimaryDisplayOrigin(self): """Get the origin on the primary display to use as a default window placement position. @return: position tuple """ # NOTE: don't default to 0,0 otherwise on osx the frame will be # stuck behind the menubar. for idx in range(wx.Display.GetCount()): disp = wx.Display(idx) if disp.IsPrimary(): drect = disp.GetClientArea() return drect.GetPosition() + (5, 5) else: return (5, 5) def HasPerspective(self, name): """Returns True if there is a perspective by the given name being managed by this manager, or False otherwise. @param name: name of perspective to look for @return: whether perspective is managed by this manager or not """ return name in self._viewset def InitWindowAlpha(self): """Initialize the windows alpha setting""" level = max(100, Profile_Get('ALPHA', default=255)) # Only set the transparency if it is not opaque if level != 255: self.SetTransparent(level) def IsPositionOnScreen(self, pos): """Check if the given position is on any of the connected displays @param pos: Position Tuple @return: bool """ bOnScreen = False if len(pos) == 2: for idx in range(wx.Display.GetCount()): disp = wx.Display(idx) drect = disp.GetClientArea() bOnScreen = drect.Contains(pos) if bOnScreen: break return bOnScreen def LoadPerspectives(self): """Loads the perspectives data into the manager. Returns the number of perspectives that were successfully loaded. @return: number of perspectives loaded """ reader = util.GetFileReader(self._base) if reader == -1: util.Log("[perspective][err] Failed to get " + "file reader for %s" % self._base) return 0 try: for line in reader.readlines(): label, val = line.split(u"=", 1) label = label.strip() if not len(label): continue self._viewset[label] = val.strip() reader.close() finally: if LAST_KEY in self._viewset: self._currview = self._viewset[LAST_KEY] del self._viewset[LAST_KEY] return len(self._viewset) def OnPerspectiveMenu(self, evt): """Handles menu events generated by the managers control menu. @param evt: event that called this handler """ e_id = evt.GetId() if e_id == ID_SAVE_PERSPECTIVE: name = wx.GetTextFromUser(_("Perspective Name"), \ _("Save Perspective")) if name: self.AddPerspective(name, p_data=None) self.SavePerspectives() Profile_Set('DEFAULT_VIEW', name) # It may make sense to update all windows to use this # perspective at this point but it may be an unexpected # event to happen when there is many windows open. Will # leave this to future consideration. for mainw in wx.GetApp().GetMainWindows(): mainw.AddPerspective(name, self._viewset[name]) elif e_id == ID_DELETE_PERSPECTIVE: views = [ view for view in self._viewset.keys() if view != AUTO_PERSPECTIVE ] name = wx.GetSingleChoice(_("Perspective to Delete"), _("Delete Perspective"), views) if name: self.RemovePerspective(name) self.SavePerspectives() for mainw in wx.GetApp().GetMainWindows(): mainw.RemovePerspective(name) else: pass # Update all windows data sets for mainw in wx.GetApp().GetMainWindows(): mainw.LoadPerspectives() elif e_id in self._ids + [ID_AUTO_PERSPECTIVE]: if e_id == ID_AUTO_PERSPECTIVE: Profile_Set('DEFAULT_VIEW', AUTO_PERSPECTIVE) self.SetAutoPerspective() else: self.SetPerspectiveById(e_id) else: evt.Skip() def OnUpdatePerspectiveMenu(self, evt): """Update the perspective menu's check mark states @param evt: UpdateUI event that called this handler """ e_id = evt.GetId() if e_id in self._ids + [ID_AUTO_PERSPECTIVE]: evt.Check(self._menu.GetLabel(e_id) == self._currview) else: evt.Skip() def RemovePerspective(self, name): """Removes a named perspective from the managed set @param name: name of perspective to remove/delete """ if name in self._viewset: del self._viewset[name] rem_id = self._menu.RemoveItemByName(name) if rem_id: self._ids.remove(rem_id) def SetAutoPerspective(self): """Set the current perspective management into automatic mode @postcondition: window is set into """ self._currview = AUTO_PERSPECTIVE self.UpdateAutoPerspective() def SavePerspectives(self): """Writes the perspectives out to disk. Returns True if all data was written and False if there was an error. @return: whether save was successful """ writer = util.GetFileWriter(self._base) if writer == -1: util.Log("[perspective][err] Failed to save %s" % self._base) return False try: self._viewset[LAST_KEY] = self._currview for perspect in self._viewset: writer.write(u"%s=%s\n" % (perspect, self._viewset[perspect])) del self._viewset[LAST_KEY] except (IOError, OSError): util.Log("[perspective][err] Write error: %s" % self._base) return False else: return True def SetPerspective(self, name): """Sets the perspective of the managed window, returns True on success and False on failure. @param name: name of perspective to set @return: whether perspective was set or not """ if name in self._viewset: self._mgr.LoadPerspective(self._viewset[name]) self._mgr.Update() self._currview = name self.SavePerspectives() return True else: # Fall back to automatic mode self.SetAutoPerspective() return False def SetPerspectiveById(self, per_id): """Sets the perspective using the given control id @param per_id: id of requested perspective @return: whether perspective was set or not """ name = None for pos in range(self._menu.GetMenuItemCount()): item = self._menu.FindItemByPosition(pos) if per_id == item.GetId(): name = item.GetLabel() break if name is not None: return self.SetPerspective(name) else: return False def UpdateAutoPerspective(self): """Update the value of the auto-perspectives current saved state @postcondition: The perspective data for the Automatic setting is updated to have data for the current state of the window. """ self._viewset[AUTO_PERSPECTIVE] = self._mgr.SavePerspective() self.SavePerspectives()