#!/usr/bin/env python # # Single Menu - Epiphany Extension # Copyright (C) 2006, 2007 Stefan Stuhr # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import gobject import gtk import epiphany import gconf from xml.parsers import expat def get_child_in_widget(widget, child_path): if not hasattr(widget, "get_children"): return None current = child_path.pop(0) if current[0] == None: child = None for child_widget in widget.get_children(): if isinstance(child_widget, current[1]): child = child_widget break if child == None: return None else: try: child = widget.get_children()[current[0]] except IndexError: return None if not isinstance(child, current[1]): return None if len(child_path) > 0: return get_child_in_widget(child, child_path) else: return child class SingleMenuMenu(gtk.Menu): def __init__(self, ui_manager): gtk.Menu.__init__(self) self._ui_manager = ui_manager self._menu_tree = ([], []) def _parser_StartElement(self, name, attrs): self._tags.append(name) if self._tags == ["ui", "menubar"] and not attrs.get("name", "") != "menubar": self._inside_menubar = True return elif len(self._tags) <= 2: self._inside_menubar = False return if not self._inside_menubar: return if self._current_branch == None: return if name == "placeholder": ui_name = attrs.get("name", name) self._current_placeholder_path.append(ui_name) if not name in ("menuitem", "separator", "menu"): return if name == "separator": ui_name = "" else: ui_name = attrs.get("name", attrs.get("action", name)) ui_name = "/".join(self._current_placeholder_path + [ui_name]) self._current_branch[0].append(ui_name) if name == "separator": data = [name, None] elif name == "menuitem": data = [name, attrs, None] elif name == "menu": data = [name, attrs, ([], []), None, None] self._current_branch[1].append(data) if name == "menu": self._branch_parents.append((self._current_branch, self._current_placeholder_path)) self._current_branch = data[2] self._current_placeholder_path = [] def _parser_EndElement(self, name): while self._tags and self._tags[-1] != name: self._tags.pop() tags = tuple(self._tags) if self._tags: self._tags.pop() if self._inside_menubar and tags == ("ui", "menubar"): self._current_branch = None self._inside_menubar = False if not self._inside_menubar: return if name == "menu" and self._current_branch != None: if len(self._branch_parents) <= 0: self._current_branch = None else: parent = self._branch_parents.pop() self._current_branch = parent[0] self._current_placeholder_path = parent[1] if self._current_branch == None: return if name == "placeholder" and len(self._current_placeholder_path) > 0: self._current_placeholder_path.pop() def update_ui_xml(self, end_cb): self._tags = [] self._inside_menubar = False self._new_menu_tree = ([], []) self._current_branch = self._new_menu_tree self._current_placeholder_path = [] self._branch_parents = [] parser = expat.ParserCreate() parser.StartElementHandler = self._parser_StartElement parser.EndElementHandler = self._parser_EndElement parser.Parse(self._ui_manager.get_ui(), True) self._update_menu(self._new_menu_tree, self._menu_tree, self) del self._menu_tree self._menu_tree = self._new_menu_tree if end_cb: end_cb() return False def _delete_menu_recursive(self, menu, start_index=0): items = menu.get_children()[start_index:] for item in items: if isinstance(item, gtk.MenuItem): submenu = item.get_submenu() if submenu: self._delete_menu_recursive(submenu) item.remove_submenu() submenu.destroy() if hasattr(item, "_smb_ext_action"): if item._smb_ext_action.get_property("action-group"): item._smb_ext_action.disconnect_proxy(item) del item._smb_ext_action item.parent.remove(item) item.destroy() def delete_single_button_menu(self): self._delete_menu_recursive(self) def _add_menuitem(self, name, data, old_branch, menu, menu_index, ui_path): new_item = False try: old_index = old_branch[0].index(name) old_data = old_branch[1][old_index] except ValueError: old_index = None is_separator = (data[0] == "separator") if old_index == None or old_data[0] != data[0]: new_item = True if not is_separator and not new_item: if old_data[1] != data[1]: new_item = True if not is_separator and not new_item: old_action = getattr(old_data[-1], "_smb_ext_action", None) if isinstance(old_action, gtk.Action): new_item = (old_action.get_property("action-group") == None) else: new_item = True if new_item and is_separator: menuitem = gtk.SeparatorMenuItem() menu.insert(menuitem, menu_index) menuitem.show() elif new_item: new_action = self._ui_manager.get_action(ui_path) if not new_action: return False menuitem = new_action.create_menu_item() if not menuitem: return False menuitem._smb_ext_action = new_action menu.insert(menuitem, menu_index) elif not new_item: menuitem = old_data[-1] menu.reorder_child(menuitem, menu_index) data[-1] = menuitem if data[0] != "menu": return True if new_item: submenu = gtk.Menu() menuitem.set_submenu(submenu) old_subbranch = ([], []) else: submenu = old_data[-2] old_subbranch = old_data[2] data[-2] = submenu self._update_menu(data[2], old_subbranch, submenu, ui_path) return True def _update_menu(self, new_branch, old_branch, menu, ui_path="/ui/menubar"): index = menu_index = -1 separators_start = True separator_last = None separator_count = 0 items_to_remove = [] for name, data in zip(new_branch[0], new_branch[1]): index += 1 if data[0] == "separator": if separator_last != None: items_to_remove.insert(0, separator_last[0]) separator_last = (index, data) continue elif separators_start: separators_start = False if separator_last != None: items_to_remove.insert(0, separator_last[0]) separator_last = None elif separator_last != None: separator_count += 1 sep_name = "-*- Separator #%d -*-" % separator_count sep_data = separator_last[1] path = "%s/%s" % (ui_path, sep_name) menu_index += 1 if not self._add_menuitem(sep_name, sep_data, old_branch, menu, menu_index, path): items_to_remove.insert(0, separator_last[0]) menu_index -= 1 separator_count -= 1 separator_last = None path = "%s/%s" % (ui_path, name) menu_index += 1 if not self._add_menuitem(name, data, old_branch, menu, menu_index, path): items_to_remove.insert(0, index) menu_index -= 1 if separator_last != None: items_to_remove.insert(0, separator_last[0]) for index in items_to_remove: new_branch[0].pop(index) new_branch[1].pop(index) menu_index += 1 self._delete_menu_recursive(menu, menu_index) GCONF_KEY_PATH = "/apps/epiphany-extensions/singlemenu/" class SingleMenu: name = "Single Menu" ui_name_prefix = "SingleMenuExt" pref_action_name = "%sPrefAction" % ui_name_prefix menu_type_labels = [ "Text", "Arrow", "Spinner", ] ui_xml = ''' ''' % (ui_name_prefix, pref_action_name) def __init__(self): self.win_data = {} self.pref_dialog = None client = self.client = gconf.client_get_default(); value = client.get(GCONF_KEY_PATH + "menutype") if value and value.type == gconf.VALUE_INT: self.menu_type = value.get_int() else: self.menu_type = 0 self.use_gconf = client.key_is_writable(GCONF_KEY_PATH + "menutype") if not self.use_gconf: del self.client def show_pref_dialog(self, *args): if self.pref_dialog: self.pref_dialog.reshow_with_initial_size() self.pref_dialog.present() return self.pref_dialog = gtk.Dialog("%s Preferences" % self.name, flags=gtk.DIALOG_NO_SEPARATOR, buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)) if hasattr(self.pref_dialog, "set_icon_name"): self.pref_dialog.set_icon_name(gtk.STOCK_PREFERENCES) self.pref_dialog.connect("response", self.pref_dialog_response) hbox = gtk.HBox(False, 12) hbox.set_border_width(8) self.pref_dialog.vbox.pack_start(hbox, False, False, 0) hbox.show() label = gtk.Label("Menu item type:") hbox.pack_start(label, False, False, 0) label.show() combobox = gtk.combo_box_new_text() for text in self.menu_type_labels: combobox.append_text(text) combobox.set_active(self.menu_type) combobox.connect("changed", self.pref_combobox_changed) hbox.pack_start(combobox, True, True, 0) combobox.show() self.pref_dialog.present() def pref_dialog_response(self, dialog, response_id): dialog.hide() dialog.destroy() self.pref_dialog = None def pref_combobox_changed(self, combobox): active = combobox.get_active() if self.menu_type == active: return self.menu_type = active if self.use_gconf: self.client.set_int(GCONF_KEY_PATH + "menutype", active) for window, data in self.win_data.iteritems(): if not data.has_key("menuitem"): continue self.delete_menuitem(data) menuitem = self.new_menuitem(self.menu_type, window) data["menuitem"] = menuitem menubar = data["menubar"] menubar.append(menuitem) menuitem.show() menuitem.set_submenu(data["menu"]) def new_menuitem(self, menu_type, window): data = self.win_data[window] if menu_type == 1: menuitem = gtk.MenuItem() arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_NONE) menuitem.add(arrow) arrow.show() return menuitem elif menu_type == 2 and data.has_key("spinner"): spinner = data["spinner"] toolitem = data["spinner_toolitem"] toolitem.hide() spinner.hide() spinner.parent.remove(spinner) spinner.set_size(gtk.ICON_SIZE_MENU) menuitem = gtk.MenuItem() menuitem.add(spinner) spinner.show() handler_id = spinner.connect("size-request", self.spinner_size_request, window) data["spinner_size_req_hid"] = handler_id return menuitem else: return gtk.MenuItem("_Menu") def delete_menuitem(self, data): menuitem = data.pop("menuitem") if data.has_key("spinner_size_req_hid"): data["spinner"].disconnect(data.pop("spinner_size_req_hid")) if data.has_key("spinner") and data["spinner"].parent == menuitem: spinner = data["spinner"] toolitem = data["spinner_toolitem"] spinner.hide() spinner.parent.remove(spinner) spinner_toolbar = data["spinner_toolbar"] icon_size = spinner_toolbar.get_icon_size() spinner.set_size(icon_size) toolitem.add(spinner) spinner.show() toolitem.show() menuitem.remove_submenu() menuitem.parent.remove(menuitem) menuitem.destroy() def attach_window(self, window): data = self.win_data[window] = {} toolbar = window.get_toolbar() vbox = toolbar.parent if not isinstance(vbox, gtk.VBox): return ui_manager = window.get_ui_manager() menubar = ui_manager.get_widget("/ui/menubar") if menubar: data["default_menubar"] = menubar handler_id = menubar.connect("show", self.menubar_show, window) data["menubar_show_hid"] = handler_id menubar.hide() packing = vbox.query_child_packing(toolbar) data["toolbar_packing"] = packing data["top_vbox"] = vbox hbox = gtk.HBox(False, 0) data["menubar_hbox"] = hbox vbox.pack_start(hbox, False, False, 0) menubar = gtk.MenuBar() data["menubar"] = menubar hbox.pack_start(menubar, False, False, 0) menubar.show() eventbox = gtk.EventBox() data["eventbox"] = eventbox hbox.pack_start(eventbox, True, True, 0) eventbox.show() toolbar.parent.remove(toolbar) eventbox.add(toolbar) hbox.show() menu = SingleMenuMenu(ui_manager) data["menu"] = menu def detach_window(self, window): data = self.win_data.pop(window) if not data.has_key("menu"): return toolbar = window.get_toolbar() vbox = data["top_vbox"] hbox = data["menubar_hbox"] hbox.hide() toolbar.parent.remove(toolbar) vbox.pack_start(toolbar) packing = data["toolbar_packing"] vbox.set_child_packing(toolbar, *packing) hbox.parent.remove(hbox) ui_manager = window.get_ui_manager() if data.has_key("ui_updated_hid"): ui_manager.disconnect(data["ui_updated_hid"]) if data.has_key("idle_updater"): gobject.source_remove(data.pop("idle_updater")) if data.has_key("ui_merge_id"): ui_manager.remove_ui(data["ui_merge_id"]) if data.has_key("action_group"): ui_manager.remove_action_group(data["action_group"]) ui_manager.ensure_update() self.delete_menuitem(data) menu = data["menu"] menu.delete_single_button_menu() menu.destroy() if data.has_key("default_menubar"): menubar = data["default_menubar"] menubar.disconnect(data["menubar_show_hid"]) if not "fullscreen" in window.window.get_state().value_nicks: menubar.show() def attach_tab(self, window, tab): data = self.win_data[window] if data.has_key("first_tab_attached"): return if not data.has_key("menu"): return data["first_tab_attached"] = True toolbar = window.get_toolbar() spinner_toolbar = get_child_in_widget(toolbar, [(0, gtk.HBox), (-1, gtk.Toolbar)]) if spinner_toolbar: data["spinner_toolbar"] = spinner_toolbar toolitem = get_child_in_widget(spinner_toolbar, [(0, gtk.ToolItem)]) if toolitem: data["spinner_toolitem"] = toolitem spinner = get_child_in_widget(toolitem, [(0, epiphany.Spinner)]) if spinner: data["spinner"] = spinner menuitem = self.new_menuitem(self.menu_type, window) data["menuitem"] = menuitem menubar = data["menubar"] menubar.append(menuitem) menuitem.show() menuitem.set_submenu(data["menu"]) ui_manager = window.get_ui_manager() action_group = gtk.ActionGroup(self.ui_name_prefix + "AG") data["action_group"] = action_group actions = [(self.pref_action_name, None, "_%s Preferences" % self.name, None, "Configure Single Menu extension", self.show_pref_dialog)] action_group.add_actions(actions, window) ui_manager.insert_action_group(action_group, 0) merge_id = ui_manager.add_ui_from_string(self.ui_xml) data["ui_merge_id"] = merge_id handler_id = ui_manager.connect("notify::ui", self.ui_updated, window) data["ui_updated_hid"] = handler_id self.ui_updated(ui_manager, None, window) def spinner_size_request(self, spinner, requisition, window): data = self.win_data[window] menuitem = data["menuitem"] toolitem = data["spinner_toolitem"] if spinner.parent == menuitem: handler_id = data["spinner_size_req_hid"] spinner.handler_block(handler_id) spinner.set_size(gtk.ICON_SIZE_MENU) spinner.handler_unblock(handler_id) elif spinner.parent == toolitem: spinner_toolbar = data["spinner_toolbar"] icon_size = spinner_toolbar.get_icon_size() handler_id = data["spinner_size_req_hid"] spinner.handler_block(handler_id) spinner.set_size(icon_size) spinner.handler_unblock(handler_id) def menubar_show(self, menubar, window): menubar.hide() def ui_updated_idle_end(self, window): data = self.win_data[window] if data.has_key("idle_updater"): data.pop("idle_updater") def ui_updated(self, ui_manager, prop_spec, window): data = self.win_data[window] if data.has_key("idle_updater"): return menu = data["menu"] source_id = gobject.idle_add(menu.update_ui_xml, lambda: self.ui_updated_idle_end(window)) data["idle_updater"] = source_id smb_extension = SingleMenu() attach_window = smb_extension.attach_window detach_window = smb_extension.detach_window attach_tab = smb_extension.attach_tab