/**************************************************************

menubar.cpp (C-Munipack project)
Menu wrapper
Copyright (C) 2008 David Motl, dmotl@volny.cz

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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

**************************************************************/

#include <stdlib.h>

#include "menubar.h"
#include "utils.h"

struct tMenuData
{
	CMenuBar	*pMe;
	GtkWidget	*pItem;
	int			menu_id, cmd_id;
	bool		separator, radio, visible;
	GList		*items;
	tMenuData	*parent;
};

// Default constructor
CMenuBar::CMenuBar()
{
	m_Data = NULL;
	m_hBar = NULL;
	m_Updating = false;
}

// Destructor
CMenuBar::~CMenuBar()
{
	g_slist_foreach(m_Data, (GFunc)FreeItem, NULL);
	g_slist_free(m_Data);
}

// Create and initialize the m_hBar
void CMenuBar::Create(const tMenu *menus, bool icons)
{
	m_Updating = true;
	if (menus) {
		m_hBar = gtk_menu_bar_new();
		for (int i=0; menus[i].text!=NULL; i++) {
			tMenuData *data = CreateMenu(&menus[i], icons, NULL);
			m_Data = g_slist_append(m_Data, data);
			gtk_menu_shell_append(GTK_MENU_SHELL(m_hBar), data->pItem);
			g_object_ref(data->pItem);
		}
	}
	gtk_widget_show(m_hBar);
	m_Updating = false;
}


tMenuData *CMenuBar::CreateMenu(const tMenu *menu, bool icons, tMenuData *parent)
{
	tMenuData *data = static_cast<tMenuData*>(g_malloc0(sizeof(tMenuData)));
	data->cmd_id = -1;
	data->menu_id = menu->menu_id;
	data->pMe = this;
	data->parent = parent;

	data->pItem = gtk_menu_item_new_with_mnemonic(menu->text);
	if ((menu->flags & MBF_RIGHT)!=0)
		gtk_menu_item_right_justify(GTK_MENU_ITEM(data->pItem));

	if (menu->items!=NULL) {
		GtkWidget *pMenu = gtk_menu_new();
		for (int i=0; menu->items[i].type!=MB_END; i++) {
			tMenuData *child = CreateItem(&menu->items[i], icons, data);
			data->items = g_list_append(data->items, child);
			gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), child->pItem);
			gtk_widget_show(child->pItem);
			g_object_ref(child->pItem);
		}
		gtk_menu_item_set_submenu(GTK_MENU_ITEM(data->pItem), pMenu);
		gtk_widget_show(pMenu);
	}

	gtk_widget_show(data->pItem);
	data->visible = true;
	return data;
}

tMenuData *CMenuBar::CreateItem(const tMenuItem *item, bool icons, tMenuData *parent)
{
	tMenuData *data = static_cast<tMenuData*>(g_malloc0(sizeof(tMenuData)));
	data->cmd_id = item->cmd_id;
	data->menu_id = -1;
	data->pMe = this;
	data->parent = parent;

	switch (item->type)
	{
	case MB_ITEM:
		data->pItem = gtk_image_menu_item_new_with_mnemonic(item->text);
		if (icons) {
			char *icon = get_icon_file(item->icon ? item->icon : "noicon");
			gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(data->pItem), 
				gtk_image_new_from_file(icon));
			g_free(icon);
		}
		g_signal_connect(G_OBJECT(data->pItem), "activate", G_CALLBACK(item_activate), data);
		break;

	case MB_CHECKBTN:
	case MB_RADIOBTN:
		data->pItem = gtk_check_menu_item_new_with_mnemonic(item->text);
		if (item->type==MB_RADIOBTN) {
			gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(data->pItem), TRUE);
			data->radio = true;
		}
		g_signal_connect(G_OBJECT(data->pItem), "toggled", G_CALLBACK(item_activate), data);
		break;

	case MB_SEPARATOR:
		data->pItem = gtk_separator_menu_item_new();
		data->separator = true;
		break;

	case MB_SUBMENU:
		data->pItem = gtk_image_menu_item_new_with_mnemonic(item->text);
		if (icons) {
			char *icon = get_icon_file(item->icon ? item->icon : "noicon");
			gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(data->pItem), 
				gtk_image_new_from_file(icon));
			g_free(icon);
		}
		if (item->submenu!=NULL) {
			GtkWidget *pMenu = gtk_menu_new();
			for (int i=0; item->submenu[i].type!=MB_END; i++) {
				tMenuData *child = CreateItem(&item->submenu[i], icons, data);
				data->items = g_list_append(data->items, child);
				gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), child->pItem);
				gtk_widget_show(child->pItem);
				g_object_ref(child->pItem);
			}
			gtk_menu_item_set_submenu(GTK_MENU_ITEM(data->pItem), pMenu);
			gtk_widget_show(pMenu);
		}
		break;
		
	case MB_RECENTMENU:
		data->pItem = gtk_image_menu_item_new_with_mnemonic(item->text);
		if (icons) {
			char *icon = get_icon_file(item->icon ? item->icon : "noicon");
			gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(data->pItem), 
				gtk_image_new_from_file(icon));
			g_free(icon);
		}
		{
			GtkWidget *pMenu = gtk_recent_chooser_menu_new();
			gtk_recent_chooser_set_show_private(GTK_RECENT_CHOOSER(pMenu), TRUE);
			gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(pMenu), 30);
			gtk_recent_chooser_set_show_icons(GTK_RECENT_CHOOSER(pMenu), FALSE);
			if (item->rc_group) {
				GtkRecentFilter *filter = gtk_recent_filter_new();
				gtk_recent_filter_add_group(filter, item->rc_group);
				gtk_recent_chooser_set_filter(GTK_RECENT_CHOOSER(pMenu), filter);
			}
			gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(pMenu), GTK_RECENT_SORT_MRU);
			gtk_menu_item_set_submenu(GTK_MENU_ITEM(data->pItem), pMenu);
			gtk_widget_show(pMenu);
			g_signal_connect(G_OBJECT(pMenu), "item-activated", G_CALLBACK(recent_item_activated), data);
		}
		break;

	default:
		break;
	}

	gtk_widget_show(data->pItem);
	data->visible = true;
	return data;
}

