/*
 * Copyright (C) 2004 John Ellis
 * Copyright (C) 2008 - 2016 The Geeqie Team
 *
 * Author: John Ellis
 *
 * 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.
 */

#include "ui-bookmark.h"

#include <algorithm>
#include <array>
#include <cstdlib>
#include <cstring>
#include <string>

#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gdk/gdk.h>
#include <glib-object.h>
#include <pango/pango.h>

#include "compat.h"
#include "history-list.h"
#include "intl.h"
#include "layout.h"
#include "main-defines.h"
#include "misc.h"
#include "pixbuf-util.h"
#include "ui-file-chooser.h"
#include "ui-fileops.h"
#include "ui-menu.h"
#include "ui-misc.h"
#include "ui-tabcomp.h"
#include "ui-utildlg.h"
#include "uri-utils.h"

/*
 *-----------------------------------------------------------------------------
 * bookmarks
 *-----------------------------------------------------------------------------
 */

#define BOOKMARK_DATA_KEY "bookmarkdata"
#define MARKER_PATH "[path]"
#define MARKER_ICON "[icon]"

namespace
{

struct BookButtonData
{
	std::string text;
	std::string name;
	std::string path;
	std::string icon;
};

struct BookMarkData
{
	GtkWidget *widget;
	GtkWidget *box;
	std::string key;

	BookmarkSelectFunc select_func;

	gboolean no_defaults;
	gboolean editable;
	gboolean only_directories;

	GtkWidget *active_button;
};

struct BookPropData
{
	GtkWidget *name_entry;
	GtkWidget *path_entry;
	GtkWidget *icon_entry;

	std::string key;
	std::string text;
};

enum {
	TARGET_URI_LIST,
	TARGET_X_URL,
	TARGET_TEXT_PLAIN
};

#if !HAVE_GTK4
constexpr std::array<GtkTargetEntry, 3> bookmark_drop_types{{
	{ const_cast<gchar *>("text/uri-list"), 0, TARGET_URI_LIST },
	{ const_cast<gchar *>("x-url/http"),    0, TARGET_X_URL },
	{ const_cast<gchar *>("_NETSCAPE_URL"), 0, TARGET_X_URL }
}};

constexpr std::array<GtkTargetEntry, 2> bookmark_drag_types{{
	{ const_cast<gchar *>("text/uri-list"), 0, TARGET_URI_LIST },
	{ const_cast<gchar *>("text/plain"),    0, TARGET_TEXT_PLAIN }
}};
#endif

std::vector<BookMarkData *> bookmark_widget_list;
std::vector<std::pair<std::string, std::string>> bookmark_default_list;

const gchar *bookmark_icon(const gchar *path)
{
	if (!isfile(path)) return nullptr;

	g_autofree gchar *real_path = realpath(path, nullptr);

	return strstr(real_path, get_collections_dir()) ? PIXBUF_INLINE_COLLECTION : GQ_ICON_FILE;
}

} // namespace

static void bookmark_populate_all(const std::string &key);


static BookButtonData *bookmark_from_string(const gchar *text)
{
	if (!text)
		{
		auto *b = new BookButtonData();
		b->name = _("New Bookmark");
		b->path = homedir();
		return b;
		}

	const gchar *path_ptr = strstr(text, MARKER_PATH);
	const gchar *icon_ptr = strstr(text, MARKER_ICON);

	if (path_ptr && icon_ptr && icon_ptr < path_ptr)
		{
		log_printf("warning, bookmark icon must be after path\n");
		return nullptr;
		}

	auto *b = new BookButtonData();
	b->text = text;

	if (path_ptr)
		{
		static const size_t marker_path_len = strlen(MARKER_PATH);
		gint l;

		l = path_ptr - text;
		b->name.assign(text, l);
		path_ptr += marker_path_len;
		if (icon_ptr)
			{
			l = icon_ptr - path_ptr;
			b->path.assign(path_ptr, l);
			}
		else
			{
			b->path = path_ptr;
			}
		}
	else
		{
		b->name = text;
		}

	if (icon_ptr)
		{
		static const size_t marker_icon_len = strlen(MARKER_ICON);
		b->icon = icon_ptr + marker_icon_len;
		}

	return b;
}

