Previous Post | Top | Next Post |
TOC
FYI: If the buggy Glade v=3.38.2 is patched, the complicated manual process is not needed.
Client-side decoration with GtkHeaderbar using Glade
Let’s consider to add client-side decorated GtkHeaderBar and to put primary menu with GtkPopover.
This may not be easy with Glade 3.38.2 on upcoming Debian Bullseye 11 since it is buggy. I created a note on my workaround at the end of this page
Let’s assume Glade is fixed by my patch or any other means and let’s use Glade to design GUI.
- Activating the “Client-side window decorations” checkbox creates to add top special area inside the window.
- Add GtkHeaderBar there. (If you have not touched Glade data except for adding the GtkWindow, this may work even with 3.38.2).
- You may increase “number of items” for GtkHeaderBar as you need.
Now you have:
GtkHeaderBar listed in the left side widget list panel has (titlebar child) added. The GtkHeaderBar widget placed at the top of the GtkWindow widget in the center panel now.
Then I made some more routines such as setting up callback functions for signals.
2 new buttons were used here.
- GtkMenuButton — A widget that shows a popup when clicked on
- GtkModelButton — A button that uses a GAction as model (I am not using GAction here.)
How to create GUI program
Now we are ready to use this touched-up Glade origin GUI design XML file from head_win.py
.
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
@Gtk.Template(filename="head_win.ui")
class Window(Gtk.Window):
__gtype_name__ = "window"
entry = Gtk.Template.Child()
label = Gtk.Template.Child()
def __init__(self):
super().__init__()
self.entry_text = ""
@Gtk.Template.Callback("on_window_destroy")
def onDestroy(self, widget):
Gtk.main_quit()
@Gtk.Template.Callback("on_button_clicked")
def onButton(self, widget):
self.label.set_label("*** " + self.entry_text + " ***")
@Gtk.Template.Callback("on_entry_activate")
def onEntry(self, widget):
self.entry_text = self.entry.get_text()
self.label.set_label(self.entry.get_text())
widget.set_text("")
@Gtk.Template.Callback("on_go_prev_clicked")
def onGoPrev(self, widget):
print("Dummy: onGoPrev")
@Gtk.Template.Callback("on_go_next_clicked")
def onGoNext(self, widget):
print("Dummy: onGoNext")
@Gtk.Template.Callback("on_configure_clicked")
def onConfigure(self, widget):
print("Dummy: onConfigure")
@Gtk.Template.Callback("on_menu1_clicked")
def onFile(self, widget):
print("Dummy: onFile")
@Gtk.Template.Callback("on_menu2_clicked")
def onEdit(self, widget):
print("Dummy: onEdit")
@Gtk.Template.Callback("on_menu3_clicked")
def onPreference(self, widget):
print("Dummy: onPreference")
@Gtk.Template.Callback("on_menu4_clicked")
def onHelp(self, widget):
print("Dummy: onHelp")
@Gtk.Template.Callback("on_menu5_clicked")
def onAbout(self, widget):
self.set_title(self.entry_text)
window = Window()
window.set_title("Demo HeaderBar")
window.show_all()
Gtk.main()
Since we use the same name for the assigned variable and the ID in XML,
Gtk.Template.Child()
takes no argument now.
Since we use different names for callback functions between Python code and UI
XML, @Gtk.Template.Callback
takes argument such as @Gtk.Template.Callback("on_button_clicked")
.
Running this with python3 head_win.py
will start the GUI as:
Click on hamburger icon to open primary menu.
Please play with this GUI while looking at your terminal outputs.
How touch-upped Glade generated XML looks
For the record, the full XML file head_win.ui
is here.
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">edit-undo</property>
</object>
<object class="GtkImage" id="image2">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">edit-redo</property>
</object>
<object class="GtkImage" id="image3">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">gtk-execute</property>
</object>
<object class="GtkPopover" id="popover1">
<property name="can-focus">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkModelButton" id="menu1">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="text" translatable="yes">File</property>
<signal name="clicked" handler="on_menu1_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkModelButton" id="menu2">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="text" translatable="yes">Edit</property>
<signal name="clicked" handler="on_menu2_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkModelButton" id="menu3">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="text" translatable="yes">Preferences</property>
<signal name="clicked" handler="on_menu3_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkModelButton" id="menu4">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="text" translatable="yes">Help</property>
<signal name="clicked" handler="on_menu4_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkModelButton" id="menu5">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="text" translatable="yes">About Application</property>
<signal name="clicked" handler="on_menu5_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
</object>
</child>
</object>
<template class="window" parent="GtkWindow">
<property name="can-focus">False</property>
<signal name="destroy" handler="on_window_destroy" swapped="no"/>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes"><EMPTY INITIAL></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>
<signal name="activate" handler="on_entry_activate" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button">
<property name="label" translatable="yes">Push this to print entered data</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_button_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child type="titlebar">
<object class="GtkHeaderBar" id="header">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="title" translatable="yes">win</property>
<property name="has-subtitle">False</property>
<property name="show-close-button">True</property>
<child>
<object class="GtkButton" id="go_prev">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="image">image1</property>
<property name="always-show-image">True</property>
<signal name="clicked" handler="on_go_prev_clicked" swapped="no"/>
</object>
</child>
<child>
<object class="GtkButton" id="go_next">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="image">image2</property>
<property name="always-show-image">True</property>
<signal name="clicked" handler="on_go_next_clicked" swapped="no"/>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkMenuButton" id="menu">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="focus-on-click">False</property>
<property name="receives-default">True</property>
<property name="popover">popover1</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">open-menu-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="configure">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="image">image3</property>
<property name="always-show-image">True</property>
<signal name="clicked" handler="on_configure_clicked" swapped="no"/>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">4</property>
</packing>
</child>
</object>
</child>
</template>
</interface>
Workaround of Glade bug
This is a note of workaround for Glade 3.38.2 which may be useful for other situation too.
If you already put something into GtkWindow, the above fails. Very similar bug exists for GtkPOpoverMenu.
- Can’t add GtkHeaderBar to “Client side window decoration” area
- Can’t add GtkHeaderBar to window with existing widgets / containers
To work around this bug, the GtkHeadBar can be placed anywhere on Glade drawing area but placing it at the end of normal window widgets makes touching up its UI XML file with the text editor easy for me later.
(Although it looked easier to reorder widgets via the text editor cut-and-paste initially, it is actually easier to play with “Pack type” and Position" in Packing tab of involved widgets.)
Activating the “Client-side window decorations” checkbox creates odd non-editable blank area inside the window. Trying to put a widget there causes the error message as follows:
This seems to be intentional design of Glade to deal newly introduced XML structure support with minimal efforts on time.
Please look into the UI XML file after above change. There is an odd XML tag <placeholder/>
which has been added inside of the newly added <child type="titlebar">...</child>
-tag pair. This meaning of type="titlebar"
is found in GtkWindow as:
The Gtk.Window implementation of the Gtk.Buildable interface supports setting a child as the titlebar by specifying “titlebar” as the “type” attribute of a
<child>
element.
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
...
<template class="window" parent="GtkWindow">
<property name="can-focus">False</property>
<child>
...
<child>
<object class="GtkHeaderBar" id="header">
... (snipped)
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
</child>
<child type="titlebar">
<placeholder/>
</child>
</template>
</interface>
This <placeholder/>
is sloppy but practical Glade support of “Client-side window decorations”.
Since Glade seems to expect us to manually touch-up this XML file, let’s do it.
First move <child><object class="GtkHeaderBar" id="header">...</child>
to just after <placeholder/>
and
remove <placeholder/>
.
Then remove now redundant <child>
and </child>
tags from newly moved
section. Indentation is non-significant. Glade also functions as a pretty
printing tool.
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
...
<template class="window" parent="GtkWindow">
<property name="can-focus">False</property>
<child>
...
<child type="titlebar">
<object class="GtkHeaderBar" id="header">
... (snipped)
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</template>
</interface>
If you open this manually modified XML file, you should get the desired result.
I verified the same trick works for the GtkPopoverMenu bug, too.
You may read followings:
- Where am I supposed to learn things that Glade “does not know”? (2021-07) -reddit
- Glade Not Recommended (2020-11) – non-valid negative points
- GtkWindow’s documentation says, “The GtkWindow implementation of the GtkBuildable interface supports setting a child as the titlebar by specifying “titlebar” as the “type” attribute of a element.” This means that if you have a
<child type="titlebar">
, the widget will be set as the titlebar, instead of being packed inside the window.
- Gtk HeaderBar ActionBar pack_start pack_end within UI xml file – method to position widget in headerbar
- GtkInspector – GtkInspector is the built-in interactive debugging support in GTK+
Previous Post | Top | Next Post |