1 /*
2  * Copyright © 2004-2008 Jens Oknelid, paskharen@gmail.com
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  *
18  * In addition, as a special exception, compiling, linking, and/or
19  * using OpenSSL with this program is allowed.
20  */
21 
22 #include "search.hh"
23 
24 #include <dcpp/FavoriteManager.h>
25 #include <dcpp/QueueManager.h>
26 #include <dcpp/ShareManager.h>
27 #include <dcpp/StringTokenizer.h>
28 #include <dcpp/Text.h>
29 #include <dcpp/UserCommand.h>
30 #include "UserCommandMenu.hh"
31 #include "wulformanager.hh"
32 #include "WulforUtil.hh"
33 
34 using namespace std;
35 using namespace dcpp;
36 
37 GtkTreeModel* Search::searchEntriesModel = NULL;
38 
Search()39 Search::Search():
40 	BookEntry(Entry::SEARCH, _("Search"), "search.glade", generateID()),
41 	droppedResult(0),
42 	searchHits(0),
43 	isHash(false),
44 	onlyFree(BOOLSETTING(SEARCH_ONLY_FREE_SLOTS)),
45 	previousGrouping(NOGROUPING)
46 {
47 	// Initialize the search entries combo box
48 	if (searchEntriesModel == NULL)
49 		searchEntriesModel = gtk_combo_box_get_model(GTK_COMBO_BOX(getWidget("comboboxentrySearch")));
50 	gtk_combo_box_set_model(GTK_COMBO_BOX(getWidget("comboboxentrySearch")), searchEntriesModel);
51 	searchEntry = gtk_bin_get_child(GTK_BIN(getWidget("comboboxentrySearch")));
52 	gtk_widget_grab_focus(getWidget("comboboxentrySearch"));
53 
54 	// Configure the dialog
55 	File::ensureDirectory(SETTING(DOWNLOAD_DIRECTORY));
56 	gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(getWidget("dirChooserDialog")), Text::fromUtf8(SETTING(DOWNLOAD_DIRECTORY)).c_str());
57 	gtk_dialog_set_alternative_button_order(GTK_DIALOG(getWidget("dirChooserDialog")), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1);
58 
59 	// Initialize check button options.
60 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(getWidget("checkbuttonSlots")), onlyFree);
61 	gtk_widget_set_sensitive(GTK_WIDGET(getWidget("checkbuttonSlots")), FALSE);
62 	gtk_widget_set_sensitive(GTK_WIDGET(getWidget("checkbuttonShared")), FALSE);
63 
64 	gtk_combo_box_set_active(GTK_COMBO_BOX(getWidget("comboboxSize")), 1);
65 	gtk_combo_box_set_active(GTK_COMBO_BOX(getWidget("comboboxUnit")), 2);
66 	gtk_combo_box_set_active(GTK_COMBO_BOX(getWidget("comboboxFile")), 0);
67 	gtk_combo_box_set_active(GTK_COMBO_BOX(getWidget("comboboxGroupBy")), (int)NOGROUPING);
68 
69 	// Initialize hub list treeview
70 	hubView.setView(GTK_TREE_VIEW(getWidget("treeviewHubs")));
71 	hubView.insertColumn("Search", G_TYPE_BOOLEAN, TreeView::BOOL, -1);
72 	hubView.insertColumn("Name", G_TYPE_STRING, TreeView::STRING, -1);
73 	hubView.insertHiddenColumn("Url", G_TYPE_STRING);
74 	hubView.finalize();
75 	hubStore = gtk_list_store_newv(hubView.getColCount(), hubView.getGTypes());
76 	gtk_tree_view_set_model(hubView.get(), GTK_TREE_MODEL(hubStore));
77 	g_object_unref(hubStore);
78 	GtkTreeViewColumn *col = gtk_tree_view_get_column(hubView.get(), hubView.col("Search"));
79 	GList *list = gtk_tree_view_column_get_cell_renderers(col);
80 	GtkCellRenderer *renderer = (GtkCellRenderer *)g_list_nth_data(list, 0);
81 	g_list_free(list);
82 
83 	// Initialize search result treeview
84 	resultView.setView(GTK_TREE_VIEW(getWidget("treeviewResult")), TRUE, "search");
85 	resultView.insertColumn(N_("Filename"), G_TYPE_STRING, TreeView::ICON_STRING, 250, "Icon");
86 	resultView.insertColumn(N_("User"), G_TYPE_STRING, TreeView::STRING, 100);
87 	resultView.insertColumn(N_("Type"), G_TYPE_STRING, TreeView::STRING, 65);
88 	resultView.insertColumn(N_("Size"), G_TYPE_STRING, TreeView::STRING, 90);
89 	resultView.insertColumn(N_("Path"), G_TYPE_STRING, TreeView::STRING, 100);
90 	resultView.insertColumn(N_("Slots"), G_TYPE_STRING, TreeView::STRING, 70);
91 	resultView.insertColumn(N_("Connection"), G_TYPE_STRING, TreeView::STRING, 100);
92 	resultView.insertColumn(N_("Hub"), G_TYPE_STRING, TreeView::STRING, 150);
93 	resultView.insertColumn(N_("Exact Size"), G_TYPE_STRING, TreeView::STRING, 100);
94 	resultView.insertColumn(N_("IP"), G_TYPE_STRING, TreeView::STRING, 100);
95 	resultView.insertColumn(N_("TTH"), G_TYPE_STRING, TreeView::STRING, 125);
96 	resultView.insertHiddenColumn("Icon", G_TYPE_STRING);
97 	resultView.insertHiddenColumn("Real Size", G_TYPE_INT64);
98 	resultView.insertHiddenColumn("Slots Order", G_TYPE_INT);
99 	resultView.insertHiddenColumn("File Order", G_TYPE_STRING);
100 	resultView.insertHiddenColumn("Hub URL", G_TYPE_STRING);
101 	resultView.insertHiddenColumn("CID", G_TYPE_STRING);
102 	resultView.insertHiddenColumn("Shared", G_TYPE_BOOLEAN);
103 	resultView.insertHiddenColumn("Free Slots", G_TYPE_INT);
104 	resultView.finalize();
105 	resultStore = gtk_tree_store_newv(resultView.getColCount(), resultView.getGTypes());
106 	searchFilterModel = gtk_tree_model_filter_new(GTK_TREE_MODEL(resultStore), NULL);
107 	gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(searchFilterModel), &Search::searchFilterFunc_gui, (gpointer)this, NULL);
108 	sortedFilterModel = gtk_tree_model_sort_new_with_model(searchFilterModel);
109 	gtk_tree_view_set_model(resultView.get(), sortedFilterModel);
110 	g_object_unref(resultStore);
111 	g_object_unref(searchFilterModel);
112 	g_object_unref(sortedFilterModel);
113 	selection = gtk_tree_view_get_selection(resultView.get());
114 	gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
115 	resultView.setSortColumn_gui("Size", "Real Size");
116 	resultView.setSortColumn_gui("Exact Size", "Real Size");
117 	resultView.setSortColumn_gui("Slots", "Slots Order");
118 	resultView.setSortColumn_gui("Filename", "File Order");
119 	gtk_tree_view_set_fixed_height_mode(resultView.get(), TRUE);
120 
121 	// Initialize the user command menu
122 	userCommandMenu = new UserCommandMenu(getWidget("usercommandMenu"), ::UserCommand::CONTEXT_SEARCH);
123 	addChild(userCommandMenu);
124 
125 	// Connect the signals to their callback functions.
126 	g_signal_connect(getContainer(), "focus-in-event", G_CALLBACK(onFocusIn_gui), (gpointer)this);
127 	g_signal_connect(getWidget("checkbuttonFilter"), "toggled", G_CALLBACK(onFilterButtonToggled_gui), (gpointer)this);
128 	g_signal_connect(getWidget("checkbuttonSlots"), "toggled", G_CALLBACK(onSlotsButtonToggled_gui), (gpointer)this);
129 	g_signal_connect(getWidget("checkbuttonShared"), "toggled", G_CALLBACK(onSharedButtonToggled_gui), (gpointer)this);
130 	g_signal_connect(renderer, "toggled", G_CALLBACK(onToggledClicked_gui), (gpointer)this);
131 	g_signal_connect(resultView.get(), "button-press-event", G_CALLBACK(onButtonPressed_gui), (gpointer)this);
132 	g_signal_connect(resultView.get(), "button-release-event", G_CALLBACK(onButtonReleased_gui), (gpointer)this);
133 	g_signal_connect(resultView.get(), "key-release-event", G_CALLBACK(onKeyReleased_gui), (gpointer)this);
134 	g_signal_connect(searchEntry, "key-press-event", G_CALLBACK(onSearchEntryKeyPressed_gui), (gpointer)this);
135 	g_signal_connect(searchEntry, "key-release-event", G_CALLBACK(onKeyReleased_gui), (gpointer)this);
136 	g_signal_connect(getWidget("entrySize"), "key-release-event", G_CALLBACK(onKeyReleased_gui), (gpointer)this);
137 	g_signal_connect(getWidget("buttonSearch"), "clicked", G_CALLBACK(onSearchButtonClicked_gui), (gpointer)this);
138 	g_signal_connect(getWidget("downloadItem"), "activate", G_CALLBACK(onDownloadClicked_gui), (gpointer)this);
139 	g_signal_connect(getWidget("downloadWholeDirItem"), "activate", G_CALLBACK(onDownloadDirClicked_gui), (gpointer)this);
140 	g_signal_connect(getWidget("searchByTTHItem"), "activate", G_CALLBACK(onSearchByTTHClicked_gui), (gpointer)this);
141 	g_signal_connect(getWidget("copyMagnetItem"), "activate", G_CALLBACK(onCopyMagnetClicked_gui), (gpointer)this);
142 	g_signal_connect(getWidget("getFileListItem"), "activate", G_CALLBACK(onGetFileListClicked_gui), (gpointer)this);
143 	g_signal_connect(getWidget("matchQueueItem"), "activate", G_CALLBACK(onMatchQueueClicked_gui), (gpointer)this);
144 	g_signal_connect(getWidget("sendPrivateMessageItem"), "activate", G_CALLBACK(onPrivateMessageClicked_gui), (gpointer)this);
145 	g_signal_connect(getWidget("addToFavoritesItem"), "activate", G_CALLBACK(onAddFavoriteUserClicked_gui), (gpointer)this);
146 	g_signal_connect(getWidget("grantExtraSlotItem"), "activate", G_CALLBACK(onGrantExtraSlotClicked_gui), (gpointer)this);
147 	g_signal_connect(getWidget("removeUserFromQueueItem"), "activate", G_CALLBACK(onRemoveUserFromQueueClicked_gui), (gpointer)this);
148 	g_signal_connect(getWidget("removeItem"), "activate", G_CALLBACK(onRemoveClicked_gui), (gpointer)this);
149 	g_signal_connect(getWidget("comboboxSize"), "changed", G_CALLBACK(onComboBoxChanged_gui), (gpointer)this);
150 	g_signal_connect(getWidget("comboboxentrySearch"), "changed", G_CALLBACK(onComboBoxChanged_gui), (gpointer)this);
151 	g_signal_connect(getWidget("comboboxUnit"), "changed", G_CALLBACK(onComboBoxChanged_gui), (gpointer)this);
152 	g_signal_connect(getWidget("comboboxFile"), "changed", G_CALLBACK(onComboBoxChanged_gui), (gpointer)this);
153 	g_signal_connect(getWidget("comboboxGroupBy"), "changed", G_CALLBACK(onGroupByComboBoxChanged_gui), (gpointer)this);
154 }
155 
~Search()156 Search::~Search()
157 {
158 	ClientManager::getInstance()->removeListener(this);
159 	SearchManager::getInstance()->removeListener(this);
160 
161 	gtk_widget_destroy(getWidget("dirChooserDialog"));
162 }
163 
show()164 void Search::show()
165 {
166 	initHubs_gui();
167 	updateStats_gui();
168 	ClientManager::getInstance()->addListener(this);
169 	SearchManager::getInstance()->addListener(this);
170 }
171 
putValue_gui(const string & str,int64_t size,SearchManager::SizeModes mode,SearchManager::TypeModes type)172 void Search::putValue_gui(const string &str, int64_t size, SearchManager::SizeModes mode, SearchManager::TypeModes type)
173 {
174 	gtk_entry_set_text(GTK_ENTRY(searchEntry), str.c_str());
175 	gtk_entry_set_text(GTK_ENTRY(getWidget("entrySize")), Util::toString(size).c_str());
176 	gtk_combo_box_set_active(GTK_COMBO_BOX(getWidget("comboboxSize")), (int)mode);
177 	gtk_combo_box_set_active(GTK_COMBO_BOX(getWidget("comboboxFile")), (int)type);
178 
179 	search_gui();
180 }
181 
initHubs_gui()182 void Search::initHubs_gui()
183 {
184 	ClientManager::getInstance()->lock();
185 
186 	Client::List& clients = ClientManager::getInstance()->getClients();
187 
188 	Client *client = NULL;
189 	for (Client::List::iterator it = clients.begin(); it != clients.end(); ++it)
190 	{
191 		client = *it;
192 		if (client->isConnected())
193 			addHub_gui(client->getHubName(), client->getHubUrl());
194 	}
195 
196 	ClientManager::getInstance()->unlock();
197 }
198 
addHub_gui(string name,string url)199 void Search::addHub_gui(string name, string url)
200 {
201 	GtkTreeIter iter;
202 	gtk_list_store_append(hubStore, &iter);
203 	gtk_list_store_set(hubStore, &iter,
204 		hubView.col("Search"), TRUE,
205 		hubView.col("Name"), name.empty() ? url.c_str() : name.c_str(),
206 		hubView.col("Url"), url.c_str(),
207 		-1);
208 }
209 
modifyHub_gui(string name,string url)210 void Search::modifyHub_gui(string name, string url)
211 {
212 	GtkTreeIter iter;
213 	GtkTreeModel *m = GTK_TREE_MODEL(hubStore);
214 	gboolean valid = gtk_tree_model_get_iter_first(m, &iter);
215 
216 	while (valid)
217 	{
218 		if (url == hubView.getString(&iter, "Url"))
219 		{
220 			gtk_list_store_set(hubStore, &iter,
221 				hubView.col("Name"), name.empty() ? url.c_str() : name.c_str(),
222 				hubView.col("Url"), url.c_str(),
223 				-1);
224 			return;
225 		}
226 		valid = gtk_tree_model_iter_next(m, &iter);
227 	}
228 }
229 
removeHub_gui(string url)230 void Search::removeHub_gui(string url)
231 {
232 	GtkTreeIter iter;
233 	GtkTreeModel *m = GTK_TREE_MODEL(hubStore);
234 	gboolean valid = gtk_tree_model_get_iter_first(m, &iter);
235 
236 	while (valid)
237 	{
238 		if (url == hubView.getString(&iter, "Url"))
239 		{
240 			gtk_list_store_remove(hubStore, &iter);
241 			return;
242 		}
243 		valid = gtk_tree_model_iter_next(m, &iter);
244 	}
245 }
246 
popupMenu_gui()247 void Search::popupMenu_gui()
248 {
249 	GtkTreeIter iter;
250 	GtkTreePath *path;
251 	GList *list = gtk_tree_selection_get_selected_rows(selection, NULL);
252 	guint count = g_list_length(list);
253 
254 	if (count == 1)
255 	{
256 		path = (GtkTreePath*)list->data; // This will be freed later
257 
258 		// If it is a parent effectively more than one row is selected
259 		if (gtk_tree_model_get_iter(sortedFilterModel, &iter, path) &&
260 		    gtk_tree_model_iter_has_child(sortedFilterModel, &iter))
261 		{
262 			gtk_widget_set_sensitive(getWidget("searchByTTHItem"), FALSE);
263 		}
264 		else
265 		{
266 			gtk_widget_set_sensitive(getWidget("searchByTTHItem"), TRUE);
267 		}
268 	}
269 	else if (count > 1)
270 	{
271 		gtk_widget_set_sensitive(getWidget("searchByTTHItem"), FALSE);
272 	}
273 
274 	GtkWidget *menuItem;
275 	string tth;
276 	bool firstTTH;
277 	bool hasTTH;
278 
279 	// Clean menus
280 	gtk_container_foreach(GTK_CONTAINER(getWidget("downloadMenu")), (GtkCallback)gtk_widget_destroy, NULL);
281 	gtk_container_foreach(GTK_CONTAINER(getWidget("downloadDirMenu")), (GtkCallback)gtk_widget_destroy, NULL);
282 	userCommandMenu->cleanMenu_gui();
283 
284 	// Build "Download to..." submenu
285 
286 	// Add favorite download directories
287 	StringPairList spl = FavoriteManager::getInstance()->getFavoriteDirs();
288 	if (spl.size() > 0)
289 	{
290 		for (StringPairIter i = spl.begin(); i != spl.end(); i++)
291 		{
292 			menuItem = gtk_menu_item_new_with_label(i->second.c_str());
293 			g_object_set_data_full(G_OBJECT(menuItem), "fav", g_strdup(i->first.c_str()), g_free);
294 			g_signal_connect(menuItem, "activate", G_CALLBACK(onDownloadFavoriteClicked_gui), (gpointer)this);
295 			gtk_menu_shell_append(GTK_MENU_SHELL(getWidget("downloadMenu")), menuItem);
296 		}
297 		menuItem = gtk_separator_menu_item_new();
298 		gtk_menu_shell_append(GTK_MENU_SHELL(getWidget("downloadMenu")), menuItem);
299 	}
300 
301 	// Add Browse item
302 	menuItem = gtk_menu_item_new_with_label(_("_Browse..."));
303 	gtk_menu_item_set_use_underline(GTK_MENU_ITEM(menuItem), TRUE);
304 	g_signal_connect(menuItem, "activate", G_CALLBACK(onDownloadToClicked_gui), (gpointer)this);
305 	gtk_menu_shell_append(GTK_MENU_SHELL(getWidget("downloadMenu")), menuItem);
306 
307 	// Add search results with the same TTH to menu
308 	firstTTH = TRUE;
309 	hasTTH = FALSE;
310 
311 	for (GList *i = list; i; i = i->next)
312 	{
313 		path = (GtkTreePath *)i->data;
314 		if (gtk_tree_model_get_iter(sortedFilterModel, &iter, path))
315 		{
316 			userCommandMenu->addHub(resultView.getString(&iter, "Hub URL"));
317 			userCommandMenu->addFile(resultView.getString(&iter, "CID"),
318 				resultView.getString(&iter, "Filename"),
319 				resultView.getString(&iter, "Path"),
320 				resultView.getValue<int64_t>(&iter, "Real Size"),
321 				resultView.getString(&iter, "TTH"));
322 
323 			if (firstTTH)
324 			{
325 				tth = resultView.getString(&iter, "TTH");
326 				firstTTH = FALSE;
327 				hasTTH = TRUE;
328 			}
329 			else if (hasTTH)
330 			{
331 				if (tth.empty() || tth != resultView.getString(&iter, "TTH"))
332 					hasTTH = FALSE; // Can't break here since we have to free all the paths
333 			}
334 		}
335 		gtk_tree_path_free(path);
336 	}
337 	g_list_free(list);
338 
339 	if (hasTTH)
340 	{
341 		StringList targets;
342 		QueueManager::getInstance()->getTargets(TTHValue(tth), targets);
343 
344 		if (targets.size() > static_cast<size_t>(0))
345 		{
346 			menuItem = gtk_separator_menu_item_new();
347 			gtk_menu_shell_append(GTK_MENU_SHELL(getWidget("downloadMenu")), menuItem);
348 			for (StringIter i = targets.begin(); i != targets.end(); ++i)
349 			{
350 				menuItem = gtk_menu_item_new_with_label(i->c_str());
351 				g_signal_connect(menuItem, "activate", G_CALLBACK(onDownloadToMatchClicked_gui), (gpointer)this);
352 				gtk_menu_shell_append(GTK_MENU_SHELL(getWidget("downloadMenu")), menuItem);
353 			}
354 		}
355 	}
356 
357 	// Build "Download whole directory to..." submenu
358 
359 	spl.clear();
360 	spl = FavoriteManager::getInstance()->getFavoriteDirs();
361 	if (spl.size() > 0)
362 	{
363 		for (StringPairIter i = spl.begin(); i != spl.end(); i++)
364 		{
365 			menuItem = gtk_menu_item_new_with_label(i->second.c_str());
366 			g_object_set_data_full(G_OBJECT(menuItem), "fav", g_strdup(i->first.c_str()), g_free);
367 			g_signal_connect(menuItem, "activate", G_CALLBACK(onDownloadFavoriteDirClicked_gui), (gpointer)this);
368 			gtk_menu_shell_append(GTK_MENU_SHELL(getWidget("downloadDirMenu")), menuItem);
369 		}
370 		menuItem = gtk_separator_menu_item_new();
371 		gtk_menu_shell_append(GTK_MENU_SHELL(getWidget("downloadDirMenu")), menuItem);
372 	}
373 
374 	menuItem = gtk_menu_item_new_with_label(_("_Browse..."));
375 	gtk_menu_item_set_use_underline(GTK_MENU_ITEM(menuItem), TRUE);
376 	g_signal_connect(menuItem, "activate", G_CALLBACK(onDownloadDirToClicked_gui), (gpointer)this);
377 	gtk_menu_shell_append(GTK_MENU_SHELL(getWidget("downloadDirMenu")), menuItem);
378 
379 	// Build user command menu
380 	userCommandMenu->buildMenu_gui();
381 
382 	gtk_menu_popup(GTK_MENU(getWidget("mainMenu")), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
383 	gtk_widget_show_all(getWidget("mainMenu"));
384 }
385 
setStatus_gui(string statusBar,string text)386 void Search::setStatus_gui(string statusBar, string text)
387 {
388 	gtk_statusbar_pop(GTK_STATUSBAR(getWidget(statusBar)), 0);
389 	gtk_statusbar_push(GTK_STATUSBAR(getWidget(statusBar)), 0, text.c_str());
390 }
391 
updateStats_gui()392 void Search::updateStats_gui()
393 {
394 	// TRANSLATORS: Appears in the status bar when a valid search result is received.
395 	setStatus_gui("statusbar2", P_("%1% Result", "%1% Results", % searchHits, searchHits));
396 
397 	// TRANSLATORS: Appears in the status bar when a search result is ignored/dropped.
398 	setStatus_gui("statusbar3", F_("%1% Dropped", % droppedResult));
399 }
400 
search_gui()401 void Search::search_gui()
402 {
403 	StringList clients;
404 	GtkTreeIter iter;
405 
406 	string text = gtk_entry_get_text(GTK_ENTRY(searchEntry));
407 	if (text.empty())
408 		return;
409 
410 	gboolean valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(hubStore), &iter);
411  	while (valid)
412  	{
413  		if (hubView.getValue<gboolean>(&iter, "Search"))
414  			clients.push_back(hubView.getString(&iter, "Url"));
415 		valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(hubStore), &iter);
416 	}
417 
418 	if (clients.size() < 1)
419 		return;
420 
421 	double lsize = Util::toDouble(gtk_entry_get_text(GTK_ENTRY(getWidget("entrySize"))));
422 
423 	switch (gtk_combo_box_get_active(GTK_COMBO_BOX(getWidget("comboboxUnit"))))
424 	{
425 		case 1:
426 			lsize *= 1024.0;
427 			break;
428 		case 2:
429 			lsize *= 1024.0 * 1024.0;
430 			break;
431 		case 3:
432 			lsize *= 1024.0 * 1024.0 * 1024.0;
433 			break;
434 	}
435 
436 	gtk_tree_store_clear(resultStore);
437 	results.clear();
438 	int64_t llsize = static_cast<int64_t>(lsize);
439 	searchlist = StringTokenizer<string>(text, ' ').getTokens();
440 
441 	// Strip out terms beginning with -
442 	text.clear();
443 	for (StringList::const_iterator si = searchlist.begin(); si != searchlist.end(); ++si)
444 		if ((*si)[0] != '-')
445 			text += *si + ' ';
446 	text = text.substr(0, std::max(text.size(), static_cast<string::size_type>(1)) - 1);
447 
448 	SearchManager::SizeModes mode((SearchManager::SizeModes)gtk_combo_box_get_active(GTK_COMBO_BOX(getWidget("comboboxSize"))));
449 	if (llsize == 0)
450 		mode = SearchManager::SIZE_DONTCARE;
451 
452 	int ftype = gtk_combo_box_get_active(GTK_COMBO_BOX(getWidget("comboboxFile")));
453 	isHash = (ftype == SearchManager::TYPE_TTH);
454 
455 	// Add new searches to the dropdown list
456 	GtkListStore *store = GTK_LIST_STORE(searchEntriesModel);
457 	size_t max = std::max(SETTING(SEARCH_HISTORY) - 1, 0);
458 	size_t count = 0;
459 	gchar *entry;
460 	valid = gtk_tree_model_get_iter_first(searchEntriesModel, &iter);
461 	while (valid)
462 	{
463 		gtk_tree_model_get(searchEntriesModel, &iter, 0, &entry, -1);
464 		if (text == string(entry) || count >= max)
465 			valid = gtk_list_store_remove(store, &iter);
466 		else
467 			valid = gtk_tree_model_iter_next(searchEntriesModel, &iter);
468 		count++;
469 		g_free(entry);
470 	}
471 
472 	gtk_list_store_prepend(store, &iter);
473 	gtk_list_store_set(store, &iter, 0, text.c_str(), -1);
474 
475 	droppedResult = 0;
476 	searchHits = 0;
477 	setStatus_gui("statusbar1", F_("Searching for %1%...", % text));
478 	updateStats_gui();
479 	setLabel_gui(text);
480 
481 	if (SearchManager::getInstance()->okToSearch())
482 	{
483 		SearchManager::getInstance()->search(clients, text, llsize, (SearchManager::TypeModes)ftype, mode, "manual");
484 
485 		if (BOOLSETTING(CLEAR_SEARCH)) // Only clear if the search was sent.
486 			gtk_entry_set_text(GTK_ENTRY(searchEntry), "");
487 	}
488 	else
489 	{
490 		int32_t waitFor = SearchManager::getInstance()->timeToSearch();
491 		string line = F_("Searching too soon, retry in %1% s", % waitFor);
492 		setStatus_gui("statusbar1", line);
493 	}
494 }
495 
addResult_gui(const SearchResultPtr result)496 void Search::addResult_gui(const SearchResultPtr result)
497 {
498 	// Check that it's not a duplicate and find parent for grouping
499 	GtkTreeIter iter;
500 	GtkTreeIter parent;
501 	GtkTreeModel *m = GTK_TREE_MODEL(resultStore);
502 	bool foundParent = FALSE;
503 
504 	vector<SearchResultPtr> &existingResults = results[result->getUser()->getCID().toBase32()];
505 	for (vector<SearchResultPtr>::iterator it = existingResults.begin(); it != existingResults.end(); it++)
506 	{
507 		// Check if it's a duplicate
508 		if (result->getFile() == (*it)->getFile())
509 			return;
510 	}
511 	existingResults.push_back(result);
512 
513 	dcpp::StringMap resultMap;
514 	parseSearchResult_gui(result, resultMap);
515 
516 	// Find grouping parent
517 	GroupType groupBy = (GroupType)gtk_combo_box_get_active(GTK_COMBO_BOX(getWidget("comboboxGroupBy")));
518 	string groupColumn = getGroupingColumn(groupBy);
519 	string groupStr = resultMap[groupColumn];
520 	gboolean valid = gtk_tree_model_get_iter_first(m, &iter);
521 
522 	while (valid && groupBy != NOGROUPING && !foundParent && !groupStr.empty())
523 	{
524 		// Found a row which matches the grouping criteria
525 		if (resultView.getString(&iter, groupColumn, m) == groupStr)
526 		{
527 			// Group the row under the existing parent row
528 			if (gtk_tree_model_iter_has_child(m, &iter))
529 			{
530 				parent = iter;
531 			}
532 			else // Group the rows under a new parent row
533 			{
534 				parent = createParentRow_gui(&iter, groupStr);
535 			}
536 
537 			foundParent = TRUE;
538 		}
539 
540 		valid = WulforUtil::getNextIter_gui(m, &iter, FALSE, FALSE);
541 	}
542 
543 	// Have to use insert with values since appending would cause searchFilterFunc to be
544 	// called with empty row which in turn will cause assert failure in treeview::getString
545 	gtk_tree_store_insert_with_values(resultStore, &iter, foundParent ? &parent : NULL, -1,
546 		resultView.col("User"), resultMap["User"].c_str(),
547 		resultView.col("Filename"), resultMap["Filename"].c_str(),
548 		resultView.col("Slots"), resultMap["Slots"].c_str(),
549 		resultView.col("Size"), resultMap["Size"].c_str(),
550 		resultView.col("Path"), resultMap["Path"].c_str(),
551 		resultView.col("Type"), resultMap["Type"].c_str(),
552 		resultView.col("Connection"), resultMap["Connection"].c_str(),
553 		resultView.col("Hub"), resultMap["Hub"].c_str(),
554 		resultView.col("Exact Size"), resultMap["Exact Size"].c_str(),
555 		resultView.col("IP"), resultMap["IP"].c_str(),
556 		resultView.col("TTH"), resultMap["TTH"].c_str(),
557 		resultView.col("Icon"), resultMap["Icon"].c_str(),
558 		resultView.col("File Order"), resultMap["File Order"].c_str(),
559 		resultView.col("Real Size"), Util::toInt64(resultMap["Real Size"]),
560 		resultView.col("Slots Order"), Util::toInt(resultMap["Slots Order"]),
561 		resultView.col("Hub URL"), resultMap["Hub URL"].c_str(),
562 		resultView.col("CID"), resultMap["CID"].c_str(),
563 		resultView.col("Shared"), Util::toInt(resultMap["Shared"]),
564 		resultView.col("Free Slots"), Util::toInt(resultMap["Free Slots"]),
565 		-1);
566 
567 	if (foundParent)
568 		updateParentRow_gui(&parent, &iter);
569 
570 	++searchHits;
571 	updateStats_gui();
572 
573 	if (BOOLSETTING(BOLD_SEARCH))
574 		setBold_gui();
575 }
576 
577 /*
578  * Move top level row to be under a newly created grouping parent.
579  */
createParentRow_gui(GtkTreeIter * child,const string & groupStr,gint position)580 GtkTreeIter Search::createParentRow_gui(GtkTreeIter *child, const string &groupStr, gint position /* = -1 */)
581 {
582 	GtkTreeIter parent;
583 	GroupType groupBy = (GroupType)gtk_combo_box_get_active(GTK_COMBO_BOX(getWidget("comboboxGroupBy")));
584 	string filename = groupStr;
585 
586 	// As a special case, use the first child's filename for TTH grouping
587 	if (groupBy == TTH)
588 		filename = resultView.getString(child, "Filename", GTK_TREE_MODEL(resultStore));
589 
590 	// Insert the new parent row
591 	gtk_tree_store_insert_with_values(resultStore, &parent, NULL, position,
592 			resultView.col("Icon"), GTK_STOCK_DND_MULTIPLE,
593 			resultView.col("Filename"), filename.c_str(),
594 			-1);
595 
596 	// Move the row to be a child of the new parent
597 	GtkTreeIter newChild = WulforUtil::copyRow_gui(resultStore, child, &parent);
598 	gtk_tree_store_remove(resultStore, child);
599 	*child = newChild;
600 
601 	return parent;
602 }
603 
updateParentRow_gui(GtkTreeIter * parent,GtkTreeIter * child)604 void Search::updateParentRow_gui(GtkTreeIter *parent, GtkTreeIter *child)
605 {
606 	// Let's make sure we really have children...
607 	gint children = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(resultStore), parent);
608 	dcassert(children != 0);
609 
610 	string users = P_("%1% User", "%1% Users", % children, children);
611 	gtk_tree_store_set(resultStore, parent, resultView.col("User"), users.c_str(), -1);
612 
613 	if (child == NULL)
614 		return;
615 
616 	GroupType groupType = (GroupType)gtk_combo_box_get_active(GTK_COMBO_BOX(getWidget("comboboxGroupBy")));
617 
618 	switch (groupType)
619 	{
620 		case NOGROUPING:
621 			break;
622 		case FILENAME:
623 			WulforUtil::copyValue_gui(resultStore, child, parent, resultView.col("File Order"));
624 			break;
625 		case FILEPATH:
626 			WulforUtil::copyValue_gui(resultStore, child, parent, resultView.col("Path"));
627 			break;
628 		case CONNECTION:
629 			WulforUtil::copyValue_gui(resultStore, child, parent, resultView.col("Connection"));
630 			break;
631 		case TTH: // Copy size as well since files with the same TTH should have the same size
632 			WulforUtil::copyValue_gui(resultStore, child, parent, resultView.col("TTH"));
633 		case SIZE:
634 		{
635 			WulforUtil::copyValue_gui(resultStore, child, parent, resultView.col("Exact Size"));
636 			WulforUtil::copyValue_gui(resultStore, child, parent, resultView.col("Size"));
637 			WulforUtil::copyValue_gui(resultStore, child, parent, resultView.col("Real Size"));
638 			break;
639 		}
640 		case USER:
641 			WulforUtil::copyValue_gui(resultStore, child, parent, resultView.col("User"));
642 			break;
643 		case HUB:
644 		{
645 			WulforUtil::copyValue_gui(resultStore, child, parent, resultView.col("Hub"));
646 			WulforUtil::copyValue_gui(resultStore, child, parent, resultView.col("Hub URL"));
647 			break;
648 		}
649 		case TYPE:
650 			WulforUtil::copyValue_gui(resultStore, child, parent, resultView.col("Type"));
651 			break;
652 		default:
653 			///@todo: throw an exception
654 			break;
655 	}
656 }
657 
ungroup_gui()658 void Search::ungroup_gui()
659 {
660 	GtkTreeIter iter;
661 	gint position = 0;
662 	GtkTreeModel *m = GTK_TREE_MODEL(resultStore);
663 	gboolean valid = gtk_tree_model_get_iter_first(m, &iter);
664 
665 	while (valid)
666 	{
667 		// Ungroup parent rows and remove them
668 		if (gtk_tree_model_iter_has_child(m, &iter))
669 		{
670 			GtkTreeIter child = iter;
671 			valid = WulforUtil::getNextIter_gui(m, &child, TRUE, FALSE);
672 
673 			// Move all children out from under the old grouping parent
674 			while (valid)
675 			{
676 				WulforUtil::copyRow_gui(resultStore, &child, NULL, position++);
677 				valid = gtk_tree_store_remove(resultStore, &child);
678 			}
679 
680 			// Delete the parent row
681 			valid = gtk_tree_store_remove(resultStore, &iter);
682 		}
683 		else // Non-parent row
684 		{
685 			++position;
686 			valid = WulforUtil::getNextIter_gui(m, &iter);
687 		}
688 	}
689 }
690 
regroup_gui()691 void Search::regroup_gui()
692 {
693 	unordered_map<string, GtkTreeIter> iterMap; // Maps group string -> parent tree iter
694 	GtkTreeIter iter;
695 	gint position = 0;
696 	GtkTreeModel *m = GTK_TREE_MODEL(resultStore);
697 	GroupType groupBy = (GroupType)gtk_combo_box_get_active(GTK_COMBO_BOX(getWidget("comboboxGroupBy")));
698 	string groupColumn = getGroupingColumn(groupBy);
699 	gboolean valid = gtk_tree_model_get_iter_first(m, &iter);
700 
701 	while (valid)
702 	{
703 		string groupStr;
704 		if (!groupColumn.empty())
705 			groupStr = resultView.getString(&iter, groupColumn, m);
706 
707 		// Don't add parent rows
708 		if (gtk_tree_model_iter_has_child(m, &iter) || groupStr.empty())
709 		{
710 			++position;
711 			valid = WulforUtil::getNextIter_gui(m, &iter);
712 			continue;
713 		}
714 
715 		unordered_map<std::string, GtkTreeIter>::iterator mapIter = iterMap.find(groupStr);
716 
717 		// New non-parent, top-level item
718 		if (mapIter == iterMap.end())
719 		{
720 			++position;
721 			iterMap[groupStr] = iter;
722 			valid = WulforUtil::getNextIter_gui(m, &iter);
723 		}
724 		else // Insert as a child under the grouping parent
725 		{
726 			GtkTreeIter parent = mapIter->second;
727 
728 			// If this is the first child to be appended, create a new parent row.
729 			if (!gtk_tree_model_iter_has_child(GTK_TREE_MODEL(resultStore), &parent))
730 			{
731 				GtkTreeIter child = parent;
732 				parent = createParentRow_gui(&child, groupStr, position);
733 				mapIter->second = parent;
734 			}
735 
736 			// Insert the row as a child
737 			GtkTreeIter child = WulforUtil::copyRow_gui(resultStore, &iter, &parent);
738 			valid = gtk_tree_store_remove(resultStore, &iter);
739 			updateParentRow_gui(&parent, &child);
740 		}
741 	}
742 }
743 
744 /*
745  * We can't rely on the string from the text box since it will be internationalized.
746  */
getGroupingColumn(GroupType groupBy)747 std::string Search::getGroupingColumn(GroupType groupBy)
748 {
749 	string column;
750 
751 	switch (groupBy)
752 	{
753 		case Search::NOGROUPING:
754 			break;
755 		case Search::FILENAME:
756 			column = "Filename";
757 			break;
758 		case Search::FILEPATH:
759 			column = "Path";
760 			break;
761 		case Search::SIZE:
762 			column = "Size";
763 			break;
764 		case Search::CONNECTION:
765 			column = "Connection";
766 			break;
767 		case Search::TTH:
768 			column = "TTH";
769 			break;
770 		case Search::USER:
771 			column = "User";
772 			break;
773 		case Search::HUB:
774 			column = "Hub";
775 			break;
776 		case Search::TYPE:
777 			column = "Type";
778 			break;
779 		default:
780 			///@todo: throw an exception
781 			break;
782 	}
783 
784 	return column;
785 }
786 
download_gui(const string & target)787 void Search::download_gui(const string &target)
788 {
789 	if (target.empty() || gtk_tree_selection_count_selected_rows(selection) <= 0)
790 		return;
791 
792 	GtkTreeIter iter;
793 	GtkTreePath *path;
794 	GroupType groupBy = (GroupType)gtk_combo_box_get_active(GTK_COMBO_BOX(getWidget("comboboxGroupBy")));
795 	GList *list = gtk_tree_selection_get_selected_rows(selection, NULL);
796 	typedef Func6<Search, string, string, string, int64_t, string, string> F6;
797 
798 	for (GList *i = list; i; i = i->next)
799 	{
800 		path = (GtkTreePath *)i->data;
801 		if (gtk_tree_model_get_iter(sortedFilterModel, &iter, path))
802 		{
803 			bool parent = gtk_tree_model_iter_has_child(sortedFilterModel, &iter);
804 			string filename = resultView.getString(&iter, "Filename");
805 
806 			do
807 			{
808 				if (!gtk_tree_model_iter_has_child(sortedFilterModel, &iter))
809 				{
810 					// Use parent filename when grouping by TTH to avoid downloading the same file multiple times
811 					if (groupBy != TTH)
812 					{
813 						filename = resultView.getString(&iter, "Path");
814 						filename += resultView.getString(&iter, "Filename");
815 					}
816 
817 					string cid = resultView.getString(&iter, "CID");
818 					int64_t size = resultView.getValue<int64_t>(&iter, "Real Size");
819 					string tth = resultView.getString(&iter, "TTH");
820 					string hubUrl = resultView.getString(&iter, "Hub URL");
821 					F6 *func = new F6(this, &Search::download_client, target, cid, filename, size, tth, hubUrl);
822 					WulforManager::get()->dispatchClientFunc(func);
823 				}
824 			}
825 			while (parent && WulforUtil::getNextIter_gui(sortedFilterModel, &iter, TRUE, FALSE));
826 		}
827 		gtk_tree_path_free(path);
828 	}
829 	g_list_free(list);
830 }
831 
onFocusIn_gui(GtkWidget * widget,GdkEventFocus * event,gpointer data)832 gboolean Search::onFocusIn_gui(GtkWidget *widget, GdkEventFocus *event, gpointer data)
833 {
834 	Search *s = (Search *)data;
835 
836 	gtk_widget_grab_focus(s->getWidget("comboboxentrySearch"));
837 
838 	return TRUE;
839 }
840 
onButtonPressed_gui(GtkWidget * widget,GdkEventButton * event,gpointer data)841 gboolean Search::onButtonPressed_gui(GtkWidget *widget, GdkEventButton *event, gpointer data)
842 {
843 	Search *s = (Search *)data;
844 	s->oldEventType = event->type;
845 
846 	if (event->button == 3)
847 	{
848 		GtkTreePath *path;
849 		if (gtk_tree_view_get_path_at_pos(s->resultView.get(), (gint)event->x, (gint)event->y, &path, NULL, NULL, NULL))
850 		{
851 			bool selected = gtk_tree_selection_path_is_selected(s->selection, path);
852 			gtk_tree_path_free(path);
853 
854 			if (selected)
855 				return TRUE;
856 		}
857 	}
858 	return FALSE;
859 }
860 
onButtonReleased_gui(GtkWidget * widget,GdkEventButton * event,gpointer data)861 gboolean Search::onButtonReleased_gui(GtkWidget *widget, GdkEventButton *event, gpointer data)
862 {
863 	Search *s = (Search *)data;
864 	gint count = gtk_tree_selection_count_selected_rows(s->selection);
865 
866 	if (count > 0 && event->type == GDK_BUTTON_RELEASE && event->button == 3)
867 		s->popupMenu_gui();
868 	else if (count == 1 && s->oldEventType == GDK_2BUTTON_PRESS && event->button == 1)
869 		s->onDownloadClicked_gui(NULL, data);
870 
871 	return FALSE;
872 }
873 
onKeyReleased_gui(GtkWidget * widget,GdkEventKey * event,gpointer data)874 gboolean Search::onKeyReleased_gui(GtkWidget *widget, GdkEventKey *event, gpointer data)
875 {
876 	Search *s = (Search *)data;
877 	if (widget == GTK_WIDGET(s->resultView.get()))
878 	{
879 		gint count = gtk_tree_selection_count_selected_rows(s->selection);
880 
881 		if (count > 0)
882 		{
883 			if (event->keyval == GDK_Return || event->keyval == GDK_KP_Enter)
884 				s->onDownloadClicked_gui(NULL, data);
885 			else if (event->keyval == GDK_Delete || event->keyval == GDK_BackSpace)
886 				s->onRemoveClicked_gui(NULL, data);
887 			else if (event->keyval == GDK_Menu || (event->keyval == GDK_F10 && event->state & GDK_SHIFT_MASK))
888 				s->popupMenu_gui();
889 		}
890 	}
891 	else
892 	{
893 		if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->getWidget("checkbuttonFilter"))))
894 			gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(s->searchFilterModel));
895 	}
896 
897 	return FALSE;
898 }
899 
onSearchEntryKeyPressed_gui(GtkWidget * widget,GdkEventKey * event,gpointer data)900 gboolean Search::onSearchEntryKeyPressed_gui(GtkWidget *widget, GdkEventKey *event, gpointer data)
901 {
902 	Search *s = (Search *)data;
903 
904 	if (event->keyval == GDK_Return || event->keyval == GDK_KP_Enter)
905 	{
906 		s->search_gui();
907 	}
908 	else if (event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
909 	{
910 		gtk_combo_box_popup(GTK_COMBO_BOX(s->getWidget("comboboxentrySearch")));
911 		return TRUE;
912 	}
913 
914 	return FALSE;
915 }
916 
onComboBoxChanged_gui(GtkWidget * widget,gpointer data)917 void Search::onComboBoxChanged_gui(GtkWidget* widget, gpointer data)
918 {
919 	Search *s = (Search *)data;
920 
921 	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->getWidget("checkbuttonFilter"))))
922 		gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(s->searchFilterModel));
923 }
924 
onGroupByComboBoxChanged_gui(GtkWidget * comboBox,gpointer data)925 void Search::onGroupByComboBoxChanged_gui(GtkWidget *comboBox, gpointer data)
926 {
927 	Search *s = (Search*)data;
928 	GroupType groupBy = (GroupType)gtk_combo_box_get_active(GTK_COMBO_BOX(comboBox));
929 
930 	s->ungroup_gui();
931 
932 	if (groupBy != NOGROUPING)
933 	{
934 		gtk_widget_set_sensitive(s->getWidget("checkbuttonFilter"), FALSE);
935 		gtk_widget_set_sensitive(s->getWidget("checkbuttonSlots"), FALSE);
936 		gtk_widget_set_sensitive(s->getWidget("checkbuttonShared"), FALSE);
937 		s->regroup_gui();
938 	}
939 	else
940 	{
941 		gtk_widget_set_sensitive(s->getWidget("checkbuttonFilter"), TRUE);
942 		gtk_widget_set_sensitive(s->getWidget("checkbuttonSlots"), FALSE);
943 		gtk_widget_set_sensitive(s->getWidget("checkbuttonShared"), FALSE);
944 	}
945 }
946 
onSearchButtonClicked_gui(GtkWidget * widget,gpointer data)947 void Search::onSearchButtonClicked_gui(GtkWidget *widget, gpointer data)
948 {
949 	Search *s = (Search *)data;
950 	s->search_gui();
951 }
952 
onFilterButtonToggled_gui(GtkToggleButton * button,gpointer data)953 void Search::onFilterButtonToggled_gui(GtkToggleButton *button, gpointer data)
954 {
955 	Search *s = (Search *)data;
956 	GtkComboBox *comboBox = GTK_COMBO_BOX(s->getWidget("comboboxGroupBy"));
957 
958 	// Disable grouping when filtering within local results
959 	if (gtk_toggle_button_get_active(button))
960 	{
961 		s->previousGrouping = (GroupType)gtk_combo_box_get_active(comboBox);
962 		gtk_combo_box_set_active(comboBox, (int)NOGROUPING);
963 		gtk_widget_set_sensitive(GTK_WIDGET(comboBox), FALSE);
964 		gtk_widget_set_sensitive(s->getWidget("checkbuttonSlots"), TRUE);
965 		gtk_widget_set_sensitive(s->getWidget("checkbuttonShared"), TRUE);
966 	}
967 	else
968 	{
969 		gtk_combo_box_set_active(comboBox, (int)s->previousGrouping);
970 		gtk_widget_set_sensitive(GTK_WIDGET(comboBox), TRUE);
971 		gtk_widget_set_sensitive(s->getWidget("checkbuttonSlots"), FALSE);
972 		gtk_widget_set_sensitive(s->getWidget("checkbuttonShared"), FALSE);
973 
974 	}
975 
976 	gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(s->searchFilterModel));
977 }
978 
onSlotsButtonToggled_gui(GtkToggleButton * button,gpointer data)979 void Search::onSlotsButtonToggled_gui(GtkToggleButton *button, gpointer data)
980 {
981 	Search *s = (Search *)data;
982 
983 	s->onlyFree = gtk_toggle_button_get_active(button);
984 	if (s->onlyFree != BOOLSETTING(SEARCH_ONLY_FREE_SLOTS))
985 		SettingsManager::getInstance()->set(SettingsManager::SEARCH_ONLY_FREE_SLOTS, s->onlyFree);
986 
987 	// Refilter current view only if "Search within local results" is enabled
988 	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->getWidget("checkbuttonFilter"))))
989 		gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(s->searchFilterModel));
990 }
991 
onSharedButtonToggled_gui(GtkToggleButton * button,gpointer data)992 void Search::onSharedButtonToggled_gui(GtkToggleButton *button, gpointer data)
993 {
994 	Search *s = (Search *)data;
995 
996 	// Refilter current view only if "Search within local results" is enabled
997 	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->getWidget("checkbuttonFilter"))))
998 		gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(s->searchFilterModel));
999 }
1000 
onToggledClicked_gui(GtkCellRendererToggle * cell,gchar * path,gpointer data)1001 void Search::onToggledClicked_gui(GtkCellRendererToggle *cell, gchar *path, gpointer data)
1002 {
1003 	Search *s = (Search *)data;
1004 	GtkTreeIter iter;
1005 
1006 	if (gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(s->hubStore), &iter, path))
1007 	{
1008 		gboolean toggled = s->hubView.getValue<gboolean>(&iter, "Search");
1009 		gtk_list_store_set(s->hubStore, &iter, s->hubView.col("Search"), !toggled, -1);
1010 	}
1011 
1012 	// Refilter current view only if "Search within local results" is enabled
1013 	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->getWidget("checkbuttonFilter"))))
1014 		gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(s->searchFilterModel));
1015 }
1016 
onDownloadClicked_gui(GtkMenuItem * item,gpointer data)1017 void Search::onDownloadClicked_gui(GtkMenuItem *item, gpointer data)
1018 {
1019 	Search *s = (Search *)data;
1020 	string target = SETTING(DOWNLOAD_DIRECTORY);
1021 	s->download_gui(target);
1022 }
1023 
onDownloadFavoriteClicked_gui(GtkMenuItem * item,gpointer data)1024 void Search::onDownloadFavoriteClicked_gui(GtkMenuItem *item, gpointer data)
1025 {
1026 	Search *s = (Search *)data;
1027 	string fav = string((gchar *)g_object_get_data(G_OBJECT(item), "fav"));
1028 	s->download_gui(fav);
1029 }
1030 
onDownloadToClicked_gui(GtkMenuItem * item,gpointer data)1031 void Search::onDownloadToClicked_gui(GtkMenuItem *item, gpointer data)
1032 {
1033 	Search *s = (Search *)data;
1034 
1035 	int response = gtk_dialog_run(GTK_DIALOG(s->getWidget("dirChooserDialog")));
1036 	gtk_widget_hide(s->getWidget("dirChooserDialog"));
1037 
1038 	if (response == GTK_RESPONSE_OK)
1039 	{
1040 		gchar *temp = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(s->getWidget("dirChooserDialog")));
1041 
1042 		if (temp)
1043 		{
1044 			string target = Text::toUtf8(temp);
1045 			g_free(temp);
1046 
1047 			if (target[target.length() - 1] != PATH_SEPARATOR)
1048 				target += PATH_SEPARATOR;
1049 
1050 			s->download_gui(target);
1051 		}
1052 	}
1053 }
1054 
onDownloadToMatchClicked_gui(GtkMenuItem * item,gpointer data)1055 void Search::onDownloadToMatchClicked_gui(GtkMenuItem *item, gpointer data)
1056 {
1057 	Search *s = (Search *)data;
1058 
1059 	if (gtk_tree_selection_count_selected_rows(s->selection) > 0)
1060 	{
1061 		string fileName = WulforUtil::getTextFromMenu(item);
1062 		GtkTreeIter iter;
1063 		GtkTreePath *path;
1064 		GList *list = gtk_tree_selection_get_selected_rows(s->selection, NULL);
1065 		typedef Func5<Search, string, string, int64_t, string, string> F5;
1066 
1067 		for (GList *i = list; i; i = i->next)
1068 		{
1069 			path = (GtkTreePath *)i->data;
1070 			if (gtk_tree_model_get_iter(s->sortedFilterModel, &iter, path))
1071 			{
1072 				bool parent = gtk_tree_model_iter_has_child(s->sortedFilterModel, &iter);
1073 
1074 				do
1075 				{
1076 					if (!gtk_tree_model_iter_has_child(s->sortedFilterModel, &iter))
1077 					{
1078 						string cid = s->resultView.getString(&iter, "CID");
1079 						int64_t size = s->resultView.getValue<int64_t>(&iter, "Real Size");
1080 						string tth = s->resultView.getString(&iter, "TTH");
1081 						string hubUrl = s->resultView.getString(&iter, "Hub URL");
1082 						F5 *func = new F5(s, &Search::addSource_client, fileName, cid, size, tth, hubUrl);
1083 						WulforManager::get()->dispatchClientFunc(func);
1084 					}
1085 				}
1086 				while (parent && WulforUtil::getNextIter_gui(s->sortedFilterModel, &iter, TRUE, FALSE));
1087 			}
1088 			gtk_tree_path_free(path);
1089 		}
1090 		g_list_free(list);
1091 	}
1092 }
1093 
onDownloadDirClicked_gui(GtkMenuItem * item,gpointer data)1094 void Search::onDownloadDirClicked_gui(GtkMenuItem *item, gpointer data)
1095 {
1096 	Search *s = (Search *)data;
1097 
1098 	if (gtk_tree_selection_count_selected_rows(s->selection) > 0)
1099 	{
1100 		GtkTreeIter iter;
1101 		GtkTreePath *path;
1102 		GList *list = gtk_tree_selection_get_selected_rows(s->selection, NULL);
1103 		string target = SETTING(DOWNLOAD_DIRECTORY);
1104 		typedef Func4<Search, string, string, string, string> F4;
1105 
1106 		for (GList *i = list; i; i = i->next)
1107 		{
1108 			path = (GtkTreePath *)i->data;
1109 			if (gtk_tree_model_get_iter(s->sortedFilterModel, &iter, path))
1110 			{
1111 				bool parent = gtk_tree_model_iter_has_child(s->sortedFilterModel, &iter);
1112 
1113 				do
1114 				{
1115 					if (!gtk_tree_model_iter_has_child(s->sortedFilterModel, &iter))
1116 					{
1117 						string cid = s->resultView.getString(&iter, "CID");
1118 						string filename = s->resultView.getString(&iter, "Path");
1119 						filename += s->resultView.getString(&iter, "Filename");
1120 						string hubUrl = s->resultView.getString(&iter, "Hub URL");
1121 						F4 *func = new F4(s, &Search::downloadDir_client, target, cid, filename, hubUrl);
1122 						WulforManager::get()->dispatchClientFunc(func);
1123 					}
1124 				}
1125 				while (parent && WulforUtil::getNextIter_gui(s->sortedFilterModel, &iter, TRUE, FALSE));
1126 			}
1127 			gtk_tree_path_free(path);
1128 		}
1129 		g_list_free(list);
1130 	}
1131 }
1132 
onDownloadFavoriteDirClicked_gui(GtkMenuItem * item,gpointer data)1133 void Search::onDownloadFavoriteDirClicked_gui(GtkMenuItem *item, gpointer data)
1134 {
1135 	Search *s = (Search *)data;
1136 	string fav = (gchar *)g_object_get_data(G_OBJECT(item), "fav");
1137 
1138 	if (!fav.empty() && gtk_tree_selection_count_selected_rows(s->selection) > 0)
1139 	{
1140 		GtkTreeIter iter;
1141 		GtkTreePath *path;
1142 		GList *list = gtk_tree_selection_get_selected_rows(s->selection, NULL);
1143 		typedef Func4<Search, string, string, string, string> F4;
1144 
1145 		for (GList *i = list; i; i = i->next)
1146 		{
1147 			path = (GtkTreePath *)i->data;
1148 			if (gtk_tree_model_get_iter(s->sortedFilterModel, &iter, path))
1149 			{
1150 				bool parent = gtk_tree_model_iter_has_child(s->sortedFilterModel, &iter);
1151 
1152 				do
1153 				{
1154 					if (!gtk_tree_model_iter_has_child(s->sortedFilterModel, &iter))
1155 					{
1156 						string cid = s->resultView.getString(&iter, "CID");
1157 						string filename = s->resultView.getString(&iter, "Path");
1158 						filename += s->resultView.getString(&iter, "Filename");
1159 						string hubUrl = s->resultView.getString(&iter, "Hub URL");
1160 						F4 *func = new F4(s, &Search::downloadDir_client, fav, cid, filename, hubUrl);
1161 						WulforManager::get()->dispatchClientFunc(func);
1162 					}
1163 				}
1164 				while (parent && WulforUtil::getNextIter_gui(s->sortedFilterModel, &iter, TRUE, FALSE));
1165 			}
1166 			gtk_tree_path_free(path);
1167 		}
1168 		g_list_free(list);
1169 	}
1170 }
1171 
onDownloadDirToClicked_gui(GtkMenuItem * item,gpointer data)1172 void Search::onDownloadDirToClicked_gui(GtkMenuItem *item, gpointer data)
1173 {
1174 	Search *s = (Search *)data;
1175 
1176 	gint response = gtk_dialog_run(GTK_DIALOG(s->getWidget("dirChooserDialog")));
1177 	gtk_widget_hide(s->getWidget("dirChooserDialog"));
1178 
1179 	if (response == GTK_RESPONSE_OK)
1180 	{
1181 		int count = gtk_tree_selection_count_selected_rows(s->selection);
1182 		gchar *temp = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(s->getWidget("dirChooserDialog")));
1183 
1184 		if (temp && count > 0)
1185 		{
1186 			string target = Text::toUtf8(temp);
1187 			g_free(temp);
1188 
1189 			if (target[target.length() - 1] != PATH_SEPARATOR)
1190 				target += PATH_SEPARATOR;
1191 
1192 			GtkTreeIter iter;
1193 			GtkTreePath *path;
1194 			GList *list = gtk_tree_selection_get_selected_rows(s->selection, NULL);
1195 			typedef Func4<Search, string, string, string, string> F4;
1196 
1197 			for (GList *i = list; i; i = i->next)
1198 			{
1199 				path = (GtkTreePath *)i->data;
1200 				if (gtk_tree_model_get_iter(s->sortedFilterModel, &iter, path))
1201 				{
1202 					bool parent = gtk_tree_model_iter_has_child(s->sortedFilterModel, &iter);
1203 
1204 					do
1205 					{
1206 						if (!gtk_tree_model_iter_has_child(s->sortedFilterModel, &iter))
1207 						{
1208 							string cid = s->resultView.getString(&iter, "CID");
1209 							string filename = s->resultView.getString(&iter, "Path");
1210 							filename += s->resultView.getString(&iter, "Filename");
1211 							string hubUrl = s->resultView.getString(&iter, "Hub URL");
1212 							F4 *func = new F4(s, &Search::downloadDir_client, target, cid, filename, hubUrl);
1213 							WulforManager::get()->dispatchClientFunc(func);
1214 						}
1215 					}
1216 					while (parent && WulforUtil::getNextIter_gui(s->sortedFilterModel, &iter, TRUE, FALSE));
1217 				}
1218 				gtk_tree_path_free(path);
1219 			}
1220 			g_list_free(list);
1221 		}
1222 	}
1223 }
1224 
onSearchByTTHClicked_gui(GtkMenuItem * item,gpointer data)1225 void Search::onSearchByTTHClicked_gui(GtkMenuItem *item, gpointer data)
1226 {
1227 	Search *s = (Search *)data;
1228 
1229 	if (gtk_tree_selection_count_selected_rows(s->selection) > 0)
1230 	{
1231 		GtkTreeIter iter;
1232 		GtkTreePath *path;
1233 		GList *list = gtk_tree_selection_get_selected_rows(s->selection, NULL);
1234 
1235 		for (GList *i = list; i; i = i->next)
1236 		{
1237 			path = (GtkTreePath *)i->data;
1238 			if (gtk_tree_model_get_iter(s->sortedFilterModel, &iter, path))
1239 			{
1240 				string tth = s->resultView.getString(&iter, "TTH");
1241 				if (!tth.empty())
1242 				{
1243 
1244 					Search *ns = WulforManager::get()->getMainWindow()->addSearch_gui();
1245 					ns->putValue_gui(tth, 0, SearchManager::SIZE_DONTCARE, SearchManager::TYPE_TTH);
1246 				}
1247 			}
1248 			gtk_tree_path_free(path);
1249 		}
1250 		g_list_free(list);
1251 	}
1252 }
1253 
onGetFileListClicked_gui(GtkMenuItem * item,gpointer data)1254 void Search::onGetFileListClicked_gui(GtkMenuItem *item, gpointer data)
1255 {
1256 	Search *s = (Search *)data;
1257 
1258 	if (gtk_tree_selection_count_selected_rows(s->selection) > 0)
1259 	{
1260 		GtkTreeIter iter;
1261 		GtkTreePath *path;
1262 		GList *list = gtk_tree_selection_get_selected_rows(s->selection, NULL);
1263 		typedef Func4<Search, string, string, bool, string> F4;
1264 
1265 		for (GList *i = list; i; i = i->next)
1266 		{
1267 			path = (GtkTreePath *)i->data;
1268 			if (gtk_tree_model_get_iter(s->sortedFilterModel, &iter, path))
1269 			{
1270 				bool parent = gtk_tree_model_iter_has_child(s->sortedFilterModel, &iter);
1271 
1272 				do
1273 				{
1274 					string cid = s->resultView.getString(&iter, "CID");
1275 					string dir = s->resultView.getString(&iter, "Path");
1276 					string hubUrl = s->resultView.getString(&iter, "Hub URL");
1277 					F4 *func = new F4(s, &Search::getFileList_client, cid, dir, FALSE, hubUrl);
1278 					WulforManager::get()->dispatchClientFunc(func);
1279 				}
1280 				while (parent && WulforUtil::getNextIter_gui(s->sortedFilterModel, &iter, TRUE, FALSE));
1281 			}
1282 			gtk_tree_path_free(path);
1283 		}
1284 		g_list_free(list);
1285 	}
1286 }
1287 
onMatchQueueClicked_gui(GtkMenuItem * item,gpointer data)1288 void Search::onMatchQueueClicked_gui(GtkMenuItem *item, gpointer data)
1289 {
1290 	Search *s = (Search *)data;
1291 
1292 	if (gtk_tree_selection_count_selected_rows(s->selection) > 0)
1293 	{
1294 		GtkTreeIter iter;
1295 		GtkTreePath *path;
1296 		GList *list = gtk_tree_selection_get_selected_rows(s->selection, NULL);
1297 		typedef Func4<Search, string, string, bool, string> F4;
1298 
1299 		for (GList *i = list; i; i = i->next)
1300 		{
1301 			path = (GtkTreePath *)i->data;
1302 			if (gtk_tree_model_get_iter(s->sortedFilterModel, &iter, path))
1303 			{
1304 				bool parent = gtk_tree_model_iter_has_child(s->sortedFilterModel, &iter);
1305 
1306 				do
1307 				{
1308 					string cid = s->resultView.getString(&iter, "CID");
1309 					string hubUrl = s->resultView.getString(&iter, "Hub URL");
1310 					F4 *func = new F4(s, &Search::getFileList_client, cid, "", TRUE, hubUrl);
1311 					WulforManager::get()->dispatchClientFunc(func);
1312 				}
1313 				while (parent && WulforUtil::getNextIter_gui(s->sortedFilterModel, &iter, TRUE, FALSE));
1314 			}
1315 			gtk_tree_path_free(path);
1316 		}
1317 		g_list_free(list);
1318 	}
1319 }
1320 
onPrivateMessageClicked_gui(GtkMenuItem * item,gpointer data)1321 void Search::onPrivateMessageClicked_gui(GtkMenuItem *item, gpointer data)
1322 {
1323 	Search *s = (Search *)data;
1324 
1325 	if (gtk_tree_selection_count_selected_rows(s->selection) > 0)
1326 	{
1327 		GtkTreeIter iter;
1328 		GtkTreePath *path;
1329 		GList *list = gtk_tree_selection_get_selected_rows(s->selection, NULL);
1330 
1331 		for (GList *i = list; i; i = i->next)
1332 		{
1333 			path = (GtkTreePath *)i->data;
1334 			if (gtk_tree_model_get_iter(s->sortedFilterModel, &iter, path))
1335 			{
1336 				bool parent = gtk_tree_model_iter_has_child(s->sortedFilterModel, &iter);
1337 
1338 				do
1339 				{
1340 					string cid = s->resultView.getString(&iter, "CID");
1341 					string hubUrl = s->resultView.getString(&iter, "Hub URL");
1342 					if (!cid.empty())
1343 						WulforManager::get()->getMainWindow()->addPrivateMessage_gui(cid, hubUrl);
1344 				}
1345 				while (parent && WulforUtil::getNextIter_gui(s->sortedFilterModel, &iter, TRUE, FALSE));
1346 			}
1347 			gtk_tree_path_free(path);
1348 		}
1349 		g_list_free(list);
1350 	}
1351 }
1352 
onAddFavoriteUserClicked_gui(GtkMenuItem * item,gpointer data)1353 void Search::onAddFavoriteUserClicked_gui(GtkMenuItem *item, gpointer data)
1354 {
1355 	Search *s = (Search *)data;
1356 
1357 	if (gtk_tree_selection_count_selected_rows(s->selection) > 0)
1358 	{
1359 		string cid;
1360 		GtkTreeIter iter;
1361 		GtkTreePath *path;
1362 		GList *list = gtk_tree_selection_get_selected_rows(s->selection, NULL);
1363 		typedef Func1<Search, string> F1;
1364 		F1 *func;
1365 
1366 		for (GList *i = list; i; i = i->next)
1367 		{
1368 			path = (GtkTreePath *)i->data;
1369 			if (gtk_tree_model_get_iter(s->sortedFilterModel, &iter, path))
1370 			{
1371 				bool parent = gtk_tree_model_iter_has_child(s->sortedFilterModel, &iter);
1372 
1373 				do
1374 				{
1375 					cid = s->resultView.getString(&iter, "CID");
1376 					func = new F1(s, &Search::addFavUser_client, cid);
1377 					WulforManager::get()->dispatchClientFunc(func);
1378 				}
1379 				while (parent && WulforUtil::getNextIter_gui(s->sortedFilterModel, &iter, TRUE, FALSE));
1380 			}
1381 			gtk_tree_path_free(path);
1382 		}
1383 		g_list_free(list);
1384 	}
1385 }
1386 
onGrantExtraSlotClicked_gui(GtkMenuItem * item,gpointer data)1387 void Search::onGrantExtraSlotClicked_gui(GtkMenuItem *item, gpointer data)
1388 {
1389 	Search *s = (Search *)data;
1390 
1391 	if (gtk_tree_selection_count_selected_rows(s->selection) > 0)
1392 	{
1393 		GtkTreeIter iter;
1394 		GtkTreePath *path;
1395 		GList *list = gtk_tree_selection_get_selected_rows(s->selection, NULL);
1396 		typedef Func2<Search, string, string> F2;
1397 
1398 		for (GList *i = list; i; i = i->next)
1399 		{
1400 			path = (GtkTreePath *)i->data;
1401 			if (gtk_tree_model_get_iter(s->sortedFilterModel, &iter, path))
1402 			{
1403 				bool parent = gtk_tree_model_iter_has_child(s->sortedFilterModel, &iter);
1404 
1405 				do
1406 				{
1407 					string cid = s->resultView.getString(&iter, "CID");
1408 					string hubUrl = s->resultView.getString(&iter, "Hub URL");
1409 					F2 *func = new F2(s, &Search::grantSlot_client, cid, hubUrl);
1410 					WulforManager::get()->dispatchClientFunc(func);
1411 				}
1412 				while (parent && WulforUtil::getNextIter_gui(s->sortedFilterModel, &iter, TRUE, FALSE));
1413 			}
1414 			gtk_tree_path_free(path);
1415 		}
1416 		g_list_free(list);
1417 	}
1418 }
1419 
onRemoveUserFromQueueClicked_gui(GtkMenuItem * item,gpointer data)1420 void Search::onRemoveUserFromQueueClicked_gui(GtkMenuItem *item, gpointer data)
1421 {
1422 	Search *s = (Search *)data;
1423 
1424 	if (gtk_tree_selection_count_selected_rows(s->selection) > 0)
1425 	{
1426 		string cid;
1427 		GtkTreeIter iter;
1428 		GtkTreePath *path;
1429 		GList *list = gtk_tree_selection_get_selected_rows(s->selection, NULL);
1430 		typedef Func1<Search, string> F1;
1431 		F1 *func;
1432 
1433 		for (GList *i = list; i; i = i->next)
1434 		{
1435 			path = (GtkTreePath *)i->data;
1436 			if (gtk_tree_model_get_iter(s->sortedFilterModel, &iter, path))
1437 			{
1438 				bool parent = gtk_tree_model_iter_has_child(s->sortedFilterModel, &iter);
1439 
1440 				do
1441 				{
1442 					cid = s->resultView.getString(&iter, "CID");
1443 					func = new F1(s, &Search::removeSource_client, cid);
1444 					WulforManager::get()->dispatchClientFunc(func);
1445 				}
1446 				while (parent && WulforUtil::getNextIter_gui(s->sortedFilterModel, &iter, TRUE, FALSE));
1447 			}
1448 			gtk_tree_path_free(path);
1449 		}
1450 		g_list_free(list);
1451 	}
1452 }
1453 
1454 // Removing a row from treeStore still leaves the SearchResultPtr to results map. This way if a duplicate
1455 // result comes in later it won't be readded, before the results map is cleared with a new search.
onRemoveClicked_gui(GtkMenuItem * item,gpointer data)1456 void Search::onRemoveClicked_gui(GtkMenuItem *item, gpointer data)
1457 {
1458 	Search *s = (Search *)data;
1459 
1460 	if (gtk_tree_selection_count_selected_rows(s->selection) > 0)
1461 	{
1462 		GtkTreeIter iter;
1463 		GtkTreeIter filterIter;
1464 		GtkTreePath *path;
1465 		GList *list = gtk_tree_selection_get_selected_rows(s->selection, NULL);
1466 		GList *refList = NULL;
1467 
1468 		// Convert it to list of GtkTreeRowReferences since modifying the model with a list of Paths is bad.
1469 		for (GList *i = list; i; i = i->next)
1470 		{
1471 			path = (GtkTreePath *)i->data;
1472 			refList = g_list_append(refList, gtk_tree_row_reference_new(s->sortedFilterModel, path));
1473 			gtk_tree_path_free(path);
1474 		}
1475 		g_list_free(list);
1476 
1477 		for (GList *i = refList; i; i = i->next)
1478 		{
1479 			path = gtk_tree_row_reference_get_path((GtkTreeRowReference*)i->data);
1480 			if (path != NULL)
1481 			{
1482 				if (gtk_tree_model_get_iter(s->sortedFilterModel, &iter, path))
1483 				{
1484 					// Remove the top-level node and it will remove any children nodes (if applicable)
1485 					gtk_tree_model_sort_convert_iter_to_child_iter(GTK_TREE_MODEL_SORT(s->sortedFilterModel), &filterIter, &iter);
1486 					gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(s->searchFilterModel), &iter, &filterIter);
1487 					gtk_tree_store_remove(s->resultStore, &iter);
1488 				}
1489 				gtk_tree_path_free(path);
1490 			}
1491 			gtk_tree_row_reference_free((GtkTreeRowReference*)i->data);
1492 		}
1493 		g_list_free(refList);
1494 	}
1495 }
1496 
onCopyMagnetClicked_gui(GtkMenuItem * item,gpointer data)1497 void Search::onCopyMagnetClicked_gui(GtkMenuItem* item, gpointer data)
1498 {
1499 	Search *s = (Search *)data;
1500 
1501 	if (gtk_tree_selection_count_selected_rows(s->selection) > 0)
1502 	{
1503 		int64_t size;
1504 		string magnets, magnet, filename, tth;
1505 		GtkTreeIter iter;
1506 		GtkTreePath *path;
1507 		GList *list = gtk_tree_selection_get_selected_rows(s->selection, NULL);
1508 
1509 		for (GList *i = list; i; i = i->next)
1510 		{
1511 			path = (GtkTreePath *)i->data;
1512 			if (gtk_tree_model_get_iter(s->sortedFilterModel, &iter, path))
1513 			{
1514 				bool parent = gtk_tree_model_iter_has_child(s->sortedFilterModel, &iter);
1515 
1516 				do
1517 				{
1518 					filename = s->resultView.getString(&iter, "Filename");
1519 					size = s->resultView.getValue<int64_t>(&iter, "Real Size");
1520 					tth = s->resultView.getString(&iter, "TTH");
1521 					magnet = WulforUtil::makeMagnet(filename, size, tth);
1522 
1523 					if (!magnet.empty())
1524 					{
1525 						if (!magnets.empty())
1526 							magnets += '\n';
1527 						magnets += magnet;
1528 					}
1529 				}
1530 				while (parent && WulforUtil::getNextIter_gui(s->sortedFilterModel, &iter, TRUE, FALSE));
1531 			}
1532 			gtk_tree_path_free(path);
1533 		}
1534 		g_list_free(list);
1535 
1536 		if (!magnets.empty())
1537 			gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), magnets.c_str(), magnets.length());
1538 	}
1539 }
1540 
parseSearchResult_gui(SearchResultPtr result,StringMap & resultMap)1541 void Search::parseSearchResult_gui(SearchResultPtr result, StringMap &resultMap)
1542 {
1543 	if (result->getType() == SearchResult::TYPE_FILE)
1544 	{
1545 		string file = WulforUtil::linuxSeparator(result->getFile());
1546 		if (file.rfind('/') == tstring::npos)
1547 		{
1548 			resultMap["Filename"] = file;
1549 		}
1550 		else
1551 		{
1552 			resultMap["Filename"] = Util::getFileName(file);
1553 			resultMap["Path"] = Util::getFilePath(file);
1554 		}
1555 
1556 		resultMap["File Order"] = "f" + resultMap["Filename"];
1557 		resultMap["Type"] = Util::getFileExt(resultMap["Filename"]);
1558 		if (!resultMap["Type"].empty() && resultMap["Type"][0] == '.')
1559 			resultMap["Type"].erase(0, 1);
1560 		resultMap["Size"] = Util::formatBytes(result->getSize());
1561 		resultMap["Exact Size"] = Util::formatExactSize(result->getSize());
1562 		resultMap["Icon"] = GTK_STOCK_FILE;
1563 		resultMap["Shared"] = Util::toString(ShareManager::getInstance()->isTTHShared(result->getTTH()));
1564 	}
1565 	else
1566 	{
1567 		string path = WulforUtil::linuxSeparator(result->getFile());
1568 		resultMap["Filename"] = WulforUtil::linuxSeparator(result->getFileName());
1569 		resultMap["Path"] = Util::getFilePath(path.substr(0, path.length() - 1)); // getFilePath just returns path unless we chop the last / off
1570 		if (resultMap["Path"].find("/") == string::npos)
1571 			resultMap["Path"] = "";
1572 		resultMap["File Order"] = "d" + resultMap["Filename"];
1573 		resultMap["Type"] = _("Directory");
1574 		resultMap["Icon"] = GTK_STOCK_DIRECTORY;
1575 		resultMap["Shared"] = "0";
1576 		if (result->getSize() > 0)
1577 		{
1578 			resultMap["Size"] = Util::formatBytes(result->getSize());
1579 			resultMap["Exact Size"] = Util::formatExactSize(result->getSize());
1580 		}
1581 	}
1582 
1583 	resultMap["User"] = WulforUtil::getNicks(result->getUser());
1584 	resultMap["CID"] = result->getUser()->getCID().toBase32();
1585 	resultMap["Slots"] = result->getSlotString();
1586 	resultMap["Connection"] = ClientManager::getInstance()->getConnection(result->getUser()->getCID());
1587 	resultMap["Hub"] = result->getHubName().empty() ? result->getHubURL().c_str() : result->getHubName().c_str();
1588 	resultMap["Hub URL"] = result->getHubURL();
1589 	resultMap["IP"] = result->getIP();
1590 	resultMap["Real Size"] = Util::toString(result->getSize());
1591 	if (result->getType() == SearchResult::TYPE_FILE)
1592 		resultMap["TTH"] = result->getTTH().toBase32();
1593 
1594 	// assumption: total slots is never above 999
1595 	resultMap["Slots Order"] = Util::toString(-1000 * result->getFreeSlots() - result->getSlots());
1596 	resultMap["Free Slots"] = Util::toString(result->getFreeSlots());
1597 }
1598 
download_client(string target,string cid,string filename,int64_t size,string tth,string hubUrl)1599 void Search::download_client(string target, string cid, string filename, int64_t size, string tth, string hubUrl)
1600 {
1601 	try
1602 	{
1603 		UserPtr user = ClientManager::getInstance()->findUser(CID(cid));
1604 		if (user == NULL)
1605 			return;
1606 
1607 		// Only files have a TTH
1608 		if (!tth.empty())
1609 		{
1610 			string subdir = Util::getFileName(filename);
1611 			QueueManager::getInstance()->add(target + subdir, size, TTHValue(tth), user, hubUrl);
1612 		}
1613 		else
1614 		{
1615 			string dir = WulforUtil::windowsSeparator(filename);
1616 			QueueManager::getInstance()->addDirectory(dir, user, hubUrl, target);
1617 		}
1618 	}
1619 	catch (const Exception&)
1620 	{
1621 	}
1622 }
1623 
downloadDir_client(string target,string cid,string filename,string hubUrl)1624 void Search::downloadDir_client(string target, string cid, string filename, string hubUrl)
1625 {
1626 	try
1627 	{
1628 		string dir;
1629 
1630 		// If it's a file (directories are assumed to end in '/')
1631 		if (filename[filename.length() - 1] != PATH_SEPARATOR)
1632 		{
1633 			dir = WulforUtil::windowsSeparator(Util::getFilePath(filename));
1634 		}
1635 		else
1636 		{
1637 			dir = WulforUtil::windowsSeparator(filename);
1638 		}
1639 
1640 		UserPtr user = ClientManager::getInstance()->findUser(CID(cid));
1641 		if (user != NULL)
1642 		{
1643 			QueueManager::getInstance()->addDirectory(dir, user, hubUrl, target);
1644 		}
1645 	}
1646 	catch (const Exception&)
1647 	{
1648 	}
1649 }
1650 
addSource_client(string source,string cid,int64_t size,string tth,string hubUrl)1651 void Search::addSource_client(string source, string cid, int64_t size, string tth, string hubUrl)
1652 {
1653 	try
1654 	{
1655 		UserPtr user = ClientManager::getInstance()->findUser(CID(cid));
1656 		if (!tth.empty() && user != NULL)
1657 		{
1658 			QueueManager::getInstance()->add(source, size, TTHValue(tth), user, hubUrl);
1659 		}
1660 	}
1661 	catch (const Exception&)
1662 	{
1663 	}
1664 }
1665 
getFileList_client(string cid,string dir,bool match,string hubUrl)1666 void Search::getFileList_client(string cid, string dir, bool match, string hubUrl)
1667 {
1668 	if (!cid.empty())
1669 	{
1670 		try
1671 		{
1672 			UserPtr user = ClientManager::getInstance()->findUser(CID(cid));
1673 			if (user)
1674 			{
1675 				QueueItem::FileFlags flags;
1676 				if (match)
1677 					flags = QueueItem::FLAG_MATCH_QUEUE;
1678 				else
1679 					flags = QueueItem::FLAG_CLIENT_VIEW;
1680 
1681 				QueueManager::getInstance()->addList(user, hubUrl, flags, dir);
1682 			}
1683 		}
1684 		catch (const Exception&)
1685 		{
1686 		}
1687 	}
1688 }
1689 
grantSlot_client(string cid,string hubUrl)1690 void Search::grantSlot_client(string cid, string hubUrl)
1691 {
1692 	if (!cid.empty())
1693 	{
1694 		UserPtr user = ClientManager::getInstance()->findUser(CID(cid));
1695 		if (user)
1696 			UploadManager::getInstance()->reserveSlot(user, hubUrl);
1697 	}
1698 }
1699 
addFavUser_client(string cid)1700 void Search::addFavUser_client(string cid)
1701 {
1702 	if (!cid.empty())
1703 	{
1704 		UserPtr user = ClientManager::getInstance()->findUser(CID(cid));
1705 		if (user)
1706 			FavoriteManager::getInstance()->addFavoriteUser(user);
1707 	}
1708 }
1709 
removeSource_client(string cid)1710 void Search::removeSource_client(string cid)
1711 {
1712 	if (!cid.empty())
1713 	{
1714 		UserPtr user = ClientManager::getInstance()->findUser(CID(cid));
1715 		if (user)
1716 			QueueManager::getInstance()->removeSource(user, QueueItem::Source::FLAG_REMOVED);
1717 	}
1718 }
1719 
on(ClientManagerListener::ClientConnected,Client * client)1720 void Search::on(ClientManagerListener::ClientConnected, Client *client) throw()
1721 {
1722 	if (client)
1723 	{
1724 		typedef Func2<Search, string, string> F2;
1725 		F2 *func = new F2(this, &Search::addHub_gui, client->getHubName(), client->getHubUrl());
1726 		WulforManager::get()->dispatchGuiFunc(func);
1727 	}
1728 }
1729 
on(ClientManagerListener::ClientUpdated,Client * client)1730 void Search::on(ClientManagerListener::ClientUpdated, Client *client) throw()
1731 {
1732 	if (client)
1733 	{
1734 		typedef Func2<Search, string, string> F2;
1735 		F2 *func = new F2(this, &Search::modifyHub_gui, client->getHubName(), client->getHubUrl());
1736 		WulforManager::get()->dispatchGuiFunc(func);
1737 	}
1738 }
1739 
on(ClientManagerListener::ClientDisconnected,Client * client)1740 void Search::on(ClientManagerListener::ClientDisconnected, Client *client) throw()
1741 {
1742 	if (client)
1743 	{
1744 		typedef Func1<Search, string> F1;
1745 		F1 *func = new F1(this, &Search::removeHub_gui, client->getHubUrl());
1746 		WulforManager::get()->dispatchGuiFunc(func);
1747 	}
1748 }
1749 
on(SearchManagerListener::SR,const SearchResultPtr & result)1750 void Search::on(SearchManagerListener::SR, const SearchResultPtr& result) throw()
1751 {
1752 	if (searchlist.empty() || result == NULL)
1753 		return;
1754 
1755 	if (isHash)
1756 	{
1757 		if (result->getType() != SearchResult::TYPE_FILE || TTHValue(searchlist[0]) != result->getTTH())
1758 		{
1759 			++droppedResult;
1760 			Func0<Search> *func = new Func0<Search>(this, &Search::updateStats_gui);
1761 			WulforManager::get()->dispatchGuiFunc(func);
1762 			return;
1763 		}
1764 	}
1765 	else
1766 	{
1767 		for (TStringIter i = searchlist.begin(); i != searchlist.end(); ++i)
1768 		{
1769 			if ((*i->begin() != '-' && Util::findSubString(result->getFile(), *i) == (string::size_type)-1) ||
1770 			    (*i->begin() == '-' && i->size() != 1 && Util::findSubString(result->getFile(), i->substr(1)) != (string::size_type)-1))
1771 			{
1772 				++droppedResult;
1773 				Func0<Search> *func = new Func0<Search>(this, &Search::updateStats_gui);
1774 				WulforManager::get()->dispatchGuiFunc(func);
1775 				return;
1776 			}
1777 		}
1778 	}
1779 
1780 	typedef Func1<Search, SearchResultPtr> F1;
1781 	F1 *func = new F1(this, &Search::addResult_gui, result);
1782 	WulforManager::get()->dispatchGuiFunc(func);
1783 }
1784 
1785 // Filtering causes Gtk-CRITICAL assertion failure, when last item is removed
1786 // see. http://bugzilla.gnome.org/show_bug.cgi?id=464173
searchFilterFunc_gui(GtkTreeModel * model,GtkTreeIter * iter,gpointer data)1787 gboolean Search::searchFilterFunc_gui(GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1788 {
1789 	Search *s = (Search *)data;
1790 	dcassert(model == GTK_TREE_MODEL(s->resultStore));
1791 
1792 	// Enabler filtering only if search within local results is checked
1793 	if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->getWidget("checkbuttonFilter"))))
1794 		return TRUE;
1795 
1796 	// Grouping shouldn't be enabled while filtering, but just in case...
1797 	if (gtk_tree_model_iter_has_child(model, iter))
1798 		return TRUE;
1799 
1800 	string hub = s->resultView.getString(iter, "Hub URL", model);
1801 	GtkTreeIter hubIter;
1802 	bool valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(s->hubStore), &hubIter);
1803 	while (valid)
1804 	{
1805 		if (hub == s->hubView.getString(&hubIter, "Url"))
1806 		{
1807 			if (!s->hubView.getValue<gboolean>(&hubIter, "Search"))
1808 				return FALSE;
1809 			else
1810 				break;
1811 		}
1812 		valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(s->hubStore), &hubIter);
1813 	}
1814 
1815 	// Filter based on free slots.
1816 	gint freeSlots = s->resultView.getValue<gint>(iter, "Free Slots", model);
1817 	if (s->onlyFree && freeSlots < 1)
1818 		return FALSE;
1819 
1820 	// Hide results already in share
1821 	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->getWidget("checkbuttonShared"))) &&
1822 		s->resultView.getValue<gboolean>(iter, "Shared", model) == TRUE)
1823 		return FALSE;
1824 
1825 	// Filter based on search terms.
1826 	string filter = Text::toLower(gtk_entry_get_text(GTK_ENTRY(s->searchEntry)));
1827 	TStringList filterList = StringTokenizer<tstring>(filter, ' ').getTokens();
1828 	string filename = Text::toLower(s->resultView.getString(iter, "Filename", model));
1829 	string path = Text::toLower(s->resultView.getString(iter, "Path", model));
1830 	for (TStringList::const_iterator term = filterList.begin(); term != filterList.end(); ++term)
1831 	{
1832 		if ((*term)[0] == '-')
1833 		{
1834 			if (filename.find((*term).substr(1)) != string::npos)
1835 				return FALSE;
1836 			else if (path.find((*term).substr(1)) != string::npos)
1837 				return FALSE;
1838 		}
1839 		else if (filename.find(*term) == string::npos && path.find(*term) == string::npos)
1840 			return FALSE;
1841 	}
1842 
1843 	// Filter based on file size.
1844 	double filterSize = Util::toDouble(gtk_entry_get_text(GTK_ENTRY(s->getWidget("entrySize"))));
1845 	if (filterSize > 0)
1846 	{
1847 		switch (gtk_combo_box_get_active(GTK_COMBO_BOX(s->getWidget("comboboxUnit"))))
1848 		{
1849 			case 1:
1850 				filterSize *= 1024.0;
1851 				break;
1852 			case 2:
1853 				filterSize *= 1024.0 * 1024.0;
1854 				break;
1855 			case 3:
1856 				filterSize *= 1024.0 * 1024.0 * 1024.0;
1857 				break;
1858 		}
1859 
1860 		int64_t size = s->resultView.getValue<int64_t>(iter, "Real Size", model);
1861 
1862 		switch (gtk_combo_box_get_active(GTK_COMBO_BOX(s->getWidget("comboboxSize"))))
1863 		{
1864 			case 0:
1865 				if (size != filterSize)
1866 					return FALSE;
1867 				break;
1868 			case 1:
1869 				if (size < filterSize)
1870 					return FALSE;
1871 				break;
1872 			case 2:
1873 				if (size > filterSize)
1874 					return FALSE;
1875 		}
1876 	}
1877 
1878 	int type = gtk_combo_box_get_active(GTK_COMBO_BOX(s->getWidget("comboboxFile")));
1879 	if (type != SearchManager::TYPE_ANY && type != ShareManager::getInstance()->getType(filename))
1880 		return FALSE;
1881 
1882 	return TRUE;
1883 }
1884 
1885