Skip to main content

Stacks and notebooks

Organize contents on screen

Gtk.Stack and Gtk.Notebook are layout containers that can hold any widgets.

A notebook provides a multipage layout including a classic tab functionality. A stack also provides this basic functionality and you will able to switch between different layout pages.

The main difference is the control widget of the stack is a separate widget (Gtk.StackSwitcher). Several stack switcher widgets can be assigned to one stack. A stack switcher can be placed into the headerbar and animated transitions between stack pages are supported.

Stack subjectively fit better into the GNOME environment but notebooks provide more customization/functionality options.

In the example there is a window containing a stack including a notebook on the third page showing different websites.

/images/21_stacknotebook.thumbnail.png

Glade

Stack

Stacks can be found in the sidebar's 'Container' section. Pages are easily created and edited via Glade. Sub-widgets in the example file are Gtk.Image, Vte.Terminal and Gtk.Notebook.

The stack switcher widget can be obtained from 'Controls and Display' and is placed to the headerbar. It's also possible to put it into a regular container widget like boxes. Pages can be shown in vertical or horizontal order. In "General > Stack" a stack element must be assigned to the widget. The page name shown by the stack switcher widget can be edited via "Packing > Title" of the sub-widget. This sub-widget has to be created in the first place, a new created stack has empty pages.

Notebook

Notebook can also be found in the 'Container' section. The tab's control unit is an integrated label child widget automatically generated on page creation. Gtk.ScrolledWindows are used here as the pages' container widgets. These are also required for displaying (long) tables (see also List-/TreeStore articles No. 1 und No. 2).

The tab bar of a notebook provides reserved space for additional widgets like fixed buttons ("General > Start/End Action"). In the example there will be created a "Home" button in the start area.

Python

There are no signals required for switching between stack pages and notebook tabs. In the example only two signals are assigned, one for catching the "exit" command within the terminal and one for the button in the notebook tab bar.

WebKit2

The webpages in the example are rendered by WebKit2. The essential module to use is WebKit2.WebView. A new WebView object itself already is a scrollable Gtk+ widget within a Gtk.Viewport element. According to the API reference it does not have to be placed in a Gtk.ScrolledWindow container widget. Having tested this that works for Gtk.Stack but not for Gtk.Notebook. That's why in the example there is used a ScrolledWindow as underlying container widget.

The following pattern is used to create a WebView widget:

#create new WebView widget
webview = WebKit2.WebView()
#send URL to widget
webview.load_uri("http://google.com")
#add webview to notebook
notebook.add(webview)
#add webview to stack
stack.add_titled(webview, name, "StackSwitcher title")

webview.show()

Listings

Python

21_stacknotebook.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import urllib.request

import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Vte", "2.91")
gi.require_version("WebKit2", "4.0")
from gi.repository import Gtk, Gio, Vte, GObject, GLib, WebKit2


class Handler:

    def on_term_child_exited(self, widget, event):
        # reset and setup terminal on exit command
        widget.reset(True, True)
        app.stack_console()

    def on_home_button_clicked(self, widget):
        # reload given URL in current tab
        page = app.obj("notebook").get_current_page()
        app.nbtabs[page][2].load_uri(app.nbtabs[page][1])


class ExampleApp:

    def __init__(self):

        self.app = Gtk.Application.new("org.application.test", Gio.ApplicationFlags(0))
        self.app.connect("activate", self.on_app_activate)

    def on_app_activate(self, app):
        GObject.type_register(Vte.Terminal)
        builder = Gtk.Builder()
        builder.add_from_file("21_stacknotebook.glade")
        builder.connect_signals(Handler())
        self.obj = builder.get_object
        self.obj("window").set_application(app)
        self.obj("window").show_all()

        # get window content
        self.stack_image()
        self.stack_console()
        self.stack_notebook()

    def run(self, argv):
        self.app.run(argv)

    def stack_image(self):
        # download and show NASA Astonomy Picture of the Day
        URL = "https://apod.nasa.gov"
        source = urllib.request.urlopen(URL).read().decode("utf-8")
        img_start = source.find("<IMG SRC=")
        img_end = source.find("alt=")
        img = source[img_start+10:img_end-2]
        IMGURL = "https://apod.nasa.gov/apod/" + img
        urllib.request.urlretrieve(IMGURL, "apod.jpg")
        self.obj("image").set_from_file("apod.jpg")

    def stack_console(self):
        # setup terminal
        self.obj("term").spawn_sync(
            Vte.PtyFlags.DEFAULT,
            None,
            ["/bin/bash"],
            None,
            GLib.SpawnFlags.DEFAULT,
            )

    def stack_notebook(self):

        self.nbtabs = [
                    ["gi_doc", "https://lazka.github.io/pgi-docs/"],
                    ["gtk_tut", "http://python-gtk-3-tutorial.readthedocs.io/en/latest/index.html"],
                    ["glade_tut", "https://encarsia.github.io/posts/tutorial-reihe-glade/"]
                    ]

        for tab in self.nbtabs:
            webview = WebKit2.WebView()
            tab.append(webview)
            webview.load_uri(tab[1])
            self.obj(tab[0]).add(webview)
            webview.show()


