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