/*
* Copyright © 2009-2017 freedcpp, http://code.google.com/p/freedcpp
* Copyright © 2011-2025 BMDC
* This file is part of BMDC.
* BMDC 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.
*
* BMDC 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 Xfce-nameday-plugin. If not, see <https://www.gnu.org/licenses/>.
*/

#include "searchspy.hh"

#include "../dcpp/SearchManager.h"
#include "../dcpp/TimerManager.h"
#include "../dcpp/Util.h"
#include "settingsmanager.hh"
#include "search.hh"
#include "wulformanager.hh"
#include "GuiUtil.hh"

using namespace std;
using namespace dcpp;

const GActionEntry SearchSpy::win_entries[] = {
    { "on-search", onSearchItemClicked_gui  , NULL, NULL, NULL },
    { "remove-item", onRemoveItemClicked_gui  , NULL, NULL, NULL },
};

SearchSpy::SearchSpy():
	BookEntry(Entry::SEARCH_SPY, _("Search Spy"), "searchspy")
{
	
	GSimpleActionGroup* simple = g_simple_action_group_new ();
	g_simple_action_group_add_entries(simple, win_entries, G_N_ELEMENTS (win_entries), (gpointer)this);
	gtk_widget_insert_action_group(getContainer(), "searchspy" ,G_ACTION_GROUP(simple));
	gtk_widget_insert_action_group(getLabelBox(), "searchspy" ,G_ACTION_GROUP(simple));
	gtk_widget_insert_action_group(getWidget("searchSpyView"), "searchspy" ,G_ACTION_GROUP(simple));

	FrameSize = (SearchType)WGETI("search-spy-frame");
	Waiting = (guint)WGETI("search-spy-waiting");
	Top = (guint)WGETI("search-spy-top");

	// Configure the dialog
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(getWidget("ignoreTTHSearchCheckButton")), SETTING(SPY_FRAME_IGNORE_TTH_SEARCHES));
	gtk_window_set_transient_for(GTK_WINDOW(getWidget("TopSearchDialog")), GTK_WINDOW(WulforManager::get()->getMainWindow()->getContainer()));
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(getWidget("frameSpinButton")), (double)FrameSize);
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(getWidget("waitingSpinButton")), (double)Waiting);
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(getWidget("topSpinButton")), (double)Top);

	// Initialize search list treeview
	searchView.setView(GTK_TREE_VIEW(getWidget("searchSpyView")), TRUE, "searchspy");
	searchView.insertColumn(_("Search String"), G_TYPE_STRING, TreeView::ICON_STRING_TEXT_COLOR, 305, "icon", "color");
	searchView.insertColumn(_("Count"), G_TYPE_STRING, TreeView::STRING, 70);
	searchView.insertColumn(_("Time"), G_TYPE_STRING, TreeView::STRING, 90);
	searchView.insertColumn(_("Status"), G_TYPE_STRING, TreeView::STRING, 90);
	searchView.insertHiddenColumn("type", G_TYPE_STRING);
	searchView.insertHiddenColumn("count", G_TYPE_UINT);
	searchView.insertHiddenColumn("tick", G_TYPE_UINT64);
	searchView.insertHiddenColumn("icon", G_TYPE_STRING);
	searchView.insertHiddenColumn("order", G_TYPE_STRING);
	searchView.insertHiddenColumn("color", G_TYPE_STRING);
	searchView.finalize();

	searchStore = gtk_list_store_newv(searchView.getColCount(), searchView.getGTypes());
	gtk_tree_view_set_model(searchView.get(), GTK_TREE_MODEL(searchStore));
	g_object_unref(searchStore);

	searchSelection = gtk_tree_view_get_selection(searchView.get());
	gtk_tree_selection_set_mode(searchSelection, GTK_SELECTION_MULTIPLE);
	searchView.setSortColumn_gui(_("Search String"), "count");
	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(searchStore), searchView.col("count"), GTK_SORT_DESCENDING);
	gtk_tree_view_column_set_sort_indicator(gtk_tree_view_get_column(searchView.get(), searchView.col(_("Search String"))), TRUE);

	topView.setView(GTK_TREE_VIEW(getWidget("topView")));
	topView.insertColumn(_("Search String"), G_TYPE_STRING, TreeView::STRING, -1);
	topView.insertHiddenColumn("type", G_TYPE_STRING);
	topView.finalize();

	topStore = gtk_list_store_newv(topView.getColCount(), topView.getGTypes());
	gtk_tree_view_set_model(topView.get(), GTK_TREE_MODEL(topStore));
	g_object_unref(topStore);

	g_signal_connect(getWidget("clearFrameButton"), "clicked", G_CALLBACK(onClearFrameClicked_gui), (gpointer)this);
	g_signal_connect(getWidget("updateFrameButton"), "clicked", G_CALLBACK(onUpdateFrameClicked_gui), (gpointer)this);
	g_signal_connect(getWidget("showTopButton"), "clicked", G_CALLBACK(onShowTopClicked_gui), (gpointer)this);
	g_signal_connect(getWidget("clearTopButton"), "clicked", G_CALLBACK(onClearTopClicked_gui), (gpointer)this);
	g_signal_connect(getWidget("searchTopButton"), "clicked", G_CALLBACK(onSearchTopClicked_gui), (gpointer)this);
	g_signal_connect(getWidget("removeTopButton"), "clicked", G_CALLBACK(onRemoveTopClicked_gui), (gpointer)this);
	g_signal_connect(getWidget("ignoreTTHSearchCheckButton"), "toggled", G_CALLBACK(onIgnoreTTHSearchToggled_gui), (gpointer)this);
