Beremiz.py
changeset 675 44b35c27e9ff
parent 666 5056f17a0c6b
child 678 92a1ec3c6fdd
--- a/Beremiz.py	Tue Jan 31 00:41:46 2012 +0100
+++ b/Beremiz.py	Tue Jan 31 23:28:03 2012 +0100
@@ -32,6 +32,7 @@
 import shutil
 import random
 import time
+from types import ListType
 
 CWD = os.path.split(os.path.realpath(__file__))[0]
 
@@ -172,6 +173,8 @@
               'size' : 18,
              }
 
+MAX_RECENT_PROJECTS = 10
+
 # Some helpers to tweak GenBitmapTextButtons
 # TODO: declare customized classes instead.
 gen_mini_GetBackgroundBrush = lambda obj:lambda dc: wx.Brush(obj.GetParent().GetBackgroundColour(), wx.SOLID)
@@ -327,13 +330,32 @@
  ID_BEREMIZPLCCONFIG, ID_BEREMIZLOGCONSOLE, 
  ID_BEREMIZINSPECTOR] = [wx.NewId() for _init_ctrls in range(5)]
 
+[ID_FILEMENURECENTPROJECTS,
+] = [wx.NewId() for _init_ctrls in range(1)]
+
+PLUGINMENU_POSITION = 3
+
 class Beremiz(IDEFrame):
 	
+    def _init_coll_MenuBar_Menus(self, parent):
+        IDEFrame._init_coll_MenuBar_Menus(self, parent)
+        
+        parent.Insert(pos=PLUGINMENU_POSITION, 
+                      menu=self.PluginMenu, title=_(u'&Plugin'))
+    
+    def _init_utils(self):
+        self.PluginMenu = wx.Menu(title='')
+        self.RecentProjectsMenu = wx.Menu(title='')
+        
+        IDEFrame._init_utils(self)
+        
     def _init_coll_FileMenu_Items(self, parent):
         AppendMenu(parent, help='', id=wx.ID_NEW,
               kind=wx.ITEM_NORMAL, text=_(u'New\tCTRL+N'))
         AppendMenu(parent, help='', id=wx.ID_OPEN,
               kind=wx.ITEM_NORMAL, text=_(u'Open\tCTRL+O'))