static gchar *bookmark_string(const gchar *name, const gchar *path, const gchar *icon)
{
	if (!name) name = _("New Bookmark");

	if (icon && icon[0] != '\0')
		{
		return g_strdup_printf("%s" MARKER_PATH "%s" MARKER_ICON "%s", name, path, icon);
		}

	return g_strdup_printf("%s" MARKER_PATH "%s", name, path);
}

static void bookmark_select_cb(GtkWidget *button, gpointer data)
{
	auto bm = static_cast<BookMarkData *>(data);
	BookButtonData *b;

	b = static_cast<BookButtonData *>(g_object_get_data(G_OBJECT(button), "bookbuttondata"));
	if (!b) return;

	if (bm->select_func) bm->select_func(b->path.c_str());
}

static void bookmark_edit_ok_cb(GenericDialog *, gpointer data)
{
	auto p = static_cast<BookPropData *>(data);
	const gchar *name;
	const gchar *icon;

	name = gq_gtk_entry_get_text(GTK_ENTRY(p->name_entry));
	g_autofree gchar *path = remove_trailing_slash(gq_gtk_entry_get_text(GTK_ENTRY(p->path_entry)));
	icon = gq_gtk_entry_get_text(GTK_ENTRY(p->icon_entry));

	g_autofree gchar *new_string = bookmark_string(name, path, icon);

	if (!p->text.empty())
		{
		history_list_item_change(p->key.c_str(), p->text.c_str(), new_string);
		}
	else
		{
		history_list_add_to_key(p->key.c_str(), new_string, 0);
		}

	if (path && path[0] != '\0') tab_completion_append_to_history(p->path_entry, path);
	if (icon && icon[0] != '\0') tab_completion_append_to_history(p->icon_entry, icon);

	bookmark_populate_all(p->key);
}

static void bookmark_edit(const std::string &key, const BookButtonData *bb, GtkWidget *parent)
{
	GtkWidget *table;

	auto *p = new BookPropData();
	p->key = key;
	p->text = bb->text;

	GenericDialog *gd = generic_dialog_new(_("Edit Bookmark"), "bookmark_edit",
	                                       parent, TRUE,
	                                       generic_dialog_dummy_cb, p);
	g_object_set_data_full(G_OBJECT(gd->dialog), "bookpropdata", p, delete_cb<BookPropData>);

	generic_dialog_add_message(gd, nullptr, _("Edit Bookmark"), nullptr, FALSE);

	generic_dialog_add_button(gd, GQ_ICON_OK, "OK",
				  bookmark_edit_ok_cb, TRUE);

	table = pref_table_new(gd->vbox, 3, 2, FALSE, TRUE);
	pref_table_label(table, 0, 0, _("Name:"), GTK_ALIGN_END);

	p->name_entry = gtk_entry_new();
	gtk_widget_set_size_request(p->name_entry, 300, -1);
	gq_gtk_entry_set_text(GTK_ENTRY(p->name_entry), bb->name.c_str());
	gq_gtk_grid_attach_default(GTK_GRID(table), p->name_entry, 1, 2, 0, 1);
	generic_dialog_attach_default(gd, p->name_entry);
	gtk_widget_show(p->name_entry);

	pref_table_label(table, 0, 1, _("Path:"), GTK_ALIGN_END);

	p->path_entry = tab_completion_new_with_history(nullptr, bb->path.c_str(), "bookmark_path", -1);
	tab_completion_add_select_button(p->path_entry, nullptr, TRUE, nullptr, nullptr, nullptr);
	gq_gtk_grid_attach_default(GTK_GRID(table), tab_completion_get_box(p->path_entry), 1, 2, 1, 2);
	generic_dialog_attach_default(gd, p->path_entry);

	pref_table_label(table, 0, 2, _("Icon:"), GTK_ALIGN_END);

	p->icon_entry = tab_completion_new_with_history(nullptr, bb->icon.c_str(), "bookmark_icons", -1);
	tab_completion_add_select_button(p->icon_entry, _("Select icon"), FALSE, nullptr, nullptr, nullptr);
	gq_gtk_grid_attach_default(GTK_GRID(table), tab_completion_get_box(p->icon_entry), 1, 2, 2, 3);
	generic_dialog_attach_default(gd, p->icon_entry);

	gtk_widget_show(gd->dialog);
}

