Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/client/gtk_base/menu_helper.py : 65%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2# This file is part of Xpra.
3# Copyright (C) 2011-2020 Antoine Martin <antoine@xpra.org>
4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
5# later version. See the file COPYING for details.
7from gi.repository import Gtk
9from xpra.util import envbool
10from xpra.os_util import OSX
11from xpra.gtk_common.gtk_util import menuitem
12from xpra.gtk_common.about import about, close_about
13from xpra.platform.gui import get_icon_size
14from xpra.log import Logger
16log = Logger("menu")
18MENU_ICONS = envbool("XPRA_MENU_ICONS", True)
19HIDE_DISABLED_MENU_ENTRIES = OSX
22def ll(m):
23 try:
24 return "%s:%s" % (type(m), m.get_label())
25 except AttributeError:
26 return str(m)
28def set_sensitive(widget, sensitive):
29 if OSX:
30 if sensitive:
31 widget.show()
32 else:
33 widget.hide()
34 widget.set_sensitive(sensitive)
37#utility method to ensure there is always only one CheckMenuItem
38#selected in a submenu:
39def ensure_item_selected(submenu, item, recurse=True):
40 if not isinstance(item, Gtk.CheckMenuItem):
41 return None
42 if item.get_active():
43 #deactivate all except this one
44 def deactivate(items, skip=None):
45 for x in items:
46 if x==skip:
47 continue
48 if isinstance(x, Gtk.MenuItem):
49 submenu = x.get_submenu()
50 if submenu and recurse:
51 deactivate(submenu.get_children(), skip)
52 if isinstance(x, Gtk.CheckMenuItem):
53 if x!=item and x.get_active():
54 x.set_active(False)
55 deactivate(submenu.get_children(), item)
56 return item
57 #ensure there is at least one other active item
58 def get_active_item(items):
59 for x in items:
60 if isinstance(x, Gtk.MenuItem):
61 submenu = x.get_submenu()
62 if submenu:
63 a = get_active_item(submenu.get_children())
64 if a:
65 return a
66 if isinstance(x, Gtk.CheckMenuItem):
67 if x.get_active():
68 return x
69 return None
70 active = get_active_item(submenu.get_children())
71 if active:
72 return active
73 #if not then keep this one active:
74 item.set_active(True)
75 return item
79def make_min_auto_menu(title, min_options, options,
80 get_current_min_value,
81 get_current_value,
82 set_min_value_cb,
83 set_value_cb):
84 #note: we must keep references to the parameters on the submenu
85 #(closures and gtk callbacks don't mix so well!)
86 submenu = Gtk.Menu()
87 submenu.get_current_min_value = get_current_min_value
88 submenu.get_current_value = get_current_value
89 submenu.set_min_value_cb = set_min_value_cb
90 submenu.set_value_cb = set_value_cb
91 fstitle = Gtk.MenuItem()
92 fstitle.set_label("Fixed %s:" % title)
93 set_sensitive(fstitle, False)
94 submenu.append(fstitle)
95 submenu.menu_items = {}
96 submenu.min_menu_items = {}
97 def populate_menu(options, value, set_fn):
98 found_match = False
99 items = {}
100 if value and value>0 and value not in options:
101 options[value] = "%s%%" % value
102 for s in sorted(options.keys()):
103 t = options.get(s)
104 qi = Gtk.CheckMenuItem(label=t)
105 qi.set_draw_as_radio(True)
106 candidate_match = s>=max(0, value)
107 qi.set_active(not found_match and candidate_match)
108 found_match |= candidate_match
109 qi.connect('activate', set_fn, submenu)
110 if s>0:
111 qi.set_tooltip_text("%s%%" % s)
112 submenu.append(qi)
113 items[s] = qi
114 return items
115 def set_value(item, ss):
116 if not item.get_active():
117 return
118 #user select a new value from the menu:
119 s = -1
120 for ts,tl in options.items():
121 if tl==item.get_label():
122 s = ts
123 break
124 if s>=0 and s!=ss.get_current_value():
125 log("setting %s to %s", title, s)
126 ss.set_value_cb(s)
127 #deselect other items:
128 for x in ss.menu_items.values():
129 if x!=item:
130 x.set_active(False)
131 #min is only relevant in auto-mode:
132 if s!=0:
133 for v,x in ss.min_menu_items.items():
134 x.set_active(v==0)
135 submenu.menu_items.update(populate_menu(options, get_current_value(), set_value))
136 submenu.append(Gtk.SeparatorMenuItem())
137 mstitle = Gtk.MenuItem()
138 mstitle.set_label("Minimum %s:" % title)
139 set_sensitive(mstitle, False)
140 submenu.append(mstitle)
141 def set_min_value(item, ss):
142 if not item.get_active():
143 return
144 #user selected a new min-value from the menu:
145 s = -1
146 for ts,tl in min_options.items():
147 if tl==item.get_label():
148 s = ts
149 break
150 if s>=0 and s!=ss.get_current_min_value():
151 log("setting min-%s to %s", title, s)
152 ss.set_min_value_cb(s)
153 #deselect other min items:
154 for x in ss.min_menu_items.values():
155 if x!=item:
156 x.set_active(False)
157 #min requires auto-mode:
158 for x in ss.menu_items.values():
159 if x.get_label()=="Auto":
160 if not x.get_active():
161 x.activate()
162 else:
163 x.set_active(False)
164 mv = -1
165 if get_current_value()<=0:
166 mv = get_current_min_value()
167 submenu.min_menu_items.update(populate_menu(min_options, mv, set_min_value))
168 submenu.show_all()
169 return submenu
171def make_encodingsmenu(get_current_encoding, set_encoding, encodings, server_encodings):
172 encodings_submenu = Gtk.Menu()
173 populate_encodingsmenu(encodings_submenu, get_current_encoding, set_encoding, encodings, server_encodings)
174 return encodings_submenu
176def populate_encodingsmenu(encodings_submenu, get_current_encoding, set_encoding, encodings, server_encodings):
177 from xpra.codecs.loader import get_encoding_help, get_encoding_name
178 encodings_submenu.get_current_encoding = get_current_encoding
179 encodings_submenu.set_encoding = set_encoding
180 encodings_submenu.encodings = encodings
181 encodings_submenu.server_encodings = server_encodings
182 encodings_submenu.index_to_encoding = {}
183 encodings_submenu.encoding_to_index = {}
184 NAME_TO_ENCODING = {}
185 for i, encoding in enumerate(encodings):
186 name = get_encoding_name(encoding)
187 descr = get_encoding_help(encoding)
188 NAME_TO_ENCODING[name] = encoding
189 encoding_item = Gtk.CheckMenuItem(label=name)
190 if descr:
191 if encoding not in server_encodings:
192 descr += "\n(not available on this server)"
193 encoding_item.set_tooltip_text(descr)
194 def encoding_changed(item):
195 ensure_item_selected(encodings_submenu, item)
196 enc = NAME_TO_ENCODING.get(item.get_label())
197 log("encoding_changed(%s) enc=%s, current=%s", item, enc, encodings_submenu.get_current_encoding())
198 if enc is not None and encodings_submenu.get_current_encoding()!=enc:
199 encodings_submenu.set_encoding(enc)
200 log("populate_encodingsmenu(..) encoding=%s, current=%s, active=%s",
201 encoding, get_current_encoding(), encoding==get_current_encoding())
202 encoding_item.set_active(encoding==get_current_encoding())
203 sensitive = encoding in server_encodings
204 if not sensitive and HIDE_DISABLED_MENU_ENTRIES:
205 continue
206 set_sensitive(encoding_item, encoding in server_encodings)
207 encoding_item.set_draw_as_radio(True)
208 encoding_item.connect("toggled", encoding_changed)
209 encodings_submenu.append(encoding_item)
210 encodings_submenu.index_to_encoding[i] = encoding
211 encodings_submenu.encoding_to_index[encoding] = i
212 encodings_submenu.show_all()
215class MenuHelper:
217 def __init__(self, client):
218 self.client = client
219 self.menu = None
220 self.menu_shown = False
221 self.menu_icon_size = get_icon_size()
223 def build(self):
224 if self.menu is None:
225 try:
226 self.menu = self.setup_menu()
227 except Exception as e:
228 log("build()", exc_info=True)
229 log.error("Error: failed to setup menu")
230 log.error(" %s", e)
231 return self.menu
233 def show_shortcuts(self, *args):
234 self.client.show_shorcuts(*args)
236 def show_session_info(self, *args):
237 self.client.show_session_info(*args)
239 def show_bug_report(self, *args):
240 self.client.show_bug_report(*args)
243 def get_image(self, icon_name, size=None):
244 return self.client.get_image(icon_name, size)
246 def setup_menu(self):
247 raise NotImplementedError()
249 def cleanup(self):
250 self.close_menu()
251 close_about()
253 def close_menu(self, *_args):
254 if self.menu_shown:
255 self.menu.popdown()
256 self.menu_shown = False
258 def menu_deactivated(self, *_args):
259 self.menu_shown = False
261 def activate(self, button=1, time=0):
262 log("activate(%s, %s)", button, time)
263 self.show_menu(button, time)
265 def popup(self, button, time):
266 log("popup(%s, %s)", button, time)
267 self.show_menu(button, time)
269 def show_menu(self, button, time):
270 self.close_menu()
271 self.menu.popup(None, None, None, None, button, time)
272 self.menu_shown = True
275 def handshake_menuitem(self, *args, **kwargs):
276 """ Same as menuitem() but this one will be disabled until we complete the server handshake """
277 mi = self.menuitem(*args, **kwargs)
278 set_sensitive(mi, False)
279 def enable_menuitem(*_args):
280 set_sensitive(mi, True)
281 self.client.after_handshake(enable_menuitem)
282 return mi
285 def make_menu(self):
286 return Gtk.Menu()
288 def menuitem(self, title, icon_name=None, tooltip=None, cb=None, **kwargs):
289 """ Utility method for easily creating an ImageMenuItem """
290 image = None
291 if MENU_ICONS:
292 image = kwargs.get("image")
293 if icon_name and not image:
294 icon_size = self.menu_icon_size or get_icon_size()
295 image = self.get_image(icon_name, icon_size)
296 return menuitem(title, image, tooltip, cb)
298 def checkitem(self, title, cb=None, active=False):
299 """ Utility method for easily creating a CheckMenuItem """
300 check_item = Gtk.CheckMenuItem(label=title)
301 check_item.set_active(active)
302 if cb:
303 check_item.connect("toggled", cb)
304 check_item.show()
305 return check_item
308 def make_aboutmenuitem(self):
309 return self.menuitem("About Xpra", "xpra.png", None, about)
311 def make_updatecheckmenuitem(self):
312 def show_update_window(*_args):
313 from xpra.client.gtk_base.update_status import getUpdateStatusWindow
314 w = getUpdateStatusWindow()
315 w.show()
316 w.check()
317 return self.menuitem("Check for updates", "update.png", None, show_update_window)
320 def make_qrmenuitem(self):
321 from xpra.net.qrcode import show_qr, get_qrencode_fn
322 def show(*_args):
323 uri = self.client.display_desc.get("display_name")
324 show_qr(uri)
325 self.qr_menuitem = self.menuitem("Show QR connection string", "qr.png", None, show)
326 qrencode_fn = get_qrencode_fn()
327 log("make_qrmenuitem() qrencode_fn=%s", qrencode_fn)
328 if qrencode_fn:
329 def with_connection(*_args):
330 uri = self.client.display_desc.get("display_name")
331 if not uri or not any(uri.startswith(proto) for proto in ("tcp:", "ws:", "wss:")):
332 set_sensitive(self.qr_menuitem, False)
333 self.qr_menuitem.set_tooltip_text("server uri is not shareable")
334 self.client.after_handshake(with_connection)
335 else:
336 set_sensitive(self.qr_menuitem, False)
337 self.qr_menuitem.set_tooltip_text("qrencode library is missing")
338 return self.qr_menuitem
340 def make_sessioninfomenuitem(self):
341 def show_session_info_cb(*_args):
342 #we define a generic callback to remove the arguments
343 #(which contain the menu widget and are of no interest to the 'show_session_info' function)
344 self.show_session_info()
345 sessioninfomenuitem = self.handshake_menuitem("Session Info", "statistics.png", None, show_session_info_cb)
346 return sessioninfomenuitem
348 def make_bugreportmenuitem(self):
349 def show_bug_report_cb(*_args):
350 self.show_bug_report()
351 return self.menuitem("Bug Report", "bugs.png", None, show_bug_report_cb)
353 def make_closemenuitem(self):
354 return self.menuitem("Close Menu", "close.png", None, self.close_menu)