Builder Files
Gtk allows one to define the UI interface using "Builder" files. "Builder" files are XML files that describe widget layout, menus, etc.
Builder files should end with .ui but many of ours end with .glade as the UI creater program Glade was used to create them. Glade works only with Gtk3. Cambalache is a Glade replacement that works with both Gtk3 and Gtk4.
Tools
gtk-builder-tool
Allows one to simplify or examine Gtk3 builder files. Using the simplify option over our builder files results in:
- Changing "True" to "1", "False" to "0"
- Changing '"' to " and ''' to '.
- Removing properties with default values, e.g.:
- "can-focus" == False
- "label-xalign" == 0
- "receives-default" == False
- "expand" == False
- "fill" == True
- "position" == 0
- "xalign" == 0.5
- Removing unneeded <packing>...</packing> (where all properties are default).
- Removing <child><placeholder/></child> and <placeholder/>
- Moving <!-- n-columns=x n-rows=y --> comment to before <child>.
- Replacing underscores by hyphens in property names.
gtk-builder-tool does not understand the "child" property (in GtkScrolledWindow, GtkViewPort).
One can use a script to restore "True", "False", (without touching the 'active' property in ComboBox's). It also restores quotes and ampersands in strings (to avoid breaking translations). See below.
gtk4-builder-tool
In addition to the uses of gtk-builder-tool, gtk4-bulder-tool has a --3to4 option that converts gtk3 builder files to gtk4 builder files, the conversion is not perfect. In particular, it does not remove properties it doesn't recognize.
Using the 'simplify' mode without --3to4 (after using gtk-builder-tool):
- Changes translatable="true" or "yes" to translatable="1".
- Strips many comments.
Using the 'simplify' mode with --3to4 results in:
- Replacing <requires lib="gtk+" version="3.24"/> with <requires lib="gtk" version="4.0"/>
- Changes <attribute name='xxx'... to <attribute name="xxx"...
- Removing the property "visible" when its value is "True" (widgets are visible by default in Gtk4).
- Replacing the property "can-focus" with value "True" by the property "focusable" with value 1.
- Removing <packing>...</packing> for GtkBox.
- Replacing <object>...</object><packing>...</packing> by <object>...<layout>...</layout></object> for GtkGrid.
- Changing <child>...</child> to <property name="child">...</property> for child of GtkFlowBoxChild and GtkViewPort.
- Changing the property "margin" to the four properties "margin-start", "margin-end", "margin-top", and "margin-bottom".
- Adding the property "draw-value" with value 1 for GtkScale.
- Adding the property "visible" with value 0 to GtkFlowBoxChild.
Properties not supported in Gtk4 and not replaced:
- GtkAspectFrame::label-xalign
- GtkAspectFrame::shadow-type
- GtkBox::double-buffered
- GtkBox::events
- GtkBox::no-show-all
- GtkButton::always-show-image
- GtkButtonBox::can-focus
- GtkButtonBox::hexpand
- GtkButtonBox::layout-style
- GtkButtonBox::visible
- GtkButton::can-default
- GtkButton::image
- GtkButton::image-position
- GtkButton::no-show-all
- GtkButton::relief
- GtkButton::use-stock
- GtkCellRendererPixbuf::stock_size
- GtkCheckButton::image
- GtkCheckButton::no-show-all
- GtkDialog::gravity
- GtkDialog::has-resize-grip
- GtkDialog::skip-taskbar-hint
- GtkDialog::type-hint
- GtkDialog::type_hint
- GtkDialog::window-position
- GtkEntry::caps-lock-warning
- GtkEntry::no_show_all
- GtkEventBox::can-focus
- GtkEventBox::can_focus
- GtkEventBox::halign
- GtkEventBox::hexpand
- GtkEventBox::name
- GtkEventBox::no-show-all
- GtkEventBox::valign
- GtkEventBox::visible
- GtkEventBox::visible-window
- GtkExpander::label-fill
- GtkFlowBox::no-show-all
- GtkFrame::shadow-type
- GtkGrid::no-show-all
- GtkImage::no-show-all
- GtkImage::stock
- GtkLabel::no-show-all
- GtkLabel::no_show_all
- GtkLabel::ypad
- GtkLinkButton::relief
- GtkMenuButton::draw-indicator
- GtkMenuButton::image
- GtkMenuButton::no-show-all
- GtkMenuButton::popup
- GtkMenuButton::relief
- GtkMenuButton::use-popover
- GtkMenu::can-focus
- GtkMenuItem::can-focus
- GtkMenuItem::use-underline
- GtkMenuItem::visible
- GtkMenu::reserve-toggle-size
- GtkMenu::visible
- GtkModelButton::action-name
- GtkModelButton::action-target
- GtkModelButton::can-focus
- GtkModelButton::halign
- GtkModelButton::inverted
- GtkModelButton::receives-default
- GtkModelButton::visible
- GtkNotebook::double-buffered
- GtkNotebook::no-show-all
- GtkPopover::constrain-to
- GtkPopover::no-show-all
- GtkPopover::relative-to
- GtkPopover::transitions-enabled
- GtkRadioMenuItem::can-focus
- GtkRadioMenuItem::draw-as-radio
- GtkRadioMenuItem::group
- GtkRadioMenuItem::use-underline
- GtkRadioMenuItem::visible
- GtkScrolledWindow::double-buffered
- GtkScrolledWindow::no-show-all
- GtkScrolledWindow::shadow-type
- GtkScrolledWindow::shadow_type
- GtkScrolledWindow::window-placement-set
- GtkSearchEntry::can-default
- GtkSearchEntry::caps-lock-warning
- GtkSearchEntry::completion
- GtkSearchEntry::is-focus
- GtkSearchEntry::primary-icon-activatable
- GtkSearchEntry::primary-icon-name
- GtkSearchEntry::primary-icon-sensitive
- GtkSeparatorMenuItem::can-focus
- GtkSeparatorMenuItem::margin-bottom
- GtkSeparatorMenuItem::margin-top
- GtkSeparatorMenuItem::visible
- GtkSpinButton::caps-lock-warning
- GtkSpinButton::input-purpose
- GtkSpinner::active
- GtkToggleButton::always-show-image
- GtkToggleButton::image
- GtkToggleButton::no-show-all
- GtkToggleButton::relief
- GtkToggleButton::use-action-appearance
- GtkToolItem::can-focus
- GtkToolItem::visible
- GtkTreeView::no_show_all
- GtkViewport::shadow-type
- GtkWindow::can_default
- GtkWindow::gravity
- GtkWindow::is_focus
- GtkWindow::urgency_hint
- GtkWindow::window-position
- GtkWindow::window_position
Restore "True"/"False"/quotes/ampersands.
Perl Script
#!/usr/bin/perl # Restore use of True (instead of '1') and False (instead of '0') after # using gtk-builder-tool or gtk4-builder-tool. use strict; use warnings; use File::Copy; open my $true_false, '<', "true_false.txt" or die "Cannot read true_false.txt"; chomp (my @properties = <$true_false>); my $pattern = ""; foreach (@properties) { $pattern = $pattern . "\""; $pattern = $pattern . $_; $pattern = $pattern . "\""; $pattern = $pattern. "|"; } chop $pattern; #print $pattern . "\n"; my $file = $ARGV[0]; #print $file . "\n"; my $file_out = $file . "_out"; open my $input, '<', $file or die "Cannot open $file"; open my $output, '>', $file_out or die "Cannot open $file_out"; my $class = ""; while (my $line = <$input>) { # Track current class if ($line =~ m/class=\"(.*?)\"/) { $class = $1; # print "Class: " . $class . "\n"; } # For gtk4-builder-tool $line =~ s/translatable=\"1\"/translatable=\"yes\"/; $line =~ s/translatable=\"0\"/translatable=\"no\"/; if ($line =~ m/($pattern)/) { # print $line; if ($line =~ m/active/ and $class =~ m/ComboBox/) { # Don't change 'active' property to True/False for ComboBox or ComboBoxText # print "FOUND COMBOBOX WITH ACTIVE\n"; } else { $line =~ s/>0</>False</; $line =~ s/>1</>True</; } # print $line; } $line =~ s/"/\"/g; # This is improper... but it would effect translations. $line =~ s/'/\'/g; print $output $line; } close $input; close $output; move ($file_out, $file) or die "Couldn't move $file_out to $file!";
Input File: save as true_false.txt
accepts-tab activate-on-single-click activates-default active always-show-image can-default can-focus caps-lock-warning column-homogeneous cursor-visible destroy-with-parent double-buffered draw-as-radio draw-indicator draw-value editable enable-popup enable-search expand fill fixed-height-mode focus-on-click has-default has-entry has-focus has-frame has-origin has-resize-grip has-tooltip headers-clickable headers-visible hexpand homogeneous hover-selection inline-completion inverted is-focus label-fill modal no-show-all numeric overlay-scrolling pass-through popup-set-width position-set primary-icon-activatable primary-icon-sensitive propagate-natural-height propagate-natural-width receives-default reserve-toggle-size resizable resize resize-toplevel reveal-child row-homogeneous search-mode-enabled secondary-use-markup selectable sensitive show-border show-expanders show-fill-level show-text shrink single-line-mode skip-taskbar-hint sort-indicator tab-expand tab-fill transitions-enabled urgency-hint use-action-appearance use-fallback use-markup use-popover use-stock use-underline vexpand visible visible-window wide-handle window-placement-set wrap
Perl program that moves button images from top of file to inside button, compatible with both Gtk3 and Gtk4.
#!/usr/bin/perl # Move button images from top of file to inside button. # Tavmjong Bah use strict; use warnings; use v5.10; use File::Copy; use XML::LibXML; use Text::Diff; my @files = <*>; my $flip_flop = 0; foreach my $file (@files) { if ($file =~ ".ui\$" or $file =~ ".glade\$") { my $file_out = $file . "_out"; my $dom = XML::LibXML->load_xml(location => $file); # Find GtkButton's foreach my $object ($dom->findnodes('//object[@class = "GtkButton"]')) { # See if button has 'image' and 'label' properties. my ($property_image) = $object->findnodes('./property[@name = "image"]'); my ($property_label) = $object->findnodes('./property[@name = "label"]'); # If button has 'image' property. if (defined($property_image)) { # Find corresponding image element. my $image = $property_image->to_literal(); my ($gtk_image) = $dom->findnodes("//object[\@id = \"$image\"]"); # my $gtk_image = $dom->getElementById("xml:$image"); # If we found image element. if (defined($gtk_image)) { # Remove 'image' property'. $property_image->unbindNode(); # Add child to object. my $child = $dom->createElement('child'); $object->appendChild($child); if (defined($property_label)) { # We have both 'image' and 'label', need to create box. say "Button with both image and label! ", $file; # Add box my $box = $dom->createElement('object'); $box->{class} = "GtkBox"; $child->appendChild($box); # Add/move image my $child_image = $dom->createElement('child'); $child_image->appendChild($gtk_image); $box->appendChild($child_image); # Add label and move 'label' property. my $child_label = $dom->createElement('child'); $box->appendChild($child_label); my $object_label = $dom->createElement('object'); $object_label->{class} = "GtkLabel"; $object_label->appendChild($property_label); $child_label->appendChild($object_label); # Make visible (Gtk3) my $property_visible = $dom->createElement('property'); $property_visible->{name} = "visible"; $property_visible->appendText('True'); $object_label->appendChild($property_visible); # my $class = $dom->createAttribute('class', 'GtkBox'); } else { # Only have image; move image to child. $child->appendChild($gtk_image); } } else { say "Warning, could not find $image in $file"; } } } # Make it possible to pretty print by removing empty text nodes. foreach ($dom->findnodes('//text()')) { $_->parentNode->removeChild($_) unless /\S/; } # say $dom->toString(1); $dom->toFile($file_out, 1); # say "DIFF ============================================="; # my $diffs = diff "$file" => "$file_out"; # print $diffs; move ($file_out, $file); } }