Laurent@814: #!/usr/bin/env python
Laurent@814: # -*- coding: utf-8 -*-
Laurent@814: 
andrej@1571: # This file is part of Beremiz, a Integrated Development Environment for
andrej@1571: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
andrej@1571: #
andrej@1571: # Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
andrej@1696: # Copyright (C) 2017: Andrey Skvortsov <andrej.skvortzov@gmail.com>
andrej@1571: #
andrej@1571: # See COPYING file for copyrights details.
andrej@1571: #
andrej@1571: # This program is free software; you can redistribute it and/or
andrej@1571: # modify it under the terms of the GNU General Public License
andrej@1571: # as published by the Free Software Foundation; either version 2
andrej@1571: # of the License, or (at your option) any later version.
andrej@1571: #
andrej@1571: # This program is distributed in the hope that it will be useful,
andrej@1571: # but WITHOUT ANY WARRANTY; without even the implied warranty of
andrej@1571: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
andrej@1571: # GNU General Public License for more details.
andrej@1571: #
andrej@1571: # You should have received a copy of the GNU General Public License
andrej@1571: # along with this program; if not, write to the Free Software
andrej@1571: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
Laurent@814: 
andrej@1881: from __future__ import absolute_import
Laurent@814: import wx
denis@1885: from wx.lib.scrolledpanel import ScrolledPanel
Laurent@814: 
surkovsv93@1970: from xmlclass.xmlclass import URI_model
surkovsv93@1970: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: #                                 Helpers
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: 
Laurent@814: REQUIRED_PARAMS = ["projectName", "productName", "productVersion", "companyName"]
Laurent@814: 
andrej@1773: [
andrej@1773:     TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE,
andrej@1773:     POUINSTANCEVARIABLESPANEL, LIBRARYTREE, SCALING, PAGETITLES
Laurent@814: ] = range(10)
Laurent@814: 
andrej@1782: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: #                       Project Properties Panel
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: 
andrej@1736: 
Laurent@814: class ProjectPropertiesPanel(wx.Notebook):
andrej@1730: 
Laurent@814:     def AddSizerParams(self, parent, sizer, params):
Laurent@814:         for idx, (name, label) in enumerate(params):
Laurent@814:             border = 0
Laurent@814:             if idx == 0:
Laurent@814:                 border |= wx.TOP
Laurent@814:             elif idx == len(params) - 1:
Laurent@814:                 border |= wx.BOTTOM
andrej@1730: 
Laurent@814:             st = wx.StaticText(parent, label=label)
andrej@1730:             sizer.AddWindow(st, border=10,
andrej@1768:                             flag=wx.ALIGN_CENTER_VERTICAL | border | wx.LEFT)
andrej@1730: 
Laurent@814:             tc = wx.TextCtrl(parent, style=wx.TE_PROCESS_ENTER)
Laurent@814:             setattr(self, name, tc)
Laurent@814:             callback = self.GetTextCtrlChangedFunction(tc, name)
Laurent@814:             self.Bind(wx.EVT_TEXT_ENTER, callback, tc)
Laurent@814:             tc.Bind(wx.EVT_KILL_FOCUS, callback)
andrej@1730:             sizer.AddWindow(tc, border=10,
andrej@1768:                             flag=wx.GROW | border | wx.RIGHT)
Laurent@814: 
andrej@2228:     def __init__(self, parent, controller=None, window=None, enable_required=True, scrolling=True):
andrej@1696:         wx.Notebook.__init__(self, parent)
Laurent@814: 
Laurent@814:         self.Controller = controller
Laurent@814:         self.ParentWindow = window
Laurent@814:         self.Values = None
andrej@1730: 
Laurent@814:         # Project Panel elements
Laurent@814: 
andrej@2228:         self.ProjectPanel = ScrolledPanel(self, style=wx.TAB_TRAVERSAL)
denis@1885:         self.ProjectPanel.SetAutoLayout(1)
andrej@2228:         if scrolling:
andrej@2228:             self.ProjectPanel.SetupScrolling()
Laurent@814:         projectpanel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=5, vgap=15)
Laurent@814:         projectpanel_sizer.AddGrowableCol(1)
Laurent@814:         self.ProjectPanel.SetSizer(projectpanel_sizer)
andrej@1730: 
Laurent@814:         self.AddSizerParams(self.ProjectPanel, projectpanel_sizer,
andrej@1768:                             [("projectName",    _('Project Name (required):')),
andrej@1768:                              ("projectVersion", _('Project Version (optional):')),
andrej@1768:                              ("productName",    _('Product Name (required):')),
andrej@1768:                              ("productVersion", _('Product Version (required):')),
andrej@1768:                              ("productRelease", _('Product Release (optional):'))])
andrej@1730: 
Laurent@814:         self.AddPage(self.ProjectPanel, _("Project"))
andrej@1730: 
Laurent@814:         # Author Panel elements
Laurent@814: 
andrej@2228:         self.AuthorPanel = ScrolledPanel(self, style=wx.TAB_TRAVERSAL)
denis@1885:         self.AuthorPanel.SetAutoLayout(1)
andrej@2228:         if scrolling:
andrej@2228:             self.AuthorPanel.SetupScrolling()
Laurent@814:         authorpanel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=4, vgap=15)
Laurent@814:         authorpanel_sizer.AddGrowableCol(1)
Laurent@814:         self.AuthorPanel.SetSizer(authorpanel_sizer)
andrej@1730: 
Laurent@814:         self.AddSizerParams(self.AuthorPanel, authorpanel_sizer,
andrej@1768:                             [("companyName",  _('Company Name (required):')),
andrej@1768:                              ("companyURL",   _('Company URL (optional):')),
andrej@1768:                              ("authorName",   _('Author Name (optional):')),
andrej@1768:                              ("organization", _('Organization (optional):'))])
andrej@1730: 
Laurent@814:         self.AddPage(self.AuthorPanel, _("Author"))
Laurent@814: 
Laurent@814:         # Graphics Panel elements
Laurent@814: 
andrej@2228:         self.GraphicsPanel = ScrolledPanel(self, style=wx.TAB_TRAVERSAL)
denis@1885:         self.GraphicsPanel.SetAutoLayout(1)
andrej@2228:         if scrolling:
andrej@2228:             self.GraphicsPanel.SetupScrolling()
Laurent@814:         graphicpanel_sizer = wx.FlexGridSizer(cols=1, hgap=5, rows=4, vgap=5)
Laurent@814:         graphicpanel_sizer.AddGrowableCol(0)
Laurent@814:         graphicpanel_sizer.AddGrowableRow(3)
Laurent@814:         self.GraphicsPanel.SetSizer(graphicpanel_sizer)
andrej@1730: 
Laurent@814:         pageSize_st = wx.StaticText(self.GraphicsPanel,
andrej@1768:                                     label=_('Page Size (optional):'))
andrej@1768:         graphicpanel_sizer.AddWindow(
andrej@1768:             pageSize_st, border=10,
andrej@1768:             flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP | wx.LEFT | wx.RIGHT)
andrej@1730: 
Laurent@814:         pageSize_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=5)
Laurent@814:         pageSize_sizer.AddGrowableCol(1)
andrej@1730:         graphicpanel_sizer.AddSizer(pageSize_sizer, border=10,
andrej@1768:                                     flag=wx.GROW | wx.LEFT | wx.RIGHT)
andrej@1730: 
Laurent@814:         for name, label in [('PageWidth', _('Width:')),
Laurent@814:                             ('PageHeight', _('Height:'))]:
Laurent@814:             st = wx.StaticText(self.GraphicsPanel, label=label)
andrej@1730:             pageSize_sizer.AddWindow(st, border=12,
andrej@1768:                                      flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT)
andrej@1730: 
andrej@1730:             sp = wx.SpinCtrl(self.GraphicsPanel,
andrej@1768:                              min=0, max=2**16, style=wx.TE_PROCESS_ENTER)
Laurent@814:             setattr(self, name, sp)
Laurent@814:             callback = self.GetPageSizeChangedFunction(sp, name)
Laurent@814:             self.Bind(wx.EVT_TEXT_ENTER, callback, sp)
Laurent@814:             sp.Bind(wx.EVT_KILL_FOCUS, callback)
Laurent@814:             pageSize_sizer.AddWindow(sp, flag=wx.GROW)
andrej@1730: 
Laurent@814:         scaling_st = wx.StaticText(self.GraphicsPanel,
andrej@1768:                                    label=_('Grid Resolution:'))
andrej@1730:         graphicpanel_sizer.AddWindow(scaling_st, border=10,
andrej@1768:                                      flag=wx.GROW | wx.LEFT | wx.RIGHT)
andrej@1730: 
Laurent@814:         scaling_nb = wx.Notebook(self.GraphicsPanel)
andrej@1730:         graphicpanel_sizer.AddWindow(scaling_nb, border=10,
andrej@1768:                                      flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
andrej@1730: 
Laurent@814:         self.Scalings = {}
andrej@1740:         for language, translation in [("FBD", _("FBD")), ("LD", _("LD")), ("SFC", _("SFC"))]:
Laurent@814:             scaling_panel = wx.Panel(scaling_nb, style=wx.TAB_TRAVERSAL)
Laurent@814:             scalingpanel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=5)
Laurent@814:             scalingpanel_sizer.AddGrowableCol(1)
Laurent@814:             scaling_panel.SetSizer(scalingpanel_sizer)
andrej@1730: 
Laurent@814:             scaling_controls = []
Laurent@814:             for idx, (name, label) in enumerate([('XScale', _('Horizontal:')),
Laurent@814:                                                  ('YScale', _('Vertical:'))]):
Laurent@814:                 if idx == 0:
Laurent@814:                     border = wx.TOP
Laurent@814:                 else:
Laurent@814:                     border = wx.BOTTOM
andrej@1730: 
Laurent@814:                 st = wx.StaticText(scaling_panel, label=label)
andrej@1768:                 scalingpanel_sizer.AddWindow(
andrej@1768:                     st, border=10,
andrej@1768:                     flag=wx.ALIGN_CENTER_VERTICAL | border | wx.LEFT)
andrej@1730: 
andrej@1730:                 sp = wx.SpinCtrl(scaling_panel,
andrej@1768:                                  min=0, max=2**16, style=wx.TE_PROCESS_ENTER)
Laurent@814:                 scaling_controls.append(sp)
Laurent@814:                 callback = self.GetScalingChangedFunction(sp, language, name)
Laurent@814:                 self.Bind(wx.EVT_TEXT_ENTER, callback, sp)
Laurent@814:                 sp.Bind(wx.EVT_KILL_FOCUS, callback)
andrej@1730:                 scalingpanel_sizer.AddWindow(sp, border=10,
andrej@1768:                                              flag=wx.GROW | border | wx.RIGHT)
andrej@1730: 
Laurent@814:             self.Scalings[language] = scaling_controls
Laurent@814:             scaling_nb.AddPage(scaling_panel, translation)
andrej@1730: 
Laurent@814:         self.AddPage(self.GraphicsPanel, _("Graphics"))
Laurent@814: 
Laurent@814:         # Miscellaneous Panel elements
Laurent@814: 
andrej@2228:         self.MiscellaneousPanel = ScrolledPanel(parent=self,
Edouard@1886:                                                 name='MiscellaneousPanel',
Edouard@1886:                                                 style=wx.TAB_TRAVERSAL)
denis@1885:         self.MiscellaneousPanel.SetAutoLayout(1)
andrej@2228:         if scrolling:
andrej@2228:             self.MiscellaneousPanel.SetupScrolling()
Laurent@814:         miscellaneouspanel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=15)
Laurent@814:         miscellaneouspanel_sizer.AddGrowableCol(1)
Laurent@814:         miscellaneouspanel_sizer.AddGrowableRow(1)
Laurent@814:         self.MiscellaneousPanel.SetSizer(miscellaneouspanel_sizer)
andrej@1730: 
Laurent@814:         language_label = wx.StaticText(self.MiscellaneousPanel,
andrej@1768:                                        label=_('Language (optional):'))
andrej@1730:         miscellaneouspanel_sizer.AddWindow(language_label, border=10,
andrej@1768:                                            flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP | wx.LEFT)
andrej@1730: 
andrej@1730:         self.Language = wx.ComboBox(self.MiscellaneousPanel,
andrej@1768:                                     style=wx.CB_READONLY)
Laurent@814:         self.Bind(wx.EVT_COMBOBOX, self.OnLanguageChanged, self.Language)
andrej@1730:         miscellaneouspanel_sizer.AddWindow(self.Language, border=10,
andrej@1768:                                            flag=wx.GROW | wx.TOP | wx.RIGHT)
andrej@1768: 
andrej@1768:         description_label = wx.StaticText(
andrej@1768:             self.MiscellaneousPanel, label=_('Content Description (optional):'))
andrej@1730:         miscellaneouspanel_sizer.AddWindow(description_label, border=10,
andrej@1768:                                            flag=wx.BOTTOM | wx.LEFT)
andrej@1768: 
andrej@1768:         self.ContentDescription = wx.TextCtrl(
andrej@1768:             self.MiscellaneousPanel, size=wx.Size(240, 150),
andrej@1768:             style=wx.TE_MULTILINE | wx.TE_PROCESS_ENTER)
andrej@1730:         self.Bind(wx.EVT_TEXT_ENTER, self.OnContentDescriptionChanged,
andrej@1768:                   self.ContentDescription)
andrej@1730:         self.ContentDescription.Bind(wx.EVT_KILL_FOCUS,
andrej@1768:                                      self.OnContentDescriptionChanged)
andrej@1730:         miscellaneouspanel_sizer.AddWindow(self.ContentDescription, border=10,
andrej@1768:                                            flag=wx.GROW | wx.BOTTOM | wx.RIGHT)
andrej@1730: 
Laurent@814:         self.AddPage(self.MiscellaneousPanel, _("Miscellaneous"))
andrej@1730: 
Laurent@814:         for param in REQUIRED_PARAMS:
Laurent@814:             getattr(self, param).Enable(enable_required)
andrej@1730: 
surkovsv93@1537:         languages = ["", "en-US", "fr-FR", "zh-CN", "ru-RU"]
andrej@1730: 
Laurent@814:         for language in languages:
Laurent@814:             self.Language.Append(language)
Laurent@814: 
Laurent@814:     def RefreshView(self):
Laurent@814:         if self.Controller is not None:
Laurent@814:             self.SetValues(self.Controller.GetProjectProperties())
Laurent@814: 
Laurent@814:     def SetValues(self, values):
Laurent@814:         self.Values = values
Laurent@814:         for item, value in values.items():
Laurent@814:             if item == "language":
Laurent@814:                 self.Language.SetStringSelection(value)
Laurent@814:             elif item == "contentDescription":
Laurent@814:                 self.ContentDescription.SetValue(value)
Laurent@814:             elif item == "pageSize":
Laurent@814:                 self.PageWidth.SetValue(value[0])
Laurent@814:                 self.PageHeight.SetValue(value[1])
Laurent@814:             elif item == "scaling":
Laurent@814:                 for language, (x, y) in value.items():
Laurent@814:                     if language in self.Scalings:
Laurent@814:                         self.Scalings[language][0].SetValue(x)
Laurent@814:                         self.Scalings[language][1].SetValue(y)
Laurent@814:             else:
Laurent@814:                 tc = getattr(self, item, None)
Laurent@814:                 if tc is not None:
Laurent@814:                     tc.SetValue(value)
andrej@1730: 
Laurent@814:     def GetValues(self):
Laurent@814:         values = {}
Laurent@814:         for param in ["projectName", "projectVersion",
Laurent@814:                       "productName", "productVersion",
Laurent@814:                       "productRelease", "companyName",
andrej@1730:                       "companyURL", "authorName",
Laurent@814:                       "organization"]:
Laurent@814:             value = getattr(self, param).GetValue()
Laurent@814:             if param in REQUIRED_PARAMS or value != "":
Laurent@814:                 values[param] = value
Laurent@814:             else:
Laurent@814:                 values[param] = None
Laurent@814:         language = self.Language.GetStringSelection()
Laurent@814:         if language != "":
Laurent@814:             values["language"] = language
Laurent@814:         else:
Laurent@814:             values["language"] = None
Laurent@814:         content_description = self.ContentDescription.GetValue()
Laurent@814:         if content_description != "":
Laurent@814:             values["contentDescription"] = content_description
Laurent@814:         else:
Laurent@814:             values["contentDescription"] = None
Laurent@814:         values["pageSize"] = (self.PageWidth.GetValue(), self.PageHeight.GetValue())
Laurent@814:         values["scaling"] = {}
Laurent@814:         for language in ["FBD", "LD", "SFC"]:
Laurent@814:             values["scaling"][language] = (self.Scalings[language][0].GetValue(),
Laurent@814:                                            self.Scalings[language][1].GetValue())
Laurent@814:         return values
andrej@1730: 
Laurent@814:     def GetTextCtrlChangedFunction(self, textctrl, name):
Laurent@814:         def TextCtrlChangedFunction(event):
andrej@1678:             if self.Controller is not None and self.Values is not None:
andrej@1678:                 old_value = self.Values.get(name)
Laurent@814:                 new_value = textctrl.GetValue()
surkovsv93@1972:                 if name in REQUIRED_PARAMS and new_value == "":
Laurent@814:                     new_value = None
surkovsv93@1970:                 if name == 'companyURL':
surkovsv93@1970:                     if not URI_model.match(new_value):
surkovsv93@1970:                         new_value = None
surkovsv93@1970:                         dialog = wx.MessageDialog(self, _('Invalid URL!\n'
surkovsv93@1970:                                                           'Please enter correct URL address.'),
surkovsv93@1970:                                                   _("Error"), wx.OK | wx.ICON_ERROR)
surkovsv93@1970:                         dialog.ShowModal()
surkovsv93@1970:                         dialog.Destroy()
Laurent@814:                 if old_value != new_value:
Laurent@814:                     self.Controller.SetProjectProperties(properties={name: new_value})
Laurent@814:                     self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU,
andrej@1768:                                                PROJECTTREE, PAGETITLES)
Laurent@814:                     wx.CallAfter(self.RefreshView)
Laurent@814:             event.Skip()
Laurent@814:         return TextCtrlChangedFunction
Laurent@814: 
Laurent@814:     def GetPageSizeChangedFunction(self, spinctrl, name):
Laurent@814:         def PageSizeChangedFunction(event):
Laurent@814:             if self.Controller is not None:
Laurent@814:                 if self.Values is not None:
Laurent@814:                     old_value = self.Values.get("pageSize")
Laurent@814:                 else:
Laurent@814:                     old_value = (0, 0)
Laurent@814:                 if name == 'PageWidth':
Laurent@814:                     new_value = (spinctrl.GetValue(), old_value[1])
Laurent@814:                 else:
Laurent@814:                     new_value = (old_value[0], spinctrl.GetValue())
Laurent@814:                 if old_value != new_value:
Laurent@814:                     self.Controller.SetProjectProperties(properties={"pageSize": new_value})
Laurent@814:                     self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU,
andrej@1768:                                                PAGETITLES, SCALING)
Laurent@814:                     wx.CallAfter(self.RefreshView)
Laurent@814:             event.Skip()
Laurent@814:         return PageSizeChangedFunction
andrej@1730: 
Laurent@814:     def GetScalingChangedFunction(self, spinctrl, language, name):
Laurent@814:         def ScalingChangedFunction(event):
Laurent@814:             if self.Controller is not None:
Laurent@814:                 old_value = (0, 0)
Laurent@814:                 if self.Values is not None:
Laurent@814:                     scaling = self.Values.get("scaling")
Laurent@814:                     if scaling is not None:
Laurent@814:                         old_value = scaling.get(language)
Laurent@814:                 if name == 'XScale':
Laurent@814:                     new_value = (spinctrl.GetValue(), old_value[1])
Laurent@814:                 else:
Laurent@814:                     new_value = (old_value[0], spinctrl.GetValue())
Laurent@814:                 if old_value != new_value:
Laurent@814:                     self.Controller.SetProjectProperties(properties={"scaling": {language: new_value}})
Laurent@814:                     self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU,
andrej@1768:                                                PAGETITLES, SCALING)
Laurent@814:                     wx.CallAfter(self.RefreshView)
Laurent@814:             event.Skip()
Laurent@814:         return ScalingChangedFunction
andrej@1730: 
Laurent@814:     def OnLanguageChanged(self, event):
Laurent@814:         if self.Controller is not None:
Laurent@814:             if self.Values is not None:
Laurent@814:                 old_value = self.Values.get("language")
Laurent@814:             else:
Laurent@814:                 old_value = None
Laurent@814:             new_value = self.Language.GetStringSelection()
Laurent@814:             if new_value == "":
Laurent@814:                 new_value = None
Laurent@814:             if old_value != new_value:
Laurent@814:                 self.Controller.SetProjectProperties(properties={"language": new_value})
Laurent@814:                 self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES)
Laurent@814:                 wx.CallAfter(self.RefreshView)
Laurent@814:         event.Skip()
andrej@1730: 
Laurent@814:     def OnContentDescriptionChanged(self, event):
Laurent@814:         if self.Controller is not None:
Laurent@814:             if self.Values is not None:
Laurent@814:                 old_value = self.Values.get("contentDescription")
Laurent@814:             else:
Laurent@814:                 old_value = None
Laurent@814:             new_value = self.ContentDescription.GetValue()
Laurent@814:             if new_value == "":
Laurent@814:                 new_value = None
Laurent@814:             if old_value != new_value:
Laurent@814:                 self.Controller.SetProjectProperties(properties={"contentDescription": new_value})
Laurent@814:                 self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES)
Laurent@814:                 wx.CallAfter(self.RefreshView)
Laurent@814:         event.Skip()