// Free memory allocated for a menu
void CMenuBar::FreeItem(tMenuData *menu, void *user_data)
{
	if (menu->items) {
		g_list_foreach(menu->items, (GFunc)FreeItem, NULL);
		g_list_free(menu->items);
	}
	if (menu->pItem) 
		g_object_unref(menu->pItem);
	g_free(menu);
}

void CMenuBar::item_activate(GtkWidget *widget, tMenuData *data)
{
	data->pMe->OnActivate(data);
}

void CMenuBar::OnActivate(tMenuData *data)
{
	if (!m_Updating) {
		if (data->radio && gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(data->pItem))) {
			m_Updating = true;
			Check(data->cmd_id, true);
			m_Updating = false;
		}
		Callback(CB_ACTIVATE, data->cmd_id);
	}
}

void CMenuBar::recent_item_activated(GtkRecentChooser *widget, tMenuData *data)
{
	data->pMe->OnRecentActivated(widget, data);
}

void CMenuBar::OnRecentActivated(GtkRecentChooser *widget, tMenuData *data)
{
	if (!m_Updating) {
		gchar *uri = gtk_recent_chooser_get_current_uri(widget);
		if (uri) {
			gchar *fpath = g_filename_from_uri(uri, NULL, NULL);
			if (fpath) {
				Callback(CB_RECENT_ACTIVATE, data->cmd_id, fpath);
				g_free(fpath);
			}
			g_free(uri);
		}
	}
}

void CMenuBar::ShowMenu(int menu_id, bool show)
{
	m_Updating = true;
	for (GSList *ptr=m_Data; ptr!=NULL; ptr=ptr->next) {
		tMenuData *data = static_cast<tMenuData*>(ptr->data);
		if (data->menu_id == menu_id) {
			if (show) {
				if (!data->visible) {
					gtk_widget_show(GTK_WIDGET(data->pItem));
					data->visible = true;
				}
			} else {
				if (data->visible) {
					gtk_widget_hide(GTK_WIDGET(data->pItem));
					data->visible = false;
				}
			}
			break;
		}
	}
	m_Updating = false;
}

void CMenuBar::Show(int cmd_id, bool show)
{
	bool found = false;

	m_Updating = true;
	for (GSList *ptr=m_Data; ptr!=NULL && !found; ptr=ptr->next) {
		tMenuData *data = static_cast<tMenuData*>(ptr->data);
		for (GList *ptr2=data->items; ptr2!=NULL; ptr2=ptr2->next) {
			if (ShowItem(static_cast<tMenuData*>(ptr2->data), cmd_id, show)) {
				found = true;
				break;
			}
		}
	}
	m_Updating = false;
}

