Skip to content

Commit

Permalink
#228 merge gtk menu into the osx menu:
Browse files Browse the repository at this point in the history
* log full stacktrace on menu errors (errors do happen in our code..)
* provide a 'make_menu' method so we don't need to import gtk from the osx menu code
* keep track of the menus definitions for each window when calling set_window_menu
* actually apply the right menu when a window gains focus
* rework the osx menu code so we can define menus without adding them to the global menu bar

git-svn-id: https://xpra.org/svn/Xpra/trunk@10705 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Oct 1, 2015
1 parent 941d38f commit 618a2ed
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 29 deletions.
3 changes: 1 addition & 2 deletions src/xpra/client/gtk_base/gtk_client_window_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,7 @@ def on_realize(self, widget):
try:
x()
except Exception as e:
log.error("Error on realize callback %s for window %i:", x, self._id)
log.error(" %s", e)
log.error("Error on realize callback %s for window %i", x, self._id, exc_info=True)
if HAS_X11_BINDINGS:
#request frame extents if the window manager supports it
self._client.request_frame_extents(self)
Expand Down
4 changes: 4 additions & 0 deletions src/xpra/client/gtk_base/gtk_tray_menu_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,10 @@ def enable_menuitem(*args):
self.client.connect("handshake-complete", enable_menuitem)
return mi


def make_menu(self):
return gtk.Menu()

def menuitem(self, title, icon_name=None, tooltip=None, cb=None):
""" Utility method for easily creating an ImageMenuItem """
image = None
Expand Down
122 changes: 122 additions & 0 deletions src/xpra/platform/darwin/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,128 @@ def get_window_frame_size(x, y, w, h):
}


#global menu handling:
window_menus = {}

def window_focused(window, event):
global window_menus
wid = window._id
menu_data = window_menus.get(wid)
log.warn("window_focused(%s, %s) menu(%s)=%s", window, event, wid, menu_data)
application_actions, window_menu = None, None
if menu_data:
menus, application_action_callback, window_action_callback = menu_data
application_actions = menus.get("application-actions")
window_actions = menus.get("window-actions")
window_menu = menus.get("window-menu")
from xpra.platform.darwin.osx_menu import getOSXMenuHelper
mh = getOSXMenuHelper()
if not menu_data or (not application_actions and not window_actions) or not window_menu:
mh.rebuild() #just the standard xpra controls
mh.add_full_menu()
return
mh.remove_all_menus()
#add all the xpra menus as sub-menus under one menu:
opt = mh.menuitem("Xpra Options")
options = mh.make_menu()
opt.set_submenu(options)
for label, submenu in mh.get_extra_menus():
item = mh.menuitem(label)
item.set_submenu(submenu)
options.add(item)
opt.show_all()
mh.add_to_menu_bar(item)
mh.menu_bar.show_all()
#add the application menus after that:
#ie: menu = {
# 'enabled': True,
# 'application-id': 'org.xpra.ExampleMenu',
# 'application-actions': {'quit': (True, '', ()), 'about': (True, '', ()), 'help': (True, '', ()), 'custom': (True, '', ()), 'activate-tab': (True, 's', ()), 'preferences': (True, '', ())},
# 'window-actions': {'edit-profile': (True, 's', ()), 'reset': (True, 'b', ()), 'about': (True, '', ()), 'help': (True, '', ()), 'fullscreen': (True, '', (0,)), 'detach-tab': (True, '', ()), 'save-contents': (True, '', ()), 'zoom': (True, 'i', ()), 'move-tab': (True, 'i', ()), 'new-terminal': (True, '(ss)', ()), 'switch-tab': (True, 'i', ()), 'new-profile': (True, '', ()), 'close': (True, 's', ()), 'show-menubar': (True, '', (1,)), 'select-all': (True, '', ()), 'copy': (True, '', ()), 'paste': (True, 's', ()), 'find': (True, 's', ()), 'preferences': (True, '', ())},
# 'window-menu': {0:
# {0: ({':section': (0, 1)}, {':section': (0, 2)}, {':section': (0, 3)}),
# 1: ({'action': 'win.new-terminal', 'target': ('default', 'default'), 'label': '_New Terminal'},),
# 2: ({'action': 'app.preferences', 'label': '_Preferences'},),
# 3: ({'action': 'app.help', 'label': '_Help'}, {'action': 'app.about', 'label': '_About'}, {'action': 'app.quit', 'label': '_Quit'}),
# }
# }
# }
#go through all the groups (not sure how we would get more than one with gtk menus.. but still):
def cb(menu_item):
#find the action for this item:
action = getattr(menu_item, "_action", "undefined")
log("application menu cb %s, action=%s", menu_item, action)
if action.startswith("app."):
callback = application_action_callback
actions = application_actions
action = action[4:]
elif action.startswith("win."):
callback = window_action_callback
actions = window_actions
action = action[4:]
else:
log.warn("Warning: unknown action type '%s'", action)
return
action_def = actions.get(action)
if action_def is None:
log.warn("Warning: cannot find action '%s'", action)
return
enabled, state, pdata = action_def[:3]
if not enabled:
log("action %s: %s is not enabled", action, action_def)
return
if len(action_def)>=4:
callback = action_def[3] #use action supplied callback
log("OSX application menu item %s, action=%s, action_def=%s, callback=%s", menu_item, action, action_def, callback)
if callback:
callback(menu_item, action, state, pdata)
for group_id in sorted(window_menu.keys()):
group = window_menu[group_id]
title = window.get_title() or "Application"
application = mh.menuitem(title)
submenu = mh.make_menu()
application.set_submenu(submenu)
for menuid in sorted(group.keys()):
menu_entries = group[menuid]
for d in menu_entries:
action = d.get("action")
if not action:
continue
label = d.get("label") or action
item = mh.menuitem(label, cb=cb)
item._action = action
item._target = d.get("target")
submenu.add(item)
log("added %s to %s menu for %s", label, title, window)
application.show_all()
mh.add_to_menu_bar(application)