static void bookmark_move(BookMarkData *bm, GtkWidget *button, gint direction)
{
	if (!bm->editable) return;

	auto *b = static_cast<BookButtonData *>(g_object_get_data(G_OBJECT(button), "bookbuttondata"));
	if (!b) return;

	g_autoptr(GList) list = gq_gtk_widget_get_children(GTK_WIDGET(bm->box));

	gint p = g_list_index(list, button);
	if (p < 0 || p + direction < 0) return;

	std::string key_holder = bm->key;
	bm->key = "_TEMPHOLDER";
	history_list_item_move(key_holder.c_str(), b->text.c_str(), -direction);
	bookmark_populate_all(key_holder);
	bm->key = key_holder;

	gq_gtk_box_reorder_child(GTK_BOX(bm->box), button, p + direction);
}

static void bookmark_menu_prop_cb(GtkWidget *widget, gpointer data)
{
	auto bm = static_cast<BookMarkData *>(data);

	if (!bm->active_button) return;

	auto *b = static_cast<BookButtonData *>(g_object_get_data(G_OBJECT(bm->active_button), "bookbuttondata"));
	if (!b) return;

	bookmark_edit(bm->key, b, widget);
}

template<gint direction>
static void bookmark_menu_move_cb(GtkWidget *, gpointer data)
{
	auto *bm = static_cast<BookMarkData *>(data);

	if (!bm->active_button) return;

	bookmark_move(bm, bm->active_button, direction);
}

static void bookmark_menu_remove_cb(GtkWidget *, gpointer data)
{
	auto bm = static_cast<BookMarkData *>(data);

	if (!bm->active_button) return;

	auto *b = static_cast<BookButtonData *>(g_object_get_data(G_OBJECT(bm->active_button), "bookbuttondata"));
	if (!b) return;

	history_list_item_remove(bm->key.c_str(), b->text.c_str());
	bookmark_populate_all(bm->key);
}

static void bookmark_menu_popup(BookMarkData *bm, GtkWidget *button, bool local)
{
	GtkWidget *menu;

	bm->active_button = button;

	menu = popup_menu_short_lived();
	menu_item_add_icon_sensitive(menu, _("_Properties…"), PIXBUF_INLINE_ICON_PROPERTIES, bm->editable,
		      G_CALLBACK(bookmark_menu_prop_cb), bm);
	menu_item_add_icon_sensitive(menu, _("Move _up"), GQ_ICON_GO_UP, bm->editable,
	                             G_CALLBACK(bookmark_menu_move_cb<-1>), bm);
	menu_item_add_icon_sensitive(menu, _("Move _down"), GQ_ICON_GO_DOWN, bm->editable,
	                             G_CALLBACK(bookmark_menu_move_cb<1>), bm);
	menu_item_add_icon_sensitive(menu, _("_Remove"), GQ_ICON_REMOVE, bm->editable,
		      G_CALLBACK(bookmark_menu_remove_cb), bm);

	if (local)
		{
		gtk_menu_popup_at_widget(GTK_MENU(menu), button, GDK_GRAVITY_NORTH_EAST, GDK_GRAVITY_CENTER, nullptr);
		}
	else
		{
		gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr);
		}
}

static gboolean bookmark_press_common(GtkWidget *button, guint button_id, gpointer data)
{
	auto bm = static_cast<BookMarkData *>(data);

	if (button_id != GDK_BUTTON_SECONDARY) return FALSE;

	bookmark_menu_popup(bm, button, false);

	return TRUE;
}

#if HAVE_GTK4
static void bookmark_gesture_press_cb(GtkGestureClick *gesture, gint, gdouble, gdouble, gpointer data)
{
	GtkWidget *button = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture));
	bookmark_press_common(button, gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(gesture)), data);
}
#else
static gboolean bookmark_press_cb(GtkWidget *button, GdkEventButton *event, gpointer data)
{
	return bookmark_press_common(button, event->button, data);
}
#endif

static gboolean bookmark_keypress_cb(GtkWidget *button, GdkEventKey *event, gpointer data)
{
	auto bm = static_cast<BookMarkData *>(data);

	switch (event->keyval)
		{
		case GDK_KEY_F10:
			if (!(event->state & GDK_CONTROL_MASK)) return FALSE;
			/* fall through */
		case GDK_KEY_Menu:
			bookmark_menu_popup(bm, button, true);
			return TRUE;
			break;
		case GDK_KEY_Up:
			if (event->state & GDK_SHIFT_MASK)
				{
				bookmark_move(bm, button, -1);
				return TRUE;
				}
			break;
		case GDK_KEY_Down:
			if (event->state & GDK_SHIFT_MASK)
				{
				bookmark_move(bm, button, 1);
				return TRUE;
				}
			break;
		default:
			break;
		}

	return FALSE;
}

