roundabout,
created on Monday, 21 July 2025, 14:14:20 (1753107260),
received on Saturday, 9 August 2025, 12:22:36 (1754742156)
Author identity: vlad <vlad.muntoiu@gmail.com>
9642a19bc742abf6f8c433f6fb58c9637ede9d3a
main.py
@@ -16,18 +16,93 @@ import gi
gi.require_version("Gtk", "4.0")
gi.require_version("Gtk4LayerShell", "1.0")
from gi.repository import Gtk, GLib, Gtk4LayerShell, Gdk, Gio
from gi.repository import Gtk, GLib, Gtk4LayerShell, Gdk, Gio, GObject
sys.path.insert(0, str((Path(__file__).parent / "shared").resolve()))
import panorama_panel
@Gtk.Template(filename="panel-configurator.ui")
class PanelConfigurator(Gtk.Frame):
__gtype_name__ = "PanelConfigurator"
panel_size_adjustment: Gtk.Adjustment = Gtk.Template.Child()
monitor_number_adjustment: Gtk.Adjustment = Gtk.Template.Child()
top_position_radio: Gtk.CheckButton = Gtk.Template.Child()
bottom_position_radio: Gtk.CheckButton = Gtk.Template.Child()
left_position_radio: Gtk.CheckButton = Gtk.Template.Child()
right_position_radio: Gtk.CheckButton = Gtk.Template.Child()
def __init__(self, panel: Panel, **kwargs):
super().__init__(**kwargs)
self.panel = panel
self.panel_size_adjustment.set_value(panel.size)
match self.panel.position:
case Gtk.PositionType.TOP:
self.top_position_radio.set_active(True)
case Gtk.PositionType.BOTTOM:
self.bottom_position_radio.set_active(True)
case Gtk.PositionType.LEFT:
self.left_position_radio.set_active(True)
case Gtk.PositionType.RIGHT:
self.right_position_radio.set_active(True)
self.top_position_radio.panel_position_target = Gtk.PositionType.TOP
self.bottom_position_radio.panel_position_target = Gtk.PositionType.BOTTOM
self.left_position_radio.panel_position_target = Gtk.PositionType.LEFT
self.right_position_radio.panel_position_target = Gtk.PositionType.RIGHT
@Gtk.Template.Callback()
def update_panel_size(self, adjustment: Gtk.Adjustment):
if not self.get_root():
return
self.panel.set_size(int(adjustment.get_value()))
@Gtk.Template.Callback()
def move_panel(self, button: Gtk.CheckButton):
if not self.get_root():
return
if not button.get_active():
return
print("Moving panel")
self.panel.set_position(button.panel_position_target)
self.update_panel_size(self.panel_size_adjustment)
# Make the applets aware of the changed orientation
for area in (self.panel.left_area, self.panel.centre_area, self.panel.right_area):
applet = area.get_first_child()
while applet:
applet.set_orientation(self.panel.get_orientation())
applet.queue_resize()
applet = applet.get_next_sibling()
@Gtk.Template.Callback()
def move_to_monitor(self, adjustment: Gtk.Adjustment):
if not self.get_root():
return
app: PanoramaPanel = self.get_root().get_application()
monitor = app.monitors[int(self.monitor_number_adjustment.get_value())]
self.panel.unmap()
Gtk4LayerShell.set_monitor(self.panel, monitor)
self.panel.show()
PANEL_POSITIONS_HUMAN = {
Gtk.PositionType.TOP: "top",
Gtk.PositionType.BOTTOM: "bottom",
Gtk.PositionType.LEFT: "left",
Gtk.PositionType.RIGHT: "right",
}
@Gtk.Template(filename="panel-manager.ui")
class PanelManager(Gtk.Window):
__gtype_name__ = "PanelManager"
panel_editing_switch = Gtk.Template.Child()
panel_editing_switch: Gtk.Switch = Gtk.Template.Child()
panel_stack: Gtk.Stack = Gtk.Template.Child()
def __init__(self, application: Gtk.Application, **kwargs):
super().__init__(application=application, **kwargs)
@@ -47,6 +122,20 @@ class PanelManager(Gtk.Window):
self.connect("close-request", lambda *args: self.destroy())
if isinstance(self.get_application(), PanoramaPanel):
app: PanoramaPanel = self.get_application()
for panel in app.panels:
configurator = PanelConfigurator(panel)
self.panel_stack.add_child(configurator)
configurator.monitor_number_adjustment.set_upper(len(app.monitors))
self.panel_stack.set_visible_child(self.panel_stack.get_first_child())
self.panel_stack.connect("notify::visible-child", self.set_visible_panel)
self.panel_stack.notify("visible-child")
def set_visible_panel(self, stack: Gtk.Stack, pspec: GObject.ParamSpec):
panel: Panel = stack.get_visible_child().panel
def set_edit_mode(self, switch, value):
if isinstance(self.get_application(), PanoramaPanel):
self.get_application().set_edit_mode(value)
@@ -87,30 +176,22 @@ class AppletArea(Gtk.Box):
class Panel(Gtk.Window):
def __init__(self, application: Gtk.Application, monitor: Gdk.Monitor, position: Gtk.PositionType = Gtk.PositionType.TOP, size: int = 40, monitor_index: int = 0):
super().__init__(application=application)
self.set_default_size(800, size)
self.set_decorated(False)
self.position = position
self.position = None
self.monitor_index = monitor_index
self.size = size
Gtk4LayerShell.init_for_window(self)
Gtk4LayerShell.set_monitor(self, monitor)
Gtk4LayerShell.set_layer(self, Gtk4LayerShell.Layer.TOP)
Gtk4LayerShell.auto_exclusive_zone_enable(self)
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, True)
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, True)
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, True)
box = Gtk.CenterBox()
match position:
case Gtk.PositionType.TOP | Gtk.PositionType.BOTTOM:
box.set_orientation(Gtk.Orientation.HORIZONTAL)
case Gtk.PositionType.LEFT | Gtk.PositionType.RIGHT:
box.set_orientation(Gtk.Orientation.VERTICAL)
self.set_child(box)
self.set_position(position)
self.set_size(size)
self.left_area = AppletArea(orientation=box.get_orientation())
self.centre_area = AppletArea(orientation=box.get_orientation())
@@ -169,6 +250,44 @@ class Panel(Gtk.Window):
box = self.get_first_child()
return box.get_orientation()
def set_size(self, value: int):
self.size = int(value)
if self.get_orientation() == Gtk.Orientation.HORIZONTAL:
self.set_size_request(800, self.size)
self.set_default_size(800, self.size)
else:
self.set_size_request(self.size, 600)
self.set_default_size(self.size, 600)
def set_position(self, position: Gtk.PositionType):
self.position = position
match self.position:
case Gtk.PositionType.TOP:
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, False)
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, True)
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, True)
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, True)
case Gtk.PositionType.BOTTOM:
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, False)
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, True)
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, True)
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, True)
case Gtk.PositionType.LEFT:
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, False)
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, True)
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, True)
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, True)
case Gtk.PositionType.RIGHT:
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, False)
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, True)
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, True)
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, True)
box = self.get_first_child()
match self.position:
case Gtk.PositionType.TOP | Gtk.PositionType.BOTTOM:
box.set_orientation(Gtk.Orientation.HORIZONTAL)
case Gtk.PositionType.LEFT | Gtk.PositionType.RIGHT:
box.set_orientation(Gtk.Orientation.VERTICAL)
def get_all_subclasses(klass: type) -> list[type]:
subclasses = []
@@ -218,8 +337,8 @@ class PanoramaPanel(Gtk.Application):
super().__init__(application_id="com.roundabout_host.panorama.panel")
self.display = Gdk.Display.get_default()
self.monitors = self.display.get_monitors()
self.applets_by_name = {}
self.panels = []
self.applets_by_name: dict[str, panorama_panel.Applet] = {}
self.panels: list[Panel] = []
self.manager_window = None
self.edit_mode = False
@@ -243,6 +362,8 @@ class PanoramaPanel(Gtk.Application):
for panel_data in yaml_file["panels"]:
position = PANEL_POSITIONS[panel_data["position"]]
monitor_index = panel_data["monitor"]
if monitor_index >= len(self.monitors):
continue
monitor = self.monitors[monitor_index]
size = panel_data["size"]
panel-configurator.ui
@@ -0,0 +1,175 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.96.1 -->
<interface>
<!-- interface-name panel-configurator.ui -->
<requires lib="gtk" version="4.2"/>
<template class="PanelConfigurator" parent="GtkFrame">
<property name="valign">start</property>
<child>
<object class="GtkFrame">
<property name="valign">start</property>
<child>
<object class="GtkListBox">
<property name="selection-mode">none</property>
<child>
<object class="GtkListBoxRow">
<property name="activatable">False</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Thic_kness</property>
<property name="use-underline">True</property>
<property name="width-request">128</property>
<property name="wrap">True</property>
<property name="xalign">-0.0</property>
</object>
</child>
<child>
<object class="GtkScale">
<property name="adjustment">
<object class="GtkAdjustment" id="panel_size_adjustment">
<property name="page-increment">1.0</property>
<property name="page-size">1.0</property>
<property name="step-increment">1.0</property>
<property name="upper">192.0</property>
<property name="value">48.0</property>
<signal name="value-changed" handler="update_panel_size"/>
</object>
</property>
<property name="digits">0</property>
<property name="hexpand">True</property>
<property name="round-digits">0</property>
<marks/>
</object>
</child>
<child>
<object class="GtkSpinButton" id="thickness_spinbutton">
<property name="adjustment">panel_size_adjustment</property>
<property name="numeric">True</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Depending on your theme and your applets, the panel may be larger than the set size.</property>
<property name="wrap">True</property>
<property name="xalign">0.0</property>
<attributes>
<attribute name="weight" value="light"/>
</attributes>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkListBoxRow">
<property name="activatable">False</property>
<child>
<object class="GtkGrid">
<property name="column-homogeneous">True</property>
<property name="row-homogeneous">True</property>
<child>
<object class="GtkCheckButton" id="top_position_radio">
<property name="group">top_position_radio</property>
<property name="label" translatable="yes">_Top</property>
<property name="use-underline">True</property>
<signal name="toggled" handler="move_panel"/>
<layout>
<property name="column">1</property>
<property name="column-span">2</property>
<property name="row">0</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkCheckButton" id="left_position_radio">
<property name="group">top_position_radio</property>
<property name="label" translatable="yes">_Left</property>
<property name="use-underline">True</property>
<signal name="toggled" handler="move_panel"/>
<layout>
<property name="column">0</property>
<property name="column-span">1</property>
<property name="row">1</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkCheckButton" id="bottom_position_radio">
<property name="group">top_position_radio</property>
<property name="label" translatable="yes">_Bottom</property>
<property name="use-underline">True</property>
<signal name="toggled" handler="move_panel"/>
<layout>
<property name="column">1</property>
<property name="column-span">2</property>
<property name="row">2</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkCheckButton" id="right_position_radio">
<property name="group">top_position_radio</property>
<property name="label" translatable="yes">_Right</property>
<property name="use-underline">True</property>
<signal name="toggled" handler="move_panel"/>
<layout>
<property name="column">3</property>
<property name="column-span">1</property>
<property name="row">1</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">_Monitor number: </property>
<property name="use-underline">True</property>
<property name="xalign">-0.0</property>
<layout>
<property name="column">1</property>
<property name="column-span">1</property>
<property name="row">1</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkSpinButton" id="monitor_number_spinbutton">
<property name="adjustment">
<object class="GtkAdjustment" id="monitor_number_adjustment">
<property name="page-increment">1.0</property>
<property name="page-size">1.0</property>
<property name="step-increment">1.0</property>
<property name="upper">64.0</property>
<signal name="value-changed" handler="move_to_monitor"/>
</object>
</property>
<layout>
<property name="column">2</property>
<property name="column-span">1</property>
<property name="row">1</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>
panel-manager.cmb
@@ -2,6 +2,6 @@
<!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd">
<!-- Created with Cambalache 0.96.1 -->
<cambalache-project version="0.96.0" target_tk="gtk-4.0">
<ui template-class="PanelManager" filename="panel-manager.ui" sha256="aa48b67ba698d273b209d7ead4e7ef3f40511b9ab555cc29c88837999013b9ba"/>
<ui template-class="PanelConfigurator" filename="panel-configurator.ui" sha256="d247205b06581ddcde855e0e0e3b9cacda44d393117393bab8505455f04bc876"/>
<ui template-class="PanelManager" filename="panel-manager.ui" sha256="c16939e25edf779ee964dba64b4fc411ec8e7b45cb4c5707e403fca4c4be07e4"/>
<ui template-class="PanelConfigurator" filename="panel-configurator.ui" sha256="3b362de1d7ee0e20a58d8412b6bea07af8f7199095aa47ef1d462ba808ae391b"/>
</cambalache-project>
panel-manager.ui
@@ -8,122 +8,83 @@
<property name="default-width">576</property>
<property name="title" translatable="yes">Panel management</property>
<child>
<object class="GtkScrolledWindow">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkViewport">
<object class="GtkCenterBox">
<property name="margin-bottom">8</property>
<property name="margin-end">8</property>
<property name="margin-start">8</property>
<property name="margin-top">8</property>
<child type="end">
<object class="GtkButton" id="next_panel_button">
<property name="action-name">win.next-panel</property>
<property name="icon-name">go-next-symbolic</property>
<property name="tooltip-text" translatable="yes">Next panel</property>
</object>
</child>
<child type="start">
<object class="GtkButton" id="previous_panel_button">
<property name="action-name">win.previous-panel</property>
<property name="has-tooltip">True</property>
<property name="icon-name">go-previous-symbolic</property>
<property name="tooltip-text" translatable="yes">Previous panel</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="vexpand">True</property>
<child>
<object class="GtkViewport">
<child>
<object class="GtkStack" id="panel_stack">
<property name="margin-end">16</property>
<property name="margin-start">16</property>
<property name="transition-type">slide-left-right</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="hexpand">True</property>
<property name="margin-bottom">8</property>
<property name="margin-end">8</property>
<property name="margin-start">8</property>
<property name="margin-top">8</property>
<child>
<object class="GtkButton" id="save_now_button">
<property name="has-tooltip">True</property>
<property name="label" translatable="yes">_Save settings now</property>
<property name="tooltip-text" translatable="yes">The configuration is also saved when exiting the app.</property>
<property name="use-underline">True</property>
<signal name="clicked" handler="save_settings"/>
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
<child>
<object class="GtkBox">
<property name="margin-bottom">32</property>
<property name="margin-end">32</property>
<property name="margin-start">32</property>
<property name="margin-top">32</property>
<property name="orientation">vertical</property>
<property name="spacing">4</property>
<property name="halign">end</property>
<child>
<object class="GtkFrame">
<property name="label-widget">
<object class="GtkLabel">
<property name="label" translatable="yes">General options</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
</property>
<child>
<object class="GtkListBox">
<property name="selection-mode">none</property>
<child>
<object class="GtkListBoxRow">
<property name="action-name">win.toggle-edit-mode-switch</property>
<child>
<object class="GtkBox">
<child>
<object class="GtkLabel">
<property name="hexpand">True</property>
<property name="label" translatable="yes">Panel _editing mode</property>
<property name="mnemonic-widget">panel_editing_switch</property>
<property name="use-underline">True</property>
<property name="wrap">True</property>
<property name="xalign">0.0</property>
</object>
</child>
<child>
<object class="GtkSwitch" id="panel_editing_switch">
<property name="halign">end</property>
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkListBoxRow">
<property name="activatable">False</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">4</property>
<child>
<object class="GtkButton" id="save_now_button">
<property name="label" translatable="yes">_Save settings now</property>
<property name="use-underline">True</property>
<signal name="clicked" handler="save_settings"/>
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="hexpand">True</property>
<property name="label" translatable="yes">The configuration is also saved when exiting the app.</property>
<property name="wrap">True</property>
<property name="xalign">0.0</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<object class="GtkLabel">
<property name="hexpand">True</property>
<property name="label" translatable="yes">Panel _editing mode</property>
<property name="mnemonic-widget">panel_editing_switch</property>
<property name="use-underline">True</property>
<property name="wrap">True</property>
<property name="xalign">0.0</property>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">4</property>
<child>
<object class="GtkCenterBox">
<child type="center">
<object class="GtkLabel" id="current_panel_label">
<property name="justify">center</property>
<property name="label" translatable="yes">No panel selected</property>
</object>
</child>
<child type="end">
<object class="GtkButton" id="next_panel_button">
<property name="action-name">win.next-panel</property>
<property name="icon-name">go-next-symbolic</property>
<property name="tooltip-text" translatable="yes">Next panel</property>
</object>
</child>
<child type="start">
<object class="GtkButton" id="previous_panel_button">
<property name="action-name">win.previous-panel</property>
<property name="has-tooltip">True</property>
<property name="icon-name">go-previous-symbolic</property>
<property name="tooltip-text" translatable="yes">Previous panel</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkStack">
<property name="transition-type">slide-left-right</property>
</object>
</child>
<object class="GtkSwitch" id="panel_editing_switch">
<property name="halign">end</property>
<property name="valign">center</property>
</object>
</child>
</object>