def add_window_hooks(window):
window.connect("focus-in-event", window_focused)

def remove_window_hooks(window):
try:
del window_menus[window._id]
except:
pass

def _set_osx_window_menu(add, wid, window, menus, application_action_callback=None, window_action_callback=None):
global window_menus
log("_set_osx_window_menu%s", (add, wid, window, menus, application_action_callback, window_action_callback))
if (not menus) or (menus.get("enabled") is not True) or (add is False):
try:
del window_menus[wid]
except:
pass
else:
window_menus[wid] = (menus, application_action_callback, window_action_callback)


def get_menu_support_function():
return _set_osx_window_menu


try:
import Quartz.CoreGraphics as CG #@UnresolvedImport
ALPHA = {
Expand Down
77 changes: 50 additions & 27 deletions src/xpra/platform/darwin/osx_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@


#control which menus are shown in the OSX global menu:
SHOW_FEATURES_MENU = True
SHOW_SOUND_MENU = True
SHOW_ENCODINGS_MENU = True
SHOW_ACTIONS_MENU = True
Expand Down Expand Up @@ -63,33 +64,39 @@ def build(self):

def rebuild(self):
log("OSXMenuHelper.rebuild()")
if self.menu_bar:
self.remove_all_menus()
self.build_menu_bar()
return self.build()
if not self.menu_bar:
return self.build()
self.remove_all_menus()
self.build_menu_bar()
return self.menu_bar

def remove_all_menus(self):
log("OSXMenuHelper.remove_all_menus()")
if self.menu_bar:
for x in self.menus.values():
self.menu_bar.remove(x)
if x in self.menu_bar.get_children():
self.menu_bar.remove(x)
x.hide()
self.menus = {}
self.full = False

def make_osxmenu(self, name):
item = gtk.MenuItem(name)
submenu = gtk.Menu()
item.set_submenu(submenu)
item.show_all()
self.menu_bar.add(item)
self.menus[name] = (item, submenu)
return submenu

def add_to_menu_bar(self, item):
submenu = item.get_submenu()
assert submenu
if item not in self.menu_bar.get_children():
self.menu_bar.add(item)
self.menus[item.get_label()] = item

def build_menu_bar(self):
log("OSXMenuHelper.build_menu_bar()")
if SHOW_ABOUT_XPRA:
info_menu = self.make_osxmenu("Info")
info = self.menuitem("Info")
info_menu = self.make_menu()
info.set_submenu(info_menu)
info_menu.add(self.menuitem("About Xpra", "information.png", None, about))
info.show_all()
self.add_to_menu_bar(info)
self.menu_bar.show_all()

def add_full_menu(self):
Expand All @@ -99,31 +106,46 @@ def add_full_menu(self):
self.full = True
assert self.client
if SHOW_ABOUT_XPRA:
_, info_menu = self.menus.get("Info")
info = self.menus.get("Info")
info_menu = info.get_submenu()
info_menu.append(self.make_sessioninfomenuitem())
info_menu.append(self.make_bugreportmenuitem())
features_menu = self.make_osxmenu("Features")
features_menu.add(self.make_bellmenuitem())
features_menu.add(self.make_cursorsmenuitem())
features_menu.add(self.make_notificationsmenuitem())
features_menu.add(self.make_swapkeysmenuitem())
features_menu.add(self.make_numlockmenuitem())
features_menu.add(self.make_openglmenuitem())
menus = self.get_extra_menus()
for label, submenu in menus:
item = self.menuitem(label)
item.set_submenu(submenu)
item.show_all()
self.add_to_menu_bar(item)
self.menu_bar.show_all()

def get_extra_menus(self):
menus = []
if SHOW_FEATURES_MENU:
features_menu = self.make_menu()
menus.append(("Features", features_menu))
features_menu.add(self.make_bellmenuitem())
features_menu.add(self.make_cursorsmenuitem())
features_menu.add(self.make_notificationsmenuitem())
features_menu.add(self.make_swapkeysmenuitem())
features_menu.add(self.make_numlockmenuitem())
features_menu.add(self.make_openglmenuitem())
if SHOW_SOUND_MENU:
sound_menu = self.make_osxmenu("Sound")
sound_menu = self.make_menu()
if self.client.speaker_allowed and len(self.client.speaker_codecs)>0:
sound_menu.add(self.make_speakermenuitem())
if self.client.microphone_allowed and len(self.client.microphone_codecs)>0:
sound_menu.add(self.make_microphonemenuitem())
menus.append(("Sound", sound_menu))
if SHOW_ENCODINGS_MENU:
encodings_menu = self.make_osxmenu("Encoding")
encodings_menu = self.make_menu()
def set_encodings_menu(*args):
from xpra.codecs.loader import PREFERED_ENCODING_ORDER
encodings = [x for x in PREFERED_ENCODING_ORDER if x in self.client.get_encodings()]
populate_encodingsmenu(encodings_menu, self.get_current_encoding, self.set_current_encoding, encodings, self.client.server_encodings)
self.client.connect("handshake-complete", set_encodings_menu)
menus.append(("Encoding", encodings_menu))
if SHOW_ACTIONS_MENU:
actions_menu = self.make_osxmenu("Actions")
actions_menu = self.make_menu()
actions_menu.add(self.make_refreshmenuitem())
actions_menu.add(self.make_raisewindowsmenuitem())
#set_sensitive(bool) does not work on OSX,
Expand All @@ -132,7 +154,8 @@ def addsnc(*args):
if self.client.start_new_commands:
actions_menu.add(self.make_startnewcommandmenuitem(True))
self.client.connect("handshake-complete", addsnc)
self.menu_bar.show_all()
menus.append(("Actions", actions_menu))
return menus

#these methods are called by the superclass
#but we don't have a quality or speed menu, so override and ignore
Expand Down Expand Up @@ -182,7 +205,7 @@ def update_numlock(self, on):

def build_dock_menu(self):
log("OSXMenuHelper.build_dock_menu()")
self.dock_menu = gtk.Menu()
self.dock_menu = self.make_menu()
if SHOW_ABOUT_XPRA:
self.dock_menu.add(self.menuitem("About Xpra", "information.png", None, about))
self.dock_menu.show_all()
Expand Down

0 comments on commit 618a2ed

Please sign in to comment.