//	g_signal_connect(searchView.get(), "key-release-event", G_CALLBACK(onKeyReleased_gui), (gpointer)this);
	g_signal_connect(getWidget("okButton"), "clicked", G_CALLBACK(onOKButtonClicked_gui), (gpointer)this);

	GtkGesture *gesture;
  	gesture = gtk_gesture_click_new ();
  	gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 3);
  	g_signal_connect (gesture, "pressed",
                    G_CALLBACK (onClickPressed_gui), (gpointer)this);
  	g_signal_connect (gesture, "released",
                    G_CALLBACK (onClickReleased_gui), (gpointer)this);
  	gtk_widget_add_controller (GTK_WIDGET(searchView.get()), GTK_EVENT_CONTROLLER (gesture));

	aSearchColor = WGETS("search-spy-a-color");
	tSearchColor = WGETS("search-spy-t-color");
	qSearchColor = WGETS("search-spy-q-color");
	cSearchColor = WGETS("search-spy-c-color");
	rSearchColor = WGETS("search-spy-r-color");
}


void SearchSpy::onClickPressed_gui(GtkGestureClick* /*gesture*/,
                                   int                /*n_press*/,
                                   double             x,
                                   double             y,
                                   gpointer         *data)
{

	SearchSpy* hub = (SearchSpy*)data;

	GMenu *menu = g_menu_new ();
	GMenuItem* item = g_menu_item_new("Search Item", "searchspy.on-search" );
	g_menu_append_item(menu ,item);
	
	GMenuItem* match = g_menu_item_new("Remove Item", "searchspy.remove-item" );
	g_menu_append_item(menu ,match);

	GtkWidget *pop = gtk_popover_menu_new_from_model(G_MENU_MODEL(menu));
	gtk_widget_set_parent(pop, GTK_WIDGET(hub->searchView.get()));
	gtk_popover_set_pointing_to(GTK_POPOVER(pop), &(const GdkRectangle){x,y,1,1});
	gtk_popover_popup (GTK_POPOVER(pop));

}

void SearchSpy::onClickReleased_gui (GtkGestureClick* gesture,
                                   int                /*n_press*/,
                                   double             x,
                                   double             y,
                                   GtkWidget         *data)
{
  gtk_gesture_set_state (GTK_GESTURE (gesture),
                         GTK_EVENT_SEQUENCE_CLAIMED);
	
}

SearchSpy::~SearchSpy()
{
	WSET("search-spy-frame", (int)gtk_spin_button_get_value(GTK_SPIN_BUTTON(getWidget("frameSpinButton"))));
	WSET("search-spy-waiting", (int)gtk_spin_button_get_value(GTK_SPIN_BUTTON(getWidget("waitingSpinButton"))));
	WSET("search-spy-top", (int)gtk_spin_button_get_value(GTK_SPIN_BUTTON(getWidget("topSpinButton"))));

	TimerManager::getInstance()->removeListener(this);
	ClientManager::getInstance()->removeListener(this);
}

void SearchSpy::show()
{
	ClientManager::getInstance()->addListener(this);
	TimerManager::getInstance()->addListener(this);
}

