/* $Id: e2_bookmark.c 853 2008-04-07 10:38:17Z tpgww $

Copyright (C) 2004-2008 tooar <tooar@gmx.net>

This file is part of emelfm2.
emelfm2 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, or (at your option)
any later version.

emelfm2 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 emelFM2; see the file GPL. If not, contact the Free Software
Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

/**
@file src/e2_bookmark.c
@brief bookmark functions

*/

#include "e2_bookmark.h"
#include <string.h>
#include "e2_dialog.h"
#include "e2_task.h"
#include "e2_option_tree.h"

static E2_OptionSet *set;

  /*****************/
 /***** utils *****/
/*****************/

/**
@brief re-create all toolbars which have bookmarks
@return
*/
static void _e2_bookmark_recreate_toolbars (void)
{
	E2_ToolbarData **thisbar;
	for (thisbar = app.bars; *thisbar != NULL; thisbar++)
	{
		if ((*thisbar)->rt->has_bookmarks) //no need to check here for bar presence
			e2_toolbar_recreate ((*thisbar)->rt);
	}
}
/**
@brief delete bookmark at @a iter
@param iter pointer to tree iter for row to be deleted
@return
*/
static void _e2_bookmark_delete_mark (GtkTreeIter *iter)
{
	e2_option_tree_del_direct (set, iter);
	_e2_bookmark_recreate_toolbars ();
	if ((app.context_menu != NULL) && (GTK_IS_MENU (app.context_menu)))
		gtk_menu_popdown (GTK_MENU (app.context_menu));
}
/**
@brief refresh the bookmarks menus
@return
*/
static void _e2_bookmark_apply_new (void)
{
//FIXME = find a smarter way to do this
	_e2_bookmark_recreate_toolbars ();
}

  /*********************/
 /***** callbacks *****/
/*********************/

