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?