void SearchSpy::preferences_gui()
{
	FrameSize = (SearchType)WGETI("search-spy-frame");
	Waiting = (guint)WGETI("search-spy-waiting");
	Top = (guint)WGETI("search-spy-top");

	resetFrame();

	// reset colors
	aSearchColor = WGETS("search-spy-a-color");
	tSearchColor = WGETS("search-spy-t-color");
	qSearchColor = WGETS("search-spy-q-color");
	cSearchColor = WGETS("search-spy-c-color");
	rSearchColor = WGETS("search-spy-r-color");

	string color, order;
	GtkTreeIter iter;

	for (UnMapIter::const_iterator it = searchIters.begin(); it != searchIters.end(); ++it)
	{
		iter = it->second;
		order = searchView.getString(&iter, "order");
		guint count = searchView.getValue<guint>(&iter, "count");

		// reset count
		if (count > Top)
		{
			gtk_list_store_set(searchStore, &iter,
				searchView.col(_("Count")), Util::toString(Top).c_str(),
				searchView.col("count"), Top,
				-1);
		}

		switch (order[0])
		{
			case 'a': color = aSearchColor; break;
			case 'c': color = cSearchColor; break;
			case 'r': color = rSearchColor; break;
			case 't': color = tSearchColor; break;
			case 'q': color = qSearchColor; break;
		}
		gtk_list_store_set(searchStore, &iter, searchView.col("color"), color.c_str(), -1);
	}

	// reset spin button
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(getWidget("frameSpinButton")), (double)FrameSize);
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(getWidget("waitingSpinButton")), (double)Waiting);
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(getWidget("topSpinButton")), (double)Top);
}

void SearchSpy::resetFrame()
{
	GtkTreeIter iter;

	if (FrameSize > 0 && searchIters.size() > FrameSize)
	{
		SearchType i = 0;
		gtk_tree_selection_select_all(searchSelection);

		for (UnMapIter::const_iterator it = searchIters.begin(); it != searchIters.end(); ++it)
		{
			if (++i > FrameSize)
				break;

			iter = it->second;
			gtk_tree_selection_unselect_iter(searchSelection, &iter);
		}
	//	onRemoveItemClicked_gui(NULL, (gpointer)this);
	}
}

bool SearchSpy::findIter_gui(const string &search, GtkTreeIter *iter)
{
	UnMapIter::const_iterator it = searchIters.find(search);

	if (it != searchIters.end())
	{
		if (iter)
			*iter = it->second;

		return TRUE;
	}

	return FALSE;
}

void SearchSpy::addTop_gui(const string &search, const string &type)
{
	GtkTreeIter iter;
	GtkTreeModel *m = GTK_TREE_MODEL(topStore);
	gboolean valid = gtk_tree_model_get_iter_first(m, &iter);

	while (valid)
	{
		string line = topView.getString(&iter, _("Search String"));

		if (search == line)
		{
			return;
		}
		valid = gtk_tree_model_iter_next(m, &iter);
	}

	gtk_list_store_append(topStore, &iter);
	gtk_list_store_set(topStore, &iter,
		topView.col(_("Search String")), search.c_str(),
		topView.col("type"), type.c_str(),
		-1);

	if (SETTING(BOLD_SEARCH_SPY))
		setUrgent_gui();
}

void SearchSpy::updateFrameSearch_gui(const string search, const string type)
{
	g_return_if_fail(FrameSize > 0 && FrameSize <= 256);

	GtkTreeIter iter;

	if (findIter_gui(search, &iter))
	{
		uint64_t tick = GET_TICK();

		if (searchView.getString(&iter, "order")[0] == 't')
		{
			updateFrameStatus_gui(NULL, tick);
			return;
		}

		string time = Util::formatTime("%H:%M:%S", GET_TIME());
		guint count = searchView.getValue<guint>(&iter, "count");
		string order = "c";

		if (count >= Top)
		{
			addTop_gui(search, type);

			count = 0;
			order = "t";
		}
		count++;
		gtk_list_store_set(searchStore, &iter,
			searchView.col(_("Count")), Util::toString(count).c_str(),
			searchView.col(_("Time")), time.c_str(),
			searchView.col("count"), count,
			searchView.col("tick"), tick,
			searchView.col("order"), order.c_str(),
			-1);
		updateFrameStatus_gui(NULL, tick);
	}
	else
	{
		string time = Util::formatTime("%H:%M:%S", GET_TIME());
		uint64_t tick = GET_TICK();

		if (searchIters.size() >= FrameSize)
		{
			if (updateFrameStatus_gui(&iter, tick))
			{
				string oldstring = searchView.getString(&iter, _("Search String"));
				searchIters.erase(oldstring);
				searchIters.insert(UnMapIter::value_type(search, iter));

				tick = GET_TICK();
				gtk_list_store_set(searchStore, &iter,
					searchView.col(_("Search String")), search.c_str(),
					searchView.col(_("Count")), "1",
					searchView.col(_("Time")), time.c_str(),
					searchView.col(_("Status")), _("waiting..."),
					searchView.col("type"), type.c_str(),
					searchView.col("count"), 1,
					searchView.col("tick"), tick,
					searchView.col("icon"), BMDC_STOCK_FIND,
					searchView.col("order"), "r",
					searchView.col("color"), rSearchColor.c_str(),
					-1);
			}
			return;
		}

		gtk_list_store_insert_with_values(searchStore, &iter, searchIters.size(),
			searchView.col(_("Search String")), search.c_str(),
			searchView.col(_("Count")), "1",
			searchView.col(_("Time")), time.c_str(),
			searchView.col("type"), type.c_str(),
			searchView.col("count"), 1,
			searchView.col("tick"), tick,
			searchView.col("order"), "a",
			-1);

		searchIters.insert(UnMapIter::value_type(search, iter));
		updateFrameStatus_gui(NULL, tick);
	}
}

