panorama_panel.py
Python script, ASCII text executable
1
import gi
2
gi.require_version("Gtk", "4.0")
3
4
from gi.repository import Gtk, Gdk, Gio, GObject, GLib
5
6
7
class Applet(Gtk.Box):
8
name = "Generic applet"
9
description = ""
10
icon = Gio.ThemedIcon.new("applications-system")
11
12
__gsignals__ = {
13
"config-changed": (GObject.SIGNAL_RUN_FIRST, None, ())
14
}
15
16
def __init__(self, orientation=Gtk.Orientation.HORIZONTAL, config=None, application=None, **kwargs):
17
super().__init__(orientation=orientation, **kwargs)
18
self.add_css_class("panorama-panel-applet")
19
if orientation == Gtk.Orientation.VERTICAL:
20
self.set_hexpand(True)
21
self.set_vexpand(False)
22
self.add_css_class("vertical")
23
elif orientation == Gtk.Orientation.HORIZONTAL:
24
self.set_vexpand(True)
25
self.set_hexpand(False)
26
self.add_css_class("horizontal")
27
28
self.drag_source = Gtk.DragSource(actions=Gdk.DragAction.MOVE)
29
self.drag_source.connect("prepare", self.provide_drag_data)
30
self.drag_source.connect("drag-begin", self.drag_begin)
31
self.drag_source.connect("drag-cancel", self.drag_cancel)
32
33
def set_orientation(self, orientation):
34
Gtk.Box.set_orientation(self, orientation)
35
if orientation == Gtk.Orientation.HORIZONTAL:
36
self.remove_css_class("vertical")
37
self.add_css_class("horizontal")
38
else:
39
self.remove_css_class("horizontal")
40
self.add_css_class("vertical")
41
42
def provide_drag_data(self, source: Gtk.DragSource, x: float, y: float):
43
app = self.get_root().get_application()
44
app.drags[id(self)] = self
45
value = GObject.Value()
46
value.init(GObject.TYPE_UINT64)
47
value.set_uint64(id(self))
48
return Gdk.ContentProvider.new_for_value(value)
49
50
def drag_begin(self, source: Gtk.DragSource, drag: Gdk.Drag):
51
paintable = Gtk.WidgetPaintable.new(self).get_current_image()
52
source.set_icon(paintable, 0, 0)
53
self.hide()
54
55
def drag_cancel(self, source: Gtk.DragSource, drag: Gdk.Drag, reason: Gdk.DragCancelReason):
56
self.show()
57
app = self.get_root().get_application()
58
del app.drags[id(self)]
59
return False
60
61
def get_config(self):
62
return {}
63
64
def shutdown(self, app: Gtk.Application):
65
return
66
67
def set_panel_position(self, position: Gtk.PositionType):
68
return
69
70
def make_draggable(self):
71
self.add_controller(self.drag_source)
72
73
def restore_drag(self):
74
self.remove_controller(self.drag_source)
75
76
def output_changed(self):
77
return
78
79
@classmethod
80
def print_log(cls, *args, **kwargs):
81
print(f"{cls.__name__}:", *args, **kwargs)
82
83
def wl_output_enter(self, output, name):
84
pass
85
86
def foreign_toplevel_new(self, manager, toplevel):
87
pass
88
89
def foreign_toplevel_output_enter(self, toplevel, output):
90
pass
91
92
def foreign_toplevel_output_leave(self, toplevel, output):
93
pass
94
95
def foreign_toplevel_app_id(self, toplevel, app_id):
96
pass
97
98
def foreign_toplevel_title(self, toplevel, title):
99
pass
100
101
def foreign_toplevel_state(self, toplevel, state):
102
pass
103
104
def foreign_toplevel_closed(self, toplevel):
105
pass
106
107
def foreign_toplevel_refresh(self):
108
pass
109
110
111
def track_popover(popover: Gtk.Popover):
112
popover.connect("show", lambda *args: _popover_shown(popover))
113
popover.connect("closed", lambda *args: _popover_hidden(popover))
114
115
116
def _popover_shown(popover: Gtk.Popover):
117
popover.get_root().open_popovers.add(id(popover))
118
if popover.get_root().autohide:
119
GLib.timeout_add(popover.get_root().hide_time // (popover.get_root().size - 1),
120
popover.get_root().slide_in)
121
122
123
def _popover_hidden(popover: Gtk.Popover):
124
popover.get_root().open_popovers.remove(id(popover))
125
if popover.get_root().autohide and not popover.get_root().open_popovers:
126
GLib.timeout_add(popover.get_root().hide_time // (popover.get_root().size - 1),
127
popover.get_root().slide_out)
128
129
130
def get_panel_position(applet: Applet) -> Gtk.PositionType:
131
return applet.get_root().position
132
133
134
OPPOSITE_POSITION = {
135
Gtk.PositionType.TOP: Gtk.PositionType.BOTTOM,
136
Gtk.PositionType.BOTTOM: Gtk.PositionType.TOP,
137
Gtk.PositionType.LEFT: Gtk.PositionType.RIGHT,
138
Gtk.PositionType.RIGHT: Gtk.PositionType.LEFT,
139
}
140
141
POSITION_TO_ARROW = {
142
Gtk.PositionType.TOP: Gtk.ArrowType.UP,
143
Gtk.PositionType.BOTTOM: Gtk.ArrowType.DOWN,
144
Gtk.PositionType.LEFT: Gtk.ArrowType.LEFT,
145
Gtk.PositionType.RIGHT: Gtk.ArrowType.RIGHT,
146
}
147
148
149
def add_icons_to_menu(popover: Gtk.PopoverMenu, size: Gtk.IconSize = Gtk.IconSize.LARGE):
150
section = popover.get_child().get_first_child().get_first_child().get_first_child()
151
while section is not None:
152
child = section.get_first_child().get_first_child()
153
154
while child is not None:
155
gutter_box: Gtk.Box = child.get_first_child()
156
if isinstance(gutter_box.get_next_sibling(), Gtk.Image):
157
# For some reason GTK creates images but they're hidden?
158
image: Gtk.Image = gutter_box.get_next_sibling()
159
label: Gtk.Label = image.get_next_sibling()
160
161
image.set_icon_size(Gtk.IconSize.LARGE)
162
image.set_margin_start(8)
163
image.set_margin_end(8)
164
165
child.add_css_class("no-menu-item-padding")
166
167
# Push the arrow to the left
168
label.set_halign(Gtk.Align.FILL)
169
label.set_hexpand(True)
170
label.set_xalign(0)
171
172
# GTK pushes its stance on icons so hard it makes them invisible multiple times;
173
# force it visible
174
image.set_visible(True)
175
image.connect("notify::visible", force_visible_on_visible_notify)
176
177
# Find the submenu if there is one
178
subchild = child.get_first_child()
179
submenu = None
180
while subchild is not None:
181
if isinstance(subchild, Gtk.PopoverMenu):
182
submenu = subchild
183
subchild = subchild.get_next_sibling()
184
185
# Recursive
186
if submenu is not None:
187
add_icons_to_menu(submenu)
188
189
child = child.get_next_sibling()
190
191
section = section.get_next_sibling()
192
193
194
def force_visible_on_visible_notify(widget, *args):
195
if not widget.get_visible():
196
widget.set_visible(True)
197