#if !HAVE_GTK4
static void bookmark_drag_set_data(GtkWidget *button,
				   GdkDragContext *context, GtkSelectionData *selection_data,
				   guint, guint, gpointer data)
{
	return;

	auto bm = static_cast<BookMarkData *>(data);
	BookButtonData *b;

	if (gdk_drag_context_get_dest_window(context) == gtk_widget_get_window(bm->widget)) return;

	b = static_cast<BookButtonData *>(g_object_get_data(G_OBJECT(button), "bookbuttondata"));
	if (!b) return;

	g_autoptr(GList) list = g_list_append(nullptr, b->path.data());
	uri_selection_data_set_uris_from_pathlist(selection_data, list);
}

static void bookmark_drag_begin(GtkWidget *button, GdkDragContext *context, gpointer)
{
	GdkPixbuf *pixbuf;
	gint x;
	gint y;
	GtkAllocation allocation;
	GdkSeat *seat;
	GdkDevice *device;

	gtk_widget_get_allocation(button, &allocation);

	pixbuf = gdk_pixbuf_get_from_window(gtk_widget_get_window(button),
					    allocation.x, allocation.y,
					    allocation.width, allocation.height);
	seat = gdk_display_get_default_seat(gdk_window_get_display(gtk_widget_get_window(button)));
	device = gdk_seat_get_pointer(seat);
	get_pointer_position(button, device, &x, &y, nullptr);

	gtk_drag_set_icon_pixbuf(context, pixbuf,
				 x - allocation.x, y - allocation.y);
	g_object_unref(pixbuf);
}
#endif

static gboolean bookmark_tooltip_cb(GtkWidget *button, gint, gint, gboolean,
                                    GtkTooltip *, gpointer user_data)
{
	auto *b = static_cast<BookButtonData *>(user_data);

	gtk_widget_set_tooltip_text(button, b->path.c_str());

	return FALSE;
}

static void bookmark_add_button(BookMarkData *bm, const gchar *text)
{
	BookButtonData *b = bookmark_from_string(text);
	if (!b) return;

	if (b->name == ".")
		{
		b->path = history_list_find_last_path_by_key("path_list");

		g_autofree gchar *buf = bookmark_string(".", b->path.c_str(), b->icon.c_str());
		history_list_item_change("bookmarks", b->text.c_str(), buf);
		b->text = buf;
		}

	GtkWidget *button = gtk_button_new();
	gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
	gq_gtk_box_pack_start(GTK_BOX(bm->box), button, FALSE, FALSE, 0);
	gtk_widget_show(button);

	g_object_set_data_full(G_OBJECT(button), "bookbuttondata", b, delete_cb<BookButtonData>);

	GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PREF_PAD_BUTTON_GAP);
	gq_gtk_container_add(button, box);
	gtk_widget_show(box);

	GtkWidget *image;
	if (!b->icon.empty())
		{
		g_autoptr(GdkPixbuf) pixbuf = nullptr;

		if (isfile(b->icon.c_str()))
			{
			g_autofree gchar *iconl = path_from_utf8(b->icon.c_str());
			pixbuf = gdk_pixbuf_new_from_file(iconl, nullptr);
			}
		else
			{
			gint w = 16;
			gint h = 16;
			gq_gtk_icon_size_lookup(GTK_ICON_SIZE_BUTTON, &w, &h);

			pixbuf = gq_gtk_icon_theme_load_icon_copy(gq_icon_theme_get_default(), b->icon.c_str(), w, GTK_ICON_LOOKUP_NO_SVG);
			}

		if (pixbuf)
			{
			gint w = 16;
			gint h = 16;
			gq_gtk_icon_size_lookup(GTK_ICON_SIZE_BUTTON, &w, &h);

			g_autoptr(GdkPixbuf) scaled = gdk_pixbuf_scale_simple(pixbuf, w, h, GDK_INTERP_BILINEAR);
			image = gtk_image_new_from_pixbuf(scaled);
			}
		else
			{
			image = gq_gtk_image_new_from_icon_name(GQ_ICON_DIRECTORY, GTK_ICON_SIZE_BUTTON);
			}
		}
	else
		{
		image = gq_gtk_image_new_from_icon_name(GQ_ICON_DIRECTORY, GTK_ICON_SIZE_BUTTON);
		}
	gq_gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 0);
	gtk_widget_show(image);

	pref_label_new(box, b->name.c_str());

	g_signal_connect(G_OBJECT(button), "clicked",
	                 G_CALLBACK(bookmark_select_cb), bm);