bool SearchSpy::updateFrameStatus_gui(GtkTreeIter *iter, uint64_t tick)
{
	if (tick == 0)
		tick = GET_TICK();

	uint64_t second = (uint64_t)Waiting * 1000;
	bool n = FALSE;
	string status, icon;
	GtkTreeIter itree;
	string color;

	for (UnMapIter::const_iterator it = searchIters.begin(); it != searchIters.end(); ++it)
	{
		itree = it->second;
		uint64_t gettick = searchView.getValue<uint64_t>(&itree, "tick");
		string order = searchView.getString(&itree, "order");

		dcassert(tick >= gettick);
		if (tick - gettick > second)
		{
			if (iter)
			{
				*iter = itree;
				n = TRUE;
			}
			status = "?";
			icon = BMDC_STOCK_DIALOG_QUESTION;

			color = qSearchColor;
			gtk_list_store_set(searchStore, &itree, searchView.col("order"), "q", -1);
		}
		else
		{
			if (order[0] == 't')
			{
				status = _("top...");
				icon = BMDC_STOCK_DIALOG_QUESTION;
			}
			else
			{
				status = _("waiting...");
				icon = BMDC_STOCK_FIND;
			}

			switch (order[0])
			{
				case 'a': color = aSearchColor; break;
				case 'c': color = cSearchColor; break;
				case 'r': color = rSearchColor; break;
				case 't': color = tSearchColor; break;
				default:  color = qSearchColor; // fix don't know color
			}
		}

		gtk_list_store_set(searchStore, &itree,
			searchView.col(_("Status")), status.c_str(),
			searchView.col("icon"), icon.c_str(),
			searchView.col("color"), color.c_str(),
			-1);
	}

	return n;
}

void SearchSpy::updateFrameStatus_gui()
{
	updateFrameStatus_gui(NULL, uint64_t(0));
	setStatus_gui(_("Updated frame search"));
}

void SearchSpy::setStatus_gui(const string text)
{
	if (!text.empty())
	{
		GtkWidget *status = getWidget("statusbar");
		gtk_statusbar_pop(GTK_STATUSBAR(status), 0);
		gtk_statusbar_push(GTK_STATUSBAR(status), 0, ("[" + Util::getShortTimeString() + "] " + text).c_str());
	}
}

void SearchSpy::onOKButtonClicked_gui(GtkWidget *, gpointer data)
{
	SearchSpy *s =  (SearchSpy *) data;

	s->FrameSize = (SearchType)gtk_spin_button_get_value(GTK_SPIN_BUTTON(s->getWidget("frameSpinButton")));
	s->Waiting = (guint)gtk_spin_button_get_value(GTK_SPIN_BUTTON(s->getWidget("waitingSpinButton")));
	s->Top = (guint)gtk_spin_button_get_value(GTK_SPIN_BUTTON(s->getWidget("topSpinButton")));
	s->resetFrame();
	s->resetCount();

	s->setStatus_gui(_("top/waiting/frame: ") + Util::toString(s->Top) + "/" + Util::toString(s->Waiting) + "/" +
		Util::toString(s->FrameSize));

	WSET("search-spy-frame", int(s->FrameSize));
	WSET("search-spy-waiting", int(s->Waiting));
	WSET("search-spy-top", int(s->Top));
}