/**
@brief (de)sensitize option tree buttons for selected option tree row
Config dialog page buttons are de-sensitized if the row is the toolbar 'parent'
@param selection pointer to selection
@param model UNUSED
@param path
@param path_currently_selected UNUSED
@param set data struct for the keybings option
@return TRUE always (the row is always selectable)
*/
static gboolean _e2_bookmark_tree_selection_check_cb
	(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path,
	gboolean path_currently_selected, E2_OptionSet *set)
{
	GtkTreeIter iter;
	if (gtk_tree_model_get_iter (set->ex.tree.model, &iter, path))
	{
		gchar *label;
		gtk_tree_model_get (set->ex.tree.model, &iter, 0, &label, -1);
		gboolean result = ! g_str_equal (label, _C(39));
		g_free (label);
		GtkTreeView *view = gtk_tree_selection_get_tree_view (selection);
		e2_option_tree_adjust_buttons (view, result);
	}
	return TRUE;
}
/**
@brief ensure that cells in the same row as any category name are hidden

Checks whether @a iter in @a model has the name of the taskbar
'parent' in column 0. If so, the cell content is hidden,

@param model treemodel for the bookmarks option tree
@param iter pointer to iter with data for the current model row
@param cell cell renderer UNUSED
@param data pointer to data UNUSED
@return TRUE (cell is visible) if row is not the task bar parent
*/
static gboolean _e2_bookmark_visible_check_cb (GtkTreeModel *model,
	GtkTreeIter *iter, GtkCellRenderer *cell, gpointer data)
{
	gchar *label;
	gtk_tree_model_get (model, iter, 0, &label, -1);
	gboolean result = !g_str_equal (label, _C(39));
	g_free (label);
	return result;
}
/**
@brief decide whether tree row is draggable
Checks whether column 0 of the current row has the string
which is the name of the "tool bar" 'parent' row.
If so, the row is not draggable
@param drag_source GtkTreeDragSource data struct
@param path tree path to a row on which user is initiating a drag
@return TRUE if the row can be dragged
*/
static gboolean _e2_bookmark_tree_draggable_check_cb
	(GtkTreeDragSource *drag_source, GtkTreePath *path)
{
	if (!GTK_IS_TREE_MODEL (drag_source))
		return TRUE;
	GtkTreeModel *model = GTK_TREE_MODEL (drag_source);
	GtkTreeIter iter;
	if (gtk_tree_model_get_iter (model, &iter, path))
	{
		gchar *label;
		gtk_tree_model_get (model, &iter, 0, &label, -1);
		gboolean result = !g_str_equal (label, _C(39));
		g_free (label);
		return result;
	}
	return TRUE;
}
/**
@brief process result from delete-confirmation dialog
@param dialog UNUSED the confirmation-dialog
@param response the response from the dialog
@param ref treerow reference for the bookmark to be deleted
@return
*/
static void _e2_bookmark_confirm_response_cb (GtkDialog *dialog,
	gint response, GtkTreeRowReference *ref)
{
	if (response == GTK_RESPONSE_YES)
	{
		if (ref != NULL)
		{
			GtkTreePath *path = gtk_tree_row_reference_get_path (ref);
			if (path != NULL)
			{
				GtkTreeIter iter;
				if (gtk_tree_model_get_iter (set->ex.tree.model, &iter, path))
					_e2_bookmark_delete_mark (&iter);
			}
		}
	}
	gtk_widget_destroy (GTK_WIDGET (dialog));
}
/**
@brief add an item (directory) to the bookmarks list

This is a callback. The activated item @a widget may be a menu
item (eg in the panebar bookmarks sub-menu) or a context menu
item (file pane or bookmarks widget).
In the former case, it will have been processed as an action, and will
have a string argument @a arg, with "pane1" or "pane2", and
possibly including one of "top", "child" (othewise the add is treated
as "after").
When @a widget is a context menu item, @a arg will be NULL.
This function needs to know which pane to use for the path to add
to the list. For menu callbacks, @a arg needs to include "pane1" or
"pane2" as appropriate. For context-menu callbacks, @a widget
will have associated data "bookmark-pane" = 0 (active), 1 or 2.

@param widget NULL if a bookmarks menu item widget was selected, else a context menu item widget
@param arg string which indicates where to put the new bookmark, or NULL
@return
*/
static void _e2_bookmark_add_cb (GtkWidget *widget, gchar *arg)
{
	gboolean before = FALSE;
	gboolean parent = FALSE;
	guint pane = E2PANECUR;	//default to active pane
	if ((arg != NULL) && (*arg != '\0'))
	{
		if (strstr (arg, _A(11)))  //_(pane1
			pane = E2PANE1;
		else if (strstr (arg, _A(12)))  //_(pane2
			pane = E2PANE2;
		if (strstr (arg, _A(114)))  //_(top
			before = TRUE;
		if (strstr (arg, _A(104))) //_(child
		{
			parent = TRUE;
			before = TRUE;
		}
	}
	else if (widget != NULL)
		pane = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), "bookmark-pane"));

	//get the relevant dir to add to the marks list
	gchar *dirname;
	switch (pane)
	{
#ifdef E2_VFSTMP
	//FIXME dirs when not mounted local
#else
		case E2PANE1:
			dirname = app.pane1_view.dir;
			break;
		case E2PANE2:
			dirname = app.pane2_view.dir;
			break;
		default:
			dirname = curr_view->dir;
#endif
			break;
	}

	gchar *basename = g_path_get_basename (dirname);
	//double any '_' CHECKME what's this about ?
/*	if (strchr (basename, '_'))
	{
		gchar *freeme = basename;
		basename = e2_utils_str_replace (basename, "_", "__");
		g_free (freeme);
	} */
	void *options[4];
	options[0] = basename;	//menu label
	options[1] = "";  //no icon
	//strip the trailing / for paths other than root
	gchar *s = NULL;
	gint len = strlen (dirname);
	if (len > 1)
	{
		s = g_strdup (dirname);	//non-NULL = freeme later
		*(s+len-1) = '\0';
		options[2] = s;
	}
	else
		options[2] = dirname;  //tip = path
	options[3] = options[2]; //path