#if HAVE_GTK4
	GtkGesture *gesture = gtk_gesture_click_new();
	gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), GDK_BUTTON_SECONDARY);
	g_signal_connect(gesture, "pressed", G_CALLBACK(bookmark_gesture_press_cb), bm);
	gtk_widget_add_controller(button, GTK_EVENT_CONTROLLER(gesture));
#else
	g_signal_connect(G_OBJECT(button), "button_press_event",
	                 G_CALLBACK(bookmark_press_cb), bm);
#endif
	g_signal_connect(G_OBJECT(button), "key_press_event",
	                 G_CALLBACK(bookmark_keypress_cb), bm);

#if !HAVE_GTK4
	gq_gtk_drag_source_set(button, GDK_BUTTON1_MASK,
	                       bookmark_drag_types.data(), bookmark_drag_types.size(),
	                       static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
	gq_drag_g_signal_connect(G_OBJECT(button), "drag_data_get",
	                         G_CALLBACK(bookmark_drag_set_data), bm);
	gq_drag_g_signal_connect(G_OBJECT(button), "drag_begin",
	                         G_CALLBACK(bookmark_drag_begin), nullptr);
#endif

	gtk_widget_set_has_tooltip(button, TRUE);
	g_signal_connect(G_OBJECT(button), "query-tooltip", G_CALLBACK(bookmark_tooltip_cb), b);
}

static void bookmark_populate(BookMarkData *bm)
{
#if HAVE_GTK4
	while (GtkWidget *child = gtk_widget_get_first_child(bm->box))
		{
		gtk_box_remove(GTK_BOX(bm->box), child);
		}
#else
	static const auto destroy_widget = [](GtkWidget *widget, gpointer)
	{
		gq_gtk_widget_destroy(widget);
	};
	gq_gtk_container_foreach(bm->box, destroy_widget, nullptr);
#endif

	if (!bm->no_defaults && !history_list_find_by_key(bm->key.c_str()))
		{
		if (bookmark_default_list.empty())
			{
			g_autofree gchar *home_buf = bookmark_string(_("Home"), homedir(), nullptr);
			history_list_add_to_key(bm->key.c_str(), home_buf, 0);

			if (bm->key != "shortcuts")
				{
				g_autofree gchar *buf = bookmark_string(".", history_list_find_last_path_by_key("path_list"), nullptr);
				history_list_add_to_key(bm->key.c_str(), buf, 0);
				}

			const gchar *path = get_desktop_dir();
			if (isname(path))
				{
				g_autofree gchar *buf = bookmark_string(_("Desktop"), path, nullptr);
				history_list_add_to_key(bm->key.c_str(), buf, 0);
				}
			}

		for (const auto &[name, default_path] : bookmark_default_list)
			{
			const gchar *path = default_path.c_str();

			if (name == ".")
				{
				if (bm->key == "shortcuts") continue;

				path = history_list_find_last_path_by_key("path_list");
				}

			g_autofree gchar *buf = bookmark_string(name.c_str(), path, nullptr);
			history_list_add_to_key(bm->key.c_str(), buf, 0);
			}
		}

	const HistoryList *history_list = history_list_find_by_key(bm->key.c_str());
	if (!history_list) return;

	for (auto work = history_list->crbegin(); work != history_list->crend(); ++work)
		{
		bookmark_add_button(bm, work->c_str());
		}
}

static void bookmark_populate_all(const std::string &key)
{
	for (BookMarkData *bm : bookmark_widget_list)
		{
		if (bm->key != key) continue;

		bookmark_populate(bm);
		}
}

#if !HAVE_GTK4
static void bookmark_dnd_get_data(GtkWidget *, GdkDragContext *,
				  gint, gint,
				  GtkSelectionData *selection_data, guint,
				  guint, gpointer data)
{
	auto bm = static_cast<BookMarkData *>(data);

	if (!bm->editable) return;

	GList *list = uri_pathlist_from_gtk_selection_data(selection_data);

	for (GList *work = list; work; work = work->next)
		{
		auto path = static_cast<gchar *>(work->data);

		if (bm->only_directories && !isdir(path)) continue;

		g_autofree gchar *buf = bookmark_string(filename_from_path(path), path, bookmark_icon(path));
		history_list_add_to_key(bm->key.c_str(), buf, 0);
		}

	g_list_free_full(list, g_free);

	bookmark_populate_all(bm->key);
}
#endif

