implementing-applets.md
Python script, Unicode text, UTF-8 text executable
How to implement applets for the Panorama panel
The Panorama panel loads Python packages from
~/local/share/panorama-panel/applets
and ~/usr/share/panorama-panel/applets
.
Each of the loaded packages can provide one or more subclasses of
panorama_panel.Applet
, which is itself a subclass of Gtk.Box
. The
panorama_panel
module is automatically made available to all modules the panel
imports this way. Your subclass can set the class attributes name
and
description
to be presented in menus.
Try to make your class name reasonably unique, because the panel identifies
applets by their __name__
and doesn't support multiple classes sharing one.
Including your name or an organisation's name in the class name is one way to
do this.
Managing configuration
In the constructor you receive the configuration (a Python dictionary) as the
third parameter. You also have to implement the get_config(self)
method to
return the configuration when asked to, that is, when the panel is saving its
settings. This will be serialised to a YAML file.
When you want to write the configuration, emit the signal config-changed
and
the panel will save it to the file.
Using other sources of configuration for your applet is not recommended because there may be multiple applet instances.
How you present the configuration graphically is entirely up to you. You can put
Gtk.Builder
files in your applet's directory to help you build the
configuration window. This can be exposed in a context menu.
Popover registration
Autohiding panels do not hide when a popover belonging to one is open. To
implement this, all root popovers that should keep the panel open must be
registered using the function
panorama_panel.track_popover(popover: Gtk.Popover)
which adds some signals to
make sure the panel doesn't disappear while the popover is open.
If you want the panel to stay open for some other reason, you can use the set
self.get_root().open_popovers
and add to it a unique integer identifier for
your reason to keep the panel open — normally, this should be a Python id()
for a relevant object to guarantee it is unique and easy to reproduce.
Other functions
The panorama_panel
module provides a few other functions to use (when asked
to pass an applet, pass self
):
get_panel_position(applet: Applet) -> Gtk.PositionType
: get the edge of the screen the panel is currently on (note that they may move, and you need to respond).
It also provides two dictionaries:
OPPOSITE_POSITION
: get the oppositeGtk.PositionType
for anotherGtk.PositionType
.POSITION_TO_ARROW
: get the correspondingGtk.ArrowType
for aGtk.PositionType
.
You can also override extra methods to get notified about other events:
set_panel_position(self, position: Gtk.PositionType)
: called when the panel is repositioned.make_draggable(self)
: if you have dangerous controllers, you can disable them when edit mode is enabled.restore_drag(self)
: restore the state when edit mode is disabled.shutdown(self)
: when the panel is shutting down; use if you want to disconnect from services or similar.
Of course, you can override any other Gtk.Box
methods as well.
Minimal example
This applet displays a configurable string. It doesn't expose a configuration
window. A more elaborate example with a configuration window, popovers and a
menu button can be found at applets/clock
.
import panorama_panel import gi gi.require_version("Gtk", "4.0") from gi.repository import Gtk class LabelApplet(panorama_panel.Applet): # Defining class properties name = "Label" description = "Show a text" def __init__(self, orientation=Gtk.Orientation.HORIZONTAL, config=None): super().__init__(orientation=orientation, config=config) if config is None: config = {} self.label = Gtk.Label() self.label.set_text(config.get("text", "Label")) self.append(self.label) def get_config(self): # It is not recommended to store a config dict in the applet, instead # generate it only when needed, to avoid duplicating state return {"text": self.label.get_text()}
1How to implement applets for the Panorama panel 2=============================================== 3 4The Panorama panel loads Python packages from 5`~/local/share/panorama-panel/applets` and `~/usr/share/panorama-panel/applets`. 6Each of the loaded packages can provide one or more subclasses of 7`panorama_panel.Applet`, which is itself a subclass of `Gtk.Box`. The 8`panorama_panel` module is automatically made available to all modules the panel 9imports this way. Your subclass can set the class attributes `name` and 10`description` to be presented in menus. 11 12Try to make your class name reasonably unique, because the panel identifies 13applets by their `__name__` and doesn't support multiple classes sharing one. 14Including your name or an organisation's name in the class name is one way to 15do this. 16 17Managing configuration 18---------------------- 19 20In the constructor you receive the configuration (a Python dictionary) as the 21third parameter. You also have to implement the `get_config(self)` method to 22return the configuration when asked to, that is, when the panel is saving its 23settings. This will be serialised to a YAML file. 24 25When you want to write the configuration, emit the signal `config-changed` and 26the panel will save it to the file. 27 28Using other sources of configuration for your applet is not recommended because 29there may be multiple applet instances. 30 31How you present the configuration graphically is entirely up to you. You can put 32`Gtk.Builder` files in your applet's directory to help you build the 33configuration window. This can be exposed in a context menu. 34 35Popover registration 36-------------------- 37 38Autohiding panels do not hide when a popover belonging to one is open. To 39implement this, all root popovers that should keep the panel open must be 40registered using the function 41`panorama_panel.track_popover(popover: Gtk.Popover)` which adds some signals to 42make sure the panel doesn't disappear while the popover is open. 43 44If you want the panel to stay open for some other reason, you can use the set 45`self.get_root().open_popovers` and add to it a unique integer identifier for 46your reason to keep the panel open — normally, this should be a Python `id()` 47for a relevant object to guarantee it is unique and easy to reproduce. 48 49Other functions 50--------------- 51 52The `panorama_panel` module provides a few other functions to use (when asked 53to pass an applet, pass `self`): 54 55* `get_panel_position(applet: Applet) -> Gtk.PositionType`: get the edge of the 56screen the panel is currently on (note that they may move, and you need to 57respond). 58 59It also provides two dictionaries: 60 61* `OPPOSITE_POSITION`: get the opposite `Gtk.PositionType` for another 62`Gtk.PositionType`. 63* `POSITION_TO_ARROW`: get the corresponding `Gtk.ArrowType` for a 64`Gtk.PositionType`. 65 66You can also override extra methods to get notified about other events: 67 68* `set_panel_position(self, position: Gtk.PositionType)`: called when the panel 69is repositioned. 70* `make_draggable(self)`: if you have dangerous controllers, you can disable 71them when edit mode is enabled. 72* `restore_drag(self)`: restore the state when edit mode is disabled. 73* `shutdown(self)`: when the panel is shutting down; use if you want to 74disconnect from services or similar. 75 76Of course, you can override any other `Gtk.Box` methods as well. 77 78Minimal example 79--------------- 80 81This applet displays a configurable string. It doesn't expose a configuration 82window. A more elaborate example with a configuration window, popovers and a 83menu button can be found at `applets/clock`. 84 85~~~python 86import panorama_panel 87 88import gi 89gi.require_version("Gtk", "4.0") 90 91from gi.repository import Gtk 92 93 94class LabelApplet(panorama_panel.Applet): 95# Defining class properties 96name = "Label" 97description = "Show a text" 98 99def __init__(self, orientation=Gtk.Orientation.HORIZONTAL, config=None): 100super().__init__(orientation=orientation, config=config) 101if config is None: 102config = {} 103self.label = Gtk.Label() 104self.label.set_text(config.get("text", "Label")) 105self.append(self.label) 106 107def get_config(self): 108# It is not recommended to store a config dict in the applet, instead 109# generate it only when needed, to avoid duplicating state 110return {"text": self.label.get_text()} 111~~~ 112