# HG changeset patch # User Andrey Skvortsov # Date 1505494881 -10800 # Node ID 4e3c78a84c64896b76a480b14beec5f5a2d13827 # Parent 70768bd1dab33ec06c5637e540f5b50c1b15bdca add custom TextCtrl allowed to enter long integer with bounds checking It's needed because wx.SpinCtrl supports only 'int' type. For UDINT and (U)LINT long type is necessary. diff -r 70768bd1dab3 -r 4e3c78a84c64 controls/CustomIntCtrl.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/controls/CustomIntCtrl.py Fri Sep 15 20:01:21 2017 +0300 @@ -0,0 +1,88 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of Beremiz, a Integrated Development Environment for +# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. +# +# Copyright (C) 2017: Andrey Skvortsov +# +# See COPYING file for copyrights details. +# +# This program 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 +# of the License, or (at your option) any later version. +# +# This program 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 program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +import wx +from wx.lib.intctrl import IntCtrl + + +class CustomIntUpdatedEvent(wx.PyCommandEvent): + def __init__(self, id, value=0, object=None): + wx.PyCommandEvent.__init__(self, CustomIntCtrl.wxEVT_COMMAND_CUSTOM_INT_UPDATED, id) + + self.__value = value + self.SetEventObject(object) + + def GetValue(self): + """Retrieve the value of the control at the time + this event was generated.""" + return self.__value + + +class CustomIntCtrl(wx.lib.intctrl.IntCtrl): + """ + This class provides a control that takes and returns long as + value, and provides bounds support and optional value limiting. + + It handles entering negative numbers more user-friendly than + original wx.lib.intctrl.IntCtrl. + + It applies limits as focus is changed to other control and + sends event afterwards to signal that editing is done. + """ + + # Used to trap events indicating that the current + # integer value of the control has been changed. + wxEVT_COMMAND_CUSTOM_INT_UPDATED = wx.NewEventType() + EVT_CUSTOM_INT = wx.PyEventBinder(wxEVT_COMMAND_CUSTOM_INT_UPDATED, 1) + + def __init__(self, *args, **kwargs): + wx.lib.intctrl.IntCtrl.__init__(self, *args, **kwargs) + self.Bind(wx.EVT_KILL_FOCUS, self.UpdateValue) + self.SetLongAllowed(True) + self.SetLimited(False) + + def GetValue(self): + """ + Returns integer (long) value of the control, + but handles entering negative numbers + """ + s = wx.TextCtrl.GetValue(self) + if s == '-': + s = '' + return self._fromGUI(s) + + def GetValueStr(self): + """Returns string value of TextCtrl""" + return wx.TextCtrl.GetValue(self) + + def UpdateValue(self, event): + self.SetLimited(True) + self.SetLimited(False) + try: + self.GetEventHandler().ProcessEvent( + CustomIntUpdatedEvent(self.GetId(), self.GetValue(), self)) + except ValueError: + return + event.Skip() diff -r 70768bd1dab3 -r 4e3c78a84c64 controls/__init__.py --- a/controls/__init__.py Fri Sep 15 19:53:49 2017 +0300 +++ b/controls/__init__.py Fri Sep 15 20:01:21 2017 +0300 @@ -28,6 +28,7 @@ from CustomGrid import CustomGrid from CustomTable import CustomTable from CustomTree import CustomTree +from CustomIntCtrl import CustomIntCtrl from DebugVariablePanel import DebugVariablePanel from DurationCellEditor import DurationCellEditor from LibraryPanel import LibraryPanel diff -r 70768bd1dab3 -r 4e3c78a84c64 tests/tools/test_CustomIntCtrl.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/tools/test_CustomIntCtrl.py Fri Sep 15 20:01:21 2017 +0300 @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of Beremiz, a Integrated Development Environment for +# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. +# +# Copyright (C) 2017: Andrey Skvortsov +# +# See COPYING file for copyrights details. +# +# This program 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 +# of the License, or (at your option) any later version. +# +# This program 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 program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import unittest +import wx +import time + +import conftest +import controls.CustomIntCtrl + + +class TestCustomIntCtrl(unittest.TestCase): + def setUp(self): + self.app = wx.App() + self.frame = wx.Frame(None) + + def tearDown(self): + self.frame.Destroy() + wx.CallAfter(self.app.Exit) + self.app.MainLoop() + + def testMaxLimit(self): + """Test working upper bound""" + self.AddControls() + self.int_ctrl.SetValue(self.max_val + 100) + self.ProcessEvents() + + self.txt_ctrl.SetFocus() + self.ProcessEvents() + self.assertEqual(self.int_ctrl.GetValue(), self.max_val) + + def testMinLimit(self): + """Test working lower bound""" + self.AddControls() + self.int_ctrl.SetValue(self.min_val - 100) + self.ProcessEvents() + + self.txt_ctrl.SetFocus() + self.ProcessEvents() + + self.assertEqual(self.int_ctrl.GetValue(), self.min_val) + + def testCorrectValue(self): + """Test case if no limiting is necessary""" + self.AddControls() + val = (self.max_val + self.min_val) / 2 + self.int_ctrl.SetValue(val) + self.ProcessEvents() + + self.txt_ctrl.SetFocus() + self.ProcessEvents() + + self.assertEqual(self.int_ctrl.GetValue(), val) + + def testEventBinding(self): + """Test event sending after edit and bound checks are done""" + self.AddControls() + self.event_happend = False + + def EventHandler(event): + self.event_happend = True + event.Skip() + + self.int_ctrl.Bind(controls.CustomIntCtrl.EVT_CUSTOM_INT, EventHandler) + + val = (self.max_val + self.min_val) / 2 + + self.int_ctrl.SetValue(val) + self.ProcessEvents() + self.txt_ctrl.SetFocus() + + self.ProcessEvents() + self.txt_ctrl.SetFocus() + self.ProcessEvents() + + self.assertEqual(self.int_ctrl.GetValue(), val) + self.assertTrue(self.event_happend) + + def testLongNumbers(self): + """Test support of long integer""" + self.AddControls() + val = 40000000000 + self.int_ctrl.SetMax(val) + self.int_ctrl.SetValue(val) + self.ProcessEvents() + + self.txt_ctrl.SetFocus() + self.ProcessEvents() + + self.assertEqual(val, val) + + def ProcessEvents(self): + for i in range(0, 10): + wx.Yield() + time.sleep(0.01) + + def AddControls(self): + vs = wx.BoxSizer(wx.VERTICAL) + self.int_ctrl = controls.CustomIntCtrl(self.frame) + self.txt_ctrl = wx.TextCtrl(self.frame) + vs.Add(self.int_ctrl, 0, wx.ALIGN_CENTRE | wx.ALL, 5) + vs.Add(self.txt_ctrl, 0, wx.ALIGN_CENTRE | wx.ALL, 5) + self.frame.SetSizer(vs) + vs.Fit(self.frame) + self.frame.Show() + self.frame.Raise() + + self.min_val = 50 + self.max_val = 100 + self.int_ctrl.SetBounds(self.min_val, self.max_val) + self.ProcessEvents() + + +if __name__ == '__main__': + unittest.main()