/*
Copyright (c) 2011 by Simon Schneegans
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 3 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, see .
*/
namespace GnomePie {
/////////////////////////////////////////////////////////////////////////
/// An ActionGroup which displays the user's main menu. It's a bit ugly,
/// but it supports both, an older version and libgnome-menus-3 at the
/// same time.
/////////////////////////////////////////////////////////////////////////
public class MenuGroup : ActionGroup {
/////////////////////////////////////////////////////////////////////
/// Used to register this type of ActionGroup. It sets the display
/// name for this ActionGroup, it's icon name and the string used in
/// the pies.conf file for this kind of ActionGroups.
/////////////////////////////////////////////////////////////////////
public static GroupRegistry.TypeDescription register() {
var description = new GroupRegistry.TypeDescription();
description.name = _("Group: Main menu");
description.icon = "gnome-main-menu";
description.description = _("Displays your main menu structure.");
description.id = "menu";
return description;
}
/////////////////////////////////////////////////////////////////////
/// True, if this MenuGroup is the top most menu.
/////////////////////////////////////////////////////////////////////
public bool is_toplevel {get; construct set; default = true;}
/////////////////////////////////////////////////////////////////////
/// The menu tree displayed by the MenuGroup. Only set for the
/// toplevel MenuGroup.
/////////////////////////////////////////////////////////////////////
private GMenu.Tree menu = null;
/////////////////////////////////////////////////////////////////////
/// A list of all sub menus of this MenuGroup.
/////////////////////////////////////////////////////////////////////
private Gee.ArrayList childs;
/////////////////////////////////////////////////////////////////////
/// Two members needed to avoid useless, frequent changes of the
/// stored Actions.
/////////////////////////////////////////////////////////////////////
private bool changing = false;
private bool changed_again = false;
/////////////////////////////////////////////////////////////////////
/// C'tor, initializes all members. Used for the toplevel menu.
/////////////////////////////////////////////////////////////////////
public MenuGroup(string parent_id) {
GLib.Object(parent_id : parent_id, is_toplevel : true);
}
/////////////////////////////////////////////////////////////////////
/// C'tor, initializes all members. Used for sub menus.
/////////////////////////////////////////////////////////////////////
public MenuGroup.sub_menu(string parent_id) {
GLib.Object(parent_id : parent_id, is_toplevel : false);
}
construct {
this.childs = new Gee.ArrayList();
if (this.is_toplevel) {
#if HAVE_GMENU_3
this.menu = new GMenu.Tree("applications.menu", GMenu.TreeFlags.INCLUDE_EXCLUDED);
this.menu.changed.connect(this.reload);
#endif
this.load_toplevel();
}
}
/////////////////////////////////////////////////////////////////////
/// Starts to load the menu.
/////////////////////////////////////////////////////////////////////
private void load_toplevel() {
#if HAVE_GMENU_3
try {
if (this.menu.load_sync()) {
this.load_contents(this.menu.get_root_directory(), this.parent_id);
}
} catch (GLib.Error e) {
warning(e.message);
}
#else
this.menu = GMenu.Tree.lookup ("applications.menu", GMenu.TreeFlags.INCLUDE_EXCLUDED);
this.menu.add_monitor(this.reload);
var dir = this.menu.get_root_directory();
this.load_contents(dir, this.parent_id);
#endif
}
/////////////////////////////////////////////////////////////////////
/// Parses the main menu recursively.
/////////////////////////////////////////////////////////////////////
private void load_contents(GMenu.TreeDirectory dir, string parent_id) {
#if HAVE_GMENU_3
var item = dir.iter();
while (true) {
var type = item.next();
if (type == GMenu.TreeItemType.INVALID)
break;
if (type == GMenu.TreeItemType.DIRECTORY && !item.get_directory().get_is_nodisplay()) {
// create a MenuGroup for sub menus
string[] icons = item.get_directory().get_icon().to_string().split(" ");
string final_icon = "application-default-icon";
// search for available icons
foreach (var icon in icons) {
if (Gtk.IconTheme.get_default().has_icon(icon)) {
final_icon = icon;
break;
}
}
var sub_menu = PieManager.create_dynamic_pie(item.get_directory().get_name(), final_icon);
var group = new MenuGroup.sub_menu(sub_menu.id);
group.add_action(new PieAction(parent_id, true));
group.load_contents(item.get_directory(), sub_menu.id);
childs.add(group);
sub_menu.add_group(group);
this.add_action(new PieAction(sub_menu.id));
} else if (type == GMenu.TreeItemType.ENTRY ) {
// create an AppAction for entries
if (!item.get_entry().get_is_excluded()) {
this.add_action(ActionRegistry.new_for_app_info(item.get_entry().get_app_info()));
}
}
}
#else
foreach (var item in dir.get_contents()) {
switch(item.get_type()) {
case GMenu.TreeItemType.DIRECTORY:
// create a MenuGroup for sub menus
if (!((GMenu.TreeDirectory)item).get_is_nodisplay()) {
var sub_menu = PieManager.create_dynamic_pie(
((GMenu.TreeDirectory)item).get_name(),
((GMenu.TreeDirectory)item).get_icon());
var group = new MenuGroup.sub_menu(sub_menu.id);
group.add_action(new PieAction(parent_id, true));
group.load_contents((GMenu.TreeDirectory)item, sub_menu.id);
childs.add(group);
sub_menu.add_group(group);
this.add_action(new PieAction(sub_menu.id));
}
break;
case GMenu.TreeItemType.ENTRY:
// create an AppAction for entries
if (!((GMenu.TreeEntry)item).get_is_nodisplay() && !((GMenu.TreeEntry)item).get_is_excluded()) {
this.add_action(new AppAction(((GMenu.TreeEntry)item).get_name(),
((GMenu.TreeEntry)item).get_icon(),
((GMenu.TreeEntry)item).get_exec()));
}
break;
}
}
#endif
}
/////////////////////////////////////////////////////////////////////
/// Reloads the menu.
/////////////////////////////////////////////////////////////////////
private void reload() {
// avoid too frequent changes...
if (!this.changing) {
this.changing = true;
Timeout.add(500, () => {
if (this.changed_again) {
this.changed_again = false;
return true;
}
// reload
message("Main menu changed...");
#if !HAVE_GMENU_3
this.menu.remove_monitor(this.reload);
#endif
this.clear();
this.load_toplevel();
this.changing = false;
return false;
});
} else {
this.changed_again = true;
}
}
/////////////////////////////////////////////////////////////////////
/// Deletes all generated Pies, when the toplevel menu is deleted.
/////////////////////////////////////////////////////////////////////
public override void on_remove() {
if (this.is_toplevel)
this.clear();
}
/////////////////////////////////////////////////////////////////////
/// Clears this ActionGroup recursively.
/////////////////////////////////////////////////////////////////////
private void clear() {
foreach (var child in childs)
child.clear();
if (!this.is_toplevel)
PieManager.remove_pie(this.parent_id);
this.delete_all();
this.childs.clear();
#if !HAVE_GMENU_3
this.menu = null;
#endif
}
}
}