Using UI templates #1

Open
opened 2020-10-07 12:42:36 +02:00 by rra · 7 comments
Owner

This issue is to keep track of figuring out how to use UI template elements.

For Dropship there is an idea to have a 'Pending Transmissions' view, where each initiated transmission is collected into an overview. There one can again copy the code, watch transfer progress, cancel this transmission or restart it. Since each transmission will have an identical interface it is best to use a template. See attached image for sketches.

Apparently glade supports 'Composite Templates' which is a way of defining a particular design as a template which can be reused throughout the application. How this is used in PyGTK is still a mistery.

Some hints in this issue-thread:
https://github.com/sebp/PyGObject-Tutorial/issues/149

edit: also
https://stackoverflow.com/questions/58315926/pygobject-template-child-not-defined

https://www.reddit.com/r/gnome/comments/81ii38/anyone_using_gnome_builder_with_python/

This issue is to keep track of figuring out how to use UI template elements. For Dropship there is an idea to have a 'Pending Transmissions' view, where each initiated transmission is collected into an overview. There one can again copy the code, watch transfer progress, cancel this transmission or restart it. Since each transmission will have an identical interface it is best to use a template. See attached image for sketches. Apparently `glade` supports 'Composite Templates' which is a way of defining a particular design as a *template* which can be reused throughout the application. How this is used in PyGTK is still a mistery. Some hints in this issue-thread: https://github.com/sebp/PyGObject-Tutorial/issues/149 edit: also https://stackoverflow.com/questions/58315926/pygobject-template-child-not-defined https://www.reddit.com/r/gnome/comments/81ii38/anyone_using_gnome_builder_with_python/
Author
Owner
Here is some example code: https://github.com/virtuald/pygi-composite-templates/blob/master/examples/from_gresource/mywidget.py based on: https://web.archive.org/web/20180308063953/http://www.virtualroadside.com/blog/index.php/2015/05/24/gtk3-composite-widget-templates-for-python/
rra self-assigned this 2020-10-07 13:51:26 +02:00
Author
Owner

This can serve as a general example:

import gi
import sys

gi.require_version('Gtk', '3.0')

from gi.repository import Gio
from gi.repository import Gtk

#adapted from https://github.com/virtuald/pygi-composite-templates/blob/master/examples/from_file/mywidget.py

@Gtk.Template.from_file('mywidget.ui')
class MyWidget(Gtk.Box):

    # Required else you would need to specify the full module
    # name in mywidget.ui (__main__+MyWidget)
    __gtype_name__ = 'MyWidget'
    
    entry = Gtk.Template.Child()
    label1 = Gtk.Template.Child()
    
    # Alternative way to specify multiple widgets
    #label1, entry = GtkTemplate.Child.widgets(2)

    def __init__(self, text):
        super(Gtk.Box, self).__init__()
        
        # This must occur *after* you initialize your base
        self.init_template()
        
        self.entry.set_text(text)
    
    @Gtk.Template.Callback()
    def button_clicked(self, widget):
        # 'object' attribute (user-data in glade) is set
        print("The button was clicked with entry text: %s" % self.entry.get_text())
        #print("The user-data is %s" % user_data)
        self.label1.set_text(self.entry.get_text())

    @Gtk.Template.Callback()
    def entry_changed(self, widget):
        # 'object' attribute (user-data in glade) is not set
        print("The entry text changed: %s" % self.entry.get_text())

    @Gtk.Template.Callback()
    def on_MyWidget_destroy(self, widget):
        print("MyWidget destroyed")


if __name__ == '__main__':
    
    win = Gtk.Window()
    win.connect('delete-event', Gtk.main_quit)
    
    widget = MyWidget("The entry text!")
    win.add(widget)
    
    win.show_all()
    
    Gtk.main()

and

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <template class="MyWidget" parent="GtkBox">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <property name="orientation">vertical</property>
    <property name="spacing">4</property>
    <signal name="destroy" handler="on_MyWidget_destroy" swapped="no"/>
    <child>
      <object class="GtkLabel" id="label1">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="halign">start</property>
        <property name="valign">start</property>
        <property name="xalign">0</property>
        <property name="label" translatable="yes">This widget is defined with composite GtkBuilder script</property>
        <property name="wrap">True</property>
      </object>
      <packing>
        <property name="expand">False</property>
        <property name="fill">True</property>
        <property name="position">0</property>
      </packing>
    </child>
    <child>
      <object class="GtkEntry" id="entry">
        <property name="visible">True</property>
        <property name="can_focus">True</property>
        <property name="invisible_char">•</property>
        <signal name="changed" handler="entry_changed" swapped="no"/>
      </object>
      <packing>
        <property name="expand">False</property>
        <property name="fill">True</property>
        <property name="position">1</property>
      </packing>
    </child>
    <child>
      <object class="GtkLabel" id="label2">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="halign">start</property>
        <property name="valign">start</property>
        <property name="xalign">0</property>
        <property name="label" translatable="yes">Press the button to fetch the internal entry text</property>
        <property name="wrap">True</property>
      </object>
      <packing>
        <property name="expand">False</property>
        <property name="fill">True</property>
        <property name="position">2</property>
      </packing>
    </child>
    <child>
      <object class="GtkButton" id="button">
        <property name="label" translatable="yes">The Button</property>
        <property name="visible">True</property>
        <property name="can_focus">True</property>
        <property name="receives_default">True</property>
        <property name="halign">end</property>
        <signal name="clicked" handler="button_clicked" object="MyWidget" swapped="no"/>
      </object>
      <packing>
        <property name="expand">False</property>
        <property name="fill">True</property>
        <property name="position">3</property>
      </packing>
    </child>
  </template>