//	E2_OptionSet *set = e2_option_get ("bookmarks");  //internal name USE THE GLOBAL VALUE
	GtkTreeIter iter;
	GtkTreeIter iter2;
	GtkTreePath *path = NULL;
	if (widget != NULL)
		path = g_object_get_data (G_OBJECT (widget), "parent-mark");
	if ((path != NULL) && gtk_tree_model_get_iter (set->ex.tree.model, &iter2, path))
		e2_option_tree_add (set, &iter, &iter2, !parent, before, 4, options);
	else
		//with no parent, the behaviour of "before" is vice versa
		e2_option_tree_add (set, &iter, NULL, FALSE, !before, 4, options);

	_e2_bookmark_recreate_toolbars ();

	if (s != NULL)
		g_free (s);
	g_free (basename);
}
/**
@brief callback for deleting a bookmark (with any children)
@param widget UNUSED the widget which was activated
@param path treestore path of bookmark to be deleted
@return
*/
static void _e2_bookmark_delete_cb (GtkWidget *widget, GtkTreePath *path)
{
	GtkTreeIter iter;
	if (gtk_tree_model_get_iter (set->ex.tree.model, &iter, path))
	{
		gint children = gtk_tree_model_iter_n_children (set->ex.tree.model, &iter);
		if (e2_option_bool_get ("bookmarks-confirm-delete")
			|| ((children > 0) && (e2_option_bool_get ("bookmarks-confirm-multi-delete")))
		)
		{
			gchar *name;
			gtk_tree_model_get (set->ex.tree.model, &iter, 0, &name, -1);

			gchar *question;
			gchar *prompt = _("Are you sure that you want to delete the bookmark");
			//we fudge here on translating the trailing '?'
			if (children > 0)
				question = g_strdup_printf ("%s '<b>%s</b>' %s <b>%d %s</b> ?",
					prompt, name, _("and"), children, (children == 1) ? _("child") : _("children"));
			else
				question = g_strdup_printf ("%s '<b>%s</b>' ?", prompt, name);

			GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION, question,
				_("confirm bookmark delete"), _e2_bookmark_confirm_response_cb,
				gtk_tree_row_reference_new (set->ex.tree.model, path));
			e2_dialog_set_negative_response (dialog, GTK_RESPONSE_NO);
			e2_dialog_show (dialog, app.main_window, 0, &E2_BUTTON_NO, &E2_BUTTON_YES, NULL);
			g_free (question);
			g_free (name);
		}
		else	//no confirmation
			_e2_bookmark_delete_mark (&iter);
	}
}
/**
@brief button click callback

Middle button will open mark in other pane, and make
that pane active, if the relevant options are in force.
Right button will create and pop up a context menu, if not
blocked (blocks will be in place for toolbar buttons, but
not for (sub)menu items)
Context menu items 'add' and 'add-child' need to know
which pane (directory) to use for the mark

@param widget clicked button or menu item
@param event gdk event data struct
@param path pointer to bookmarks set treemodel path to the string which holds the bookmark directory path
@return TRUE (block further handlers) if certain btn 2 or 3 conditions are satisfied
*/
gboolean e2_bookmark_click_cb (GtkWidget *widget,
	GdkEventButton *event, GtkTreePath* path)
{
	printd (DEBUG, "e2_bookmark_click_cb");
	//get which pane's menu was clicked (NULL for active pane)
	gpointer pane = g_object_get_data (G_OBJECT (widget), "bookmark-pane");
	switch (event->button)
	{
		case 2:
		{
			if (e2_option_bool_get ("bookmarks-button2-other"))
			{
				gint other = (curr_pane == &app.pane1) ? 2 : 1;
				if (GPOINTER_TO_INT (pane) != other)
				{
					gchar *mark = g_object_get_data (G_OBJECT (widget), "bookmark-path");
					if (mark != NULL)
					{
#ifdef E2_VFSTMP
			//FIXME bookmark parsing and setting for v-dirs
						if (0)
							e2_pane_change_space_byuri (other_pane, "some string");
#endif
						e2_pane_change_dir (other_pane, mark);	//CHECKME mark utf8
						if (e2_option_bool_get ("bookmarks-focus-after-open"))
							e2_pane_activate_other ();
						return TRUE;
					}
				}
			}
		}
			break;
		case 3:
		{
			//check whether context menu is valid for this widget
			gpointer context = g_object_get_data (G_OBJECT (widget), "with-context");
			if (context != NULL && GPOINTER_TO_INT (context) == 1)
				return FALSE;
			printd (DEBUG, "bookmark menu");
			GtkWidget *menu = gtk_menu_new ();
			GtkWidget *menuitem;
			menuitem = e2_menu_add (menu, _("_Add after"), GTK_STOCK_ADD,
				_("Bookmark the current directory after the selected bookmark"),
				_e2_bookmark_add_cb, NULL);
			g_object_set_data (G_OBJECT (menuitem), "parent-mark", path);
			//pass through the pane no. (or NULL for default) to use when adding an item
			g_object_set_data (G_OBJECT (menuitem), "bookmark-pane", pane);

			menuitem = e2_menu_add (menu, _("Add as _child"), GTK_STOCK_INDENT,
				_("Bookmark the current directory a a child of the selected bookmark"),
				_e2_bookmark_add_cb, _A(104));	//_(child
			g_object_set_data (G_OBJECT (menuitem), "parent-mark", path);
			//pass through the pane no. (or NULL for default) to use when adding an item
			g_object_set_data (G_OBJECT (menuitem), "bookmark-pane", pane);

			e2_menu_add (menu, _("_Delete"), GTK_STOCK_DELETE,
				_("Delete the selected bookmark, and its children if any"),
				_e2_bookmark_delete_cb, path);
/*  just rely on the edit item in the context menu, now
			e2_menu_add_separator (menu);
			e2_menu_add (menu, _("_Edit"), GTK_STOCK_PREFERENCES,
				_("Open the bookmarks configuration dialog"),
				_e2_bookmarks_configure, NULL);
*/
			e2_menu_popup (menu, event->button, event->time);
			return TRUE;
		}
			break;
		default:
			break;
	}
	return FALSE;
}

  /*******************/
 /***** actions *****/
