{"id":1433,"date":"2021-02-15T18:11:53","date_gmt":"2021-02-15T17:11:53","guid":{"rendered":"http:\/\/hobbykeller.spdns.de\/?p=1433"},"modified":"2021-02-15T18:11:53","modified_gmt":"2021-02-15T17:11:53","slug":"creating-a-custom-entry-widget-in-pygtk-for-use-with-glade","status":"publish","type":"post","link":"https:\/\/hobbykeller.spdns.de\/?p=1433","title":{"rendered":"Creating a custom Entry widget in PyGtk for use with Glade"},"content":{"rendered":"\n<p>The <code>GtkEntry<\/code> widget is pretty dumb: Whatever input is sent to the entry field (whether by user input or by the <code>set_text<\/code> method) is treated as a <code>String<\/code>. Time to beef up the GtkEntry with an extension that we call SmartEntryFloat. This post is a sequel to my <a href=\"http:\/\/hobbykeller.spdns.de\/?p=1290\" data-type=\"post\" data-id=\"1290\">introductory post<\/a> on creating custom widgets in PyGtk and integrating them into the Glade UI designer.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1. What we would like to have<\/h2>\n\n\n\n<p>Our SmartEntryFloat should be an entry field that is particularly geared towards numeric input. We would like to expect the following features:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1.1 Logical separation between actual numeric <code>float<\/code> value and <code>String<\/code> representation displayed or entered<\/h3>\n\n\n\n<ul class=\"wp-block-list\"><li>Entry of a String representing a valid float is directly converted and stored into a <code>value <\/code>instance variable (of type float). Use <code>focus_out<\/code> to trigger update of instance variable.<\/li><li>Expose a <code>get_value<\/code> method that returns the content of the float instance variable value. No more need for clumsy cast operations every time we want to process a numeric content from a <code>GtkEntry<\/code>.<\/li><li>Maybe a <code>set_value<\/code> method that programmatically sets the <code>value<\/code> property of our SmartEntryFloat object and the String in the entry field.<\/li><li>Assure two way synchronization between the float number held in the value instance variable and the String displayed in the field. Programmatically changing the value variable should trigger an update in the String displayed. Changing the String representation of the entry should change the float value.<\/li><\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">1.2 Consistency checking of user input<\/h3>\n\n\n\n<ul class=\"wp-block-list\"><li>Check if entered String can be cast to a reasonable float at all &#8211; if not warn user by setting the background color of the entry field to red.<\/li><li>Add option to specify allowed numerical range of input. E.g. a smart entry field for the height of a person in meters should only contain values higher than 0 and less than 3 meters. Use 2 <code>float<\/code> instance variables <code>min_val<\/code> and <code>max_val<\/code> and 2 <code>boolean<\/code> variables <code>min_included<\/code> and <code>max_included<\/code> to define the numerical intervals of values allowed. <\/li><\/ul>\n\n\n\n<div class=\"wp-block-simple-alerts-for-gutenberg-alert-boxes sab-alert sab-alert-info\" role=\"alert\">While at the point of writing, it is technically still possible to change the background color of a Gtk.Entry with  <code>override_background_color<\/code> and <code>modify_bg_color<\/code> in a single line of code, Python issues a warning that <a rel=\"noreferrer noopener\" href=\"https:\/\/stackoverflow.com\/questions\/32436862\/gtk-widget-cant-change-bg-color-pygtk\" data-type=\"URL\" data-id=\"https:\/\/stackoverflow.com\/questions\/32436862\/gtk-widget-cant-change-bg-color-pygtk\" target=\"_blank\">these methods have been officially declared deprecated<\/a>. Instead Gtk expects the developers to use CSS to alter the background color &#8211; which is way more complicated than coloring the background with a single command.<br>I am going to dedicate a separate text on how to use CSS in pyGtk.<\/div>\n\n\n\n<h3 class=\"wp-block-heading\">1.3 Formatted String representation<\/h3>\n\n\n\n<ul class=\"wp-block-list\"><li>Specify decimal seperators and thousand seperators as class variables.<\/li><li>Specify number of decimal places displayed. In case the actual value has more decimal places than displayed according to the formatting, a hint to the user should be given (e.g. yellow background when focus out, on focus in, the entry should display the number with full accuracy so that the user can check the non-rounded value by tabbing into the field).<\/li><li>Consistency checks for correctly formatted strings that represent input numbers should be interpreted as valid input and internally cast to a float and stored into the <code>value<\/code> instance variable.<\/li><li>Unformatted strings representing a valid floating value should be converted into formatted strings using the specified decimal and thousand separators. The <code>value<\/code> instance variable should be updated accordingly.<\/li><\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">1.4 Further improvements<\/h3>\n\n\n\n<p>In order to make sure that the value instance variable is updated even when the SmartEntry has not lost focus, we could also use the <code>changed<\/code> signal to set a <code>Timer<\/code> object (instance variable) in motion. While editing still occurs and new changed signals are fired. The timer is cancelled and restarted again and again. Once the entry object stops firing <code>changed<\/code> events and the timer expires, consistency checks are performed, the <code>value<\/code> instance variable is updated and formatting is done.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. What we need<\/h2>\n\n\n\n<p>As already described in the previous post on custom widgets in pyGTK and Glade, we need:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>a custom catalog xml file that tells Glade all necessary info about our custom widget: what&#8217;s its name, what widget group it should belong to, from what base class it derives its properties and signals that we can set in Glade, the icon that should be used for the widget in the toolbox, etc.<\/li><li>a custom module file that holds the programmatical logic of the icon: how it should respond to signals, what additional properties and methods it should feature, what override methods there should be. This is a Python class file.<\/li><li>While it is not strictly speaking a component of the custom widget itself, we need a test driver GUI application: this is a <code>.glade<\/code> file (essentially an xml file containing all GUI layout information) and a <code>.py<\/code> file containing the application code and reading the information of the <code>.glade<\/code> file with the GtkBuilder.<\/li><\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2.1 Custom catalog<\/h3>\n\n\n\n<p>Our custom catalog looks as follows:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"theme:sublime-text font:monospace height-set:true height:300 toolbar:2 wrap-toggle:false show-plain:3 lang:xhtml decode:true \" title=\"Custom catalog for SmartWidgetFloat\">&lt;glade-catalog name=\"SmartWidgets\"\n    library=\"gladepython\"\n    domain=\"glade-3\"\n    depends=\"gtk+\"&gt;\n\n &lt;!--&lt;init-function&gt;glade_python_init&lt;\/init-function&gt; --&gt;\n\n &lt;glade-widget-classes&gt;\n   &lt;glade-widget-class title=\"SmartEntryFloat\" name=\"SmartEntryFloat\"\n                       generic-name=\"smart-entry-float\" parent=\"GtkEntry\"\n                       icon-name=\"widget-gtk-entry\"\/&gt;\n &lt;\/glade-widget-classes&gt;\n\n &lt;glade-widget-group name=\"python\" title=\"Python\"&gt;\n   &lt;glade-widget-class-ref name=\"SmartEntryFloat\"\/&gt;\n &lt;\/glade-widget-group&gt;\n&lt;\/glade-catalog&gt;<\/pre><\/div>\n\n\n\n<div class=\"wp-block-simple-alerts-for-gutenberg-alert-boxes sab-alert sab-alert-info\" role=\"alert\">As SmartWidges will be useful in numerous applications, we will put the catalog file directly into the default directory where Glade stores its catalogs:  <code>\/usr\/share\/glade\/catalogs\/SmartWidgets.xml<\/code><br>This way we can reuse the catalog any time we open Glade without having to specify an additional catalog location under preferences.<\/div>\n\n\n\n<p>Even though we don&#8217;t have added the code module yet, Glade will already feature an icon for our SmartWidgetFloat in its toolbox:<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:100%\">\n<div class=\"wp-block-media-text alignwide is-stacked-on-mobile\" style=\"grid-template-columns:24% auto\"><figure class=\"wp-block-media-text__media\"><img loading=\"lazy\" decoding=\"async\" width=\"150\" height=\"150\" src=\"http:\/\/hobbykeller.spdns.de\/wp-content\/uploads\/2021\/02\/SmartEntryFloat_Toolbox-150x150.png\" alt=\"\" class=\"wp-image-1440 size-thumbnail\" srcset=\"https:\/\/hobbykeller.spdns.de\/wp-content\/uploads\/2021\/02\/SmartEntryFloat_Toolbox-150x150.png 150w, https:\/\/hobbykeller.spdns.de\/wp-content\/uploads\/2021\/02\/SmartEntryFloat_Toolbox-125x125.png 125w\" sizes=\"auto, (max-width: 150px) 100vw, 150px\" \/><\/figure><div class=\"wp-block-media-text__content\">\n<p>When we launch Glade from the console by typing <code>glade<\/code>, though, we can see that Glade produces some warnings related to our tool. These warnings are issued because there is no module file yet. So the next thing we come up with is our Python file that determines the logic of the class.<\/p>\n<\/div><\/div>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"theme:terminal font:ubuntu-mono font-size-enable:false height:300 toolbar:1 nums-toggle:false wrap-toggle:false show-plain:3 lang:default decode:true \" title=\"Glade warnings related to missing module file\" >(glade:61403): GladeUI-WARNING **: 02:13:06.981: We could not find the symbol \"smart_entry_float_get_type\"\n\n(glade:61403): GladeUI-WARNING **: 02:13:06.981: Could not get the type from \"SmartEntryFloat\"\n<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">2.2 Module file<\/h2>\n\n\n\n<p>The module file for the <code>AwesomeTextView<\/code> custom widget in my previous demonstration lacked one essential feature: There were <strong>no overrides for signal handlers<\/strong> (such as how to respond to events like <code>clicked<\/code> or <code>changed<\/code>). I would not have known <a rel=\"noreferrer noopener\" href=\"https:\/\/stackoverflow.com\/questions\/7706192\/custom-signal-from-widget-to-widget\" data-type=\"URL\" data-id=\"https:\/\/stackoverflow.com\/questions\/7706192\/custom-signal-from-widget-to-widget\" target=\"_blank\">how to write callback functions in pyGTK<\/a> if it had not been for a post on stackoverflow.com. Also helpful on <a rel=\"noreferrer noopener\" href=\"https:\/\/stackoverflow.com\/questions\/26399614\/preedit-changed-event-in-python-gtk-entry-does-not-work\" data-type=\"URL\" data-id=\"https:\/\/stackoverflow.com\/questions\/26399614\/preedit-changed-event-in-python-gtk-entry-does-not-work\" target=\"_blank\">how to connect the callback <\/a>was another discussion, again on stackoverflow.com. Although both posts relate to the now deprecated Gtk2 world, they were still helpful in guessing my way to a comparable solution in Gtk3.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2.2.1 Handling signals emitted by our custom widgets<\/h3>\n\n\n\n<p>Whenever we want to handle a signal emitted by our SmartEntryFloat, we have to perform 3 steps:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Find out the exact <strong>signal name<\/strong> &#8211; this is where Glade helps a lot because the <code>Signal<\/code> tab in the right pane lists all standard signals for a widget. E.g., the signal that responds to entry field losing the focus is <code>focus-out-event<\/code>.<\/li><li><strong>Connect<\/strong> the signal to a standard handler function (&#8216;callback&#8217;) in the module code. This can normally happen in the <code>__init__<\/code> function of the class as it is part of the usual housekeeping that happens when an instance of our <code>SmartEntryFloat<\/code> class is created. We have to issue a command like <code>self.connect('focus-out-event', self.on_focus_out_event)<\/code>. Notice that the first argument is the exact signal id string we found out in 1. The second argument is a reference to the callback function that we want to be carried out once the signal is triggered. The usual naming convention is on_ followed by the signal name with dashes converted to undescores (i.e. on_focus_out_event)<\/li><li><strong>Write the callback function<\/strong>, which tells the SmartWidget what to do when the particular event connected to the function has occured.<\/li><\/ol>\n\n\n\n<p>So to make a long story short, this is what my module file looks like:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"theme:sublime-text font:ubuntu-mono font-size-enable:false height-set:true show-lang:1 lang:python decode:true \" title=\"SmartWidgetFloat module file\" >from gi.repository import Gtk, Gdk, Pango\nimport uuid\n\n\nclass SmartEntryFloat(Gtk.Entry):\n\n    __gtype_name__ = 'SmartEntryFloat'\n    decSeparator = \".\"\n    kSeparator = \"'\"\n    font = 'Monotype'\n\n    def __init__(self):\n        Gtk.Entry.__init__(self)\n        self.connect('focus-out-event', self.on_focus_out_event)\n        self.connect('focus-in-event', self.on_focus_in_event)\n        self.value = None\n        self.numberDecimals = -1  # neg 1 = leave places as they are\n        self.restrictions = {'minVal': None, 'maxVal': None,\n                             'minInclude': False, 'maxInclude': False}\n        self.cssId = str(uuid.uuid1())\n        self.styleContext = None\n        self.status = {'empty': False, 'conv_err': False, 'rng_err': False, 'dec_warn': False}\n        self.css_amends()\n\n    def on_focus_in_event(self, widget, event):\n        if not self.status['empty']:\n            displayString = self.get_formatted_value(blFullPrecision=True)\n            self.set_text(displayString)\n\n    def on_focus_out_event(self, widget, event):\n        userInput = self.get_text().strip()\n        if userInput == \"\":\n            self.value = None\n            self.status['empty'] = True\n            self.status['conv_err'] = False\n            self.status['rng_err'] = False\n            self.status['dec_wan'] = False\n            self.styleContext.remove_class('error')\n            self.styleContext.remove_class('warn')\n        else:\n            self.value = self.validate_input(userInput)\n            if self.value:\n                self.status['empty'] = False\n                self.set_text(self.get_formatted_value())\n                self.update_dec_warn()\n            if self.status['conv_err'] or self.status['rng_err']:\n                self.styleContext.remove_class('warn')\n                self.styleContext.add_class('error')\n            else:\n                self.styleContext.remove_class('error')\n                if self.status['dec_warn']:\n                    self.styleContext.add_class('warn')\n                else:\n                    self.styleContext.remove_class('warn')\n\n    def css_amends(self):\n        self.set_name(self.cssId)\n        screen = Gdk.Screen.get_default()\n        gtk_provider = Gtk.CssProvider()\n        gtk_context = Gtk.StyleContext()\n        gtk_context.add_provider_for_screen(screen, gtk_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)\n        cssErr = \"#\" + self.cssId + \".error {background-image: image(red);}\"\n        cssWarn = \"#\" + self.cssId + \".warn {background-image: image(yellow);}\"\n        cssSet = cssErr + '\\n' + cssWarn\n        gtk_provider.load_from_data(bytearray(cssSet, 'utf8'))\n        self.styleContext = self.get_style_context()\n        self.set_font(SmartEntryFloat.font)\n\n    def set_font(self, fontDescr):\n        self.modify_font(Pango.FontDescription(fontDescr))\n\n    def set_decimal_places(self, numDecimals=-1):\n        self.numberDecimals = numDecimals\n\n    def set_range_allowed(self, minVal=None, maxVal=None,\n                          minInclude=False, maxInclude=False):\n        self.restrictions['minVal'] = minVal\n        self.restrictions['maxVal'] = maxVal\n        self.restrictions['minInclude'] = minInclude\n        self.restrictions['maxInclude'] = maxInclude\n\n    def set_value(self, floatVal):\n        ''' Sets the value and the display to a float value. Assumes that\n        a regular float value is passed and does not trigger any error\n        coloring of the input field. Yellow coloring is triggered though.'''\n        self.value = self.validate_input(str(floatVal))\n        if self.value:\n            self.status['empty'] = False\n            self.set_text(self.get_formatted_value())\n            self.update_dec_warn()\n            if self.status['dec_warn']:\n                self.styleContext.add_class('warn')\n            else:\n                self.styleContext.remove_class('warn')\n        else:\n            self.set_text(\"\")\n            self.status['empty'] = False\n            self.status['conv_err'] = False   # No coloring b\/c not user induce\n            self.status['rng_err'] = False\n            self.status['dec_warn'] = False\n            self.styleContext.remove_class('error')\n            self.styleContext.remove_class('warn')\n\n    def validate_input(self, strIn):\n        strInput = strIn.replace(SmartEntryFloat.kSeparator, '').strip()\n        floatVal = None\n        try:\n            floatVal = float(strInput)\n        except ValueError:\n            self.status['conv_err'] = True\n            return None\n        self.status['conv_err'] = False\n        blMinValOK = blMaxValOK = True\n\n        if self.restrictions['minVal'] is not None:\n            if self.restrictions['minInclude']:\n                blMinValOK = floatVal &gt;= self.restrictions['minVal']\n            else:\n                blMinValOK = floatVal &gt; self.restrictions['minVal']\n        if blMinValOK is False:\n            self.status['rng_err'] = True\n            return None\n\n        if self.restrictions['maxVal'] is not None:\n            if self.restrictions['maxInclude']:\n                blMaxValOK = floatVal &lt;= self.restrictions['maxVal']\n            else:\n                blMaxValOK = floatVal &lt; self.restrictions['maxVal']\n        if blMaxValOK is False:\n            self.status['rng_err'] = True\n            return None\n        self.status['conv_err'] = self.status['rng_err'] = False\n        return floatVal\n\n    def update_dec_warn(self):\n        \"\"\"Checks if value displayed has a loss of precision against the\n        actual numerical value stored whenever self.numDecimals &gt; -1.\n        Will adjust self.status['dec_warn'] accordingly and return the\n        updated value.\"\"\"\n        if self.get_text() == \"\" or self.numberDecimals == -1:\n            self.status['dec_warn'] = False\n            return False\n        strDisplayed = self.get_text().strip()\n        cleanString = strDisplayed.replace(SmartEntryFloat.kSeparator, \"\")\n        strValStored = str(self.value)\n        if len(strValStored) &gt; len(cleanString):\n            self.status['dec_warn'] = True\n            return True\n        else:\n            self.status['dec_warn']= False\n            return False\n\n    def get_formatted_value(self, *, blFullPrecision=False):\n        if self.value is None:\n            return \"\"\n        strPreformattedValue = \"\"\n        if self.numberDecimals == -1 or blFullPrecision:\n            strPreformattedValue = f\"{self.value:,}\"\n        else:\n            preFromatStr = \"{:,.\" + str(self.numberDecimals) + \"f}\"\n            strPreformattedValue = preFromatStr.format(self.value)\n        strPreformattedValue = strPreformattedValue.replace(',', \"k\")\n        strPreformattedValue = strPreformattedValue.replace('.', \"d\")\n        strPreformattedValue = strPreformattedValue.replace('k', SmartEntryFloat.kSeparator)\n        strPreformattedValue = strPreformattedValue.replace('d', SmartEntryFloat.decSeparator)\n        return strPreformattedValue\n\n    def set_global_separators(*, decimalSeparator=None, thousandsSeparator=None):\n        if decimalSeparator == thousandsSeparator:\n            return\n        if decimalSeparator is not None and thousandsSeparator is not None:\n            SmartEntryFloat.decSeparator = decimalSeparator\n            SmartEntryFloat.kSeparator = thousandsSeparator\n            return\n        if decimalSeparator is not None:\n            if SmartEntryFloat.kSeparator != decimalSeparator:\n                SmartEntryFloat.decSeparator = decimalSeparator\n                return\n        if thousandsSeparator is not None:\n            if SmartEntryFloat.decSeparator != thousandsSeparator:\n                SmartEntryFloat.kSeparator = thousandsSeparator<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">2.3 The test driver program<\/h2>\n\n\n\n<p>As a testing application we need to set up:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>A Glade layout which features two of our SmartEntryFloat widgets. Basically one widget would be enough to test &#8211; but in order to make sure that CSS-driven coloring of the background colors does only affect the entry object where an erroneous input took place and not all widgets, we prefer to run a test in a layout with more than one SmartEntryFloat widget.<\/li><li>A Python code that picks up the <code>.glade<\/code> layout file and sets up our custom widget (e.g. font, decimal places displayed, numerical range allowed if any)<\/li><\/ol>\n\n\n\n<p>So here&#8217;s what the .glade file looks like:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"theme:sublime-text font:ubuntu-mono font-size-enable:false height-set:true lang:xhtml decode:true \" title=\"Test driver Glade file\" >&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\n&lt;!-- Generated with glade 3.22.2 --&gt;\n&lt;interface&gt;\n  &lt;requires lib=\"gtk+\" version=\"3.20\"\/&gt;\n  &lt;requires lib=\"SmartWidgets\" version=\"0.0\"\/&gt;\n  &lt;object class=\"GtkWindow\" id=\"win\"&gt;\n    &lt;property name=\"can_focus\"&gt;False&lt;\/property&gt;\n    &lt;signal name=\"delete-event\" handler=\"on_win_delete_event\" swapped=\"no\"\/&gt;\n    &lt;child type=\"titlebar\"&gt;\n      &lt;placeholder\/&gt;\n    &lt;\/child&gt;\n    &lt;child&gt;\n      &lt;object class=\"GtkBox\"&gt;\n        &lt;property name=\"visible\"&gt;True&lt;\/property&gt;\n        &lt;property name=\"can_focus\"&gt;False&lt;\/property&gt;\n        &lt;property name=\"orientation\"&gt;vertical&lt;\/property&gt;\n        &lt;child&gt;\n          &lt;object class=\"GtkButton\" id=\"btn1\"&gt;\n            &lt;property name=\"label\" translatable=\"yes\"&gt;button&lt;\/property&gt;\n            &lt;property name=\"visible\"&gt;True&lt;\/property&gt;\n            &lt;property name=\"can_focus\"&gt;True&lt;\/property&gt;\n            &lt;property name=\"receives_default\"&gt;True&lt;\/property&gt;\n            &lt;signal name=\"clicked\" handler=\"on_btn1_clicked\" swapped=\"no\"\/&gt;\n          &lt;\/object&gt;\n          &lt;packing&gt;\n            &lt;property name=\"expand\"&gt;False&lt;\/property&gt;\n            &lt;property name=\"fill\"&gt;True&lt;\/property&gt;\n            &lt;property name=\"position\"&gt;0&lt;\/property&gt;\n          &lt;\/packing&gt;\n        &lt;\/child&gt;\n        &lt;child&gt;\n          &lt;object class=\"SmartEntryFloat\" id=\"sefInput\"&gt;\n            &lt;property name=\"visible\"&gt;True&lt;\/property&gt;\n            &lt;property name=\"can_focus\"&gt;True&lt;\/property&gt;\n            &lt;property name=\"xalign\"&gt;1&lt;\/property&gt;\n          &lt;\/object&gt;\n          &lt;packing&gt;\n            &lt;property name=\"expand\"&gt;False&lt;\/property&gt;\n            &lt;property name=\"fill\"&gt;True&lt;\/property&gt;\n            &lt;property name=\"position\"&gt;1&lt;\/property&gt;\n          &lt;\/packing&gt;\n        &lt;\/child&gt;\n        &lt;child&gt;\n          &lt;object class=\"GtkButton\" id=\"btn2\"&gt;\n            &lt;property name=\"label\" translatable=\"yes\"&gt;button&lt;\/property&gt;\n            &lt;property name=\"visible\"&gt;True&lt;\/property&gt;\n            &lt;property name=\"can_focus\"&gt;True&lt;\/property&gt;\n            &lt;property name=\"receives_default\"&gt;True&lt;\/property&gt;\n          &lt;\/object&gt;\n          &lt;packing&gt;\n            &lt;property name=\"expand\"&gt;False&lt;\/property&gt;\n            &lt;property name=\"fill\"&gt;True&lt;\/property&gt;\n            &lt;property name=\"position\"&gt;2&lt;\/property&gt;\n          &lt;\/packing&gt;\n        &lt;\/child&gt;\n        &lt;child&gt;\n          &lt;object class=\"SmartEntryFloat\" id=\"sefAnother\"&gt;\n            &lt;property name=\"visible\"&gt;True&lt;\/property&gt;\n            &lt;property name=\"can_focus\"&gt;True&lt;\/property&gt;\n          &lt;\/object&gt;\n          &lt;packing&gt;\n            &lt;property name=\"expand\"&gt;False&lt;\/property&gt;\n            &lt;property name=\"fill\"&gt;True&lt;\/property&gt;\n            &lt;property name=\"position\"&gt;3&lt;\/property&gt;\n          &lt;\/packing&gt;\n        &lt;\/child&gt;\n      &lt;\/object&gt;\n    &lt;\/child&gt;\n  &lt;\/object&gt;\n&lt;\/interface&gt;\n<\/pre><\/div>\n\n\n\n<p>And here&#8217;s our Python test driver code. Notice that it imports our SmartEntry module file in line 4. The previous .glade file is imported by the builder instruction in line 12.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"theme:sublime-text font:ubuntu-mono font-size-enable:false height-set:true lang:python decode:true \" title=\"SmartEntryFloat test driver code\" >import gi\ngi.require_version('Gtk', '3.0')\nfrom gi.repository import Gtk\nfrom SmartWidgets import SmartEntryFloat\n\n\nclass App():\n\n    builder = Gtk.Builder()\n\n    def __init__(self):\n        self.builder.add_from_file('testdriver.glade')\n        win = self.builder.get_object('win')\n        sefInput = self.builder.get_object('sefInput')\n        sefInput.set_range_allowed()\n        sefInput.set_font('Monospace')\n        sefInput.set_decimal_places(2)\n        #sefInput.set_range_allowed(minVal=50, minInclude=True, maxVal=100, maxInclude=True)\n        sefInput.set_value(63.4554)\n        win.show()\n\n    def on_win_delete_event(self, widget):\n        Gtk.main_quit()\n\n\nif __name__ == '__main__':\n    app = App()\n    Gtk.main()\n<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">3. Where to put which file<\/h2>\n\n\n\n<p>Apart from the custom catalog <code>SmartWidgets.xml<\/code> for Glade which resides in <code>\/usr\/share\/glade\/catalogs\/<\/code> on my I have so far placed the remaining files (module file, test driver program and .glade definition) of the GUI layout into a single development directory. <\/p>\n\n\n\n<p>But where should the Python module file go if we want to have it in place to be imported for any other development project we are having in place on our machine? I followed the <a rel=\"noreferrer noopener\" href=\"https:\/\/stackoverflow.com\/questions\/16196268\/where-should-i-put-my-own-python-module-so-that-it-can-be-imported\" data-type=\"URL\" data-id=\"https:\/\/stackoverflow.com\/questions\/16196268\/where-should-i-put-my-own-python-module-so-that-it-can-be-imported\" target=\"_blank\">advice of this discussion on stackoverflow.com<\/a>:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"theme:shell-default font:ubuntu-mono font-size-enable:false lang:sh decode:true \" title=\"How to find Pyton's path to look for custom module files\" >ilek@i7:~$ python3 -m site --user-site\n\/home\/ilek\/.local\/lib\/python3.8\/site-packages\n<\/pre><\/div>\n\n\n\n<p>So my custom modules go to the directory shown in line 2 of the output above.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The GtkEntry widget is pretty dumb: Whatever input is sent to the entry field (whether by user input or by the set_text method) is treated as a String. Time to<span class=\"more-button\"><a href=\"https:\/\/hobbykeller.spdns.de\/?p=1433\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\">Creating a custom Entry widget in PyGtk for use with Glade<\/span><\/a><\/span><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[255,258,254,1],"tags":[301,302,297,277,288,304,287,303,295,250,299,300],"class_list":["post-1433","post","type-post","status-publish","format-standard","hentry","category-gtk","category-gui","category-python","category-uncategorized","tag-callback","tag-catalog-file","tag-custom-widgets","tag-glade","tag-gtk-2","tag-gtk-entry-2","tag-gtk3","tag-handler-function","tag-inheritance","tag-pygtk","tag-signals","tag-widgets"],"_links":{"self":[{"href":"https:\/\/hobbykeller.spdns.de\/index.php?rest_route=\/wp\/v2\/posts\/1433","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hobbykeller.spdns.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hobbykeller.spdns.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hobbykeller.spdns.de\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/hobbykeller.spdns.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1433"}],"version-history":[{"count":16,"href":"https:\/\/hobbykeller.spdns.de\/index.php?rest_route=\/wp\/v2\/posts\/1433\/revisions"}],"predecessor-version":[{"id":1459,"href":"https:\/\/hobbykeller.spdns.de\/index.php?rest_route=\/wp\/v2\/posts\/1433\/revisions\/1459"}],"wp:attachment":[{"href":"https:\/\/hobbykeller.spdns.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1433"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hobbykeller.spdns.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1433"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hobbykeller.spdns.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1433"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}