Source code for sugar.graphics.toolbox
# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2025 MostlyK
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
#
# SPDX-License-Identifier: LGPL-2.1-or-later
"""
Toolbox
==================
A toolbox holds a group of toolbars in a list. One toolbar is displayed
at a time. Toolbars are assigned an index and can be accessed using this index.
Indices are generated in the order the toolbars are added.
"""
from typing import Optional
import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, GObject
from sugar.graphics import style
[docs]
class Toolbox(Gtk.Box):
"""
Class to represent the toolbox of an activity. Groups a
number of toolbars vertically, which can be accessed using their
indices. The current toolbar is the only one displayed.
Emits `current-toolbar-changed` signal when the
current toolbar is changed. This signal takes the current page index
as an argument.
"""
__gtype_name__ = 'SugarToolbox'
__gsignals__ = {
'current-toolbar-changed': (GObject.SignalFlags.RUN_FIRST,
None, ([int])),
}
[docs]
def __init__(self):
super().__init__(orientation=Gtk.Orientation.VERTICAL)
self._notebook = Gtk.Notebook()
self._notebook.set_tab_pos(Gtk.PositionType.BOTTOM)
self._notebook.set_show_border(False)
self._notebook.set_show_tabs(False)
self._notebook.set_hexpand(True)
self._notebook.set_vexpand(True)
self.append(self._notebook)
# horizontal separator as we had Gtk.HSeparator before.
self._separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
self._separator.set_visible(False)
self.append(self._separator)
self._apply_toolbox_styling()
self._notebook.connect('notify::page', self._notify_page_cb)
def _apply_toolbox_styling(self):
"""Apply Sugar-style toolbox styling."""
css = f"""
notebook.toolbox {{
background: {style.COLOR_TOOLBAR_GREY.get_css_rgba()};
}}
notebook.toolbox > header {{
background: {style.COLOR_TOOLBAR_GREY.get_css_rgba()};
padding: {style.TOOLBOX_TAB_VBORDER}px {style.TOOLBOX_TAB_HBORDER}px;
}}
notebook.toolbox > header > tabs > tab {{
background: transparent;
border: none;
padding: {style.TOOLBOX_TAB_VBORDER}px {style.TOOLBOX_TAB_HBORDER}px;
min-width: {style.TOOLBOX_TAB_LABEL_WIDTH}px;
}}
notebook.toolbox > header > tabs > tab:checked {{
background: alpha(@theme_selected_bg_color, 0.1);
}}
separator.toolbox-separator {{
background: {style.COLOR_PANEL_GREY.get_css_rgba()};
min-height: {style.TOOLBOX_SEPARATOR_HEIGHT}px;
}}
"""
style.apply_css_to_widget(self, css)
self._notebook.add_css_class('toolbox')
self._separator.add_css_class('toolbox-separator')
def _notify_page_cb(self, notebook, pspec):
"""Handle page change notification."""
current_page = notebook.get_current_page()
self.emit('current-toolbar-changed', current_page)
[docs]
def add_toolbar(self, name: str, toolbar: Gtk.Widget):
"""
Adds a toolbar to this toolbox. Toolbar will be added
to the end of this toolbox, and its index will be
1 greater than the previously added index (index will be
0 if it is the first toolbar added).
Args:
name (str): name of toolbar to be added
toolbar (Gtk.Widget): Widget to be appended to this toolbox
"""
label = Gtk.Label(label=name)
label.set_halign(Gtk.Align.START)
label.set_hexpand(True)
# Set minimum width for consistent tab sizing
label.set_size_request(style.TOOLBOX_TAB_LABEL_WIDTH, -1)
container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
container.set_hexpand(True)
container.set_vexpand(True)
container.set_margin_start(style.TOOLBOX_HORIZONTAL_PADDING)
container.set_margin_end(style.TOOLBOX_HORIZONTAL_PADDING)
container.append(toolbar)
page_num = self._notebook.append_page(container, label)
# Show tabs if we have more than one page
if self._notebook.get_n_pages() > 1:
self._notebook.set_show_tabs(True)
self._separator.set_visible(True)
return page_num
[docs]
def remove_toolbar(self, index: int):
"""
Removes toolbar at the index specified.
Args:
index (int): index of the toolbar to be removed
"""
if index < 0 or index >= self._notebook.get_n_pages():
raise IndexError(f"Toolbar index {index} out of range")
self._notebook.remove_page(index)
if self._notebook.get_n_pages() < 2:
self._notebook.set_show_tabs(False)
self._separator.set_visible(False)
[docs]
def set_current_toolbar(self, index: int):
"""
Sets the current toolbar to that of the index specified and
displays it.
Args:
index (int): index of toolbar to be set as current toolbar
"""
if index < 0 or index >= self._notebook.get_n_pages():
raise IndexError(f"Toolbar index {index} out of range")
self._notebook.set_current_page(index)
[docs]
def get_current_toolbar(self) -> int:
"""
Returns current toolbar index.
Returns:
int: Index of current toolbar
"""
return self._notebook.get_current_page()
[docs]
def get_toolbar_count(self) -> int:
"""
Returns the number of toolbars in this toolbox.
Returns:
int: Number of toolbars
"""
return self._notebook.get_n_pages()
[docs]
def get_toolbar_at(self, index: int) -> Optional[Gtk.Widget]:
"""
Get the toolbar widget at the specified index.
Args:
index (int): Index of toolbar to retrieve
Returns:
Gtk.Widget: Toolbar widget or None if index is invalid
"""
if index < 0 or index >= self._notebook.get_n_pages():
return None
page = self._notebook.get_nth_page(index)
if page and isinstance(page, Gtk.Box):
# Return the first child (the actual toolbar)
child = page.get_first_child()
return child
return page
[docs]
def set_toolbar_label(self, index: int, label: str):
"""
Set the label text for a toolbar tab.
Args:
index (int): Index of toolbar
label (str): New label text
"""
if index < 0 or index >= self._notebook.get_n_pages():
raise IndexError(f"Toolbar index {index} out of range")
page = self._notebook.get_nth_page(index)
tab_label = self._notebook.get_tab_label(page)
if isinstance(tab_label, Gtk.Label):
tab_label.set_text(label)
[docs]
def get_toolbar_label(self, index: int) -> Optional[str]:
"""
Get the label text for a toolbar tab.
Args:
index (int): Index of toolbar
Returns:
str: Label text or None if index is invalid
"""
if index < 0 or index >= self._notebook.get_n_pages():
return None
page = self._notebook.get_nth_page(index)
tab_label = self._notebook.get_tab_label(page)
if isinstance(tab_label, Gtk.Label):
return tab_label.get_text()
return None
current_toolbar = property(get_current_toolbar, set_current_toolbar)