1 /*
2 * Copyright © 2004-2008 Jens Oknelid, paskharen@gmail.com
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 *
18 * In addition, as a special exception, compiling, linking, and/or
19 * using OpenSSL with this program is allowed.
20 */
21
22 #include "hub.hh"
23
24 #include <dcpp/FavoriteManager.h>
25 #include <dcpp/HashManager.h>
26 #include <dcpp/SearchManager.h>
27 #include <dcpp/ShareManager.h>
28 #include <dcpp/UserCommand.h>
29 #include "privatemessage.hh"
30 #include "search.hh"
31 #include "settingsmanager.hh"
32 #include "UserCommandMenu.hh"
33 #include "wulformanager.hh"
34 #include "WulforUtil.hh"
35
36 using namespace std;
37 using namespace dcpp;
38
Hub(const string & address,const string & encoding)39 Hub::Hub(const string &address, const string &encoding):
40 BookEntry(Entry::HUB, address, "hub.glade", address),
41 client(NULL),
42 historyIndex(0),
43 totalShared(0),
44 selectedTag(NULL),
45 address(address),
46 encoding(encoding),
47 scrollToBottom(TRUE)
48 {
49 // Configure the dialog
50 gtk_dialog_set_alternative_button_order(GTK_DIALOG(getWidget("passwordDialog")), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1);
51
52 // Initialize nick treeview
53 nickView.setView(GTK_TREE_VIEW(getWidget("nickView")), true, "hub");
54 nickView.insertColumn(N_("User"), G_TYPE_STRING, TreeView::ICON_STRING, 100, "Icon");
55 nickView.insertColumn(N_("Shared"), G_TYPE_INT64, TreeView::SIZE, 75);
56 nickView.insertColumn(N_("Description"), G_TYPE_STRING, TreeView::STRING, 85);
57 nickView.insertColumn(N_("Tag"), G_TYPE_STRING, TreeView::STRING, 100);
58 nickView.insertColumn(N_("Connection"), G_TYPE_STRING, TreeView::STRING, 85);
59 nickView.insertColumn(N_("IP"), G_TYPE_STRING, TreeView::STRING, 85);
60 nickView.insertColumn(N_("Email"), G_TYPE_STRING, TreeView::STRING, 90);
61 nickView.insertHiddenColumn("Icon", G_TYPE_STRING);
62 nickView.insertHiddenColumn("Nick Order", G_TYPE_STRING);
63 nickView.insertHiddenColumn("CID", G_TYPE_STRING);
64 nickView.finalize();
65 nickStore = gtk_list_store_newv(nickView.getColCount(), nickView.getGTypes());
66 gtk_tree_view_set_model(nickView.get(), GTK_TREE_MODEL(nickStore));
67 g_object_unref(nickStore);
68 nickSelection = gtk_tree_view_get_selection(nickView.get());
69 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(nickView.get()), GTK_SELECTION_MULTIPLE);
70 nickView.setSortColumn_gui("User", "Nick Order");
71 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(nickStore), nickView.col("Nick Order"), GTK_SORT_ASCENDING);
72 gtk_tree_view_column_set_sort_indicator(gtk_tree_view_get_column(nickView.get(), nickView.col("User")), TRUE);
73 gtk_tree_view_set_fixed_height_mode(nickView.get(), TRUE);
74 gtk_tree_view_set_search_equal_func(nickView.get(), onNickListSearch_gui, 0,0);
75
76 // Initialize the chat window
77 if (BOOLSETTING(USE_OEM_MONOFONT))
78 {
79 PangoFontDescription *fontDesc = pango_font_description_new();
80 pango_font_description_set_family(fontDesc, "Mono");
81 gtk_widget_modify_font(getWidget("chatText"), fontDesc);
82 pango_font_description_free(fontDesc);
83 }
84 chatBuffer = gtk_text_buffer_new(NULL);
85 gtk_text_view_set_buffer(GTK_TEXT_VIEW(getWidget("chatText")), chatBuffer);
86 GtkTextIter iter;
87 gtk_text_buffer_get_end_iter(chatBuffer, &iter);
88 chatMark = gtk_text_buffer_create_mark(chatBuffer, NULL, &iter, FALSE);
89 handCursor = gdk_cursor_new(GDK_HAND2);
90
91 // Initialize the user command menu
92 userCommandMenu = new UserCommandMenu(getWidget("usercommandMenu"), ::UserCommand::CONTEXT_CHAT);
93 addChild(userCommandMenu);
94
95 GtkAdjustment *adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(getWidget("chatScroll")));
96
97 // Connect the signals to their callback functions.
98 g_signal_connect(getContainer(), "focus-in-event", G_CALLBACK(onFocusIn_gui), (gpointer)this);
99 g_signal_connect(nickView.get(), "button-press-event", G_CALLBACK(onNickListButtonPress_gui), (gpointer)this);
100 g_signal_connect(nickView.get(), "button-release-event", G_CALLBACK(onNickListButtonRelease_gui), (gpointer)this);
101 g_signal_connect(nickView.get(), "key-release-event", G_CALLBACK(onNickListKeyRelease_gui), (gpointer)this);
102 g_signal_connect(getWidget("chatEntry"), "activate", G_CALLBACK(onSendMessage_gui), (gpointer)this);
103 g_signal_connect(getWidget("chatEntry"), "key-press-event", G_CALLBACK(onEntryKeyPress_gui), (gpointer)this);
104 g_signal_connect(getWidget("chatText"), "motion-notify-event", G_CALLBACK(onChatPointerMoved_gui), (gpointer)this);
105 g_signal_connect(getWidget("chatText"), "visibility-notify-event", G_CALLBACK(onChatVisibilityChanged_gui), (gpointer)this);
106 g_signal_connect(adjustment, "value_changed", G_CALLBACK(onChatScroll_gui), (gpointer)this);
107 g_signal_connect(adjustment, "changed", G_CALLBACK(onChatResize_gui), (gpointer)this);
108 g_signal_connect(getWidget("copyNickItem"), "activate", G_CALLBACK(onCopyNickItemClicked_gui), (gpointer)this);
109 g_signal_connect(getWidget("browseItem"), "activate", G_CALLBACK(onBrowseItemClicked_gui), (gpointer)this);
110 g_signal_connect(getWidget("matchItem"), "activate", G_CALLBACK(onMatchItemClicked_gui), (gpointer)this);
111 g_signal_connect(getWidget("msgItem"), "activate", G_CALLBACK(onMsgItemClicked_gui), (gpointer)this);
112 g_signal_connect(getWidget("grantItem"), "activate", G_CALLBACK(onGrantItemClicked_gui), (gpointer)this);
113 g_signal_connect(getWidget("copyLinkItem"), "activate", G_CALLBACK(onCopyURIClicked_gui), (gpointer)this);
114 g_signal_connect(getWidget("openLinkItem"), "activate", G_CALLBACK(onOpenLinkClicked_gui), (gpointer)this);
115 g_signal_connect(getWidget("copyhubItem"), "activate", G_CALLBACK(onCopyURIClicked_gui), (gpointer)this);
116 g_signal_connect(getWidget("openhubItem"), "activate", G_CALLBACK(onOpenHubClicked_gui), (gpointer)this);
117 g_signal_connect(getWidget("copyMagnetItem"), "activate", G_CALLBACK(onCopyURIClicked_gui), (gpointer)this);
118 g_signal_connect(getWidget("searchMagnetItem"), "activate", G_CALLBACK(onSearchMagnetClicked_gui), (gpointer)this);
119 g_signal_connect(getWidget("magnetPropertiesItem"), "activate", G_CALLBACK(onMagnetPropertiesClicked_gui), (gpointer)this);
120 g_signal_connect(getWidget("removeUserItem"), "activate", G_CALLBACK(onRemoveUserItemClicked_gui), (gpointer)this);
121 g_signal_connect(getWidget("favoriteUserItem"), "activate", G_CALLBACK(onAddFavoriteUserClicked_gui), (gpointer)this);
122
123 gtk_widget_grab_focus(getWidget("chatEntry"));
124
125 // Set the pane position
126 gint panePosition = WGETI("nick-pane-position");
127 if (panePosition > 10)
128 {
129 gint width;
130 GtkWindow *window = GTK_WINDOW(WulforManager::get()->getMainWindow()->getContainer());
131 gtk_window_get_size(window, &width, NULL);
132 gtk_paned_set_position(GTK_PANED(getWidget("pane")), width - panePosition);
133 }
134
135 history.push_back("");
136 }
137
~Hub()138 Hub::~Hub()
139 {
140 disconnect_client();
141
142 // Save the pane position
143 gint width;
144 GtkWindow *window = GTK_WINDOW(WulforManager::get()->getMainWindow()->getContainer());
145 gtk_window_get_size(window, &width, NULL);
146 gint panePosition = width - gtk_paned_get_position(GTK_PANED(getWidget("pane")));
147 if (panePosition > 10)
148 WSET("nick-pane-position", panePosition);
149
150 gtk_widget_destroy(getWidget("passwordDialog"));
151
152 if (handCursor)
153 {
154 gdk_cursor_unref(handCursor);
155 handCursor = NULL;
156 }
157 }
158
show()159 void Hub::show()
160 {
161 // Connect to the hub
162 typedef Func2<Hub, string, string> F2;
163 F2 *func = new F2(this, &Hub::connectClient_client, address, encoding);
164 WulforManager::get()->dispatchClientFunc(func);
165 }
166
setStatus_gui(string statusBar,string text)167 void Hub::setStatus_gui(string statusBar, string text)
168 {
169 if (!statusBar.empty() && !text.empty())
170 {
171 if (statusBar == "statusMain")
172 text = "[" + Util::getShortTimeString() + "] " + text;
173
174 gtk_statusbar_pop(GTK_STATUSBAR(getWidget(statusBar)), 0);
175 gtk_statusbar_push(GTK_STATUSBAR(getWidget(statusBar)), 0, text.c_str());
176 }
177 }
178
findUser_gui(const string & cid,GtkTreeIter * iter)179 bool Hub::findUser_gui(const string &cid, GtkTreeIter *iter)
180 {
181 unordered_map<std::string, GtkTreeIter>::const_iterator it = userIters.find(cid);
182
183 if (it != userIters.end())
184 {
185 if (iter)
186 *iter = it->second;
187
188 return TRUE;
189 }
190
191 return FALSE;
192 }
193
findNick_gui(const string & nick,GtkTreeIter * iter)194 bool Hub::findNick_gui(const string &nick, GtkTreeIter *iter)
195 {
196 unordered_map<std::string, std::string>::const_iterator it = userMap.find(nick);
197
198 if (it != userMap.end())
199 return findUser_gui(it->second, iter);
200
201 return FALSE;
202 }
203
updateStats_gui()204 void Hub::updateStats_gui()
205 {
206 // TRANSLATORS: Count of users appearing in the status bar.
207 setStatus_gui("statusUsers", P_("%1% User", "%1% Users", % userMap.size(), userMap.size()));
208 setStatus_gui("statusShared", Util::formatBytes(totalShared));
209 }
210
updateUser_gui(ParamMap params,bool showJoin)211 void Hub::updateUser_gui(ParamMap params, bool showJoin)
212 {
213 GtkTreeIter iter;
214 int64_t shared = Util::toInt64(params["Shared"]);
215 const string& cid = params["CID"];
216 const string icon = "linuxdcpp-" + params["Icon"];
217
218 if (findUser_gui(cid, &iter))
219 {
220 totalShared += shared - nickView.getValue<int64_t>(&iter, "Shared");
221 string nick = nickView.getString(&iter, "User");
222
223 if (nick != params["User"])
224 {
225 // User has changed nick, update userMap and remove the old Nick tag
226 userMap.erase(nick);
227 removeTag_gui(nick);
228 userMap[params["User"]] = cid;
229 }
230
231 gtk_list_store_set(nickStore, &iter,
232 nickView.col("User"), params["User"].c_str(),
233 nickView.col("Shared"), shared,
234 nickView.col("Description"), params["Description"].c_str(),
235 nickView.col("Tag"), params["Tag"].c_str(),
236 nickView.col("Connection"), params["Connection"].c_str(),
237 nickView.col("IP"), params["IP"].c_str(),
238 nickView.col("Email"), params["Email"].c_str(),
239 nickView.col("Icon"), icon.c_str(),
240 nickView.col("Nick Order"), params["Nick Order"].c_str(),
241 nickView.col("CID"), cid.c_str(),
242 -1);
243 }
244 else
245 {
246 totalShared += shared;
247 userMap[params["User"]] = cid;
248
249 gtk_list_store_insert_with_values(nickStore, &iter, userMap.size(),
250 nickView.col("User"), params["User"].c_str(),
251 nickView.col("Shared"), shared,
252 nickView.col("Description"), params["Description"].c_str(),
253 nickView.col("Tag"), params["Tag"].c_str(),
254 nickView.col("Connection"), params["Connection"].c_str(),
255 nickView.col("IP"), params["IP"].c_str(),
256 nickView.col("Email"), params["Email"].c_str(),
257 nickView.col("Icon"), icon.c_str(),
258 nickView.col("Nick Order"), params["Nick Order"].c_str(),
259 nickView.col("CID"), cid.c_str(),
260 -1);
261
262 userIters[cid] = iter;
263
264 if (showJoin)
265 addStatusMessage_gui(F_("%1% has joined", % params["User"]));
266 }
267
268 updateStats_gui();
269 }
270
removeUser_gui(string cid)271 void Hub::removeUser_gui(string cid)
272 {
273 GtkTreeIter iter;
274 string nick;
275
276 if (findUser_gui(cid, &iter))
277 {
278 nick = nickView.getString(&iter, "User");
279 totalShared -= nickView.getValue<int64_t>(&iter, "Shared");
280 gtk_list_store_remove(nickStore, &iter);
281 removeTag_gui(nick);
282 userMap.erase(nick);
283 userIters.erase(cid);
284 updateStats_gui();
285 }
286 }
287
288 /*
289 * Remove nick tag from text view
290 */
removeTag_gui(const std::string & nick)291 void Hub::removeTag_gui(const std::string &nick)
292 {
293 GtkTextTagTable *textTagTable = gtk_text_buffer_get_tag_table(chatBuffer);
294 GtkTextTag *tag = gtk_text_tag_table_lookup(textTagTable, nick.c_str());
295 if (tag)
296 gtk_text_tag_table_remove(textTagTable, tag);
297 }
298
clearNickList_gui()299 void Hub::clearNickList_gui()
300 {
301 // Remove all old nick tags from the text view
302 unordered_map<string, string>::const_iterator it;
303 for (it = userMap.begin(); it != userMap.end(); ++it)
304 removeTag_gui(it->first);
305
306 gtk_list_store_clear(nickStore);
307 userMap.clear();
308 userIters.clear();
309 totalShared = 0;
310 updateStats_gui();
311 }
312
popupNickMenu_gui()313 void Hub::popupNickMenu_gui()
314 {
315 // Build user command menu
316 userCommandMenu->cleanMenu_gui();
317
318 GtkTreeIter iter;
319 GList *list = gtk_tree_selection_get_selected_rows(nickSelection, NULL);
320
321 for (GList *i = list; i; i = i->next)
322 {
323 GtkTreePath *path = (GtkTreePath *)i->data;
324 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(nickStore), &iter, path))
325 {
326 userCommandMenu->addUser(nickView.getString(&iter, "CID"));
327 }
328 gtk_tree_path_free(path);
329 }
330 g_list_free(list);
331
332 userCommandMenu->addHub(client->getHubUrl());
333 userCommandMenu->buildMenu_gui();
334
335 gtk_menu_popup(GTK_MENU(getWidget("nickMenu")), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
336 gtk_widget_show_all(getWidget("nickMenu"));
337 }
338
getPassword_gui()339 void Hub::getPassword_gui()
340 {
341 gint ret;
342
343 ret = gtk_dialog_run(GTK_DIALOG(getWidget("passwordDialog")));
344 gtk_widget_hide(getWidget("passwordDialog"));
345
346 if (ret == GTK_RESPONSE_OK)
347 {
348 string password = gtk_entry_get_text(GTK_ENTRY(getWidget("passwordEntry")));
349 typedef Func1<Hub, string> F1;
350 F1 *func = new F1(this, &Hub::setPassword_client, password);
351 WulforManager::get()->dispatchClientFunc(func);
352 }
353 else
354 client->disconnect(TRUE);
355 }
356
addStatusMessage_gui(string message)357 void Hub::addStatusMessage_gui(string message)
358 {
359 if (!message.empty())
360 {
361 setStatus_gui("statusMain", message);
362
363 if (BOOLSETTING(STATUS_IN_CHAT))
364 {
365 string line = "*** " + message;
366 addMessage_gui(line);
367 }
368 }
369 }
370
addMessage_gui(string message)371 void Hub::addMessage_gui(string message)
372 {
373 // See lp:541548, Some broken hubs end emotes with \0 instead of |. The core
374 // then passes the whole command from $ to next | to GUI and the null byte then
375 // messes the utf8 validation done by gtk_text_buffer_insert. So we drop everything
376 // after \0.
377 message = message.c_str();
378 if (message.empty())
379 return;
380
381 GtkTextIter iter;
382 string line = "";
383
384 // Add a new line if this isn't the first line in buffer.
385 if (gtk_text_buffer_get_char_count(chatBuffer) > 0)
386 line = "\n";
387
388 if (BOOLSETTING(TIME_STAMPS))
389 line += "[" + Util::getShortTimeString() + "] ";
390
391 line += message;
392
393 gtk_text_buffer_get_end_iter(chatBuffer, &iter);
394 gtk_text_buffer_insert(chatBuffer, &iter, line.c_str(), line.size());
395
396 applyTags_gui(line);
397
398 gtk_text_buffer_get_end_iter(chatBuffer, &iter);
399
400 // Limit size of chat text
401 if (gtk_text_buffer_get_line_count(chatBuffer) > maxLines)
402 {
403 GtkTextIter next;
404 gtk_text_buffer_get_start_iter(chatBuffer, &iter);
405 gtk_text_buffer_get_iter_at_line(chatBuffer, &next, 1);
406 gtk_text_buffer_delete(chatBuffer, &iter, &next);
407 }
408 }
409
applyTags_gui(const string & line)410 void Hub::applyTags_gui(const string &line)
411 {
412 GtkTextIter iter;
413 GtkTextIter startIter;
414 GtkTextIter endIter;
415 bool firstNick = FALSE;
416 string::size_type start;
417 string::size_type end = 0;
418
419 gtk_text_buffer_get_end_iter(chatBuffer, &iter);
420
421 // Tag nicknames and URIs
422 while ((start = line.find_first_not_of(" \n\r\t", end)) != string::npos)
423 {
424 end = line.find_first_of(" \n\r\t", start);
425 if (end == string::npos)
426 end = line.size();
427
428 // Special case: catch nicks in the form <nick> at the beginning of the line.
429 if (!firstNick && start < end - 1 && line[start] == '<' && line[end - 1] == '>')
430 {
431 ++start;
432 --end;
433 firstNick = TRUE;
434 }
435
436 GCallback callback = NULL;
437 bool isNick = FALSE;
438 string tagName = line.substr(start, end - start);
439
440 if (findNick_gui(tagName, NULL))
441 {
442 isNick = TRUE;
443 callback = G_CALLBACK(onNickTagEvent_gui);
444 }
445 else
446 {
447 if (WulforUtil::isLink(tagName))
448 callback = G_CALLBACK(onLinkTagEvent_gui);
449 else if (WulforUtil::isHubURL(tagName))
450 callback = G_CALLBACK(onHubTagEvent_gui);
451 else if (WulforUtil::isMagnet(tagName))
452 callback = G_CALLBACK(onMagnetTagEvent_gui);
453 }
454
455 if (callback)
456 {
457 // check for the tag in our buffer
458 GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(chatBuffer), tagName.c_str());
459
460 if (!tag)
461 {
462 if (isNick)
463 tag = gtk_text_buffer_create_tag(chatBuffer, tagName.c_str(), "style", PANGO_STYLE_ITALIC, NULL);
464 else
465 tag = gtk_text_buffer_create_tag(chatBuffer, tagName.c_str(), "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE, NULL);
466
467 g_signal_connect(tag, "event", callback, (gpointer)this);
468 }
469
470 startIter = endIter = iter;
471 gtk_text_iter_backward_chars(&startIter, g_utf8_strlen(line.c_str() + start, -1));
472 gtk_text_iter_backward_chars(&endIter, g_utf8_strlen(line.c_str() + end, -1));
473 gtk_text_buffer_apply_tag(chatBuffer, tag, &startIter, &endIter);
474 }
475 }
476 }
477
478 /*
479 * Unfortunately, we can't underline the tag on mouse over since it would
480 * underline all the tags with that name.
481 */
updateCursor_gui(GtkWidget * widget)482 void Hub::updateCursor_gui(GtkWidget *widget)
483 {
484 gint x, y, buf_x, buf_y;
485 GtkTextIter iter;
486 GSList *tagList;
487 GtkTextTag *newTag = NULL;
488
489 gdk_window_get_pointer(widget->window, &x, &y, NULL);
490
491 // Check for tags under the cursor, and change mouse cursor appropriately
492 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_WIDGET, x, y, &buf_x, &buf_y);
493 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &iter, buf_x, buf_y);
494 tagList = gtk_text_iter_get_tags(&iter);
495
496 if (tagList != NULL)
497 {
498 newTag = GTK_TEXT_TAG(tagList->data);
499 g_slist_free(tagList);
500 }
501
502 if (newTag != selectedTag)
503 {
504 // Cursor is in transition.
505 if (newTag != NULL)
506 {
507 // Cursor is entering a tag.
508 selectedTagStr = newTag->name;
509 if (selectedTag == NULL)
510 {
511 // Cursor was in neutral space.
512 gdk_window_set_cursor(gtk_text_view_get_window(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT), handCursor);
513 }
514 }
515 else
516 {
517 // Cursor is entering neutral space.
518 gdk_window_set_cursor(gtk_text_view_get_window(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT), NULL);
519 }
520 selectedTag = newTag;
521 }
522 }
523
onFocusIn_gui(GtkWidget * widget,GdkEventFocus * event,gpointer data)524 gboolean Hub::onFocusIn_gui(GtkWidget *widget, GdkEventFocus *event, gpointer data)
525 {
526 Hub *hub = (Hub *)data;
527
528 gtk_widget_grab_focus(hub->getWidget("chatEntry"));
529
530 return TRUE;
531 }
532
onNickListButtonPress_gui(GtkWidget * widget,GdkEventButton * event,gpointer data)533 gboolean Hub::onNickListButtonPress_gui(GtkWidget *widget, GdkEventButton *event, gpointer data)
534 {
535 Hub *hub = (Hub *)data;
536
537 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_2BUTTON_PRESS)
538 hub->oldType = event->type;
539
540 if (event->button == 3)
541 {
542 GtkTreePath *path;
543 if (gtk_tree_view_get_path_at_pos(hub->nickView.get(), (gint)event->x, (gint)event->y, &path, NULL, NULL, NULL))
544 {
545 bool selected = gtk_tree_selection_path_is_selected(hub->nickSelection, path);
546 gtk_tree_path_free(path);
547
548 if (selected)
549 return TRUE;
550 }
551 }
552
553 return FALSE;
554 }
555
onNickListButtonRelease_gui(GtkWidget * widget,GdkEventButton * event,gpointer data)556 gboolean Hub::onNickListButtonRelease_gui(GtkWidget *widget, GdkEventButton *event, gpointer data)
557 {
558 Hub *hub = (Hub *)data;
559
560 if (gtk_tree_selection_count_selected_rows(hub->nickSelection) > 0)
561 {
562 if (event->button == 1 && hub->oldType == GDK_2BUTTON_PRESS)
563 {
564 hub->onBrowseItemClicked_gui(NULL, data);
565 }
566 else if (event->button == 2 && event->type == GDK_BUTTON_RELEASE)
567 {
568 hub->onMsgItemClicked_gui(NULL, data);
569 }
570 else if (event->button == 3 && event->type == GDK_BUTTON_RELEASE)
571 {
572 hub->popupNickMenu_gui();
573 }
574 }
575
576 return FALSE;
577 }
578
onNickListKeyRelease_gui(GtkWidget * widget,GdkEventKey * event,gpointer data)579 gboolean Hub::onNickListKeyRelease_gui(GtkWidget *widget, GdkEventKey *event, gpointer data)
580 {
581 Hub *hub = (Hub *)data;
582
583 if (gtk_tree_selection_count_selected_rows(hub->nickSelection) > 0)
584 {
585 if (event->keyval == GDK_Menu || (event->keyval == GDK_F10 && event->state & GDK_SHIFT_MASK))
586 {
587 hub->popupNickMenu_gui();
588 }
589 else if (event->keyval == GDK_Return || event->keyval == GDK_KP_Enter)
590 {
591 hub->onBrowseItemClicked_gui(NULL, data);
592 }
593 }
594
595 return FALSE;
596 }
597
598 /*
599 * Implements a case-insensitive substring search for UTF-8 strings.
600 */
onNickListSearch_gui(GtkTreeModel * model,gint column,const gchar * key,GtkTreeIter * iter,gpointer data)601 gboolean Hub::onNickListSearch_gui(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer data)
602 {
603 gboolean result = TRUE;
604 gchar *nick;
605 gtk_tree_model_get(model, iter, column, &nick, -1);
606
607 gchar *keyCasefold = g_utf8_casefold(key, -1);
608 gchar *nickCasefold = g_utf8_casefold(nick, -1);
609
610 // Return false per search equal func API if the key is contained within the nick
611 if (g_strstr_len(nickCasefold, -1, keyCasefold) != NULL)
612 result = FALSE;
613
614 g_free(nick);
615 g_free(keyCasefold);
616 g_free(nickCasefold);
617
618 return result;
619 }
620
onEntryKeyPress_gui(GtkWidget * entry,GdkEventKey * event,gpointer data)621 gboolean Hub::onEntryKeyPress_gui(GtkWidget *entry, GdkEventKey *event, gpointer data)
622 {
623 Hub *hub = (Hub *)data;
624
625 if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
626 {
627 size_t index = hub->historyIndex - 1;
628 if (index >= 0 && index < hub->history.size())
629 {
630 hub->historyIndex = index;
631 gtk_entry_set_text(GTK_ENTRY(entry), hub->history[index].c_str());
632 }
633 return TRUE;
634 }
635 else if (event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
636 {
637 size_t index = hub->historyIndex + 1;
638 if (index >= 0 && index < hub->history.size())
639 {
640 hub->historyIndex = index;
641 gtk_entry_set_text(GTK_ENTRY(entry), hub->history[index].c_str());
642 }
643 return TRUE;
644 }
645 else if (event->keyval == GDK_Tab || event->keyval == GDK_ISO_Left_Tab)
646 {
647 string current;
648 string::size_type start, end;
649 string text(gtk_entry_get_text(GTK_ENTRY(entry)));
650 int curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
651
652 // Allow tab to focus other widgets if entry is empty
653 if (curpos <= 0 && text.empty())
654 return FALSE;
655
656 // Erase ": " at the end of the nick.
657 if (curpos > 2 && text.substr(curpos - 2, 2) == ": ")
658 {
659 text.erase(curpos - 2, 2);
660 curpos -= 2;
661 }
662
663 start = text.rfind(' ', curpos - 1);
664 end = text.find(' ', curpos - 1);
665
666 // Text to match starts at the beginning
667 if (start == string::npos)
668 start = 0;
669 else
670 ++start;
671
672 if (start < end)
673 {
674 current = text.substr(start, end - start);
675
676 if (hub->completionKey.empty() || Text::toLower(current).find(Text::toLower(hub->completionKey)) == string::npos)
677 hub->completionKey = current;
678
679 GtkTreeIter iter;
680 bool valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(hub->nickStore), &iter);
681 bool useNext = (current == hub->completionKey);
682 string key = Text::toLower(hub->completionKey);
683 string complete = hub->completionKey;
684
685 while (valid)
686 {
687 string nick = hub->nickView.getString(&iter, "User");
688 string::size_type tagEnd = 0;
689 if (useNext && (tagEnd = Text::toLower(nick).find(key)) != string::npos)
690 {
691 if (tagEnd == 0 || nick.find_first_of("]})", tagEnd - 1) == tagEnd - 1)
692 {
693 complete = nick;
694 if (start <= 0)
695 complete.append(": ");
696 break;
697 }
698 }
699
700 if (nick == current)
701 useNext = TRUE;
702
703 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(hub->nickStore),&iter);
704 }
705
706 text.replace(start, end - start, complete);
707 gtk_entry_set_text(GTK_ENTRY(entry), text.c_str());
708 gtk_editable_set_position(GTK_EDITABLE(entry), start + complete.length());
709 }
710 else
711 hub->completionKey.clear();
712
713 return TRUE;
714 }
715
716 hub->completionKey.clear();
717 return FALSE;
718 }
719
onNickTagEvent_gui(GtkTextTag * tag,GObject * textView,GdkEvent * event,GtkTextIter * iter,gpointer data)720 gboolean Hub::onNickTagEvent_gui(GtkTextTag *tag, GObject *textView, GdkEvent *event, GtkTextIter *iter, gpointer data)
721 {
722 Hub *hub = (Hub *)data;
723
724 if (event->type == GDK_BUTTON_PRESS)
725 {
726 GtkTreeIter nickIter;
727 if (hub->findNick_gui(tag->name, &nickIter))
728 {
729 // Select the user in the nick list view
730 GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(hub->nickStore), &nickIter);
731 gtk_tree_view_scroll_to_cell(hub->nickView.get(), path, gtk_tree_view_get_column(hub->nickView.get(), hub->nickView.col("User")), FALSE, 0.0, 0.0);
732 gtk_tree_view_set_cursor(hub->nickView.get(), path, NULL, FALSE);
733 gtk_tree_path_free(path);
734
735 if (event->button.button == 3)
736 hub->popupNickMenu_gui();
737 }
738
739 return TRUE;
740 }
741 return FALSE;
742 }
743
onLinkTagEvent_gui(GtkTextTag * tag,GObject * textView,GdkEvent * event,GtkTextIter * iter,gpointer data)744 gboolean Hub::onLinkTagEvent_gui(GtkTextTag *tag, GObject *textView, GdkEvent *event, GtkTextIter *iter, gpointer data)
745 {
746 Hub *hub = (Hub *)data;
747
748 if (event->type == GDK_BUTTON_PRESS)
749 {
750 switch (event->button.button)
751 {
752 case 1:
753 onOpenLinkClicked_gui(NULL, data);
754 break;
755 case 3:
756 // Popup uri context menu
757 gtk_widget_show_all(hub->getWidget("linkMenu"));
758 gtk_menu_popup(GTK_MENU(hub->getWidget("linkMenu")), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
759 break;
760 }
761 return TRUE;
762 }
763 return FALSE;
764 }
765
onHubTagEvent_gui(GtkTextTag * tag,GObject * textView,GdkEvent * event,GtkTextIter * iter,gpointer data)766 gboolean Hub::onHubTagEvent_gui(GtkTextTag *tag, GObject *textView, GdkEvent *event, GtkTextIter *iter, gpointer data)
767 {
768 Hub *hub = (Hub *)data;
769
770 if (event->type == GDK_BUTTON_PRESS)
771 {
772 switch (event->button.button)
773 {
774 case 1:
775 onOpenHubClicked_gui(NULL, data);
776 break;
777 case 3:
778 // Popup uri context menu
779 gtk_widget_show_all(hub->getWidget("hubMenu"));
780 gtk_menu_popup(GTK_MENU(hub->getWidget("hubMenu")), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
781 break;
782 }
783 return TRUE;
784 }
785 return FALSE;
786 }
787
onMagnetTagEvent_gui(GtkTextTag * tag,GObject * textView,GdkEvent * event,GtkTextIter * iter,gpointer data)788 gboolean Hub::onMagnetTagEvent_gui(GtkTextTag *tag, GObject *textView, GdkEvent *event, GtkTextIter *iter, gpointer data)
789 {
790 Hub *hub = (Hub *)data;
791
792 if (event->type == GDK_BUTTON_PRESS)
793 {
794 switch (event->button.button)
795 {
796 case 1:
797 // Search for magnet
798 onSearchMagnetClicked_gui(NULL, data);
799 break;
800 case 3:
801 // Popup magnet context menu
802 gtk_widget_show_all(hub->getWidget("magnetMenu"));
803 gtk_menu_popup(GTK_MENU(hub->getWidget("magnetMenu")), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
804 break;
805 }
806 return TRUE;
807 }
808 return FALSE;
809 }
810
onChatPointerMoved_gui(GtkWidget * widget,GdkEventMotion * event,gpointer data)811 gboolean Hub::onChatPointerMoved_gui(GtkWidget *widget, GdkEventMotion *event, gpointer data)
812 {
813 Hub *hub = (Hub *)data;
814
815 hub->updateCursor_gui(widget);
816
817 return FALSE;
818 }
819
onChatVisibilityChanged_gui(GtkWidget * widget,GdkEventVisibility * event,gpointer data)820 gboolean Hub::onChatVisibilityChanged_gui(GtkWidget *widget, GdkEventVisibility *event, gpointer data)
821 {
822 Hub *hub = (Hub *)data;
823
824 hub->updateCursor_gui(widget);
825
826 return FALSE;
827 }
828
onChatScroll_gui(GtkAdjustment * adjustment,gpointer data)829 void Hub::onChatScroll_gui(GtkAdjustment *adjustment, gpointer data)
830 {
831 Hub *hub = (Hub *)data;
832 gdouble value = gtk_adjustment_get_value(adjustment);
833 hub->scrollToBottom = value >= (adjustment->upper - adjustment->page_size);
834 }
835
onChatResize_gui(GtkAdjustment * adjustment,gpointer data)836 void Hub::onChatResize_gui(GtkAdjustment *adjustment, gpointer data)
837 {
838 Hub *hub = (Hub *)data;
839 gdouble value = gtk_adjustment_get_value(adjustment);
840
841 if (hub->scrollToBottom && value < (adjustment->upper - adjustment->page_size))
842 {
843 GtkTextIter iter;
844
845 gtk_text_buffer_get_end_iter(hub->chatBuffer, &iter);
846 gtk_text_buffer_move_mark(hub->chatBuffer, hub->chatMark, &iter);
847 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(hub->getWidget("chatText")), hub->chatMark, 0, FALSE, 0, 0);
848 }
849 }
850
onSendMessage_gui(GtkEntry * entry,gpointer data)851 void Hub::onSendMessage_gui(GtkEntry *entry, gpointer data)
852 {
853 string text = gtk_entry_get_text(entry);
854 if (text.empty())
855 return;
856
857 gtk_entry_set_text(entry, "");
858 Hub *hub = (Hub *)data;
859 typedef Func1<Hub, string> F1;
860 F1 *func;
861 typedef Func2<Hub, string, bool> F2;
862 F2 *func2;
863
864 // Store line in chat history
865 hub->history.pop_back();
866 hub->history.push_back(text);
867 hub->history.push_back("");
868 hub->historyIndex = hub->history.size() - 1;
869 if (hub->history.size() > maxHistory + 1)
870 hub->history.erase(hub->history.begin());
871
872 // Process special commands
873 if (text[0] == '/')
874 {
875 string command, param;
876 string::size_type separator = text.find_first_of(' ');
877 if (separator != string::npos && text.size() > separator + 1)
878 {
879 command = text.substr(1, separator - 1);
880 param = text.substr(separator + 1);
881 }
882 else
883 {
884 command = text.substr(1);
885 }
886 std::transform(command.begin(), command.end(), command.begin(), (int(*)(int))tolower);
887
888 if (command == "away")
889 {
890 if (Util::getAway() && param.empty())
891 {
892 Util::setAway(FALSE);
893 Util::setManualAway(FALSE);
894 hub->addStatusMessage_gui(_("Away mode off"));
895 }
896 else
897 {
898 Util::setAway(TRUE);
899 Util::setManualAway(TRUE);
900 Util::setAwayMessage(param);
901 hub->addStatusMessage_gui(F_("Away mode on: %1%", % Util::getAwayMessage()));
902 }
903 }
904 else if (command == "back")
905 {
906 Util::setAway(FALSE);
907 hub->addStatusMessage_gui(_("Away mode off"));
908 }
909 else if (command == "clear")
910 {
911 GtkTextIter startIter, endIter;
912 gtk_text_buffer_get_start_iter(hub->chatBuffer, &startIter);
913 gtk_text_buffer_get_end_iter(hub->chatBuffer, &endIter);
914 gtk_text_buffer_delete(hub->chatBuffer, &startIter, &endIter);
915 }
916 else if (command == "close")
917 {
918 /// @todo: figure out why this sometimes closes and reopens the tab
919 WulforManager::get()->getMainWindow()->removeBookEntry_gui(hub);
920 }
921 else if (command == "favorite" || command == "fav")
922 {
923 WulforManager::get()->dispatchClientFunc(new Func0<Hub>(hub, &Hub::addAsFavorite_client));
924 }
925 else if (command == "getlist")
926 {
927 if (hub->userMap.find(param) != hub->userMap.end())
928 {
929 func2 = new F2(hub, &Hub::getFileList_client, hub->userMap[param], FALSE);
930 WulforManager::get()->dispatchClientFunc(func2);
931 }
932 else
933 hub->addStatusMessage_gui(_("User not found"));
934 }
935 else if (command == "grant")
936 {
937 if (hub->userMap.find(param) != hub->userMap.end())
938 {
939 func = new F1(hub, &Hub::grantSlot_client, hub->userMap[param]);
940 WulforManager::get()->dispatchClientFunc(func);
941 }
942 else
943 hub->addStatusMessage_gui(_("User not found"));
944 }
945 else if (command == "help")
946 {
947 // TRANSLATORS: /commands aren't translatable. So leave them as they are in the help string
948 hub->addStatusMessage_gui(_("Available commands: /away <message>, /back, /clear, /close, /favorite, "\
949 "/getlist <user>, /grant <user>, /help, /join <address>, /me <message>, /pm <user>, /rebuild, /refresh, /userlist"));
950 }
951 else if (command == "join" && !param.empty())
952 {
953 if (BOOLSETTING(JOIN_OPEN_NEW_WINDOW))
954 {
955 // Assumption: new hub is same encoding as current hub.
956 WulforManager::get()->getMainWindow()->showHub_gui(param, hub->encoding);
957 }
958 else
959 {
960 typedef Func2<Hub, string, bool> F2;
961 F2 *func = new F2(hub, &Hub::redirect_client, param, TRUE);
962 WulforManager::get()->dispatchClientFunc(func);
963 }
964 }
965 else if (command == "me")
966 {
967 func2 = new F2(hub, &Hub::sendMessage_client, param, true);
968 WulforManager::get()->dispatchClientFunc(func2);
969 }
970 else if (command == "pm")
971 {
972 if (hub->userMap.find(param) != hub->userMap.end())
973 WulforManager::get()->getMainWindow()->addPrivateMessage_gui(hub->userMap[param], hub->client->getHubUrl());
974 else
975 hub->addStatusMessage_gui(_("User not found"));
976 }
977 else if (command == "rebuild")
978 {
979 WulforManager::get()->dispatchClientFunc(new Func0<Hub>(hub, &Hub::rebuildHashData_client));
980 }
981 else if (command == "refresh")
982 {
983 WulforManager::get()->dispatchClientFunc(new Func0<Hub>(hub, &Hub::refreshFileList_client));
984 }
985 else if (command == "userlist")
986 {
987 if (GTK_WIDGET_VISIBLE(hub->getWidget("scrolledwindow2")))
988 gtk_widget_hide(hub->getWidget("scrolledwindow2"));
989 else
990 gtk_widget_show_all(hub->getWidget("scrolledwindow2"));
991 }
992 else if (BOOLSETTING(SEND_UNKNOWN_COMMANDS))
993 {
994 func2 = new F2(hub, &Hub::sendMessage_client, text, false);
995 WulforManager::get()->dispatchClientFunc(func2);
996 }
997 else
998 {
999 hub->addStatusMessage_gui(F_("Unknown command '%1%': Type /help for a list of available commands", % text));
1000 }
1001
1002 }
1003 else
1004 {
1005 func2 = new F2(hub, &Hub::sendMessage_client, text, false);
1006 WulforManager::get()->dispatchClientFunc(func2);
1007 }
1008 }
1009
onCopyNickItemClicked_gui(GtkMenuItem * item,gpointer data)1010 void Hub::onCopyNickItemClicked_gui(GtkMenuItem *item, gpointer data)
1011 {
1012 Hub *hub = (Hub *)data;
1013
1014 if (gtk_tree_selection_count_selected_rows(hub->nickSelection) > 0)
1015 {
1016 string nicks;
1017 GtkTreeIter iter;
1018 GtkTreePath *path;
1019 GList *list = gtk_tree_selection_get_selected_rows(hub->nickSelection, NULL);
1020
1021 for (GList *i = list; i; i = i->next)
1022 {
1023 path = (GtkTreePath *)i->data;
1024 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(hub->nickStore), &iter, path))
1025 {
1026 nicks += hub->nickView.getString(&iter, "User") + ' ';
1027 }
1028 gtk_tree_path_free(path);
1029 }
1030 g_list_free(list);
1031
1032 if (!nicks.empty())
1033 {
1034 nicks.erase(nicks.length() - 1);
1035 gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), nicks.c_str(), nicks.length());
1036 }
1037 }
1038 }
1039
onBrowseItemClicked_gui(GtkMenuItem * item,gpointer data)1040 void Hub::onBrowseItemClicked_gui(GtkMenuItem *item, gpointer data)
1041 {
1042 Hub *hub = (Hub *)data;
1043
1044 if (gtk_tree_selection_count_selected_rows(hub->nickSelection) > 0)
1045 {
1046 string cid;
1047 GtkTreeIter iter;
1048 GtkTreePath *path;
1049 typedef Func2<Hub, string, bool> F2;
1050 F2 *func;
1051 GList *list = gtk_tree_selection_get_selected_rows(hub->nickSelection, NULL);
1052
1053 for (GList *i = list; i; i = i->next)
1054 {
1055 path = (GtkTreePath *)i->data;
1056 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(hub->nickStore), &iter, path))
1057 {
1058 cid = hub->nickView.getString(&iter, "CID");
1059 func = new F2(hub, &Hub::getFileList_client, cid, FALSE);
1060 WulforManager::get()->dispatchClientFunc(func);
1061 }
1062 gtk_tree_path_free(path);
1063 }
1064 g_list_free(list);
1065 }
1066 }
1067
onMatchItemClicked_gui(GtkMenuItem * item,gpointer data)1068 void Hub::onMatchItemClicked_gui(GtkMenuItem *item, gpointer data)
1069 {
1070 Hub *hub = (Hub *)data;
1071
1072 if (gtk_tree_selection_count_selected_rows(hub->nickSelection) > 0)
1073 {
1074 string cid;
1075 GtkTreeIter iter;
1076 GtkTreePath *path;
1077 typedef Func2<Hub, string, bool> F2;
1078 F2 *func;
1079 GList *list = gtk_tree_selection_get_selected_rows(hub->nickSelection, NULL);
1080
1081 for (GList *i = list; i; i = i->next)
1082 {
1083 path = (GtkTreePath *)i->data;
1084 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(hub->nickStore), &iter, path))
1085 {
1086 cid = hub->nickView.getString(&iter, "CID");
1087 func = new F2(hub, &Hub::getFileList_client, cid, TRUE);
1088 WulforManager::get()->dispatchClientFunc(func);
1089 }
1090 gtk_tree_path_free(path);
1091 }
1092 g_list_free(list);
1093 }
1094 }
1095
onMsgItemClicked_gui(GtkMenuItem * item,gpointer data)1096 void Hub::onMsgItemClicked_gui(GtkMenuItem *item, gpointer data)
1097 {
1098 Hub *hub = (Hub *)data;
1099
1100 if (gtk_tree_selection_count_selected_rows(hub->nickSelection) > 0)
1101 {
1102 string cid;
1103 GtkTreeIter iter;
1104 GtkTreePath *path;
1105 GList *list = gtk_tree_selection_get_selected_rows(hub->nickSelection, NULL);
1106 const string &hubUrl = hub->client->getHubUrl();
1107
1108 for (GList *i = list; i; i = i->next)
1109 {
1110 path = (GtkTreePath *)i->data;
1111 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(hub->nickStore), &iter, path))
1112 {
1113 cid = hub->nickView.getString(&iter, "CID");
1114 WulforManager::get()->getMainWindow()->addPrivateMessage_gui(cid, hubUrl);
1115 }
1116 gtk_tree_path_free(path);
1117 }
1118 g_list_free(list);
1119 }
1120 }
1121
onGrantItemClicked_gui(GtkMenuItem * item,gpointer data)1122 void Hub::onGrantItemClicked_gui(GtkMenuItem *item, gpointer data)
1123 {
1124 Hub *hub = (Hub *)data;
1125
1126 if (gtk_tree_selection_count_selected_rows(hub->nickSelection) > 0)
1127 {
1128 string cid;
1129 GtkTreeIter iter;
1130 GtkTreePath *path;
1131 typedef Func1<Hub, string> F1;
1132 F1 *func;
1133 GList *list = gtk_tree_selection_get_selected_rows(hub->nickSelection, NULL);
1134
1135 for (GList *i = list; i; i = i->next)
1136 {
1137 path = (GtkTreePath *)i->data;
1138 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(hub->nickStore), &iter, path))
1139 {
1140 cid = hub->nickView.getString(&iter, "CID");
1141 func = new F1(hub, &Hub::grantSlot_client, cid);
1142 WulforManager::get()->dispatchClientFunc(func);
1143 }
1144 gtk_tree_path_free(path);
1145 }
1146 g_list_free(list);
1147 }
1148 }
1149
onRemoveUserItemClicked_gui(GtkMenuItem * item,gpointer data)1150 void Hub::onRemoveUserItemClicked_gui(GtkMenuItem *item, gpointer data)
1151 {
1152 Hub *hub = (Hub *)data;
1153
1154 if (gtk_tree_selection_count_selected_rows(hub->nickSelection) > 0)
1155 {
1156 string cid;
1157 GtkTreeIter iter;
1158 GtkTreePath *path;
1159 typedef Func1<Hub, string> F1;
1160 F1 *func;
1161 GList *list = gtk_tree_selection_get_selected_rows(hub->nickSelection, NULL);
1162
1163 for (GList *i = list; i; i = i->next)
1164 {
1165 path = (GtkTreePath *)i->data;
1166 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(hub->nickStore), &iter, path))
1167 {
1168 cid = hub->nickView.getString(&iter, "CID");
1169 func = new F1(hub, &Hub::removeUserFromQueue_client, cid);
1170 WulforManager::get()->dispatchClientFunc(func);
1171 }
1172 gtk_tree_path_free(path);
1173 }
1174 g_list_free(list);
1175 }
1176 }
1177
onCopyURIClicked_gui(GtkMenuItem * item,gpointer data)1178 void Hub::onCopyURIClicked_gui(GtkMenuItem *item, gpointer data)
1179 {
1180 Hub *hub = (Hub *)data;
1181
1182 gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), hub->selectedTagStr.c_str(), hub->selectedTagStr.length());
1183 }
1184
onOpenLinkClicked_gui(GtkMenuItem * item,gpointer data)1185 void Hub::onOpenLinkClicked_gui(GtkMenuItem *item, gpointer data)
1186 {
1187 Hub *hub = (Hub *)data;
1188
1189 WulforUtil::openURI(hub->selectedTagStr);
1190 }
1191
onOpenHubClicked_gui(GtkMenuItem * item,gpointer data)1192 void Hub::onOpenHubClicked_gui(GtkMenuItem *item, gpointer data)
1193 {
1194 Hub *hub = (Hub *)data;
1195
1196 WulforManager::get()->getMainWindow()->showHub_gui(hub->selectedTagStr);
1197 }
1198
onSearchMagnetClicked_gui(GtkMenuItem * item,gpointer data)1199 void Hub::onSearchMagnetClicked_gui(GtkMenuItem *item, gpointer data)
1200 {
1201 Hub *hub = (Hub*)data;
1202 WulforManager::get()->getMainWindow()->addMagnetSearch_gui(hub->selectedTagStr);
1203 }
1204
onMagnetPropertiesClicked_gui(GtkMenuItem * item,gpointer data)1205 void Hub::onMagnetPropertiesClicked_gui(GtkMenuItem *item, gpointer data)
1206 {
1207 Hub *hub = (Hub *)data;
1208
1209 WulforManager::get()->getMainWindow()->openMagnetDialog_gui(hub->selectedTagStr);
1210 }
1211
onAddFavoriteUserClicked_gui(GtkMenuItem * item,gpointer data)1212 void Hub::onAddFavoriteUserClicked_gui(GtkMenuItem *item, gpointer data)
1213 {
1214 Hub *hub = (Hub *)data;
1215
1216 if (gtk_tree_selection_count_selected_rows(hub->nickSelection) > 0)
1217 {
1218 string cid, nick;
1219 GtkTreeIter iter;
1220 GtkTreePath *path;
1221 typedef Func1<Hub, string> F1;
1222 GList *list = gtk_tree_selection_get_selected_rows(hub->nickSelection, NULL);
1223
1224 for (GList *i = list; i; i = i->next)
1225 {
1226 path = (GtkTreePath *)i->data;
1227 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(hub->nickStore), &iter, path))
1228 {
1229 cid = hub->nickView.getString(&iter, "CID");
1230 nick = hub->nickView.getString(&iter, "User");
1231 if (!cid.empty() && nick != hub->client->getMyNick())
1232 {
1233 F1 *func = new F1(hub, &Hub::addFavoriteUser_client, cid);
1234 WulforManager::get()->dispatchClientFunc(func);
1235 }
1236 }
1237 gtk_tree_path_free(path);
1238 }
1239 g_list_free(list);
1240 }
1241 }
1242
addFavoriteUser_client(const string cid)1243 void Hub::addFavoriteUser_client(const string cid)
1244 {
1245 UserPtr user = ClientManager::getInstance()->findUser(CID(cid));
1246
1247 if (user)
1248 {
1249 FavoriteManager::getInstance()->addFavoriteUser(user);
1250 }
1251 }
1252
connectClient_client(string address,string encoding)1253 void Hub::connectClient_client(string address, string encoding)
1254 {
1255 dcassert(client == NULL);
1256
1257 if (address.substr(0, 6) == "adc://" || address.substr(0, 7) == "adcs://")
1258 encoding = "UTF-8";
1259 else if (encoding.empty() || encoding == "Global hub default") // latter for 1.0.3 backwards compatability
1260 encoding = WGETS("default-charset");
1261
1262 if (encoding == WulforUtil::ENCODING_LOCALE)
1263 encoding = Text::systemCharset;
1264
1265 // Only pick "UTF-8" part of "UTF-8 (Unicode)".
1266 string::size_type i = encoding.find(' ', 0);
1267 if (i != string::npos)
1268 encoding = encoding.substr(0, i);
1269
1270 client = ClientManager::getInstance()->getClient(address);
1271 client->setEncoding(encoding);
1272 client->addListener(this);
1273 client->connect();
1274 }
1275
disconnect_client()1276 void Hub::disconnect_client()
1277 {
1278 if (client)
1279 {
1280 client->removeListener(this);
1281 client->disconnect(TRUE);
1282 ClientManager::getInstance()->putClient(client);
1283 client = NULL;
1284 }
1285 }
1286
setPassword_client(string password)1287 void Hub::setPassword_client(string password)
1288 {
1289 if (client && !password.empty())
1290 {
1291 client->setPassword(password);
1292 client->password(password);
1293 }
1294 }
1295
sendMessage_client(string message,bool thirdPerson)1296 void Hub::sendMessage_client(string message, bool thirdPerson)
1297 {
1298 if (client && !message.empty())
1299 client->hubMessage(message, thirdPerson);
1300 }
1301
getFileList_client(string cid,bool match)1302 void Hub::getFileList_client(string cid, bool match)
1303 {
1304 string message;
1305
1306 if (!cid.empty())
1307 {
1308 try
1309 {
1310 UserPtr user = ClientManager::getInstance()->findUser(CID(cid));
1311 if (user)
1312 {
1313 if (user == ClientManager::getInstance()->getMe())
1314 {
1315 // Don't download file list, open locally instead
1316 WulforManager::get()->getMainWindow()->openOwnList_client(TRUE);
1317 }
1318 else if (match)
1319 {
1320 QueueManager::getInstance()->addList(user, client->getHubUrl(), QueueItem::FLAG_MATCH_QUEUE);
1321 }
1322 else
1323 {
1324 QueueManager::getInstance()->addList(user, client->getHubUrl(), QueueItem::FLAG_CLIENT_VIEW);
1325 }
1326 }
1327 else
1328 {
1329 message = _("User not found");
1330 }
1331 }
1332 catch (const Exception &e)
1333 {
1334 message = e.getError();
1335 LogManager::getInstance()->message(message);
1336 }
1337 }
1338
1339 if (!message.empty())
1340 {
1341 typedef Func1<Hub, string> F1;
1342 F1 *func = new F1(this, &Hub::addStatusMessage_gui, message);
1343 WulforManager::get()->dispatchGuiFunc(func);
1344 }
1345 }
1346
grantSlot_client(string cid)1347 void Hub::grantSlot_client(string cid)
1348 {
1349 string message = _("User not found");
1350
1351 if (!cid.empty())
1352 {
1353 UserPtr user = ClientManager::getInstance()->findUser(CID(cid));
1354 if (user)
1355 {
1356 UploadManager::getInstance()->reserveSlot(user, client->getHubUrl());
1357 message = F_("Slot granted to %1%", % WulforUtil::getNicks(user));
1358 }
1359 }
1360
1361 typedef Func1<Hub, string> F1;
1362 F1 *func = new F1(this, &Hub::addStatusMessage_gui, message);
1363 WulforManager::get()->dispatchGuiFunc(func);
1364 }
1365
removeUserFromQueue_client(std::string cid)1366 void Hub::removeUserFromQueue_client(std::string cid)
1367 {
1368 if (!cid.empty())
1369 {
1370 UserPtr user = ClientManager::getInstance()->findUser(CID(cid));
1371 if (user)
1372 QueueManager::getInstance()->removeSource(user, QueueItem::Source::FLAG_REMOVED);
1373 }
1374 }
1375
redirect_client(string address,bool follow)1376 void Hub::redirect_client(string address, bool follow)
1377 {
1378 if (!address.empty())
1379 {
1380 if (ClientManager::getInstance()->isConnected(address))
1381 {
1382 string error = _("Unable to connect: already connected to the requested hub");
1383 typedef Func1<Hub, string> F1;
1384 F1 *f1 = new F1(this, &Hub::addStatusMessage_gui, error);
1385 WulforManager::get()->dispatchGuiFunc(f1);
1386 return;
1387 }
1388
1389 if (follow)
1390 {
1391 // the client is dead, long live the client!
1392 disconnect_client();
1393
1394 Func0<Hub> *func = new Func0<Hub>(this, &Hub::clearNickList_gui);
1395 WulforManager::get()->dispatchGuiFunc(func);
1396
1397 connectClient_client(address, encoding);
1398 }
1399 }
1400 }
1401
rebuildHashData_client()1402 void Hub::rebuildHashData_client()
1403 {
1404 HashManager::getInstance()->rebuild();
1405 }
1406
refreshFileList_client()1407 void Hub::refreshFileList_client()
1408 {
1409 try
1410 {
1411 ShareManager::getInstance()->setDirty();
1412 ShareManager::getInstance()->refresh(true);
1413 }
1414 catch (const ShareException& e)
1415 {
1416 }
1417 }
1418
addAsFavorite_client()1419 void Hub::addAsFavorite_client()
1420 {
1421 typedef Func1<Hub, string> F1;
1422 F1 *func;
1423
1424 FavoriteHubEntry *existingHub = FavoriteManager::getInstance()->getFavoriteHubEntry(client->getHubUrl());
1425
1426 if (!existingHub)
1427 {
1428 FavoriteHubEntry aEntry;
1429 aEntry.setServer(client->getHubUrl());
1430 aEntry.setName(client->getHubName());
1431 aEntry.setDescription(client->getHubDescription());
1432 aEntry.setConnect(FALSE);
1433 aEntry.setNick(client->getMyNick());
1434 aEntry.setEncoding(encoding);
1435 FavoriteManager::getInstance()->addFavorite(aEntry);
1436 func = new F1(this, &Hub::addStatusMessage_gui, _("Favorite hub added"));
1437 WulforManager::get()->dispatchGuiFunc(func);
1438 }
1439 else
1440 {
1441 func = new F1(this, &Hub::addStatusMessage_gui, _("Favorite hub already exists"));
1442 WulforManager::get()->dispatchGuiFunc(func);
1443 }
1444 }
1445
reconnect_client()1446 void Hub::reconnect_client()
1447 {
1448 Func0<Hub> *func = new Func0<Hub>(this, &Hub::clearNickList_gui);
1449 WulforManager::get()->dispatchGuiFunc(func);
1450
1451 if (client)
1452 client->reconnect();
1453 }
1454
getParams_client(ParamMap & params,const Identity & id)1455 void Hub::getParams_client(ParamMap ¶ms, const Identity &id)
1456 {
1457 if (id.getUser()->isSet(User::DCPLUSPLUS))
1458 params["Icon"] = "dc++";
1459 else
1460 params["Icon"] = "normal";
1461
1462 if (id.getUser()->isSet(User::PASSIVE))
1463 params["Icon"] += "-fw";
1464
1465 if (id.isOp())
1466 {
1467 params["Icon"] += "-op";
1468 params["Nick Order"] = "o" + id.getNick();
1469 }
1470 else
1471 {
1472 params["Nick Order"] = "u" + id.getNick();
1473 }
1474
1475 params["User"] = id.getNick();
1476 params["Shared"] = Util::toString(id.getBytesShared());
1477 params["Description"] = id.getDescription();
1478 params["Tag"] = id.getTag();
1479 params["Connection"] = id.getConnection();
1480 params["IP"] = id.getIp();
1481 params["Email"] = id.getEmail();
1482 params["CID"] = id.getUser()->getCID().toBase32();
1483 }
1484
showJoins_client(const UserPtr & user)1485 bool Hub::showJoins_client(const UserPtr &user)
1486 {
1487 return BOOLSETTING(SHOW_JOINS) || (BOOLSETTING(FAV_SHOW_JOINS) &&
1488 FavoriteManager::getInstance()->isFavoriteUser(user));
1489 }
1490
on(ClientListener::Connecting,Client *)1491 void Hub::on(ClientListener::Connecting, Client *) throw()
1492 {
1493 typedef Func1<Hub, string> F1;
1494 F1 *f1 = new F1(this, &Hub::addStatusMessage_gui, F_("Connecting to %1%", % client->getHubUrl()));
1495 WulforManager::get()->dispatchGuiFunc(f1);
1496 }
1497
on(ClientListener::Connected,Client *)1498 void Hub::on(ClientListener::Connected, Client *) throw()
1499 {
1500 typedef Func1<Hub, string> F1;
1501 F1 *func = new F1(this, &Hub::addStatusMessage_gui, _("Connected"));
1502 WulforManager::get()->dispatchGuiFunc(func);
1503 }
1504
on(ClientListener::UserUpdated,Client *,const OnlineUser & user)1505 void Hub::on(ClientListener::UserUpdated, Client *, const OnlineUser &user) throw()
1506 {
1507 Identity id = user.getIdentity();
1508
1509 if (!id.isHidden())
1510 {
1511 bool showJoin = showJoins_client(id.getUser());
1512 ParamMap params;
1513 getParams_client(params, id);
1514 typedef Func2<Hub, ParamMap, bool> F2;
1515 F2 *func = new F2(this, &Hub::updateUser_gui, params, showJoin);
1516 WulforManager::get()->dispatchGuiFunc(func);
1517 }
1518 }
1519
on(ClientListener::UsersUpdated,Client *,const OnlineUserList & list)1520 void Hub::on(ClientListener::UsersUpdated, Client *, const OnlineUserList &list) throw()
1521 {
1522 Identity id;
1523 typedef Func2<Hub, ParamMap, bool> F2;
1524 F2 *func;
1525
1526 for (OnlineUserList::const_iterator it = list.begin(); it != list.end(); ++it)
1527 {
1528 id = (*it)->getIdentity();
1529 if (!id.isHidden())
1530 {
1531 bool showJoin = showJoins_client(id.getUser());
1532 ParamMap params;
1533 getParams_client(params, id);
1534 func = new F2(this, &Hub::updateUser_gui, params, showJoin);
1535 WulforManager::get()->dispatchGuiFunc(func);
1536 }
1537 }
1538 }
1539
on(ClientListener::UserRemoved,Client *,const OnlineUser & user)1540 void Hub::on(ClientListener::UserRemoved, Client *, const OnlineUser &user) throw()
1541 {
1542 string nick = user.getIdentity().getNick();
1543 string cid = user.getUser()->getCID().toBase32();
1544 typedef Func1<Hub, string> F1;
1545 F1 *func;
1546
1547 if (showJoins_client(user.getUser()))
1548 {
1549 func = new F1(this, &Hub::addStatusMessage_gui, F_("%1% has quit", % nick));
1550 WulforManager::get()->dispatchGuiFunc(func);
1551 }
1552
1553 func = new F1(this, &Hub::removeUser_gui, cid);
1554 WulforManager::get()->dispatchGuiFunc(func);
1555 }
1556
on(ClientListener::Redirect,Client *,const string & address)1557 void Hub::on(ClientListener::Redirect, Client *, const string &address) throw()
1558 {
1559 // redirect_client() crashes unless I put it into the dispatcher (why?)
1560 typedef Func2<Hub, string, bool> F2;
1561 F2 *func = new F2(this, &Hub::redirect_client, address, BOOLSETTING(AUTO_FOLLOW));
1562 WulforManager::get()->dispatchClientFunc(func);
1563 }
1564
on(ClientListener::Failed,Client *,const string & reason)1565 void Hub::on(ClientListener::Failed, Client *, const string &reason) throw()
1566 {
1567 Func0<Hub> *f0 = new Func0<Hub>(this, &Hub::clearNickList_gui);
1568 WulforManager::get()->dispatchGuiFunc(f0);
1569
1570 typedef Func1<Hub, string> F1;
1571 F1 *f1 = new F1(this, &Hub::addStatusMessage_gui, F_("Connect failed: %1%", % reason));
1572 WulforManager::get()->dispatchGuiFunc(f1);
1573 }
1574
on(ClientListener::GetPassword,Client *)1575 void Hub::on(ClientListener::GetPassword, Client *) throw()
1576 {
1577 if (!client->getPassword().empty())
1578 client->password(client->getPassword());
1579 else
1580 {
1581 Func0<Hub> *func = new Func0<Hub>(this, &Hub::getPassword_gui);
1582 WulforManager::get()->dispatchGuiFunc(func);
1583 }
1584 }
1585
on(ClientListener::HubUpdated,Client *)1586 void Hub::on(ClientListener::HubUpdated, Client *) throw()
1587 {
1588 typedef Func1<Hub, string> F1;
1589 string hubName;
1590
1591 if (client->getHubName().empty())
1592 hubName = client->getAddress() + ":" + Util::toString(client->getPort());
1593 else
1594 hubName = client->getHubName();
1595
1596 if (!client->getHubDescription().empty())
1597 hubName += " - " + client->getHubDescription();
1598
1599 F1 *func1 = new F1(this, &BookEntry::setLabel_gui, hubName);
1600 WulforManager::get()->dispatchGuiFunc(func1);
1601 }
1602
on(ClientListener::Message,Client *,const OnlineUser & from,const string & message,bool thirdPerson)1603 void Hub::on(ClientListener::Message, Client *, const OnlineUser &from, const string &message, bool thirdPerson) throw()
1604 {
1605 if (!message.empty())
1606 {
1607 string line;
1608
1609 if (thirdPerson)
1610 line = "* " + from.getIdentity().getNick() + " " + message;
1611 else
1612 line = "<" + from.getIdentity().getNick() + "> " + message;
1613
1614 if (BOOLSETTING(FILTER_MESSAGES))
1615 {
1616 if ((message.find("Hub-Security") != string::npos && message.find("was kicked by") != string::npos) ||
1617 (message.find("is kicking") != string::npos && message.find("because:") != string::npos))
1618 {
1619 typedef Func1<Hub, string> F1;
1620 F1 *func = new F1(this, &Hub::addStatusMessage_gui, line);
1621 WulforManager::get()->dispatchGuiFunc(func);
1622 return;
1623 }
1624 }
1625
1626 if (BOOLSETTING(LOG_MAIN_CHAT))
1627 {
1628 StringMap params;
1629 params["message"] = line;
1630 client->getHubIdentity().getParams(params, "hub", false);
1631 params["hubURL"] = client->getHubUrl();
1632 client->getMyIdentity().getParams(params, "my", true);
1633 LOG(LogManager::CHAT, params);
1634 }
1635
1636 typedef Func1<Hub, string> F1;
1637 F1 *func = new F1(this, &Hub::addMessage_gui, line);
1638 WulforManager::get()->dispatchGuiFunc(func);
1639
1640 // Set urgency hint if message contains user's nick
1641 if (BOOLSETTING(BOLD_HUB) && from.getIdentity().getUser() != client->getMyIdentity().getUser())
1642 {
1643 if (message.find(client->getMyIdentity().getNick()) != string::npos)
1644 {
1645 typedef Func0<Hub> F0;
1646 F0 *func = new F0(this, &Hub::setUrgent_gui);
1647 WulforManager::get()->dispatchGuiFunc(func);
1648 }
1649 }
1650 }
1651 }
1652
on(ClientListener::StatusMessage,Client *,const string & message,int)1653 void Hub::on(ClientListener::StatusMessage, Client *, const string &message, int /* flag */) throw()
1654 {
1655 if (!message.empty())
1656 {
1657 if (BOOLSETTING(FILTER_MESSAGES))
1658 {
1659 if ((message.find("Hub-Security") != string::npos && message.find("was kicked by") != string::npos) ||
1660 (message.find("is kicking") != string::npos && message.find("because:") != string::npos))
1661 {
1662 typedef Func1<Hub, string> F1;
1663 F1 *func = new F1(this, &Hub::addStatusMessage_gui, message);
1664 WulforManager::get()->dispatchGuiFunc(func);
1665 return;
1666 }
1667 }
1668
1669 if (BOOLSETTING(LOG_STATUS_MESSAGES))
1670 {
1671 StringMap params;
1672 client->getHubIdentity().getParams(params, "hub", FALSE);
1673 params["hubURL"] = client->getHubUrl();
1674 client->getMyIdentity().getParams(params, "my", TRUE);
1675 params["message"] = message;
1676 LOG(LogManager::STATUS, params);
1677 }
1678
1679 typedef Func1<Hub, string> F1;
1680 F1 *func = new F1(this, &Hub::addMessage_gui, message);
1681 WulforManager::get()->dispatchGuiFunc(func);
1682 }
1683 }
1684
on(ClientListener::PrivateMessage,Client *,const OnlineUser & from,const OnlineUser & to,const OnlineUser & replyTo,const string & msg,bool thirdPerson)1685 void Hub::on(ClientListener::PrivateMessage, Client *, const OnlineUser &from,
1686 const OnlineUser& to, const OnlineUser& replyTo, const string &msg, bool thirdPerson) throw()
1687 {
1688 string error;
1689 const OnlineUser& user = (replyTo.getUser() == ClientManager::getInstance()->getMe()) ? to : replyTo;
1690 string line;
1691
1692 if (thirdPerson)
1693 line = "* " + from.getIdentity().getNick() + " " + msg;
1694 else
1695 line = "<" + from.getIdentity().getNick() + "> " + msg;
1696
1697 if (user.getIdentity().isHub() && BOOLSETTING(IGNORE_HUB_PMS))
1698 {
1699 error = _("Ignored private message from hub");
1700 typedef Func1<Hub, string> F1;
1701 F1 *func = new F1(this, &Hub::addStatusMessage_gui, error);
1702 WulforManager::get()->dispatchGuiFunc(func);
1703 }
1704 else if (user.getIdentity().isBot() && BOOLSETTING(IGNORE_BOT_PMS))
1705 {
1706 error = F_("Ignored private message from bot %1%", % user.getIdentity().getNick());
1707 typedef Func1<Hub, string> F1;
1708 F1 *func = new F1(this, &Hub::addStatusMessage_gui, error);
1709 WulforManager::get()->dispatchGuiFunc(func);
1710 }
1711 else
1712 {
1713 typedef Func4<MainWindow, string, string, string, bool> F4;
1714 F4 *func = new F4(WulforManager::get()->getMainWindow(), &MainWindow::addPrivateMessage_gui,
1715 user.getUser()->getCID().toBase32(), client->getHubUrl(), line, TRUE);
1716 WulforManager::get()->dispatchGuiFunc(func);
1717 }
1718 }
1719
on(ClientListener::NickTaken,Client *)1720 void Hub::on(ClientListener::NickTaken, Client *) throw()
1721 {
1722 typedef Func1<Hub, string> F1;
1723 F1 *func = new F1(this, &Hub::addStatusMessage_gui, _("Nick already taken"));
1724 WulforManager::get()->dispatchGuiFunc(func);
1725 }
1726
on(ClientListener::SearchFlood,Client *,const string & msg)1727 void Hub::on(ClientListener::SearchFlood, Client *, const string &msg) throw()
1728 {
1729 typedef Func1<Hub, string> F1;
1730 F1 *func = new F1(this, &Hub::addStatusMessage_gui, F_("Search spam detected from %1%", % msg));
1731 WulforManager::get()->dispatchGuiFunc(func);
1732 }
1733
1734