static void bookmark_data_destroy(gpointer data)
{
	auto *bm = static_cast<BookMarkData *>(data);

	if (auto it = std::find(bookmark_widget_list.cbegin(), bookmark_widget_list.cend(), bm);
	    it != bookmark_widget_list.cend())
		{
		bookmark_widget_list.erase(it);
		}

	delete bm;
}

GtkWidget *bookmark_list_new(const gchar *key, const BookmarkSelectFunc &select_func)
{
	auto *bm = new BookMarkData();
	bm->key = key ? key : "bookmarks";
	bm->select_func = select_func;
	bm->no_defaults = FALSE;
	bm->editable = TRUE;
	bm->only_directories = FALSE;

	GtkWidget *scrolled = gq_gtk_scrolled_window_new(nullptr, nullptr);

	g_autoptr(PangoLayout) layout = gtk_widget_create_pango_layout(scrolled, "reasonable width");
	GqSize size;
	pango_layout_get_pixel_size(layout, &size.width, &size.height);
	gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(scrolled), size.width);

	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

	bm->box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
	gq_gtk_container_add(scrolled, bm->box);
	gtk_widget_show(bm->box);

	bookmark_populate(bm);

	g_object_set_data_full(G_OBJECT(bm->box), BOOKMARK_DATA_KEY, bm, bookmark_data_destroy);
	g_object_set_data(G_OBJECT(scrolled), BOOKMARK_DATA_KEY, bm);
	bm->widget = scrolled;

#if !HAVE_GTK4
	gq_gtk_drag_dest_set(scrolled,
	                  static_cast<GtkDestDefaults>(GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP),
	                  bookmark_drop_types.data(), bookmark_drop_types.size(),
	                  static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
	g_signal_connect(G_OBJECT(scrolled), "drag_data_received",
			 G_CALLBACK(bookmark_dnd_get_data), bm);
#endif

	bookmark_widget_list.push_back(bm);

	return scrolled;
}

void bookmark_list_set_key(GtkWidget *list, const gchar *key)
{
	BookMarkData *bm;

	if (!list || !key) return;

	bm = static_cast<BookMarkData *>(g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY));
	if (!bm || bm->key == key) return;

	bm->key = key;

	bookmark_populate(bm);
}

void bookmark_list_set_no_defaults(GtkWidget *list, gboolean no_defaults)
{
	BookMarkData *bm;

	bm = static_cast<BookMarkData *>(g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY));
	if (!bm) return;

	bm->no_defaults = no_defaults;
}

void bookmark_list_set_editable(GtkWidget *list, gboolean editable)
{
	BookMarkData *bm;

	bm = static_cast<BookMarkData *>(g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY));
	if (!bm) return;

	bm->editable = editable;
}

void bookmark_list_set_only_directories(GtkWidget *list, gboolean only_directories)
{
	BookMarkData *bm;

	bm = static_cast<BookMarkData *>(g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY));
	if (!bm) return;

	bm->only_directories = only_directories;
}

void bookmark_list_add(GtkWidget *list, const gchar *name, const gchar *path)
{
	BookMarkData *bm;

	bm = static_cast<BookMarkData *>(g_object_get_data(G_OBJECT(list), BOOKMARK_DATA_KEY));
	if (!bm) return;

	g_autofree gchar *buf = bookmark_string(name, path, bookmark_icon(path));
	history_list_add_to_key(bm->key.c_str(), buf, 0);

	bookmark_populate_all(bm->key);
}

/**
 * @brief Allows apps to set up the defaults
 */
static void bookmark_add_default(const gchar *name, const gchar *path)
{
	if (!name || !path) return;

	bookmark_default_list.emplace_back(name, path);
}

void bookmark_setup_default()
{
	g_autofree gchar *current_path = get_current_dir();
	bookmark_add_default(".", current_path);
	bookmark_add_default(_("Home"), homedir());
	bookmark_add_default(_("Desktop"), get_desktop_dir());
	bookmark_add_default(_("Collections"), get_collections_dir());
}