void SearchSpy::resetCount()
{
	GtkTreeIter iter;
	for (UnMapIter::const_iterator it = searchIters.begin(); it != searchIters.end(); ++it)
	{
		iter = it->second;
		guint count = searchView.getValue<guint>(&iter, "count");

		if (count > Top)
		{
			gtk_list_store_set(searchStore, &iter,
				searchView.col(_("Count")), Util::toString(Top).c_str(),
				searchView.col("count"), Top,
				-1);
		}
	}
}

void SearchSpy::onShowTopClicked_gui(GtkWidget*, gpointer data)
{
	SearchSpy *s = (SearchSpy *)data;

	GtkWidget *dialog = s->getWidget("TopSearchDialog");
//	gint response = gtk_dialog_run(GTK_DIALOG(dialog));
	gtk_widget_hide(dialog);
}

void SearchSpy::onClearTopClicked_gui(GtkWidget*, gpointer data)
{
	SearchSpy *s = (SearchSpy *)data;
	gtk_list_store_clear(s->topStore);
}

void SearchSpy::onSearchTopClicked_gui(GtkWidget*, gpointer data)
{
	SearchSpy *s = (SearchSpy *)data;

	GtkTreeIter iter;
	GtkTreeSelection *selection = gtk_tree_view_get_selection(s->topView.get());

	if (gtk_tree_selection_get_selected(selection, NULL, &iter))
	{
		string type = s->topView.getString(&iter, "type");
		string search = s->topView.getString(&iter, _("Search String"));
		SearchEntry *ss = WulforManager::get()->getMainWindow()->addSearch_gui();

		if (type[0] == 't')
		{
			ss->putValue_gui(search, 0, SearchManager::SIZE_DONTCARE, SearchManager::TYPE_TTH);
		}
		else if (type[0] == 's')
		{
			ss->putValue_gui(search, 0, SearchManager::SIZE_DONTCARE, SearchManager::TYPE_ANY);
		}
	}
}

void SearchSpy::onRemoveTopClicked_gui(GtkWidget*, gpointer data)
{
	SearchSpy *s = (SearchSpy *)data;

	GtkTreeIter iter;
	GtkTreeSelection *selection = gtk_tree_view_get_selection(s->topView.get());

	if (gtk_tree_selection_get_selected(selection, NULL, &iter))
	{
		gtk_list_store_remove(s->topStore, &iter);
	}
}

void SearchSpy::onClearFrameClicked_gui(GtkWidget*, gpointer data)
{
	SearchSpy *s = (SearchSpy *)data;

	gtk_list_store_clear(s->searchStore);
	s->searchIters.clear();
	s->setStatus_gui(_("Cleaned frame search"));
}

void SearchSpy::onUpdateFrameClicked_gui(GtkWidget*, gpointer data)
{
	SearchSpy *s = (SearchSpy *)data;

	s->updateFrameStatus_gui();
}

void SearchSpy::onIgnoreTTHSearchToggled_gui(GtkWidget *widget, gpointer )
{
	gboolean toggle = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
	SettingsManager::getInstance()->set(SettingsManager::SPY_FRAME_IGNORE_TTH_SEARCHES, toggle);
}

void SearchSpy::onRemoveItemClicked_gui(GtkWidget *widget,GVariant  *parameter, gpointer data)
{
	SearchSpy *s = (SearchSpy *)data;

	if (gtk_tree_selection_count_selected_rows(s->searchSelection) > 0)
	{
		StringList search;
		GtkTreeIter iter;
		GtkTreePath *path;
		GList *list = gtk_tree_selection_get_selected_rows(s->searchSelection, NULL);

		for (GList *i = list; i; i = i->next)
		{
			path = (GtkTreePath *) i->data;
			if (gtk_tree_model_get_iter(GTK_TREE_MODEL(s->searchStore), &iter, path))
			{
				search.push_back(s->searchView.getString(&iter, _("Search String")));
			}
			gtk_tree_path_free(path);
		}
		g_list_free(list);

		for (StringIterC it = search.begin(); it != search.end(); ++it)
		{
			if (s->findIter_gui(*it, &iter))
			{
				gtk_list_store_remove(s->searchStore, &iter);
				s->searchIters.erase(*it);
			}
		}
	}
}