</interface>

This can serve as a general example: ``` import gi import sys gi.require_version('Gtk', '3.0') from gi.repository import Gio from gi.repository import Gtk #adapted from https://github.com/virtuald/pygi-composite-templates/blob/master/examples/from_file/mywidget.py @Gtk.Template.from_file('mywidget.ui') class MyWidget(Gtk.Box): # Required else you would need to specify the full module # name in mywidget.ui (__main__+MyWidget) __gtype_name__ = 'MyWidget' entry = Gtk.Template.Child() label1 = Gtk.Template.Child() # Alternative way to specify multiple widgets #label1, entry = GtkTemplate.Child.widgets(2) def __init__(self, text): super(Gtk.Box, self).__init__() # This must occur *after* you initialize your base self.init_template() self.entry.set_text(text) @Gtk.Template.Callback() def button_clicked(self, widget): # 'object' attribute (user-data in glade) is set print("The button was clicked with entry text: %s" % self.entry.get_text()) #print("The user-data is %s" % user_data) self.label1.set_text(self.entry.get_text()) @Gtk.Template.Callback() def entry_changed(self, widget): # 'object' attribute (user-data in glade) is not set print("The entry text changed: %s" % self.entry.get_text()) @Gtk.Template.Callback() def on_MyWidget_destroy(self, widget): print("MyWidget destroyed") if __name__ == '__main__': win = Gtk.Window() win.connect('delete-event', Gtk.main_quit) widget = MyWidget("The entry text!") win.add(widget) win.show_all() Gtk.main() ``` and ``` <?xml version="1.0" encoding="UTF-8"?> <!-- Generated with glade 3.18.3 --> <interface> <requires lib="gtk+" version="3.20"/> <template class="MyWidget" parent="GtkBox"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="orientation">vertical</property> <property name="spacing">4</property> <signal name="destroy" handler="on_MyWidget_destroy" swapped="no"/> <child> <object class="GtkLabel" id="label1"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="halign">start</property> <property name="valign">start</property> <property name="xalign">0</property> <property name="label" translatable="yes">This widget is defined with composite GtkBuilder script</property> <property name="wrap">True</property> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">0</property> </packing> </child> <child> <object class="GtkEntry" id="entry"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="invisible_char">•</property> <signal name="changed" handler="entry_changed" swapped="no"/> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">1</property> </packing> </child> <child> <object class="GtkLabel" id="label2"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="halign">start</property> <property name="valign">start</property> <property name="xalign">0</property> <property name="label" translatable="yes">Press the button to fetch the internal entry text</property> <property name="wrap">True</property> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">2</property> </packing> </child> <child> <object class="GtkButton" id="button"> <property name="label" translatable="yes">The Button</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> <property name="halign">end</property> <signal name="clicked" handler="button_clicked" object="MyWidget" swapped="no"/> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">3</property> </packing> </child> </template> </interface> ```
Owner

re: this comment I saw in https://github.com/virtuald/pygi-composite-templates/issues/9 that the upstream pygobject has a template implementation which is different from the custom pygi-composite-templates repository, see https://gitlab.gnome.org/GNOME/pygobject/-/merge_requests/52. So, some code differs, it seems. Not sure if you were aware. A lot of this is undocumented it seems...

re: [this comment](https://git.vvvvvvaria.org/rra/dropship/issues/1#issuecomment-103) I saw in https://github.com/virtuald/pygi-composite-templates/issues/9 that the upstream pygobject has a template implementation which is different from the custom pygi-composite-templates repository, see https://gitlab.gnome.org/GNOME/pygobject/-/merge_requests/52. So, some code differs, it seems. Not sure if you were aware. A lot of this is undocumented it seems...
Owner
Raised https://gitlab.gnome.org/GNOME/pygobject/-/issues/429.
Author
Owner

Raised https://gitlab.gnome.org/GNOME/pygobject/-/issues/429.

If we get these kinds of errors again, double check the template file to make sure that it is indeed a template and not a widget object: bea60004ce

> Raised https://gitlab.gnome.org/GNOME/pygobject/-/issues/429. If we get these kinds of errors again, double check the template file to make sure that it is indeed a template and not a widget object: https://git.vvvvvvaria.org/rra/dropship/commit/bea60004ce89e4d3694df698c769d4df4c12724e
Owner

This feels solved now 🌱

This feels solved now 🌱
Author
Owner

Yes it does, I'm just gonna take some time and summarize the helpful bits over at https://git.vvvvvvaria.org/rra/dropship/wiki before I close!

Yes it does, I'm just gonna take some time and summarize the helpful bits over at https://git.vvvvvvaria.org/rra/dropship/wiki before I close!
Sign in to join this conversation.
No Label
No Milestone
No Assignees
2 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: rra/dropship#1
No description provided.