/*******************/

/**
@brief add item to bookmarks menu
@param from pointer to widget activated to initiate the action
@param art pointer to runtime data for the action
@return TRUE always
*/
static gboolean _e2_bookmark_add (gpointer from, E2_ActionRuntime *art)
{
	_e2_bookmark_add_cb (NULL, (gchar *) art->data);
	return TRUE;
}
/**
@brief show bookmarks menu in active pane
@param from pointer to widget activated to initiate the action
@param art pointer to runtime data for the action
@return TRUE if menu was created
*/
static gboolean _e2_bookmark_show (gpointer from, E2_ActionRuntime *art)
{
	E2_OptionSet *set = e2_option_get ("bookmarks");
	GtkTreeIter iter;
	if (gtk_tree_model_get_iter_first (set->ex.tree.model, &iter))
	{
		gchar *action_name = g_strconcat (_A(0),".",_A(24),NULL);
		E2_Action *action = e2_action_get (action_name);
		g_free (action_name);
		GtkWidget *menu = gtk_menu_new ();
		e2_menu_create_bookmarks_menu (action, set, menu, &iter);
		guint32 time = gtk_get_current_event_time ();
		gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
		(GtkMenuPositionFunc) e2_fileview_set_menu_position,
			curr_view->treeview, 0, time);
		return TRUE;
	}
	return FALSE;
}
/**
@brief open bookmark @a path, in pane @a num
Action data includes pointerized 0 (from "<widget>" action),
1 or 2 (pane number), or NULL for current pane
Action data includes path of directory to open, utf8 string
Downstream functions expect BGL to be closed
@param from pointer to widget activated to initiate the action
@param art pointer to runtime data for the action
@return TRUE always
*/
gboolean e2_bookmark_open (gpointer from, E2_ActionRuntime *art)
{
	guint panenum = GPOINTER_TO_UINT (art->action->data);
	gchar *path = (gchar *) art->data;
	printd (DEBUG, "e2_bookmark_open (num:%d,path:%s)", panenum, path);
	switch (panenum)
	{
		case E2PANE1:
#ifdef E2_VFSTMP
		//FIXME handle case where pane is non-mounted
			if (0)
				e2_pane_change_space_byuri (&app.pane1, "someplace");
#endif
			e2_pane_change_dir (&app.pane1, path);
			if (e2_option_bool_get ("bookmarks-focus-after-open")
				&& curr_pane != &app.pane1)
					e2_pane_activate_other ();
			break;
		case E2PANE2:
#ifdef E2_VFSTMP
		//FIXME handle case where pane is non-mounted
			if (0)
				e2_pane_change_space_byuri (&app.pane2, "someplace");
#endif
			e2_pane_change_dir (&app.pane2, path);
			if (e2_option_bool_get ("bookmarks-focus-after-open")
				&& curr_pane != &app.pane2)
					e2_pane_activate_other ();
			break;
		default:
#ifdef E2_VFSTMP
		//FIXME handle case where pane is non-mounted
			if (0)
				e2_pane_change_space_byuri (curr_pane, "someplace");
#endif
			e2_pane_change_dir (NULL, path);
			break;
	}
	return TRUE;
}

