PyGTK to GTK GObject Notes - wb8tyw/D-Rats GitHub Wiki

Style sheets and themes

Many items have been deprecated in favor of Style sheets and themes. Need to figure out how implement the functionality. For now, I am commenting stuff out that does not seem to affect the basic functionality.

Issues to be followed up on:

  • FormManager.list wants do a set_rules_hint on a Gtk.Treeview object to even/odd rules in different colors, also known as zebra striping.

CRITICAL **: Stack overflow protection.

This is a message that shows up when trying to access some members like "fg" of a Gtk.Style() object.

** (formgui.py:3948): CRITICAL **: 11:42:54.960: Stack overflow protection. Can't copy array element into GIArgument.

This has been deprecated in favor of Gtk.StyleContext and that seems to involve Cascading Style Sheets. This is used in D-RATS to give a visual indication that a field is editable in formgue.py. Currently I have that code commented out.

Warning: assertion 'G_VALUE_HOLDS_DOUBLE (value)' failed

This is a message that pops up as soon as a spinbutton widget is touched.

/usr/lib/python3/dist-packages/gi/overrides/Gtk.py:1641: Warning: g_value_get_double: assertion 'G_VALUE_HOLDS_DOUBLE (value)' failed
  return _Gtk_main(*args, **kwargs)

Not very useful. Shows up a bit as as problem reports where it states that the code was fixed to prevent it, but I could not find any links to what was changed to fix it.

Finally compared the python GObject gtk spinbutton example with the original code.

old code:

        def set_value(spin, scrolltype, name):
            self.__values[name] = spin.get_value()
            return False

            adj = gtk.Adjustment(s, i, u, i)
            val = gtk.SpinButton(adj, digits=0)
            val.connect("input", set_value, l)

We need to connect up the "value-changed" signal to the set_value callback routine. I changed to use the get_value_as_int method since we want only integer values. Also used the internally created adjustment widget.

        def set_value(spin_button, name):
            self.__values[name] = spin_button.get_value_as_int()
            return False

            spin_button = Gtk.SpinButton.new_with_range(increment,
                                                        max_value,
                                                        increment)
            spin_button.set_digits(0)
            spin_button.set_value(start_value)
            spin_button.connect("value-changed", set_value, name)
            spin_button.show()

Gtk.Table is deprecated for Gtk.Grid

This is turning out to be a challenge, and and sure there is probably a better way than what I am doing.

Many options have been removed.

conntest.py

First place to try to convert was conntest.py.

The online articles suggested that all I really needed to do was change the parameters to the grid.attach from the table.attach as below.

            #           w    l  t    r-l+1  b - t + 1
            grid.attach(label, 0, row, 1, 2)
            #              w    l  r  t    b      x opt
            # table.attach(lab, 0, 1, row, row+1, Gtk.AttachOptions.SHRINK)
   ...
            grid.attach(spin_button, 1, row, 2, 2)
            # table.attach(val, 1, 2, row, row+1, Gtk.AttachOptions.SHRINK)

That did not work well. All the rows ended up pretty much overlaid and unreadable. By some trial and error, I determined that adding "grid.set_row_spacing(30)" made things look readable.

Some more investigation showed that the height of a spin_button for me was 27, which is why it worked.

But that is also a problem, as that height is determined by the current font that I am using, and I am not sure what that would be on someone else's system.

So the solution seems to be to set the spacing based on the size of the widgets in the grid, something that Gtk.Table is able to do for you.

I am sure that there must be a better way, but this is what I ended up with for the first attempt. The final solution for conntest.py comes later in this article.

For this case because the items in the rows were different heights, I had to specify the height for each of the items. The additional columns in a row work with attach_next_to method.

        height = 0
        grid = Gtk.Grid()
        grid.set_column_spacing(3)

        for name, start_value, increment, max_value in gridspec:
            label = Gtk.Label.new(name + ":")
            label.show()
            if not height:
            #              w    l  r  t    b      x opt
            # table.attach(lab, 0, 1, row, row+1, Gtk.AttachOptions.SHRINK)

            spin_button = Gtk.SpinButton.new_with_range(increment,
                                                        max_value,
                                                        increment)
            spin_button.set_digits(0)
            spin_button.set_value(start_value)
            spin_button.connect("value-changed", set_value, name)
            spin_button.show()
            if not height:
                label_height = max(label.get_preferred_height())
                sb_height = max(spin_button.get_preferred_height())
                height = max(label_height, sb_height)

            #           w    l  t    r-l+1  b - t + 1
            grid.attach(label, 0, row, 1, height)
            grid.attach_next_to(spin_button, label, Gtk.PositionType.RIGHT,
                                1, height)
            # table.attach(val, 1, 2, row, row+1, Gtk.AttachOptions.SHRINK)

            set_value(spin_button, name)

            row += height
        return grid

