How to create a tour for your gtk application

I would like to create a tour for my application by guiding the user for the first time. Currently, I created my own idea to guide the user using Gtk.Menu. It is working Ok. However, it is not the best solution.

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk

from typing import Dict

from .utils import load_icon

class TourGuide:
    tour_steps: Dict[Gtk.Widget, str] = {} # tour_steps = {Widget_Message:Gtk.Widget ...}

    _self = None
    def __new__(cls):
        if cls._self is None:
            cls._self = super().__new__(cls)
        return cls._self

    @staticmethod
    def add_widget_to_the_tour(widget: Gtk.Widget, widget_message):
        TourGuide.tour_steps[widget] = widget_message

    @staticmethod
    def start_tour(*args, **kwargs):
        try:
            widget = next(iter(TourGuide.tour_steps))
        except:
            # Tour ended
            return
        
        message = TourGuide.tour_steps.pop(widget)

        tour_popover = TourPopover(message, TourGuide.start_tour, TourGuide.end_tour)

        tour_popover.popup_at_widget(
            widget.get_child() if hasattr(widget, 'get_child') else widget, 
            Gdk.Gravity.NORTH, 
            Gdk.Gravity.SOUTH, 
            None
        )

    @staticmethod
    def end_tour(*args, **kwargs):
        del TourGuide.tour_steps

class TourPopover(Gtk.Menu):
    def __init__(self, widget_message, next_widget_func, end_tour_func):
        super().__init__()
        self.connect('destroy', next_widget_func)

        # *********** Widget Message ***********
        message_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)

        message_label = Gtk.Label(widget_message)
        message_label.set_line_wrap(True)
        message_label.set_max_width_chars(30)
        message_label.set_width_chars(30)
        message_label.set_halign(Gtk.Align.START)
        message_box.pack_start(message_label, True, True, 0)

        forward_image = Gtk.Image()
        forward_image.set_from_pixbuf(
            load_icon(
                'mtq-forward', custome_size=(24, 24)
            )
        )
        forward_image.set_halign(Gtk.Align.END)
        message_box.pack_start(forward_image, True, True, 0)

        message_menu_item = Gtk.MenuItem()
        message_menu_item.add(message_box)
        message_menu_item.connect('enter-notify-event', self._hover)
        message_menu_item.connect('leave-notify-event', self._unhover)
        message_menu_item.connect('activate', next_widget_func)

        self.add(message_menu_item)
        self.add(Gtk.SeparatorMenuItem())

        # *********** Control Buttons ***********
        end_button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)

        end_button_label = Gtk.Label('Stop Tour')
        end_button_label.set_line_wrap(True)
        end_button_label.set_max_width_chars(30)
        end_button_label.set_width_chars(30)
        end_button_label.set_halign(Gtk.Align.START)
        end_button_box.pack_start(end_button_label, True, True, 0)

        end_button_image = Gtk.Image()
        end_button_image.set_from_pixbuf(
            load_icon(
                'mtq-stop', custome_size=(24, 24)
            )
        )
        end_button_image.set_halign(Gtk.Align.END)
        end_button_box.pack_start(end_button_image, True, True, 0)

        end_button_menu_item = Gtk.MenuItem()
        end_button_menu_item.add(end_button_box)
        end_button_menu_item.connect('activate', end_tour_func)
        end_button_menu_item.connect('enter-notify-event', self._hover) 
        end_button_menu_item.connect('leave-notify-event', self._unhover)
        self.add(end_button_menu_item)

        self.show_all()

    def _hover(self, *args, **kwargs):
        hand = Gdk.Cursor(Gdk.CursorType.HAND2)
        self.get_window().set_cursor(hand)

    def _unhover(self, *args, **kwargs):
        self.get_window().set_cursor(None)

Basically, the previous code will popup a Gtk.Menu on every widget added using add_widget_to_the_tour. is there a better way to do a tour for gtk application?

I would strongly advise against using a GtkMenu. You should likely use a GtkPopover or make your own GtkWindow of type GTK_WINDOW_POPUP.

GtkMenu is a menu: it has requirements on the widgets it can hold, and the behaviour it has to have. You’ll end up fighting against it and the toolkit pretty soon.

1 Like

This topic was automatically closed 45 days after the last reply. New replies are no longer allowed.