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.