For this stats table, everything was labels, so the width was the same, so I could set the grid spacing and just use grid coordinates for the attachment.

        grid = Gtk.Grid()
        # table = Gtk.Table.new(3, 4, False)

        col = 0
        row = 0
        height = 0
        width = 0
        for i in ["", _("Sent"), _("Received"), _("Total")]:
            label = Gtk.Label.new(i)

            label.show()
            if not height:
                height = max(label.get_preferred_height())
                grid.set_row_spacing(height)
            label_width = (max(label.get_preferred_width()))
            width = (max(width, label_width))

            #           w    l    t  r-l+1  b - t + 1
            grid.attach(label, col, 0, 2, 2)
            #              w   l     r     t   b
            # table.attach(lab, col, col+1, 0, 1)
            col += 1

        label = Gtk.Label.new(_("Packets"))
        label.show()
        label_width = (max(label.get_preferred_width()))
        width = (max(width, label_width))

        grid.attach(label, 0, 1, 2, 2)
        # table.attach(lab, 0, 1, 1, 2)

        label = Gtk.Label.new(_("Bytes"))
        label.show()
        label_width = (max(label.get_preferred_width()))
        width = (max(width, label_width))
        grid.set_column_spacing(width + 2)
        grid.attach(label, 0, 2, 2, 2)
        # table.attach(lab, 0, 1, 2, 3)

        self.__stats_vals = {}

        spec = [("ps", "pr", "pt"),
                ("bs", "br", "bt")]

        _row = 1
        for row in spec:
            _col = 1
            for col in row:
                lab = Gtk.Label.new()
                lab.show()
                self.__stats_vals[col] = lab
                grid.attach(lab, _col, _row, 2, 2)
                #table.attach(lab, _col, _col+1, _row, _row+1)
                _col += 1
            _row += 1

        grid.show()

After fighting with the complexities of formgui.py below, I went back and simplified the code.

The spinbox grid:

        row = 0

        def set_value(spin_button, name):
            self.__values[name] = spin_button.get_value_as_int()
            return False

        grid = Gtk.Grid()
        grid.set_column_spacing(5)

        for name, start_value, increment, max_value in gridspec:
            label = Gtk.Label.new(name + ":")
            label.show()
            spin_button = Gtk.SpinButton.new_with_range(increment,
                                                        max_value,
                                                        increment)
            spin_button.set_digits(0)
            spin_button.set_value(start_value)
            spin_button.connect("value-changed", set_value, name)
            spin_button.show()

            grid.attach(label, 0, row, 1, 1)
            grid.attach_next_to(spin_button, label, Gtk.PositionType.RIGHT,
                                1, 1)
            set_value(spin_button, name)

            row += 1
        return grid

The statistics grid:

        grid = Gtk.Grid()
        grid.set_column_spacing(10)

        grid_col = 0
        for i in ["", _("Sent"), _("Received"), _("Total")]:
            label = Gtk.Label.new(i)

            label.show()
            grid.attach(label, grid_col, 0, 1, 1)
            grid_col += 1

        label = Gtk.Label.new(_("Packets"))
        label.show()

        grid.attach(label, 0, 1, 1, 1)

        label = Gtk.Label.new(_("Bytes"))
        label.show()
        grid.attach(label, 0, 2, 1, 1)

        self.__stats_vals = {}

        spec = [("ps", "pr", "pt"),
                ("bs", "br", "bt")]

        grid_row = 1
        for row in spec:
            grid_col = 1
            for col in row:
                label = Gtk.Label.new()
                label.show()
                self.__stats_vals[col] = label
                grid.attach(label, grid_col, grid_row, 1, 1)
                grid_col += 1
            grid_row += 1

        grid.show()
        return grid

formgui.py

The conntest.py seemed simple enough, but it turns out setting the grid spacing only works if you have a table where all your rows or columns are uniform. Not the case for formgui.

Turns out that I have conntest.py and config.py somewhat wrong, but have formgui.py finally worked out. will update that section later.

I do not need the width and height of the widgets, I just need to use "attach_next_to" after attaching the first widget to the grid. For some widgets, I have to set them to allow expansion.