/**
@brief open a dialog showing bookmarks config data
@param from pointer to widget activated to initiate the action
@param art pointer to runtime data for the action
@return TRUE if the dialog was created
*/
static gboolean _e2_bookmarks_configure (gpointer from, E2_ActionRuntime *art)
{
	return (
	e2_config_dialog_single ("bookmarks", _e2_bookmark_apply_new, TRUE) //internal name, no translation
	!= NULL);
}

  /******************/
 /***** public *****/
/******************/

/* this used only at session end, don't bother
void e2_bookmark_clean ()
{
//	e2_action_unregister (_A(21));	//_("<bookmark>"));
} */
/**
@brief register bookmark-related actions
@return
*/
void e2_bookmark_actions_register (void)
{
	gchar *action_name = g_strconcat(_A(10),".",_A(21),NULL); //pane_active.<bookmarks>
		e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		_e2_bookmark_show, NULL, FALSE);
	action_name = g_strconcat(_A(0),".",_A(26),NULL); //bookmark.add
		e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		_e2_bookmark_add, NULL, TRUE);
	action_name = g_strconcat(_A(0),".",_A(24),NULL);	//bookmark.<list> = not pane specific
	e2_action_register (action_name, E2_ACTION_TYPE_BOOKMARKS,
		e2_bookmark_open, NULL, TRUE,	//data = pointerised 0 = current pane
		E2_ACTION_EXCLUDE_ACCEL, NULL);
	action_name = g_strconcat(_A(2),".",_C(1),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		_e2_bookmarks_configure, NULL, FALSE);
}
/**
@brief install default tree options for boookmarks
This function is called only if the default is missing from the config file
@param set pointer to set data
@return
*/
static void _e2_bookmark_tree_defaults (E2_OptionSet *set)
{
	e2_option_tree_setup_defaults (set,
	g_strdup("bookmarks=<"),  //internal name
	g_strconcat(_C(39),"|||",NULL),	//needs to be same as arg to taskbar item - see e2_toolbar.c
	g_strconcat("\t","<b>",_("_home"),"</b>|gtk-home|$HOME|~",NULL),
//this next is too specific for a default value
//	g_strconcat("\t",_("down"),"||",_("/mnt/downloads"),"|"G_DIR_SEPARATOR_S"mnt"G_DIR_SEPARATOR_S"downloads",NULL),
	g_strconcat("\t",_("cdrom"),"|gtk-cdrom|",G_DIR_SEPARATOR_S"media"G_DIR_SEPARATOR_S"cdrom",
		"|"G_DIR_SEPARATOR_S"media"G_DIR_SEPARATOR_S"cdrom",NULL),
	g_strconcat(G_DIR_SEPARATOR_S"||",_("root"),"|"G_DIR_SEPARATOR_S,NULL),
	g_strconcat(_("home"),"||",_("Your home directory"),"|~",NULL),
	g_strconcat(_("media"),"||",G_DIR_SEPARATOR_S"media","|"G_DIR_SEPARATOR_S"media",NULL),	//_I( no point in translating tip = path
	g_strconcat(_("mnt"),"||",G_DIR_SEPARATOR_S"mnt","|"G_DIR_SEPARATOR_S"mnt",NULL),	//ditto
	g_strconcat(_("usr"),"||",G_DIR_SEPARATOR_S"usr","|"G_DIR_SEPARATOR_S"usr",NULL),	//ditto
	g_strconcat(_("usr/local"),"||",G_DIR_SEPARATOR_S"usr"G_DIR_SEPARATOR_S"local",
		"|"G_DIR_SEPARATOR_S"usr"G_DIR_SEPARATOR_S"local",NULL),	//ditto
	g_strconcat(_("trash"),"||",_("default trash directory"),
		"|~"G_DIR_SEPARATOR_S".local"G_DIR_SEPARATOR_S"share"G_DIR_SEPARATOR_S"Trash"G_DIR_SEPARATOR_S"files",
		NULL),
	g_strdup(">"),
	NULL);
}
/**
@brief register bookmark-related options
@return
*/
void e2_bookmark_options_register (void)
{
//no screen rebuilds needed after any change to these options
//	E2_OptionSet * SET THE GLOBAL VALUE
	set = e2_option_tree_register ("bookmarks", _C(1), _C(1),  //_("bookmarks"
		NULL, _e2_bookmark_tree_selection_check_cb, _e2_bookmark_tree_draggable_check_cb,
		E2_OPTION_TREE_UP_DOWN | E2_OPTION_TREE_ADD_DEL,
		E2_OPTION_FLAG_BASIC);
	e2_option_tree_add_column (set, _("Label"), E2_OPTION_TREE_TYPE_STR, 0, G_DIR_SEPARATOR_S,
		0, NULL, NULL);
	e2_option_tree_add_column (set, _("Icon"), E2_OPTION_TREE_TYPE_ICON, 0, "",
		0, _e2_bookmark_visible_check_cb, NULL);
	e2_option_tree_add_column (set, _("Tooltip"), E2_OPTION_TREE_TYPE_STR, 0, "",
		0, _e2_bookmark_visible_check_cb, NULL);
	e2_option_tree_add_column (set, _("Path"), E2_OPTION_TREE_TYPE_STR, 0, G_DIR_SEPARATOR_S,
		0, _e2_bookmark_visible_check_cb, NULL);
	e2_option_tree_create_store (set);

	e2_option_tree_prepare_defaults (set, _e2_bookmark_tree_defaults);

	gchar* group_name = g_strconcat(_C(1),".",_C(26),":",_C(25),NULL);  //_("bookmarks.options:miscellaneous"
	e2_option_bool_register ("bookmarks-button2-other",
		group_name, _("open bookmark in other pane on middle-button click"),
		_("Clicking the middle mouse button on a bookmark will open it in the other file pane"),
		NULL, TRUE,
		E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP);
	e2_option_bool_register ("bookmarks-focus-after-open",
		group_name, _("focus file pane after opening a bookmark in it"),
		_("After opening a bookmark in the inactive file pane, that pane will become the active one"),
		NULL, TRUE,
		E2_OPTION_FLAG_ADVANCED);

	group_name = g_strconcat(_C(1),".",_C(26),":",_C(7),NULL);  //_("bookmarks.options:confirmation"
	e2_option_bool_register ("bookmarks-confirm-delete",
		group_name, _("confirm any delete of a selected bookmark"),
		_("You will be asked to confirm, before deleting any bookmark"),
		"bookmarks-confirm-multi-delete", FALSE,
		E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP);
	e2_option_bool_register ("bookmarks-confirm-multi-delete",
		group_name, _("confirm any delete of multiple bookmarks"),
		_("You will be asked to confirm, before deleting any bookmark that has 'children'"),
		NULL, TRUE,
		E2_OPTION_FLAG_ADVANCED);
}