app = ExampleApp()
app.run(sys.argv)

Glade

21_stacknotebook.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.1 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <requires lib="vte-2.91" version="0.50"/>
  <object class="GtkImage" id="image1">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <property name="icon_name">go-home</property>
  </object>
  <object class="GtkApplicationWindow" id="window">
    <property name="width_request">800</property>
    <property name="height_request">600</property>
    <property name="can_focus">False</property>
    <child>
      <object class="GtkStack" id="stack">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="transition_type">crossfade</property>
        <child>
          <object class="GtkImage" id="image">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="stock">gtk-missing-image</property>
          </object>
          <packing>
            <property name="name">page0</property>
            <property name="title" translatable="yes">Astronomy Picture of the Day</property>
          </packing>
        </child>
        <child>
          <object class="VteTerminal" id="term">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="hscroll_policy">natural</property>
            <property name="vscroll_policy">natural</property>
            <property name="encoding">UTF-8</property>
            <property name="scroll_on_keystroke">True</property>
            <property name="scroll_on_output">False</property>
            <signal name="child-exited" handler="on_term_child_exited" swapped="no"/>
          </object>
          <packing>
            <property name="name">page3</property>
            <property name="title" translatable="yes">Terminal</property>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkNotebook" id="notebook">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <child>
              <object class="GtkScrolledWindow" id="gi_doc">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="shadow_type">in</property>
                <child>
                  <placeholder/>
                </child>
              </object>
            </child>
            <child type="tab">
              <object class="GtkLabel">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">PyGObject API Reference </property>
              </object>
              <packing>
                <property name="tab_fill">False</property>
              </packing>
            </child>
            <child>
              <object class="GtkScrolledWindow" id="gtk_tut">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="shadow_type">in</property>
                <child>
                  <placeholder/>
                </child>
              </object>
              <packing>
                <property name="position">1</property>
              </packing>
            </child>
            <child type="tab">
              <object class="GtkLabel">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">Python GTK+ 3 Tutorial</property>
              </object>
              <packing>
                <property name="position">1</property>
                <property name="tab_fill">False</property>
              </packing>
            </child>
            <child>
              <object class="GtkScrolledWindow" id="glade_tut">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="shadow_type">in</property>
                <child>
                  <placeholder/>
                </child>
              </object>
              <packing>
                <property name="position">2</property>
              </packing>
            </child>
            <child type="tab">
              <object class="GtkLabel">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">Glade-Tutorial</property>
              </object>
              <packing>
                <property name="position">2</property>
                <property name="tab_fill">False</property>
              </packing>
            </child>
            <child type="action-start">
              <object class="GtkButton" id="home_button">
                <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_home_button_clicked" swapped="no"/>
              </object>
              <packing>
                <property name="tab_fill">False</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="name">page1</property>
            <property name="title" translatable="yes">Notebook</property>
            <property name="position">2</property>
          </packing>
        </child>
      </object>
    </child>
    <child type="titlebar">
      <object class="GtkHeaderBar">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="show_close_button">True</property>
        <child>
          <object class="GtkStackSwitcher">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="stack">stack</property>
          </object>
        </child>
      </object>
    </child>
  </object>
</interface>

Comment on

Comments

Comments powered by Disqus