static void bookmark_add_response_cb(GtkFileChooser *chooser, gint response_id, gpointer data)
{
	if (response_id == GTK_RESPONSE_ACCEPT)
		{
		GtkWidget *name_entry = gtk_file_chooser_get_extra_widget(chooser);
		const gchar *name = gtk_entry_get_text(GTK_ENTRY(name_entry));
		gboolean empty_name = (g_strcmp0(name, "") == 0);

		g_autoptr(GFile) file = gtk_file_chooser_get_file(chooser);
		g_autofree gchar *selected_dir = g_file_get_path(file);

		auto *list = static_cast<GtkWidget *>(data);
		bookmark_list_add(list, empty_name ? filename_from_path(selected_dir) : name, selected_dir);
		}

	gq_gtk_widget_destroy(GTK_WIDGET(chooser));
}

void bookmark_add_dialog(const gchar *title, GtkWidget *list)
{
	FileChooserDialogData fcdd{};

	fcdd.action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
	fcdd.accept_text = _("Open");
	fcdd.data = list;
	fcdd.entry_text = _("Optional name…");
	fcdd.entry_tooltip =  _("Optional alias name for the shortcut.\nThis may be amended or added from the Sort Manager pane.\nIf none given, the basename of the folder is used");
	fcdd.filename = layout_get_path(get_current_layout());
	fcdd.response_callback = G_CALLBACK(bookmark_add_response_cb);
	fcdd.title = title;

	GtkFileChooserDialog *dialog = file_chooser_dialog_new(fcdd);

	gq_gtk_widget_show_all(GTK_WIDGET(dialog));
}

/*
 *-----------------------------------------------------------------------------
 * combo with history key
 *-----------------------------------------------------------------------------
 */

struct HistoryComboData
{
	GtkWidget *combo;
	GtkWidget *entry;
	std::string history_key;
	gint history_levels;
};

/* if text is NULL, entry is set to the most recent item */
GtkWidget *history_combo_new(GtkWidget **entry, const gchar *text,
                             std::string history_key, gint max_levels)
{
	auto *hc = new HistoryComboData();
	hc->history_key = std::move(history_key);
	hc->history_levels = max_levels;

	hc->combo = gtk_combo_box_text_new_with_entry();

	hc->entry = gq_gtk_bin_get_child(GTK_WIDGET(hc->combo));

	g_object_set_data_full(G_OBJECT(hc->combo), "history_combo_data", hc, delete_cb<HistoryComboData>);
	g_object_set_data(G_OBJECT(hc->entry), "history_combo_data", hc);

	gint n = 0;
	const HistoryList *history_list = history_list_find_by_key(hc->history_key.c_str());
	if (history_list)
		{
		n = history_list->size();
		for (const std::string &item : *history_list)
			{
			gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(hc->combo), item.c_str());
			}
		}

	if (text)
		{
		gq_gtk_entry_set_text(GTK_ENTRY(hc->entry), text);
		}
	else if (n > 0)
		{
		gtk_combo_box_set_active(GTK_COMBO_BOX(hc->combo), 0);
		}

	if (entry) *entry = hc->entry;
	return hc->combo;
}

/* if text is NULL, current entry text is used
 * widget can be the combo or entry widget
 */
void history_combo_append_history(GtkWidget *widget, const gchar *text)
{
	HistoryComboData *hc;

	hc = static_cast<HistoryComboData *>(g_object_get_data(G_OBJECT(widget), "history_combo_data"));
	if (!hc)
		{
		log_printf("widget is not a history combo\n");
		return;
		}

	g_autofree gchar *new_text = g_strdup(text ? text : gq_gtk_entry_get_text(GTK_ENTRY(hc->entry)));

	if (new_text && new_text[0] != '\0')
		{
		GtkTreeModel *store;

		history_list_add_to_key(hc->history_key.c_str(), new_text, hc->history_levels);

		gtk_combo_box_set_active(GTK_COMBO_BOX(hc->combo), -1);

		store = gtk_combo_box_get_model(GTK_COMBO_BOX(hc->combo));
		gtk_list_store_clear(GTK_LIST_STORE(store));

		const HistoryList *history_list = history_list_find_by_key(hc->history_key.c_str());
		if (!history_list) return;

		for (const std::string &item : *history_list)
			{
			gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(hc->combo), item.c_str());
			}
		}
}
/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