First grid use:

        grid = Gtk.Grid.new()

        src_label = Gtk.Label.new(_("Source Callsign"))
        src_label.show()

        srcbox = Gtk.Entry()
        srcbox.set_text(self.get_path_src())
        srcbox.set_editable(False)
        srcbox.show()
        self._srcbox = srcbox

        dst_label = Gtk.Label.new(_("Destination Callsign"))
        dst_label.show()

        dstbox = Gtk.Entry()
        dstbox.set_text(self.get_path_dst())
        dstbox.show()
        self._dstbox = dstbox

        grid.attach(src_label, 0, 0, 1, 1)
        grid.attach_next_to(srcbox, src_label, Gtk.PositionType.RIGHT, 1, 1)
        grid.attach_next_to(dst_label, srcbox, Gtk.PositionType.RIGHT, 1, 1)
        grid.attach_next_to(dstbox, dst_label, Gtk.PositionType.RIGHT, 1, 1)

This does not quite work with text input boxes that Gtk.Table would expand to fill the window. Instead I get a small entry box.

Second more advanced Grid usage to get the input boxes working right.

The trick was to use widget "set_hexpand" and "set_vexpand" methods.

Now this is really not much more difficult to code than the Gtk.Table.

        field_grid = Gtk.Grid.new()

        row = 0
        col = 0
        scrollw = Gtk.ScrolledWindow()
        scrollw.set_policy(Gtk.PolicyType.NEVER,
                           Gtk.PolicyType.AUTOMATIC)
        scrollw.add(field_grid)
        field_grid.show()
        scrollw.show()
        self.vbox.pack_start(scrollw, 1, 1, 1)

        msg_field = None
        chk_field = None

        prev_widget = None
        for field in self.fields:
            if field.ident == "_auto_check":
                chk_field = field
            elif field.ident == "_auto_message":
                msg_field = field
            # pylint: disable=fixme
            elif field.ident == "_auto_senderX": # FIXME
                if not field.entry.widget.get_text():
                    call = self.config.get("user", "callsign")
                    field.entry.widget.set_text(call)
                field.entry.widget.set_property("editable", False)
            elif field.ident == "_auto_position":
                if not field.entry.widget.get_text():
                    self.logger.info("import . mainapp")
                    from . import mainapp # Dirty hack
                    pos = mainapp.get_mainapp().get_position()
                    field.entry.widget.set_text(pos.coordinates())

            label = Gtk.Label.new(field.caption)
            label.show()
            widget = field.get_widget()
            if field.entry.vertical:
                # field_box.attach(label, 0, 2, row, row+1,
                #                  Gtk.AttachOptions.SHRINK,
                #                  Gtk.AttachOptions.SHRINK)
                if prev_widget:
                    field_grid.attach_next_to(label, prev_widget,
                                              Gtk.PositionType.BOTTOM, col, 1)
                else:
                    field_grid.attach(label, col, row, 1, col)
                row += 1
                widget.set_hexpand(True)
                widget.set_vexpand(True)
                field_grid.attach_next_to(widget, label,
                                          Gtk.PositionType.BOTTOM, col, 1)
                prev_widget = widget
            elif field.entry.nolabel:
                # field_box.attach(widget, 0, 2, row, row+1,
                #                  Gtk.AttachOptions.SHRINK,
                #                  Gtk.AttachOptions.SHRINK)
                widget.set_hexpand(True)
                widget.set_vexpand(True)
                if prev_widget:
                    field_grid.attach_next_to(widget, prev_widget,
                                              Gtk.PositionType.BOTTOM, col, 1)
                else:
                    field_grid.attach(widget, col, row, 1, 1)
                prev_widget = widget
            else:
                # field_box.attach(label, 0, 1, row, row+1,
                #                  Gtk.AttachOptions.SHRINK,
                #                  Gtk.AttachOptions.SHRINK, 5)
                if prev_widget:
                    field_grid.attach_next_to(label, prev_widget,
                                              Gtk.PositionType.BOTTOM, 1, 1)
                else:
                    field_grid.attach(label, col, row, 1, 1)
                prev_widget = label
                # field_box.attach(widget, 1, 2, row, row+1, yoptions=0)
                widget.set_hexpand(True)
                field_grid.attach_next_to(widget, label,
                                          Gtk.PositionType.RIGHT, 1, 1)
                col += 1
            row += 1
            col += 1

config.py

Config.py turned out to be a lot like ui/conntest.py.

The display on AntiX-linux and msys2-windows are different for the lists of items like radio ports that are initially empty. On AntiX-linux, a blank row where the table would be is until at least one item is present, and then it shows up with titles. On msys2-windows, the title is displayed.

Currently I do not know why that is different.