# HG changeset patch # User laurent # Date 1316801260 -7200 # Node ID 5024d42e1050866fb1c60dedd26fdf303580248b # Parent 3f92a5e18804395bf4bce0bb5b74a54d17a59339 Adding custom cell editor for interval value in task informations of ResourceEditor diff -r 3f92a5e18804 -r 5024d42e1050 RessourceEditor.py --- a/RessourceEditor.py Thu Sep 22 15:33:31 2011 +0200 +++ b/RessourceEditor.py Fri Sep 23 20:07:40 2011 +0200 @@ -25,6 +25,7 @@ import wx import wx.grid +from dialogs import DurationEditorDialog #------------------------------------------------------------------------------- # Configuration Editor class @@ -214,10 +215,13 @@ renderer = None colname = self.GetColLabelValue(col, False) grid.SetReadOnly(row, col, False) - if colname in ["Name", "Interval"]: + if colname == "Name": editor = wx.grid.GridCellTextEditor() renderer = wx.grid.GridCellStringRenderer() - if colname == "Interval" and self.GetValueByName(row, "Triggering") != "Cyclic": + elif colname == "Interval": + editor = DurationCellEditor(self) + renderer = wx.grid.GridCellStringRenderer() + if self.GetValueByName(row, "Triggering") != "Cyclic": grid.SetReadOnly(row, col, True) elif colname == "Single": editor = wx.grid.GridCellChoiceEditor() @@ -464,7 +468,7 @@ self.TasksDefaultValue = {"Name" : "", "Triggering" : "", "Single" : "", "Interval" : "", "Priority" : 0} self.TasksTable = ResourceTable(self, [], GetTasksTableColnames()) self.TasksTable.SetColAlignements([wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_RIGHT, wx.ALIGN_RIGHT]) - self.TasksTable.SetColSizes([200, 100, 100, 100, 100]) + self.TasksTable.SetColSizes([200, 100, 100, 150, 100]) self.TasksGrid.SetTable(self.TasksTable) self.TasksGrid.SetRowLabelSize(0) self.TasksTable.ResetView(self.TasksGrid) @@ -538,13 +542,13 @@ task_select = min(task_select, len(tasks) - 1) col = self.TasksGrid.GetGridCursorCol() self.TasksGrid.SetGridCursor(task_select, col) - self.TasksGrid.MakeCellVisible(instance_select, col) + self.TasksGrid.MakeCellVisible(task_select, col) self.TasksGrid.SetFocus() if instance_select is not None and len(instances) > 0: if instance_select == -1: instance_select = len(instances) - 1 else: - task_select = min(task_select, len(instances) - 1) + instance_select = min(instance_select, len(instances) - 1) col = self.InstancesGrid.GetGridCursorCol() self.InstancesGrid.SetGridCursor(instance_select, col) self.InstancesGrid.MakeCellVisible(instance_select, col) @@ -678,4 +682,122 @@ self.TasksTable.AddError(infos[1:]) elif infos[0] == "instance": self.InstancesTable.AddError(infos[1:]) - + + +class DurationCellControl(wx.PyControl): + + def _init_coll_MainSizer_Items(self, parent): + parent.AddWindow(self.Duration, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.EditButton, 0, border=0, flag=wx.GROW) + + def _init_coll_MainSizer_Growables(self, parent): + parent.AddGrowableCol(0) + parent.AddGrowableRow(0) + + def _init_sizers(self): + self.MainSizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=0) + + self._init_coll_MainSizer_Items(self.MainSizer) + self._init_coll_MainSizer_Growables(self.MainSizer) + + self.SetSizer(self.MainSizer) + + def _init_ctrls(self, prnt): + wx.Control.__init__(self, id=-1, + name='DurationCellControl', parent=prnt, + size=wx.DefaultSize, style=0) + + # create location text control + self.Duration = wx.TextCtrl(id=-1, name='Duration', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 0), style=wx.TE_PROCESS_ENTER) + self.Duration.Bind(wx.EVT_KEY_DOWN, self.OnDurationChar) + + # create browse button + self.EditButton = wx.Button(id=-1, label='...', + name='EditButton', parent=self, pos=wx.Point(0, 0), + size=wx.Size(30, 0), style=0) + self.EditButton.Bind(wx.EVT_BUTTON, self.OnEditButtonClick) + + self.Bind(wx.EVT_SIZE, self.OnSize) + + self._init_sizers() + + ''' + Custom cell editor control with a text box and a button that launches + the DurationEditorDialog. + ''' + def __init__(self, parent): + self._init_ctrls(parent) + + def SetValue(self, value): + self.Duration.SetValue(value) + + def GetValue(self): + return self.Duration.GetValue() + + def OnSize(self, event): + self.Layout() + + def OnEditButtonClick(self, event): + # pop up the Duration Editor dialog + dialog = DurationEditorDialog(self) + dialog.SetDuration(self.Duration.GetValue()) + if dialog.ShowModal() == wx.ID_OK: + # set the duration + self.Duration.SetValue(dialog.GetDuration()) + + dialog.Destroy() + + self.Duration.SetFocus() + + def OnDurationChar(self, event): + keycode = event.GetKeyCode() + if keycode == wx.WXK_RETURN or keycode == wx.WXK_TAB: + self.Parent.Parent.ProcessEvent(event) + self.Parent.Parent.SetFocus() + else: + event.Skip() + + def SetInsertionPoint(self, i): + self.Duration.SetInsertionPoint(i) + + def SetFocus(self): + self.Duration.SetFocus() + +class DurationCellEditor(wx.grid.PyGridCellEditor): + ''' + Grid cell editor that uses DurationCellControl to display an edit button. + ''' + def __init__(self, table): + wx.grid.PyGridCellEditor.__init__(self) + self.Table = table + + def __del__(self): + self.CellControl = None + + def Create(self, parent, id, evt_handler): + self.CellControl = DurationCellControl(parent) + self.SetControl(self.CellControl) + if evt_handler: + self.CellControl.PushEventHandler(evt_handler) + + def BeginEdit(self, row, col, grid): + self.CellControl.SetValue(self.Table.GetValueByName(row, 'Interval')) + self.CellControl.SetFocus() + + def EndEdit(self, row, col, grid): + duration = self.CellControl.GetValue() + old_duration = self.Table.GetValueByName(row, 'Interval') + if duration != old_duration: + self.Table.SetValueByName(row, 'Interval', duration) + return True + return False + + def SetSize(self, rect): + self.CellControl.SetDimensions(rect.x + 1, rect.y, + rect.width, rect.height, + wx.SIZE_ALLOW_MINUS_ONE) + + def Clone(self): + return DurationCellEditor(self.Table) + diff -r 3f92a5e18804 -r 5024d42e1050 VariablePanel.py --- a/VariablePanel.py Thu Sep 22 15:33:31 2011 +0200 +++ b/VariablePanel.py Fri Sep 23 20:07:40 2011 +0200 @@ -962,7 +962,7 @@ # create browse button self.BrowseButton = wx.Button(id=-1, label='...', - name='staticText1', parent=self, pos=wx.Point(0, 0), + name='BrowseButton', parent=self, pos=wx.Point(0, 0), size=wx.Size(30, 0), style=0) self.BrowseButton.Bind(wx.EVT_BUTTON, self.OnBrowseButtonClick) @@ -972,7 +972,7 @@ ''' Custom cell editor control with a text box and a button that launches - the BrowseVariableLocationsDialog. + the BrowseLocationsDialog. ''' def __init__(self, parent, locations): self._init_ctrls(parent) @@ -1060,7 +1060,7 @@ wx.SIZE_ALLOW_MINUS_ONE) def Clone(self): - return LocationCellEditor(self.Table, self.Locations) + return LocationCellEditor(self.Table, self.Controler) def GetDirChoiceOptions(): _ = lambda x : x diff -r 3f92a5e18804 -r 5024d42e1050 dialogs/DurationEditorDialog.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dialogs/DurationEditorDialog.py Fri Sep 23 20:07:40 2011 +0200 @@ -0,0 +1,219 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor +#based on the plcopen standard. +# +#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD +# +#See COPYING file for copyrights details. +# +#This library is free software; you can redistribute it and/or +#modify it under the terms of the GNU General Public +#License as published by the Free Software Foundation; either +#version 2.1 of the License, or (at your option) any later version. +# +#This library is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +#General Public License for more details. +# +#You should have received a copy of the GNU General Public +#License along with this library; if not, write to the Free Software +#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import re + +import wx + +SECOND = 1000 +MINUTE = 60 * SECOND +HOUR = 60 * MINUTE +DAY = 24 * HOUR + +IEC_TIME_MODEL = re.compile("(?:(?:T|TIME)#)?(-)?(?:(%(float)s)D_?)?(?:(%(float)s)H_?)?(?:(%(float)s)M(?!S)_?)?(?:(%(float)s)S_?)?(?:(%(float)s)MS)?" % {"float": "[0-9]+(?:\.[0-9]+)?"}) + +#------------------------------------------------------------------------------- +# Edit Duration Value Dialog +#------------------------------------------------------------------------------- + +[ID_DURATIONEDITORDIALOG, ID_DURATIONEDITORDIALOGDAYSLABEL, + ID_DURATIONEDITORDIALOGDAYS, ID_DURATIONEDITORDIALOGHOURSLABEL, + ID_DURATIONEDITORDIALOGHOURS, ID_DURATIONEDITORDIALOGMINUTESLABEL, + ID_DURATIONEDITORDIALOGMINUTES, ID_DURATIONEDITORDIALOGSECONDSLABEL, + ID_DURATIONEDITORDIALOGSECONDS, ID_DURATIONEDITORDIALOGMILLISECONDSLABEL, + ID_DURATIONEDITORDIALOGMILLISECONDS, +] = [wx.NewId() for _init_ctrls in range(11)] + +class DurationEditorDialog(wx.Dialog): + + if wx.VERSION < (2, 6, 0): + def Bind(self, event, function, id = None): + if id is not None: + event(self, id, function) + else: + event(self, function) + + def _init_coll_MainSizer_Items(self, parent): + parent.AddSizer(self.ControlsSizer, 0, border=20, flag=wx.TOP|wx.LEFT|wx.RIGHT|wx.GROW) + parent.AddSizer(self.ButtonSizer, 0, border=20, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW) + + def _init_coll_MainSizer_Growables(self, parent): + parent.AddGrowableCol(0) + parent.AddGrowableRow(0) + + def _init_coll_ControlsSizer_Items(self, parent): + parent.AddWindow(self.DaysLabel, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.HoursLabel, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.MinutesLabel, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.SecondsLabel, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.MillisecondsLabel, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.Days, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.Hours, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.Minutes, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.Seconds, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.Milliseconds, 0, border=0, flag=wx.GROW) + + def _init_coll_ControlsSizer_Growables(self, parent): + parent.AddGrowableCol(0) + parent.AddGrowableCol(1) + parent.AddGrowableCol(2) + parent.AddGrowableCol(3) + parent.AddGrowableCol(4) + + def _init_sizers(self): + self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10) + self.ControlsSizer = wx.FlexGridSizer(cols=5, hgap=10, rows=2, vgap=10) + + self._init_coll_MainSizer_Items(self.MainSizer) + self._init_coll_MainSizer_Growables(self.MainSizer) + self._init_coll_ControlsSizer_Items(self.ControlsSizer) + self._init_coll_ControlsSizer_Growables(self.ControlsSizer) + + self.SetSizer(self.MainSizer) + + def _init_ctrls(self, prnt): + wx.Dialog.__init__(self, id=ID_DURATIONEDITORDIALOG, + name='DurationEditorDialog', parent=prnt, + size=wx.Size(600, 200), style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER, + title=_('Edit Duration')) + + self.DaysLabel = wx.StaticText(id=ID_DURATIONEDITORDIALOGDAYSLABEL, + label=_('Days:'), name='DaysLabel', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + + self.Days = wx.TextCtrl(id=ID_DURATIONEDITORDIALOGDAYS, value='0', + name='Days', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 24), style=wx.TE_PROCESS_ENTER) + self.Bind(wx.EVT_TEXT_ENTER, self.GetControlValueTestFunction(self.Days), id=ID_DURATIONEDITORDIALOGDAYS) + + self.HoursLabel = wx.StaticText(id=ID_DURATIONEDITORDIALOGHOURSLABEL, + label=_('Hours:'), name='HoursLabel', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + + self.Hours = wx.TextCtrl(id=ID_DURATIONEDITORDIALOGHOURS, value='0', + name='Hours', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 24), style=wx.TE_PROCESS_ENTER) + self.Bind(wx.EVT_TEXT_ENTER, self.GetControlValueTestFunction(self.Hours), id=ID_DURATIONEDITORDIALOGHOURS) + + self.MinutesLabel = wx.StaticText(id=ID_DURATIONEDITORDIALOGMINUTESLABEL, + label=_('Minutes:'), name='MinutesLabel', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + + self.Minutes = wx.TextCtrl(id=ID_DURATIONEDITORDIALOGMINUTES, value='0', + name='Minutes', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 24), style=wx.TE_PROCESS_ENTER) + self.Bind(wx.EVT_TEXT_ENTER, self.GetControlValueTestFunction(self.Minutes), id=ID_DURATIONEDITORDIALOGMINUTES) + + self.SecondsLabel = wx.StaticText(id=ID_DURATIONEDITORDIALOGSECONDSLABEL, + label=_('Seconds:'), name='SecondsLabel', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + + self.Seconds = wx.TextCtrl(id=ID_DURATIONEDITORDIALOGSECONDS, value='0', + name='Seconds', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 24), style=wx.TE_PROCESS_ENTER) + self.Bind(wx.EVT_TEXT_ENTER, self.GetControlValueTestFunction(self.Seconds), id=ID_DURATIONEDITORDIALOGSECONDS) + + self.MillisecondsLabel = wx.StaticText(id=ID_DURATIONEDITORDIALOGMILLISECONDSLABEL, + label=_('Milliseconds:'), name='MillisecondsLabel', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + + self.Milliseconds = wx.TextCtrl(id=ID_DURATIONEDITORDIALOGMILLISECONDS, value='0', + name='Milliseconds', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 24), style=wx.TE_PROCESS_ENTER) + self.Bind(wx.EVT_TEXT_ENTER, self.GetControlValueTestFunction(self.Milliseconds), id=ID_DURATIONEDITORDIALOGMILLISECONDS) + + self.ButtonSizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE) + if wx.VERSION >= (2, 5, 0): + self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.ButtonSizer.GetAffirmativeButton().GetId()) + else: + self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.ButtonSizer.GetChildren()[0].GetSizer().GetChildren()[0].GetWindow().GetId()) + + self._init_sizers() + + def __init__(self, parent): + self._init_ctrls(parent) + + def SetDuration(self, value): + result = IEC_TIME_MODEL.match(value.upper()) + if result is not None: + values = result.groups() + for control, index in [(self.Days, 1), (self.Hours, 2), + (self.Minutes, 3), (self.Seconds, 4), + (self.Milliseconds, 5)]: + value = values[index] + if value is not None: + control.SetValue(str(value)) + + def GetControlValueTestFunction(self, control): + def OnValueChanged(event): + try: + value = float(control.GetValue()) + except ValueError, e: + message = wx.MessageDialog(self, _("Invalid value!\nYou must fill a numeric value."), _("Error"), wx.OK|wx.ICON_ERROR) + message.ShowModal() + message.Destroy() + event.Skip() + return OnValueChanged + + def GetDuration(self): + milliseconds = 0 + for control, factor in [(self.Days, DAY), (self.Hours, HOUR), + (self.Minutes, MINUTE), (self.Seconds, SECOND), + (self.Milliseconds, 1)]: + + milliseconds += float(control.GetValue()) * factor + + not_null = False + duration = "T#" + for value, format in [(int(milliseconds) / DAY, "%dd"), + ((int(milliseconds) % DAY) / HOUR, "%dh"), + ((int(milliseconds) % HOUR) / MINUTE, "%dm"), + ((int(milliseconds) % MINUTE) / SECOND, "%ds")]: + + if value > 0 or not_null: + duration += format % value + not_null = True + + duration += "%gms" % (milliseconds % SECOND) + return duration + + def OnOK(self, event): + errors = [] + for control, name in [(self.Days, "days"), (self.Hours, "hours"), + (self.Minutes, "minutes"), (self.Seconds, "seconds"), + (self.Milliseconds, "milliseconds")]: + try: + value = float(control.GetValue()) + except ValueError, e: + errors.append(name) + if len(errors) > 0: + if len(errors) == 1: + message = _("Field %s hasn't a valid value!") % errors[0] + else: + message = _("Fields %s haven't a valid value!") % ",".join(errors) + dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR) + dialog.ShowModal() + dialog.Destroy() + else: + self.EndModal(wx.ID_OK) diff -r 3f92a5e18804 -r 5024d42e1050 dialogs/ForceVariableDialog.py --- a/dialogs/ForceVariableDialog.py Thu Sep 22 15:33:31 2011 +0200 +++ b/dialogs/ForceVariableDialog.py Fri Sep 23 20:07:40 2011 +0200 @@ -52,7 +52,7 @@ HOUR = 60 * MINUTE DAY = 24 * HOUR -IEC_TIME_MODEL = re.compile("(?:(?:T|TIME)#)?(-)?(?:(%(float)s)D_?)?(?:(%(float)s)H_?)?(?:(%(float)s)M_?)?(?:(%(float)s)S_?)?(?:(%(float)s)MS)?" % {"float": "[0-9]+(?:\.[0-9]+)?"}) +IEC_TIME_MODEL = re.compile("(?:(?:T|TIME)#)?(-)?(?:(%(float)s)D_?)?(?:(%(float)s)H_?)?(?:(%(float)s)M(?!S)_?)?(?:(%(float)s)S_?)?(?:(%(float)s)MS)?" % {"float": "[0-9]+(?:\.[0-9]+)?"}) IEC_DATE_MODEL = re.compile("(?:(?:D|DATE)#)?([0-9]{4})-([0-9]{2})-([0-9]{2})") IEC_DATETIME_MODEL = re.compile("(?:(?:DT|DATE_AND_TIME)#)?([0-9]{4})-([0-9]{2})-([0-9]{2})-([0-9]{2}):([0-9]{2}):([0-9]{2}(?:\.[0-9]+)?)") IEC_TIMEOFDAY_MODEL = re.compile("(?:(?:TOD|TIME_OF_DAY)#)?([0-9]{2}):([0-9]{2}):([0-9]{2}(?:\.[0-9]+)?)") diff -r 3f92a5e18804 -r 5024d42e1050 dialogs/__init__.py --- a/dialogs/__init__.py Thu Sep 22 15:33:31 2011 +0200 +++ b/dialogs/__init__.py Fri Sep 23 20:07:40 2011 +0200 @@ -36,3 +36,4 @@ from SFCDivergenceDialog import SFCDivergenceDialog from ForceVariableDialog import ForceVariableDialog from ArrayTypeDialog import ArrayTypeDialog +from DurationEditorDialog import DurationEditorDialog