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