bool CMenuBar::ShowItem(tMenuData *data, int cmd_id, bool show)
{
	if (data->cmd_id == cmd_id) {
		bool changed = false;
		if (show) {
			if (!data->visible) {
				gtk_widget_show(GTK_WIDGET(data->pItem));
				data->visible = true;
				changed = true;
			}
		} else {
			if (data->visible) {
				gtk_widget_hide(GTK_WIDGET(data->pItem));
				data->visible = false;
				changed = true;
			}
		}
		if (changed) {
			// Show/hide separators
			tMenuData *last_item = NULL, *last_sep = NULL;
			for (GList *aux = data->parent->items; aux!=NULL; aux=aux->next) {
				tMenuData *data = static_cast<tMenuData*>(aux->data);
				if (!data->separator) {
					if (data->visible) {
						if (last_item && last_sep) {
							gtk_widget_show(GTK_WIDGET(last_sep->pItem));
							last_sep->visible = true;
							last_sep = NULL;
						}
						last_item = data;
					}
				} else {
					if (data->visible) {
						gtk_widget_hide(GTK_WIDGET(data->pItem));
						data->visible = false;
					}
					last_sep = data;
				}
			}
		}
		return true;
	}

	for (GList *ptr=data->items; ptr!=NULL; ptr=ptr->next) {
		if (ShowItem(static_cast<tMenuData*>(ptr->data), cmd_id, show))
			return true;
	}

	return false;
}

void CMenuBar::EnableMenu(int menu_id, bool enable)
{
	GSList *ptr;

	m_Updating = true;
	for (ptr=m_Data; ptr!=NULL; ptr=ptr->next) {
		tMenuData *data = static_cast<tMenuData*>(ptr->data);
		if (data->menu_id == menu_id) {
			gtk_widget_set_sensitive(GTK_WIDGET(data->pItem), enable);
			break;
		}
	}
	m_Updating = false;
}

void CMenuBar::Enable(int cmd_id, bool enable)
{
	bool found = false;

	m_Updating = true;
	for (GSList *ptr=m_Data; ptr!=NULL && !found; ptr=ptr->next) {
		tMenuData *data = static_cast<tMenuData*>(ptr->data);
		for (GList *ptr2=data->items; ptr2!=NULL; ptr2=ptr2->next) {
			if (EnableItem(static_cast<tMenuData*>(ptr2->data), cmd_id, enable)) {
				found = true;
				break;
			}
		}
	}
	m_Updating = false;
}

bool CMenuBar::EnableItem(tMenuData *data, int cmd_id, bool enable)
{
	if (data->cmd_id == cmd_id) {
		gtk_widget_set_sensitive(GTK_WIDGET(data->pItem), enable);
		return true;
	}
	for (GList *ptr=data->items; ptr!=NULL; ptr=ptr->next) {
		if (EnableItem(static_cast<tMenuData*>(ptr->data), cmd_id, enable))
			return true;
	}
	return false;
}

void CMenuBar::Check(int cmd_id, bool check)
{
	bool found = false;

	m_Updating = true;
	for (GSList *ptr=m_Data; ptr!=NULL && !found; ptr=ptr->next) {
		tMenuData *data = static_cast<tMenuData*>(ptr->data);
		for (GList *ptr2=data->items; ptr2!=NULL; ptr2=ptr2->next) {
			if (CheckItem(static_cast<tMenuData*>(ptr2->data), cmd_id, check, ptr2)) {
				found = true;
				break;
			}
		}
	}
	m_Updating = false;
}

bool CMenuBar::CheckItem(tMenuData *data, int cmd_id, bool check, GList *ptr)
{
	GList *ptr2;

	if (data->cmd_id == cmd_id) {
		gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(data->pItem), check);
		if (data->radio && check) {
			// Uncheck all item in the same group
			for (ptr2=ptr->prev; ptr2!=NULL && ((tMenuData*)ptr2->data)->radio; ptr2=ptr2->prev) 
				gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(((tMenuData*)ptr2->data)->pItem), false);
			for (ptr2=ptr->next; ptr2!=NULL && ((tMenuData*)ptr2->data)->radio; ptr2=ptr2->next) 
				gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(((tMenuData*)ptr2->data)->pItem), false);
		}
		return true;
	}
	for (GList *ptr=data->items; ptr!=NULL; ptr=ptr->next) {
		if (CheckItem((tMenuData*)ptr->data, cmd_id, check, ptr))
			return true;
	}
	return false;
}

bool CMenuBar::IsChecked(int cmd_id)
{
	bool checked = false, found = false;

	m_Updating = true;
	for (GSList *ptr=m_Data; ptr!=NULL && !found; ptr=ptr->next) {
		tMenuData *data = (tMenuData*)ptr->data;
		for (GList *ptr2=data->items; ptr2!=NULL; ptr2=ptr2->next)  {
			if (IsItemChecked((tMenuData*)ptr2->data, cmd_id, &checked)) {
				found = true;
				break;
			}
		}
	}
	m_Updating = false;
	return checked;
}

bool CMenuBar::IsItemChecked(tMenuData *data, int cmd_id, bool *value)
{
	if (data->cmd_id == cmd_id) {
		*value = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(data->pItem))!=FALSE;
		return true;
	}
	for (GList *ptr=data->items; ptr!=NULL; ptr=ptr->next) {
		if (IsItemChecked((tMenuData*)ptr->data, cmd_id, value))
			return true;
	}
	return false;
}
