# HG changeset patch # User laurent # Date 1328048883 -3600 # Node ID 44b35c27e9ff0cb0cdfaad49a7049b9a1758d80b # Parent 761de581cf7a1cfaa2f70f1039448a0fa7d81747 Adding support for quickly open recent projects in file menu diff -r 761de581cf7a -r 44b35c27e9ff Beremiz.py --- 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: diff -r 761de581cf7a -r 44b35c27e9ff plugger.py --- a/plugger.py Tue Jan 31 00:41:46 2012 +0100 +++ b/plugger.py Tue Jan 31 23:28:03 2012 +0100 @@ -135,14 +135,20 @@ def PlugPath(self,PlugName=None): if not PlugName: - PlugName = self.BaseParams.getName() + PlugName = self.PlugName() return os.path.join(self.PlugParent.PlugPath(), PlugName + NameTypeSeparator + self.PlugType) + def PlugName(self): + return self.BaseParams.getName() + + def PlugEnabled(self): + return self.BaseParams.getEnabled() + def PlugFullName(self): parent = self.PlugParent.PlugFullName() if parent != "": - return parent + "." + self.BaseParams.getName() + return parent + "." + self.PlugName() return self.BaseParams.getName() def GetIconPath(self, name): @@ -498,10 +504,11 @@ self._View = self.EditorType(app_frame.TabsOpened, self, app_frame) - app_frame.EditProjectElement(self._View, self.GetFilename()) - - def OnCloseEditor(self): - self._View = None + app_frame.EditProjectElement(self._View, self.PlugName()) + + def OnCloseEditor(self, view): + if self._View == view: + self._View = None def OnPlugClose(self): if self._View is not None: @@ -1840,7 +1847,7 @@ self._connect_debug() else: self.logger.write_error(_("Couldn't start PLC !\n")) - self.UpdateMethodsFromPLCStatus() + wx.CallAfter(self.UpdateMethodsFromPLCStatus) # def _Do_Test_Debug(self): @@ -1869,7 +1876,7 @@ self.KillDebugThread() - self.UpdateMethodsFromPLCStatus() + wx.CallAfter(self.UpdateMethodsFromPLCStatus) def _Connect(self): # don't accept re-connetion is already connected @@ -1963,7 +1970,7 @@ def _Disconnect(self): self._connector = None self.StatusTimer.Stop() - self.UpdateMethodsFromPLCStatus() + wx.CallAfter(self.UpdateMethodsFromPLCStatus) def _Transfer(self): # Get the last build PLC's @@ -2003,7 +2010,7 @@ else: self.logger.write_error(_("No PLC to transfer (did build succeed ?)\n")) - self.UpdateMethodsFromPLCStatus() + wx.CallAfter(self.UpdateMethodsFromPLCStatus) PluginMethods = [ {"bitmap" : opjimg("Build"), diff -r 761de581cf7a -r 44b35c27e9ff plugins/c_ext/CFileEditor.py --- a/plugins/c_ext/CFileEditor.py Tue Jan 31 00:41:46 2012 +0100 +++ b/plugins/c_ext/CFileEditor.py Tue Jan 31 23:28:03 2012 +0100 @@ -882,7 +882,7 @@ self.SetIcon(wx.BitmapFromImage(img.Rescale(16, 16))) def __del__(self): - self.Controler.OnCloseEditor() + self.Controler.OnCloseEditor(self) def GetTitle(self): fullname = self.Controler.PlugFullName() diff -r 761de581cf7a -r 44b35c27e9ff plugins/c_ext/c_ext.py --- a/plugins/c_ext/c_ext.py Tue Jan 31 00:41:46 2012 +0100 +++ b/plugins/c_ext/c_ext.py Tue Jan 31 23:28:03 2012 +0100 @@ -48,9 +48,6 @@ def CFileName(self): return os.path.join(self.PlugPath(), "cfile.xml") - def GetFilename(self): - return self.MandatoryParams[1].getName() - def GetBaseTypes(self): return self.GetPlugRoot().GetBaseTypes()