void SearchSpy::onSearchItemClicked_gui(GtkWidget *widget,GVariant  *parameter, gpointer data)
{
	SearchSpy *s = (SearchSpy *)data;

	if (gtk_tree_selection_count_selected_rows(s->searchSelection) == 1)
	{
		GList *list = gtk_tree_selection_get_selected_rows(s->searchSelection, NULL);

		if (list != NULL)
		{
			GtkTreeIter iter;
			GtkTreePath *path = (GtkTreePath *) list->data;

			if (gtk_tree_model_get_iter(GTK_TREE_MODEL(s->searchStore), &iter, path))
			{
				string type = s->searchView.getString(&iter, "type");
				string search = s->searchView.getString(&iter, _("Search String"));
				SearchEntry *ss = WulforManager::get()->getMainWindow()->addSearch_gui();

				if (type[0] == 't')
				{
					ss->putValue_gui(search, 0, SearchManager::SIZE_DONTCARE, SearchManager::TYPE_TTH);
				}
				else if (type[0] == 's')
				{
					ss->putValue_gui(search, 0, SearchManager::SIZE_DONTCARE, SearchManager::TYPE_ANY);
				}
			}
			gtk_tree_path_free(path);
		}
		g_list_free(list);
	}
}
/*
gboolean SearchSpy::onButtonPressed_gui(GtkWidget*, GdkEventButton *event, gpointer data)
{
	SearchSpy *s = (SearchSpy *)data;
	s->previous = event->type;

	if (event->button == 3)
	{
		GtkTreePath *path;

		if (gtk_tree_view_get_path_at_pos(s->searchView.get(), (gint)event->x, (gint)event->y, &path, NULL, NULL, NULL))
		{
			bool selected = gtk_tree_selection_path_is_selected(s->searchSelection, path);
			gtk_tree_path_free(path);

			if (selected)
				return TRUE;
		}
	}
	return FALSE;
}

gboolean SearchSpy::onButtonReleased_gui(GtkWidget*, GdkEventButton *event, gpointer data)
{
	SearchSpy *s = (SearchSpy *)data;

	if (gtk_tree_selection_count_selected_rows(s->searchSelection) > 0)
	{
		if (event->button == 1 && s->previous == GDK_2BUTTON_PRESS)
		{
			// search string/tth
			s->onSearchItemClicked_gui(NULL, data);
		}
		else if (event->button == 3 && event->type == GDK_BUTTON_RELEASE)
		{
			// show menu
			#if GTK_CHECK_VERSION(3,22,0)
			gtk_menu_popup_at_pointer(GTK_MENU(s->getWidget("menu")),NULL);
			#else
			gtk_menu_popup(GTK_MENU(s->getWidget("menu")), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
			#endif
		}
	}

	return FALSE;
}

gboolean SearchSpy::onKeyReleased_gui(GtkWidget*, GdkEventKey *event, gpointer data)
{
	SearchSpy *s = (SearchSpy *)data;

	if (gtk_tree_selection_count_selected_rows(s->searchSelection) > 0)
	{
		if (event->keyval == GDK_KEY_Delete || event->keyval == GDK_KEY_BackSpace)
		{
			s->onRemoveItemClicked_gui(NULL, data);
		}
		else if (event->keyval == GDK_KEY_Menu || (event->keyval == GDK_KEY_F10 && event->state & GDK_SHIFT_MASK))
		{
			#if GTK_CHECK_VERSION(3,22,0)
			gtk_menu_popup_at_pointer(GTK_MENU(s->getWidget("menu")),NULL);
			#else
			gtk_menu_popup(GTK_MENU(s->getWidget("menu")), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
			#endif
		}
	}

	return FALSE;
}
*/
void SearchSpy::on(ClientManagerListener::IncomingSearch, const string& s) noexcept
{
	if(SETTING(SPY_FRAME_IGNORE_TTH_SEARCHES) && s.compare(0, 4, "TTH:") == 0)
		return;

	string search, type;
	if(s.compare(0, 4, "TTH:") == 0)
	{
		type = "t";
		search = s.substr(4);
	}
	else
	{
		type = "s";
		search = s;
		string::size_type i;
		while((i = search.find("$")) != string::npos)
			search[i] = ' ';
	}
	typedef Func2<SearchSpy, string, string> F2;
	F2 *func = new F2(this, &SearchSpy::updateFrameSearch_gui, search, type);
	WulforManager::get()->dispatchGuiFunc(func);
}

void SearchSpy::on(TimerManagerListener::Minute, uint32_t ) noexcept
{
	typedef Func0<SearchSpy> F0;
	F0 *func = new F0(this, &SearchSpy::updateFrameStatus_gui);
	WulforManager::get()->dispatchGuiFunc(func);
}