+        parent.AppendMenu(ID_FILEMENURECENTPROJECTS, _("Recent Projects"), self.RecentProjectsMenu)
+        parent.AppendSeparator()
         AppendMenu(parent, help='', id=wx.ID_SAVE,
               kind=wx.ITEM_NORMAL, text=_(u'Save\tCTRL+S'))
         AppendMenu(parent, help='', id=wx.ID_SAVEAS,
@@ -453,6 +475,8 @@
         # Variable allowing disabling of PLCConfig scroll when Popup shown 
         self.ScrollingEnabled = True
         
+        self.LastPanelSelected = None
+        
         self.PluginInfos = {}
         
         # Define Tree item icon list
@@ -494,6 +518,7 @@
         self.Bind(wx.EVT_CLOSE, self.OnCloseFrame)
         
         self._Refresh(TITLE, TOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU)
+        self.RefreshPluginMenu()
         self.LogConsole.SetFocus()
 
     def RiseLogConsole(self):
@@ -610,6 +635,8 @@
         event.Skip()
     
     def RefreshFileMenu(self):
+        self.RefreshRecentProjectsMenu()
+        
         if self.PluginRoot is not None:
             selected = self.TabsOpened.GetSelection()
             if selected >= 0:
@@ -642,7 +669,67 @@
             self.FileMenu.Enable(wx.ID_SAVEAS, False)
             self.FileMenu.Enable(wx.ID_PROPERTIES, False)
             self.FileMenu.Enable(wx.ID_CLOSE_ALL, False)
-        
+    
+    def RefreshRecentProjectsMenu(self):
+        for i in xrange(self.RecentProjectsMenu.GetMenuItemCount()):
+            item = self.RecentProjectsMenu.FindItemByPosition(0)
+            self.RecentProjectsMenu.Delete(item.GetId())
+        
+        recent_projects = cPickle.loads(str(self.Config.Read("RecentProjects", cPickle.dumps([]))))
+        self.FileMenu.Enable(ID_FILEMENURECENTPROJECTS, len(recent_projects) > 0)
+        for idx, projectpath in enumerate(recent_projects):
+            id = wx.NewId()
+            AppendMenu(self.RecentProjectsMenu, help='', id=id, 
+                       kind=wx.ITEM_NORMAL, text="%d: %s" % (idx + 1, projectpath))
+            self.Bind(wx.EVT_MENU, self.GenerateOpenRecentProjectFunction(projectpath), id=id)
+    
+    def GenerateOpenRecentProjectFunction(self, projectpath):
+        def OpenRecentProject(event):
+            if self.PluginRoot is not None and not self.CheckSaveBeforeClosing():
+                return
+            
+            self.OpenProject(projectpath)
+        return OpenRecentProject
+    
+    def GenerateMenuRecursive(self, items, menu):
+        for kind, infos in items:
+            if isinstance(kind, ListType):
+                text, id = infos
+                submenu = wx.Menu('')
+                self.GenerateMenuRecursive(kind, submenu)
+                menu.AppendMenu(id, text, submenu)
+            elif kind == wx.ITEM_SEPARATOR:
+                menu.AppendSeparator()
+            else:
+                text, id, help, callback = infos
+                AppendMenu(menu, help='', id=id, kind=kind, text=text)
+                if callback is not None:
+                    self.Bind(wx.EVT_MENU, callback, id=id)
+    
+    def RefreshPluginMenu(self):
+        if self.PluginRoot is not None:
+            selected = self.TabsOpened.GetSelection()
+            if selected >= 0:
+                panel = self.TabsOpened.GetPage(selected)
+            else:
+                panel = None
+            if panel != self.LastPanelSelected:
+                for i in xrange(self.PluginMenu.GetMenuItemCount()):
+                    item = self.PluginMenu.FindItemByPosition(0)
+                    self.PluginMenu.Delete(item.GetId())
+                self.LastPanelSelected = panel
+                if panel is not None:
+                    items = panel.GetPluginMenuItems()
+                else:
+                    items = []
+                self.MenuBar.EnableTop(PLUGINMENU_POSITION, len(items) > 0)
+                self.GenerateMenuRecursive(items, self.PluginMenu)
+            if panel is not None:
+                panel.RefreshPluginMenu(self.PluginMenu)
+        else:
+            self.MenuBar.EnableTop(PLUGINMENU_POSITION, False)
+        self.MenuBar.UpdateMenus()
+    
     def RefreshScrollBars(self):
         xstart, ystart = self.PLCConfig.GetViewStart()
         window_size = self.PLCConfig.GetClientSize()
@@ -741,6 +828,25 @@
         self.RefreshScrollBars()
         self.Thaw()
 
+    def GenerateEnableButton(self, parent, sizer, plugin):
+        enabled = plugin.PlugEnabled()
+        if enabled is not None:
+            enablebutton_id = wx.NewId()
+            enablebutton = wx.lib.buttons.GenBitmapToggleButton(id=enablebutton_id, bitmap=wx.Bitmap(Bpath( 'images', 'Disabled.png')),
+                  name='EnableButton', parent=parent, size=wx.Size(16, 16), pos=wx.Point(0, 0), style=0)#wx.NO_BORDER)
+            enablebutton.SetToolTipString(_("Enable/Disable this plugin"))
+            make_genbitmaptogglebutton_flat(enablebutton)
+            enablebutton.SetBitmapSelected(wx.Bitmap(Bpath( 'images', 'Enabled.png')))
+            enablebutton.SetToggle(enabled)
+            def toggleenablebutton(event):
+                res = self.SetPluginParamsAttribute(plugin, "BaseParams.Enabled", enablebutton.GetToggle())
+                enablebutton.SetToggle(res)
+                event.Skip()
+            enablebutton.Bind(wx.EVT_BUTTON, toggleenablebutton, id=enablebutton_id)
+            sizer.AddWindow(enablebutton, 0, border=0, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
+        else:
+            sizer.AddSpacer(wx.Size(16, 16))
+    
     def GenerateMethodButtonSizer(self, plugin, parent, horizontal = True):
         normal_bt_font=wx.Font(faces["size"] / 3, wx.DEFAULT, wx.NORMAL, wx.NORMAL, faceName = faces["helv"])
         mouseover_bt_font=wx.Font(faces["size"] / 3, wx.DEFAULT, wx.NORMAL, wx.NORMAL, underline=True, faceName = faces["helv"])
@@ -773,6 +879,65 @@
                 msizer.AddWindow(button, 0, border=0, flag=wx.ALIGN_CENTER)
         return msizer
 
+    def GenerateParamsPanel(self, plugin, bkgdclr):
+        rightwindow = wx.Panel(self.PLCConfig, -1, size=wx.Size(-1, -1))
+        rightwindow.SetBackgroundColour(bkgdclr)
+        
+        rightwindowmainsizer = wx.BoxSizer(wx.VERTICAL)
+        rightwindow.SetSizer(rightwindowmainsizer)
+        
+        rightwindowsizer = wx.FlexGridSizer(cols=2, rows=1)
+        rightwindowsizer.AddGrowableCol(1)
+        rightwindowsizer.AddGrowableRow(0)
+        rightwindowmainsizer.AddSizer(rightwindowsizer, 0, border=0, flag=wx.GROW)
+        
+        msizer = self.GenerateMethodButtonSizer(plugin, rightwindow, not self.PluginInfos[plugin]["right_visible"])
+        rightwindowsizer.AddSizer(msizer, 0, border=0, flag=wx.GROW)
+        
+        rightparamssizer = wx.BoxSizer(wx.HORIZONTAL)
+        rightwindowsizer.AddSizer(rightparamssizer, 0, border=0, flag=wx.ALIGN_RIGHT)
+        
+        paramswindow = wx.Panel(rightwindow, -1, size=wx.Size(-1, -1))
+        paramswindow.SetBackgroundColour(bkgdclr)
+        
+        psizer = wx.BoxSizer(wx.HORIZONTAL)
+        paramswindow.SetSizer(psizer)
+        self.PluginInfos[plugin]["params"] = paramswindow
+        
+        rightparamssizer.AddWindow(paramswindow, 0, border=5, flag=wx.ALL)
+        
+        plugin_infos = plugin.GetParamsAttributes()
+        if len(plugin_infos) > 0:
+            self.RefreshSizerElement(paramswindow, psizer, plugin, plugin_infos, None, False)
+            
+            if not self.PluginInfos[plugin]["right_visible"]:
+                paramswindow.Hide()
+            
+            rightminimizebutton_id = wx.NewId()
+            rightminimizebutton = wx.lib.buttons.GenBitmapToggleButton(id=rightminimizebutton_id, bitmap=wx.Bitmap(Bpath( 'images', 'Maximize.png')),
+                  name='MinimizeButton', parent=rightwindow, pos=wx.Point(0, 0),
+                  size=wx.Size(24, 24), style=wx.NO_BORDER)
+            make_genbitmaptogglebutton_flat(rightminimizebutton)
+            rightminimizebutton.SetBitmapSelected(wx.Bitmap(Bpath( 'images', 'Minimize.png')))
+            rightminimizebutton.SetToggle(self.PluginInfos[plugin]["right_visible"])
+            rightparamssizer.AddWindow(rightminimizebutton, 0, border=5, flag=wx.ALL)
+                        
+            def togglerightwindow(event):
+                if rightminimizebutton.GetToggle():
+                    rightparamssizer.Show(0)
+                    msizer.SetCols(1)
+                else:
+                    rightparamssizer.Hide(0)
+                    msizer.SetCols(len(plugin.PluginMethods))
+                self.PluginInfos[plugin]["right_visible"] = rightminimizebutton.GetToggle()
+                self.PLCConfigMainSizer.Layout()
+                self.RefreshScrollBars()
+                event.Skip()
+            rightminimizebutton.Bind(wx.EVT_BUTTON, togglerightwindow, id=rightminimizebutton_id)
+        
+        return rightwindow
+    
+
     def RefreshPluginTree(self):
         self.Freeze()
         self.ClearSizer(self.PluginTreeSizer)
@@ -859,7 +1024,7 @@
                     self.CollapseLocation(locations_infos, child, force, False)
         if locations_infos["root"]["left"] is not None and refresh_size:
             self.RefreshTreeCtrlSize(locations_infos["root"]["left"])
-                
+    
     def GenerateTreeBranch(self, plugin):
         leftwindow = wx.Panel(self.PLCConfig, -1, size=wx.Size(-1, -1))
         if plugin.PlugTestModified():
@@ -901,20 +1066,8 @@
 
         rolesizer = wx.BoxSizer(wx.HORIZONTAL)
         leftsizer.AddSizer(rolesizer, 0, border=0, flag=wx.GROW|wx.RIGHT)
-
-        enablebutton_id = wx.NewId()
-        enablebutton = wx.lib.buttons.GenBitmapToggleButton(id=enablebutton_id, bitmap=wx.Bitmap(Bpath( 'images', 'Disabled.png')),
-              name='EnableButton', parent=leftwindow, size=wx.Size(16, 16), pos=wx.Point(0, 0), style=0)#wx.NO_BORDER)
-        enablebutton.SetToolTipString(_("Enable/Disable this plugin"))
-        make_genbitmaptogglebutton_flat(enablebutton)
-        enablebutton.SetBitmapSelected(wx.Bitmap(Bpath( 'images', 'Enabled.png')))
-        enablebutton.SetToggle(plugin.MandatoryParams[1].getEnabled())
-        def toggleenablebutton(event):
-            res = self.SetPluginParamsAttribute(plugin, "BaseParams.Enabled", enablebutton.GetToggle())
-            enablebutton.SetToggle(res)
-            event.Skip()
-        enablebutton.Bind(wx.EVT_BUTTON, toggleenablebutton, id=enablebutton_id)
-        rolesizer.AddWindow(enablebutton, 0, border=0, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
+        
+        self.GenerateEnableButton(leftwindow, rolesizer, plugin)
 
         roletext = wx.StaticText(leftwindow, -1)
         roletext.SetLabel(plugin.PlugHelp)
@@ -1013,62 +1166,9 @@
         tc.ChangeValue(plugin.MandatoryParams[1].getName())
         tc.Bind(wx.EVT_TEXT, self.GetTextCtrlCallBackFunction(tc, plugin, "BaseParams.Name"), id=tc_id)
         iecsizer.AddWindow(tc, 0, border=5, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
-       
-        rightwindow = wx.Panel(self.PLCConfig, -1, size=wx.Size(-1, -1))
-        rightwindow.SetBackgroundColour(bkgdclr)
-        
-        self.PluginTreeSizer.AddWindow(rightwindow, 0, border=0, flag=wx.GROW)
-        
-        rightwindowmainsizer = wx.BoxSizer(wx.VERTICAL)
-        rightwindow.SetSizer(rightwindowmainsizer)
-        
-        rightwindowsizer = wx.FlexGridSizer(cols=2, rows=1)
-        rightwindowsizer.AddGrowableCol(1)
-        rightwindowsizer.AddGrowableRow(0)
-        rightwindowmainsizer.AddSizer(rightwindowsizer, 0, border=8, flag=wx.TOP|wx.GROW)
-        
-        msizer = self.GenerateMethodButtonSizer(plugin, rightwindow, not self.PluginInfos[plugin]["right_visible"])
-        rightwindowsizer.AddSizer(msizer, 0, border=0, flag=wx.GROW)
-        
-        rightparamssizer = wx.BoxSizer(wx.HORIZONTAL)
-        rightwindowsizer.AddSizer(rightparamssizer, 0, border=0, flag=wx.ALIGN_RIGHT)
-        
-        paramswindow = wx.Panel(rightwindow, -1, size=wx.Size(-1, -1))
-        paramswindow.SetBackgroundColour(bkgdclr)
-        
-        psizer = wx.BoxSizer(wx.HORIZONTAL)
-        paramswindow.SetSizer(psizer)
-        self.PluginInfos[plugin]["params"] = paramswindow
-        
-        rightparamssizer.AddWindow(paramswindow, 0, border=5, flag=wx.ALL)
-        
-        plugin_infos = plugin.GetParamsAttributes()
-        self.RefreshSizerElement(paramswindow, psizer, plugin, plugin_infos, None, False)
-        
-        if not self.PluginInfos[plugin]["right_visible"]:
-            paramswindow.Hide()
-        
-        rightminimizebutton_id = wx.NewId()
-        rightminimizebutton = wx.lib.buttons.GenBitmapToggleButton(id=rightminimizebutton_id, bitmap=wx.Bitmap(Bpath( 'images', 'Maximize.png')),
-              name='MinimizeButton', parent=rightwindow, pos=wx.Point(0, 0),
-              size=wx.Size(24, 24), style=wx.NO_BORDER)
-        make_genbitmaptogglebutton_flat(rightminimizebutton)
-        rightminimizebutton.SetBitmapSelected(wx.Bitmap(Bpath( 'images', 'Minimize.png')))
-        rightminimizebutton.SetToggle(self.PluginInfos[plugin]["right_visible"])
-        rightparamssizer.AddWindow(rightminimizebutton, 0, border=5, flag=wx.ALL)
-                    
-        def togglerightwindow(event):
-            if rightminimizebutton.GetToggle():
-                rightparamssizer.Show(0)
-                msizer.SetCols(1)
-            else:
-                rightparamssizer.Hide(0)
-                msizer.SetCols(len(plugin.PluginMethods))
-            self.PluginInfos[plugin]["right_visible"] = rightminimizebutton.GetToggle()
-            self.PLCConfigMainSizer.Layout()
-            self.RefreshScrollBars()
-            event.Skip()
-        rightminimizebutton.Bind(wx.EVT_BUTTON, togglerightwindow, id=rightminimizebutton_id)
+        
+        rightwindow = self.GenerateParamsPanel(plugin, bkgdclr)
+        self.PluginTreeSizer.AddWindow(rightwindow, 0, border=8, flag=wx.TOP|wx.GROW)
         
         self.PluginInfos[plugin]["left"] = leftwindow
         self.PluginInfos[plugin]["right"] = rightwindow
@@ -1385,7 +1485,7 @@
                         spinctrl.SetValue(element_infos["value"])
                         spinctrl.Bind(wx.EVT_SPINCTRL, self.GetTextCtrlCallBackFunction(spinctrl, plugin, element_path), id=id)
                     else:
-                        choices = cPickle.loads(str(self.Config.Read(element_path, cPickle.dumps([""]))))                           
+                        choices = cPickle.loads(str(self.Config.Read(element_path, cPickle.dumps([""]))))
                         textctrl = TextCtrlAutoComplete.TextCtrlAutoComplete(id=id, 
                                                                      name=element_infos["name"], 
                                                                      parent=parent, 
@@ -1451,26 +1551,33 @@
         
         dialog = wx.DirDialog(self , _("Choose a project"), defaultpath, wx.DD_NEW_DIR_BUTTON)
         if dialog.ShowModal() == wx.ID_OK:
-            projectpath = dialog.GetPath()
-            if os.path.isdir(projectpath):
-                self.Config.Write("lastopenedfolder", os.path.dirname(projectpath))
-                self.Config.Flush()
+            self.OpenProject(dialog.GetPath())
+        dialog.Destroy()
+    
+    def OpenProject(self, projectpath):
+        if os.path.isdir(projectpath):
+            self.Config.Write("lastopenedfolder", os.path.dirname(projectpath))
+            recent_projects = cPickle.loads(str(self.Config.Read("RecentProjects", cPickle.dumps([]))))
+            if projectpath in recent_projects:
+                recent_projects.remove(projectpath)
+            recent_projects.insert(0, projectpath)
+            self.Config.Write("RecentProjects", cPickle.dumps(recent_projects[:MAX_RECENT_PROJECTS]))
+            self.Config.Flush()
+            self.ResetView()
+            self.PluginRoot = PluginsRoot(self, self.Log)
+            self.Controler = self.PluginRoot
+            result = self.PluginRoot.LoadProject(projectpath)
+            if not result:
+                if self.EnableDebug:
+                    self.DebugVariablePanel.SetDataProducer(self.PluginRoot)
+                self._Refresh(TYPESTREE, INSTANCESTREE, LIBRARYTREE)
+                self.RefreshAll()
+            else:
                 self.ResetView()
-                self.PluginRoot = PluginsRoot(self, self.Log)
-                self.Controler = self.PluginRoot
-                result = self.PluginRoot.LoadProject(projectpath)
-                if not result:
-                    if self.EnableDebug:
-                        self.DebugVariablePanel.SetDataProducer(self.PluginRoot)
-                    self._Refresh(TYPESTREE, INSTANCESTREE, LIBRARYTREE)
-                    self.RefreshAll()
-                else:
-                    self.ResetView()
-                    self.ShowErrorMessage(result)
-            else:
-                self.ShowErrorMessage(_("\"%s\" folder is not a valid Beremiz project\n") % projectpath)
-            self._Refresh(TITLE, TOOLBAR, FILEMENU, EDITMENU)
-        dialog.Destroy()
+                self.ShowErrorMessage(result)
+        else:
+            self.ShowErrorMessage(_("\"%s\" folder is not a valid Beremiz project\n") % projectpath)
+        self._Refresh(TITLE, TOOLBAR, FILEMENU, EDITMENU)
     
     def OnCloseProjectMenu(self, event):
         if self.PluginRoot is not None and not self.CheckSaveBeforeClosing():
@@ -1505,6 +1612,14 @@
     def OnAboutMenu(self, event):
         OpenHtmlFrame(self,_("About Beremiz"), Bpath("doc","about.html"), wx.Size(550, 500))
     
+    def OnPouSelectedChanged(self, event):
+        wx.CallAfter(self.RefreshPluginMenu)
+        IDEFrame.OnPouSelectedChanged(self, event)
+    
+    def OnPageClose(self, event):
+        wx.CallAfter(self.RefreshPluginMenu)
+        IDEFrame.OnPageClose(self, event)
+    
     def GetAddButtonFunction(self, plugin, window):
         def AddButtonFunction(event):
             if plugin and len(plugin.PlugChildsTypes) > 0: