1 #include "flist.h"
2
3 // TODO: Separate from UI or include in UI.
4
5 #include "avatar.h"
6 #include "friend.h"
7 #include "groups.h"
8 #include "debug.h"
9 #include "macros.h"
10 #include "self.h"
11 #include "settings.h"
12 #include "text.h"
13 #include "theme.h"
14 #include "tox.h"
15 #include "utox.h"
16
17 #include "ui/contextmenu.h"
18 #include "ui/draw.h"
19 #include "ui/dropdown.h"
20 #include "ui/edit.h"
21 #include "ui/button.h"
22 #include "ui/scrollable.h"
23 #include "ui/switch.h"
24 #include "ui/tooltip.h"
25
26 #include "layout/background.h"
27 #include "layout/friend.h"
28 #include "layout/group.h"
29 #include "layout/settings.h"
30 #include "layout/sidebar.h"
31
32 #include "native/time.h"
33 #include "native/ui.h"
34
35 #include <limits.h>
36 #include <stdlib.h>
37 #include <string.h>
38
39 #ifdef UNITY
40 #include "xlib/mmenu.h"
41 extern bool unity_running;
42 #endif
43
44 /* I think these are pointers to the panels they're named after. */
45 static ITEM item_add, item_settings, item_transfer;
46
47 // full list of friends and group chats
48 static ITEM *item;
49 static uint32_t itemcount;
50
51 // list of chats actually shown in the GUI after filtering
52 // (actually indices pointing to chats in the chats array)
53 static uint32_t *shown_list;
54 static uint32_t showncount;
55
56 // search and filter stuff
57 static char * search_string;
58 static uint8_t filter;
59
60 static ITEM *mouseover_item;
61 static ITEM *nitem; // item that selected_item is being dragged over
62 static ITEM *selected_item = &item_add;
63
64 static ITEM *right_mouse_item;
65
66
67 static bool mouse_in_list;
68 static bool selected_item_mousedown;
69 static bool selected_item_mousedown_move_pend;
70
71 static int selected_item_dy; // y offset of selected item being dragged from its original position
72
flist_draw_itembox(ITEM * i,int x,int y,int width)73 static void flist_draw_itembox(ITEM *i, int x, int y, int width) {
74 int height;
75
76 if (settings.use_mini_flist) {
77 height = SCALE(ROSTER_BOX_HEIGHT / 2);
78 } else {
79 height = SCALE(ROSTER_BOX_HEIGHT);
80 }
81
82 if (selected_item == i) {
83 drawrect(x, y + 1, width, y + height, COLOR_BKGRND_MAIN);
84 } else if (mouseover_item == i) {
85 drawrect(x, y + 1, width, y + height, COLOR_BKGRND_LIST_HOVER);
86 }
87 }
88
flist_draw_name(ITEM * i,int x,int y,int width,char * name,char * msg,uint16_t name_length,uint16_t msg_length,bool color_overide,uint32_t color)89 static void flist_draw_name(ITEM *i, int x, int y, int width, char *name, char *msg, uint16_t name_length, uint16_t msg_length,
90 bool color_overide, uint32_t color)
91 {
92 if (!color_overide) {
93 color = (selected_item == i) ? COLOR_MAIN_TEXT : COLOR_LIST_TEXT;
94 }
95 setcolor(color);
96 setfont(FONT_LIST_NAME);
97 /* Always draw name*/
98 drawtextrange(x, width - SCALE(SIDEBAR_PADDING * 5), y, name, name_length);
99
100 if (!settings.use_mini_flist) {
101 /* Name + user status msg*/
102 if (!color_overide) {
103 color = (selected_item == i) ? COLOR_MAIN_TEXT_SUBTEXT : COLOR_LIST_TEXT_SUBTEXT;
104 }
105 setcolor(color);
106 setfont(FONT_STATUS);
107 drawtextrange(x, width - SCALE(SIDEBAR_PADDING * 5), y + SCALE(16), msg, msg_length);
108 }
109 }
110
flist_draw_status_icon(uint8_t status,int x,int y,bool notify)111 static void flist_draw_status_icon(uint8_t status, int x, int y, bool notify) {
112 y -= BM_STATUS_WIDTH / 2;
113 x -= BM_STATUS_WIDTH / 2;
114 drawalpha(BM_ONLINE + status, x, y, BM_STATUS_WIDTH, BM_STATUS_WIDTH, status_color[status]);
115
116 if (notify) {
117 y += BM_STATUS_WIDTH / 2;
118 y -= BM_STATUS_NOTIFY_WIDTH / 2;
119 x += BM_STATUS_WIDTH / 2;
120 x -= BM_STATUS_NOTIFY_WIDTH / 2;
121 drawalpha(BM_STATUS_NOTIFY, x, y, BM_STATUS_NOTIFY_WIDTH, BM_STATUS_NOTIFY_WIDTH, status_color[status]);
122 }
123 }
124
drawitem(ITEM * i,int x,int y,int width)125 static void drawitem(ITEM *i, int x, int y, int width) {
126 flist_draw_itembox(i, x + SCALE(SCROLL_WIDTH), y, width);
127
128 int box_height;
129 int avatar_x;
130 int avatar_y;
131 int name_x;
132 int name_y;
133 int default_w;
134 int group_bitmap;
135 int contact_bitmap;
136
137
138 if (settings.use_mini_flist) {
139 box_height = SCALE(ROSTER_BOX_HEIGHT / 2);
140 avatar_x = x + SCALE(SCROLL_WIDTH);
141 avatar_y = y + SCALE(ROSTER_AVATAR_TOP / 2);
142 name_x = avatar_x + BM_CONTACT_WIDTH / 2 + SCALE(5);
143 name_y = y + SCALE(ROSTER_NAME_TOP / 2);
144 default_w = BM_CONTACT_WIDTH / 2;
145 group_bitmap = BM_GROUP_MINI;
146 contact_bitmap = BM_CONTACT_MINI;
147 } else {
148 box_height = SCALE(ROSTER_BOX_HEIGHT);
149 avatar_x = x + SCALE(SCROLL_WIDTH);
150 avatar_y = y + SCALE(ROSTER_AVATAR_TOP);
151 name_x = avatar_x + BM_CONTACT_WIDTH + SCALE(5);
152 name_y = y + SCALE(ROSTER_NAME_TOP);
153 default_w = BM_CONTACT_WIDTH;
154 group_bitmap = BM_GROUP;
155 contact_bitmap = BM_CONTACT;
156 }
157
158 switch (i->type) {
159 case ITEM_FRIEND: {
160 FRIEND *f = get_friend(i->id_number);
161 uint8_t status = f->online ? f->status : 3;
162
163 // draw avatar or default image
164 if (friend_has_avatar(f)) {
165 draw_avatar_image(f->avatar->img, avatar_x, avatar_y, f->avatar->width, f->avatar->height,
166 default_w, default_w);
167 } else {
168 drawalpha(contact_bitmap, avatar_x, avatar_y, default_w, default_w,
169 (selected_item == i) ? COLOR_MAIN_TEXT : COLOR_LIST_TEXT);
170 }
171
172 flist_draw_name(i, name_x, name_y, width, UTOX_FRIEND_NAME(f), f->status_message, UTOX_FRIEND_NAME_LENGTH(f), f->status_length,
173 0, 0);
174
175 flist_draw_status_icon(status, width - SCALE(15), y + box_height / 2, f->unread_msg);
176 break;
177 }
178
179 case ITEM_GROUP: {
180 GROUPCHAT *g = get_group(i->id_number);
181 drawalpha(group_bitmap, avatar_x, avatar_y, default_w, default_w,
182 (selected_item == i) ? COLOR_MAIN_TEXT : COLOR_LIST_TEXT);
183
184 bool color_overide = false;
185 uint32_t color = 0;
186 if (g->muted) {
187 color_overide = true;
188 color = COLOR_GROUP_MUTED;
189 } else {
190 uint64_t time = get_time();
191 for (unsigned int j = 0; j < g->peer_count; ++j) {
192 if (time - g->last_recv_audio[j] <= (uint64_t)1 * 1000 * 1000 * 1000) {
193 color_overide = true;
194 color = COLOR_GROUP_AUDIO;
195 break;
196 }
197 }
198 }
199
200 flist_draw_name(i, name_x, name_y, width, g->name, g->topic, g->name_length, g->topic_length, color_overide, color);
201
202 flist_draw_status_icon(0, SCALE(width - 15), y + box_height / 2, g->unread_msg);
203 break;
204 }
205
206 case ITEM_FREQUEST: {
207 FREQUEST *r = get_frequest(i->id_number);
208 if (!r) {
209 LOG_WARN("FList", "Can't get the request at this number.");
210 break;
211 }
212
213 char name[TOX_ADDRESS_SIZE * 2];
214 id_to_string(name, r->bin_id);
215
216 drawalpha(contact_bitmap, avatar_x, y + ROSTER_AVATAR_TOP, default_w, default_w,
217 (selected_item == i) ? COLOR_MAIN_TEXT : COLOR_LIST_TEXT);
218 flist_draw_name(i, name_x, name_y, width, name, r->msg, sizeof(name), r->length, 0, 0);
219 break;
220 }
221
222 case ITEM_GROUP_CREATE: {
223 drawalpha(group_bitmap, avatar_x, y + ROSTER_AVATAR_TOP, default_w, default_w,
224 (selected_item == i) ? COLOR_MAIN_TEXT : COLOR_LIST_TEXT);
225 flist_draw_name(i, name_x, name_y, width, S(CREATEGROUPCHAT), NULL, SLEN(CREATEGROUPCHAT),
226 0, 1, (selected_item == i) ? COLOR_MAIN_TEXT : COLOR_LIST_TEXT);
227 break;
228 }
229
230 default: {
231 LOG_ERR("F-List", "Trying to draw an item that we shouldn't be drawing!");
232 break;
233 }
234 }
235 }
236
237 // find index of given item in shown_list, or INT_MAX if it can't be found
find_item_shown_index(ITEM * it)238 static unsigned int find_item_shown_index(ITEM *it) {
239 for (unsigned int i = 0; i < showncount; ++i) {
240 if (shown_list[i] == it - item) { // (it - item) returns the index of the item in the full items list
241 return i;
242 }
243 }
244 return INT_MAX; // can't be found!
245 }
246
flist_re_scale(void)247 void flist_re_scale(void) {
248 if (settings.use_mini_flist) {
249 scrollbar_flist.content_height = SCALE(ROSTER_BOX_HEIGHT / 2) * showncount;
250 } else {
251 scrollbar_flist.content_height = SCALE(ROSTER_BOX_HEIGHT) * showncount;
252 }
253 }
254
friend_matches_search_string(FRIEND * f,char * str)255 bool friend_matches_search_string(FRIEND *f, char *str) {
256 return !str
257 || strstr_case(f->name, str)
258 || (f->alias && strstr_case(f->alias, str))
259 || strstr_case(f->id_str, str);
260 }
261
flist_update_shown_list(void)262 void flist_update_shown_list(void) {
263 uint32_t j; // index in shown_list array
264 for (uint32_t i = j = 0; i < itemcount; i++) {
265 ITEM *it = &item[i];
266 if (it->type != ITEM_FRIEND) {
267 shown_list[j++] = i;
268 continue;
269 }
270 FRIEND *f = get_friend(it->id_number);
271 if (search_string) {
272 if (friend_matches_search_string(f, search_string)) {
273 shown_list[j++] = i;
274 }
275 } else if ((!filter || f->online || f->unread_msg || it == selected_item)) {
276 shown_list[j++] = i;
277 }
278 }
279
280 showncount = j;
281 flist_re_scale();
282 }
283
284 /* returns address of item at current index and appends the group create entry */
newitem(void)285 static ITEM *newitem(void) {
286 item = realloc(item, (itemcount + 1) * sizeof(ITEM));
287 shown_list = realloc(shown_list, (itemcount + 1) * sizeof(uint32_t));
288 if (!item || !shown_list) {
289 LOG_FATAL_ERR(EXIT_MALLOC, "flist", "Could not allocate memory for friend list.");
290 }
291
292 unsigned int index = itemcount - 1;
293 item[index + 1].type = ITEM_GROUP_CREATE;
294 item[index + 1].id_number = UINT32_MAX;
295 itemcount++;
296
297 flist_update_shown_list();
298
299 return &item[index];
300 }
301
302 // return item that the user is mousing over
item_hit(int mx,int my,int UNUSED (height))303 static ITEM *item_hit(int mx, int my, int UNUSED(height)) {
304 int real_height = SCALE(ROSTER_BOX_HEIGHT);
305 if (settings.use_mini_flist) {
306 real_height /= 2;
307 }
308
309 /* Mouse is outside the list */
310 if (mx < SCROLL_WIDTH || mx >= SCALE(230) || my < 0 // TODO magic numbers are bad 230 should be width
311 || my >= (int)(showncount * real_height)) { /* TODO: Height is a bit buggy, Height needs /2
312 * figure out why! */
313 mouse_in_list = false;
314 return NULL;
315 } else {
316 mouse_in_list = 1;
317 }
318
319 uint32_t item_idx = my / real_height;
320 mouse_in_list = true;
321
322 /* mouse is below the last item */
323 if (item_idx >= showncount) {
324 return NULL;
325 }
326
327 return &item[shown_list[item_idx]];
328 }
329
flist_get_filter(void)330 uint8_t flist_get_filter(void) {
331 return filter;
332 }
333
flist_set_filter(uint8_t new_filter)334 void flist_set_filter(uint8_t new_filter) {
335 filter = new_filter;
336 flist_update_shown_list();
337 }
338
flist_search(char * str)339 void flist_search(char *str) {
340 search_string = str;
341 flist_update_shown_list();
342 }
343
344 // change the selected item by [offset] items in the shown list
change_tab(int offset)345 static void change_tab(int offset) {
346 /* Pg-Up/Dn broke on the create group icon,
347 * remoing this if seems to work but I don't know what it was doing here
348 * so I commented it in case it breaks stuff... */
349 // if (selected_item->type == ITEM_FRIEND ||
350 // selected_item->type == ITEM_GROUP) {
351 unsigned int index = find_item_shown_index(selected_item);
352 if (index != INT_MAX) {
353 // flist_selectchat will check if out of bounds
354 flist_selectchat((index + offset + showncount) % showncount);
355 }
356 // }
357 }
358
flist_previous_tab(void)359 void flist_previous_tab(void) {
360 change_tab(-1);
361 }
362
flist_next_tab(void)363 void flist_next_tab(void) {
364 change_tab(1);
365 }
366
flist_first_tab(void)367 void flist_first_tab(void) {
368 flist_selectchat(0);
369 }
370
flist_last_tab(void)371 void flist_last_tab(void) {
372 if (showncount < 2) {
373 /* No friends and groups in the list, nothing to do */
374 return;
375 }
376
377 flist_selectchat(showncount - 2);
378 }
379
380 /* TODO: move this out of here!
381 * maybe to ui.c ? */
382 static int current_width; // I know, but I'm in a hurry, so I'll fix this later
page_close(ITEM * i)383 static void page_close(ITEM *i) {
384 switch (i->type) {
385 case ITEM_FRIEND: {
386 FRIEND *f = get_friend(i->id_number);
387
388 current_width = f->msg.width;
389
390 free(f->typed);
391 f->typed_length = edit_chat_msg_friend.length;
392 f->typed = calloc(1, f->typed_length);
393 if (!f->typed) {
394 LOG_ERR("flist", "Unable to calloc for f->typed.");
395 return;
396 }
397
398 memcpy(f->typed, edit_chat_msg_friend.data, f->typed_length);
399
400 f->msg.scroll = messages_friend.content_scroll->d;
401
402 f->edit_history = edit_chat_msg_friend.history;
403 f->edit_history_cur = edit_chat_msg_friend.history_cur;
404 f->edit_history_length = edit_chat_msg_friend.history_length;
405
406 panel_chat.disabled = true;
407 panel_friend.disabled = true;
408 panel_friend_chat.disabled = true;
409 panel_friend_video.disabled = true;
410 panel_friend_settings.disabled = true;
411 panel_friend_confirm_deletion.disabled = true;
412 settings.inline_video = true;
413
414 panel_friend_request.disabled = true;
415 break;
416 }
417
418 case ITEM_FREQUEST: {
419 panel_chat.disabled = true;
420 panel_friend_request.disabled = true;
421 break;
422 }
423
424 case ITEM_GROUP: {
425 GROUPCHAT *g = get_group(selected_item->id_number);
426 if (g) {
427 current_width = g->msg.width;
428
429 free(g->typed);
430 g->typed_length = edit_chat_msg_group.length;
431 g->typed = calloc(1, g->typed_length);
432 if (!g->typed) {
433 LOG_ERR("F-List", "Unable to calloc for g->typed.");
434 return;
435 }
436
437 memcpy(g->typed, edit_chat_msg_group.data, g->typed_length);
438
439 g->msg.scroll = messages_group.content_scroll->d;
440
441 g->edit_history = edit_chat_msg_group.history;
442 g->edit_history_cur = edit_chat_msg_group.history_cur;
443 g->edit_history_length = edit_chat_msg_group.history_length;
444 }
445
446 panel_chat.disabled = true;
447 panel_group.disabled = true;
448
449 break;
450 }
451
452 case ITEM_SETTINGS: {
453 if (panel_profile_password.disabled) {
454 panel_splash_page.disabled = true;
455 settings.show_splash = false;
456
457 panel_settings_master.disabled = true;
458 panel_overhead.disabled = true;
459
460 panel_profile_password_settings.disabled = true;
461 panel_nospam_settings.disabled = true;
462
463 button_settings.disabled = false;
464
465 reset_settings_controls();
466 }
467 break;
468 }
469
470 case ITEM_ADD: {
471 button_add_new_contact.disabled = false;
472 panel_add_friend.disabled = true;
473 break;
474 }
475
476 case ITEM_GROUP_CREATE: {
477 panel_chat.disabled = true;
478 panel_group_create.disabled = true;
479 break;
480 }
481
482 case ITEM_NONE: {
483 break;
484 }
485 }
486 }
487
page_open(ITEM * i)488 static void page_open(ITEM *i) {
489 switch (i->type) {
490 case ITEM_FREQUEST: {
491 panel_chat.disabled = false;
492 panel_friend_request.disabled = false;
493 break;
494 }
495
496 case ITEM_FRIEND: {
497 FRIEND *f = get_friend(i->id_number);
498 if (!f) {
499 LOG_ERR("Flist", "Could not get friend data from item");
500 return;
501 }
502
503 #ifdef UNITY
504 if (unity_running) {
505 mm_rm_entry(f->id_bin);
506 }
507 #endif
508
509 memcpy(edit_chat_msg_friend.data, f->typed, f->typed_length);
510 edit_chat_msg_friend.length = f->typed_length;
511
512 f->msg.width = current_width;
513 f->msg.id = f->number;
514 f->unread_msg = false;
515 /* We use the MESSAGES struct from the friend, but we need the info from the panel. */
516 messages_friend.object = ((void **)&f->msg);
517 messages_updateheight((MESSAGES *)messages_friend.object, current_width);
518
519 ((MESSAGES *)messages_friend.object)->cursor_over_msg = UINT32_MAX;
520 ((MESSAGES *)messages_friend.object)->cursor_over_position = UINT32_MAX;
521 ((MESSAGES *)messages_friend.object)->cursor_down_msg = UINT32_MAX;
522 ((MESSAGES *)messages_friend.object)->cursor_down_position = UINT32_MAX;
523 ((MESSAGES *)messages_friend.object)->cursor_over_uri = UINT32_MAX;
524
525 scrollbar_friend.content_height = f->msg.height;
526 messages_friend.content_scroll->d = f->msg.scroll;
527
528 edit_chat_msg_friend.history = f->edit_history;
529 edit_chat_msg_friend.history_cur = f->edit_history_cur;
530 edit_chat_msg_friend.history_length = f->edit_history_length;
531 edit_setfocus(&edit_chat_msg_friend);
532
533 panel_chat.disabled = 0;
534 panel_friend.disabled = 0;
535 panel_friend_chat.disabled = 0;
536 panel_friend_video.disabled = 1;
537 panel_friend_settings.disabled = 1;
538 break;
539 }
540
541 case ITEM_GROUP: {
542 GROUPCHAT *g = get_group(i->id_number);
543 if (!g) {
544 LOG_FATAL_ERR(EXIT_FAILURE, "F-List", "Selected group no longer exists. Group number: %u", i->id_number);
545 }
546
547 memcpy(edit_chat_msg_group.data, g->typed, g->typed_length);
548 edit_chat_msg_group.length = g->typed_length;
549
550 g->msg.width = current_width;
551 g->msg.id = g->number;
552 g->unread_msg = 0;
553 /* We use the MESSAGES struct from the group, but we need the info from the panel. */
554 messages_group.object = &g->msg;
555 messages_updateheight((MESSAGES *)messages_group.object, current_width);
556
557 ((MESSAGES *)messages_group.object)->cursor_over_msg = UINT32_MAX;
558 ((MESSAGES *)messages_group.object)->cursor_over_position = UINT32_MAX;
559 ((MESSAGES *)messages_group.object)->cursor_down_msg = UINT32_MAX;
560 ((MESSAGES *)messages_group.object)->cursor_down_position = UINT32_MAX;
561 ((MESSAGES *)messages_group.object)->cursor_over_uri = UINT32_MAX;
562
563 messages_group.content_scroll->content_height = g->msg.height;
564 messages_group.content_scroll->d = g->msg.scroll;
565 edit_setfocus(&edit_chat_msg_group);
566
567 edit_chat_msg_group.history = g->edit_history;
568 edit_chat_msg_group.history_cur = g->edit_history_cur;
569 edit_chat_msg_group.history_length = g->edit_history_length;
570
571 panel_chat.disabled = false;
572 panel_group.disabled = false;
573 panel_group_chat.disabled = false;
574 panel_group_video.disabled = true;
575 panel_group_settings.disabled = true;
576 panel_group_create.disabled = true;
577 break;
578 }
579
580 case ITEM_SETTINGS: {
581 if (panel_profile_password.disabled) {
582 button_settings.disabled = 1;
583 panel_overhead.disabled = 0;
584 panel_settings_master.disabled = 0;
585 }
586 break;
587 }
588
589 case ITEM_ADD: {
590 button_add_new_contact.disabled = 1;
591 panel_overhead.disabled = 0;
592 panel_add_friend.disabled = 0;
593 edit_setfocus(&edit_add_new_friend_id);
594 break;
595 }
596
597 case ITEM_GROUP_CREATE: {
598 panel_chat.disabled = false;
599 panel_group_create.disabled = false;
600 // postmessage_toxcore(TOX_GROUP_CREATE, 0, 0, NULL);
601 break;
602 }
603
604 case ITEM_NONE: {
605 break;
606 }
607 }
608 }
609
show_page(ITEM * i)610 static void show_page(ITEM *i) {
611 // TODO!!
612 // panel_item[selected_item->type - 1].disabled = 1;
613 // panel_item[i->type - 1].disabled = 0;
614 edit_resetfocus();
615
616 /* First things first, we need to deselect and store the old data. */
617 page_close(selected_item);
618
619 /* Now we activate/select the new page, and load stored data */
620 page_open(i);
621
622 selected_item = i;
623
624 addfriend_status = 0;
625
626 flist_update_shown_list();
627 }
628
flist_start(void)629 void flist_start(void) {
630 selected_item = &item_settings;
631 button_settings.disabled = true;
632
633 item_add.type = ITEM_ADD;
634 item_settings.type = ITEM_SETTINGS;
635
636 itemcount = self.friend_list_count + self.groups_list_count;
637 itemcount += 1; /* for ITEM_GROUP_CREATE */
638
639 item = calloc(itemcount, sizeof(ITEM));
640 shown_list = calloc(itemcount, sizeof(uint32_t));
641 if (!item || !shown_list) {
642 LOG_FATAL_ERR(EXIT_MALLOC, "flist", "Could not allocate memory for friend list.");
643 }
644 ITEM *i = item;
645 for (uint32_t num = 0; num < self.friend_list_count; ++num) {
646 const FRIEND *f = get_friend(num);
647 if (!f) {
648 continue;
649 }
650
651 i->type = ITEM_FRIEND;
652 i->id_number = f->number;
653 i++;
654 }
655
656 for (uint32_t num = 0; num < self.groups_list_count; num++) {
657 const GROUPCHAT *g = get_group(num);
658 if (!g) {
659 continue;
660 }
661
662 i->type = ITEM_GROUP;
663 i->id_number = g->number;
664 i++;
665 }
666
667 i->type = ITEM_GROUP_CREATE;
668 i->id_number = UINT32_MAX;
669
670 search_string = NULL;
671 flist_update_shown_list();
672 }
673
flist_add_friend(FRIEND * f,const char * msg,const int msg_length)674 void flist_add_friend(FRIEND *f, const char *msg, const int msg_length) {
675 ITEM *i = newitem();
676 if (!i) {
677 LOG_ERR("Flist", "Failed to create an item in the friend list for a friend.");
678 return;
679 }
680
681 i->type = ITEM_FRIEND;
682 i->id_number = f->number;
683
684 if (msg_length > 0) {
685 message_add_type_text(&f->msg, true, msg, msg_length, true, false);
686 }
687 }
688
flist_add_friend_accepted(FRIEND * f,FREQUEST * req)689 void flist_add_friend_accepted(FRIEND *f, FREQUEST *req) {
690 for (uint32_t i = 0; i < itemcount; ++i) {
691 if (item[i].type == ITEM_FREQUEST && item[i].id_number == req->number) {
692 LOG_INFO("FList", "Friend found and accepted.");
693 item[i].type = ITEM_FRIEND;
694 item[i].id_number = f->number;
695
696 if (&item[i] == selected_item) {
697 // panel_item[selected_item->type - 1].disabled = 1;
698 // panel_item[ITEM_FRIEND - 1].disabled = 0;
699
700 messages_friend.object = &f->msg;
701 ((MESSAGES *)messages_friend.object)->cursor_over_msg = UINT32_MAX;
702 messages_friend.content_scroll->content_height = f->msg.height;
703 messages_friend.content_scroll->d = f->msg.scroll;
704
705 if (req->length > 0) {
706 message_add_type_text(&f->msg, false, req->msg, req->length, true, false);
707 }
708
709 f->msg.id = f->number;
710 }
711
712 return;
713 }
714 }
715 }
716
flist_add_group(GROUPCHAT * g)717 void flist_add_group(GROUPCHAT *g) {
718 ITEM *i = newitem();
719 if (!i) {
720 LOG_ERR("Flist", "Failed to create an item in the friend list for a groupchat.");
721 return;
722 }
723
724 i->type = ITEM_GROUP;
725 i->id_number = g->number;
726 }
727
flist_add_frequest(FREQUEST * r)728 void flist_add_frequest(FREQUEST *r) {
729 ITEM *i = newitem();
730 if (!i) {
731 LOG_ERR("Flist", "Failed to create an item in the friend list for a friend request.");
732 return;
733 }
734
735 i->type = ITEM_FREQUEST;
736 i->id_number = r->number;
737 }
738
739 void group_av_peer_remove(GROUPCHAT *g, int peernumber);
740
741 // FIXME removing multiple items without moving the mouse causes asan neg-size-param error on memmove!
deleteitem(ITEM * i)742 static void deleteitem(ITEM *i) {
743 uint32_t countof_item = itemcount;
744 right_mouse_item = NULL;
745
746 if (i == selected_item) {
747 if (i == &item[itemcount] - 1) {
748 if (i == item) {
749 show_page(&item_add);
750 } else {
751 show_page(i - 1);
752 }
753 } else {
754 show_page(i + 1);
755 }
756 }
757
758 switch (i->type) {
759 case ITEM_FRIEND: {
760 FRIEND *f = get_friend(i->id_number);
761 postmessage_toxcore(TOX_FRIEND_DELETE, f->number, 0, f);
762 break;
763 }
764
765 case ITEM_GROUP: {
766 GROUPCHAT *g = get_group(i->id_number);
767 postmessage_toxcore(TOX_GROUP_PART, g->number, 0, NULL);
768 group_free(g);
769 break;
770 }
771
772 case ITEM_FREQUEST: {
773 friend_request_free(i->id_number);
774 break;
775 }
776
777 default: {
778 return;
779 }
780 }
781
782 itemcount--;
783
784 int size = (&item[itemcount] - i) * sizeof(ITEM);
785 memmove(i, i + 1, size);
786
787 if (i != selected_item && selected_item > i && selected_item >= item && selected_item < item + countof_item) {
788 selected_item--;
789 }
790
791 flist_update_shown_list();
792 redraw(); // flist_draw();
793 }
794
flist_delete_sitem(void)795 void flist_delete_sitem(void) {
796 if (selected_item >= item && selected_item < item + itemcount) {
797 deleteitem(selected_item);
798 }
799 }
800
flist_delete_rmouse_item(void)801 void flist_delete_rmouse_item(void) {
802 if (right_mouse_item >= item && right_mouse_item < item + itemcount) {
803 deleteitem(right_mouse_item);
804 }
805 }
806
flist_freeall(void)807 void flist_freeall(void) {
808 for (ITEM *i = item; i != item + itemcount; i++) {
809 switch (i->type) {
810 case ITEM_FRIEND: {
811 friend_free(get_friend(i->id_number));
812 break;
813 }
814
815 case ITEM_GROUP: {
816 group_free(get_group(i->id_number));
817 break;
818 }
819
820 case ITEM_FREQUEST: {
821 friend_request_free(i->id_number);
822 break;
823 }
824
825 default: {
826 break;
827 }
828 }
829 }
830 itemcount = 0;
831 showncount = 0;
832 free(item);
833 free(shown_list);
834 }
835
flist_selectchat(int index)836 void flist_selectchat(int index) {
837 if (index >= 0 && (unsigned)index < showncount) {
838 show_page(&item[shown_list[index]]);
839 }
840 }
841
flist_reselect_current(void)842 void flist_reselect_current(void) {
843 show_page(selected_item);
844 }
845
flist_selectsettings(void)846 void flist_selectsettings(void) {
847 show_page(&item_settings);
848 }
849
flist_selectaddfriend(void)850 void flist_selectaddfriend(void) {
851 show_page(&item_add);
852 }
853
flist_selectswap(void)854 void flist_selectswap(void) {
855 show_page(&item_transfer);
856 }
857
858 /******************************************************************************
859 ****** Updated functions ******
860 ******************************************************************************/
861
862 static struct {
863 ITEM_TYPE type;
864 uint8_t * data;
865 } push_pop;
866
push_selected(void)867 static void push_selected(void) {
868 push_pop.type = selected_item->type;
869
870 switch (push_pop.type) {
871 case ITEM_NONE:
872 case ITEM_SETTINGS:
873 case ITEM_ADD: {
874 return;
875 }
876
877 case ITEM_FRIEND: {
878 push_pop.data = calloc(1, TOX_PUBLIC_KEY_SIZE);
879 FRIEND *f = get_friend(selected_item->id_number);
880 if (!f) {
881 LOG_ERR("Flist", "id_number is out of sync with friend_list"); // TODO should this be an exit code?
882 // It's a critical error that could do
883 // a lot of damage
884 return;
885 }
886 memcpy(push_pop.data, &f->id_bin, TOX_PUBLIC_KEY_SIZE);
887 break;
888 }
889 case ITEM_FREQUEST:
890 case ITEM_GROUP:
891 case ITEM_GROUP_CREATE: {
892 return;
893 }
894 }
895 }
896
pop_selected(void)897 static void pop_selected(void) {
898 switch (push_pop.type) {
899 case ITEM_NONE:
900 case ITEM_SETTINGS: {
901 show_page(&item_settings);
902 return;
903 }
904
905 case ITEM_ADD: {
906 show_page(&item_add);
907 return;
908 }
909
910 case ITEM_FRIEND: {
911 for (uint16_t i = 0; i < itemcount; ++i) {
912 if (item[i].type == ITEM_FRIEND) {
913 FRIEND *f = get_friend(item[i].id_number);
914 if (memcmp(push_pop.data, &f->id_bin, TOX_PUBLIC_KEY_SIZE) == 0) {
915 show_page(&item[i]);
916 return;
917 }
918 }
919 }
920 show_page(&item_settings);
921 break;
922 }
923
924 case ITEM_FREQUEST:
925 case ITEM_GROUP:
926 case ITEM_GROUP_CREATE: {
927 show_page(&item_settings);
928 return;
929 }
930 }
931 }
932
flist_select_last(void)933 void flist_select_last(void) {
934 /* -2 should be the last, -1 is the create group */
935 show_page(&item[itemcount - 2]);
936 }
937
flist_dump_contacts(void)938 void flist_dump_contacts(void) {
939 push_selected();
940 flist_freeall();
941 }
942
flist_reload_contacts(void)943 void flist_reload_contacts(void) {
944 flist_start();
945 pop_selected();
946 }
947
flist_get_friend(void)948 FRIEND *flist_get_friend(void) {
949 if (flist_get_type() == ITEM_FRIEND) {
950 return get_friend(selected_item->id_number);
951 }
952 return NULL;
953 }
954
flist_get_frequest(void)955 FREQUEST *flist_get_frequest(void) {
956 if (flist_get_type() == ITEM_FREQUEST) {
957 return get_frequest(selected_item->id_number);
958 }
959
960 return NULL;
961 }
962
flist_get_groupchat(void)963 GROUPCHAT *flist_get_groupchat(void) {
964 if (flist_get_type() == ITEM_GROUP) {
965 return get_group(selected_item->id_number);
966 }
967
968 return NULL;
969 }
970
flist_get_type(void)971 ITEM_TYPE flist_get_type(void) {
972 return selected_item->type;
973 }
974
975 /**
976 * @brief Extract string ToxId from Tox URI.
977 *
978 * @param str Null-terminated Tox URI.
979 * @param tox_id Extracted ToxId; it has to be at least TOX_ADDRESS_SIZE * 2 + 1.
980 *
981 * @return True if success false otherwise.
982 */
get_tox_id_from_uri(const char * str,char * tox_id)983 static bool get_tox_id_from_uri(const char *str, char *tox_id) {
984 const char *tox_uri_scheme = "tox:";
985 const int tox_uri_scheme_length = 4;
986
987 if (strncmp(str, tox_uri_scheme, tox_uri_scheme_length) == 0 &&
988 strlen(str) - tox_uri_scheme_length == TOX_ADDRESS_SIZE * 2) {
989 memcpy(tox_id, &str[tox_uri_scheme_length], TOX_ADDRESS_SIZE * 2);
990 tox_id[TOX_ADDRESS_SIZE * 2] = '\0';
991 return true;
992 }
993
994 return false;
995 }
996
try_open_tox_uri(const char * str)997 bool try_open_tox_uri(const char *str) {
998 char tox_id[TOX_ADDRESS_SIZE * 2 + 1];
999
1000 if (!get_tox_id_from_uri(str, tox_id)) {
1001 return false;
1002 }
1003
1004 FRIEND *friend = get_friend_by_id(tox_id);
1005
1006 if (friend) {
1007 flist_selectchat(friend->number);
1008 } else if (tox_thread_init == UTOX_TOX_THREAD_INIT_SUCCESS) {
1009 edit_setstr(&edit_add_new_friend_id, tox_id, TOX_ADDRESS_SIZE * 2);
1010 edit_setstr(&edit_search, (char *)"", 0);
1011 flist_selectaddfriend();
1012 edit_setfocus(&edit_add_new_friend_msg);
1013 }
1014
1015 return true;
1016 }
1017
1018 /******************************************************************************
1019 ****** UI functions ******
1020 ******************************************************************************/
1021
flist_draw(void * UNUSED (n),int x,int y,int width,int UNUSED (height))1022 void flist_draw(void *UNUSED(n), int x, int y, int width, int UNUSED(height)) {
1023 int real_height = 0;
1024 if (settings.use_mini_flist) {
1025 real_height = SCALE(ROSTER_BOX_HEIGHT / 2);
1026 } else {
1027 real_height = SCALE(ROSTER_BOX_HEIGHT);
1028 }
1029
1030 ITEM *mi = NULL; // item being dragged
1031 int my; // y of item being dragged
1032
1033 for (unsigned int i = 0; i < showncount; i++) {
1034 ITEM *it = &item[shown_list[i]];
1035 if (it == selected_item && (selected_item_dy >= 5 || selected_item_dy <= -5)) {
1036 mi = it;
1037 my = y + selected_item_dy;
1038 } else {
1039 drawitem(it, x, y, width);
1040 }
1041 y += real_height;
1042 }
1043
1044 if (mi) {
1045 drawitem(mi, x, my, width);
1046 }
1047 }
1048
flist_mmove(void * UNUSED (n),int UNUSED (x),int UNUSED (y),int UNUSED (width),int height,int mx,int my,int UNUSED (dx),int dy)1049 bool flist_mmove(void *UNUSED(n), int UNUSED(x), int UNUSED(y), int UNUSED(width), int height, int mx, int my,
1050 int UNUSED(dx), int dy)
1051 {
1052 int real_height = 0;
1053
1054 if (settings.use_mini_flist) {
1055 real_height = ROSTER_BOX_HEIGHT / 2;
1056 } else {
1057 real_height = ROSTER_BOX_HEIGHT;
1058 }
1059
1060 ITEM *i = item_hit(mx, my, height);
1061
1062 bool draw = false;
1063
1064 if (i != mouseover_item) {
1065 mouseover_item = i;
1066 draw = true;
1067 }
1068
1069 if (selected_item_mousedown) {
1070 // drag item
1071 selected_item_dy += dy;
1072 nitem = NULL;
1073
1074 if (selected_item_mousedown_move_pend == true && (selected_item_dy >= 5 || selected_item_dy <= -5)) {
1075 selected_item_mousedown_move_pend = false;
1076 show_page(i);
1077 }
1078
1079 if (abs(selected_item_dy) >= real_height / 2) {
1080
1081 int d; // offset, in number of items, of where the dragged item is compared to where it started
1082 if (selected_item_dy > 0) {
1083 d = (selected_item_dy + real_height / 2) / real_height;
1084 } else {
1085 d = (selected_item_dy - real_height / 2) / real_height;
1086 }
1087 unsigned int index = find_item_shown_index(selected_item);
1088 if (index != INT_MAX) { // selected_item was found in shown list
1089 index += d; // get item being dragged over
1090
1091 // set item being dragged over
1092 if (index < itemcount) {
1093 nitem = &item[shown_list[index]];
1094 }
1095 }
1096 }
1097
1098 draw = true;
1099 } else {
1100 tooltip_draw();
1101 }
1102
1103 return draw;
1104 }
1105
flist_mdown(void * UNUSED (n))1106 bool flist_mdown(void *UNUSED(n)) {
1107 tooltip_mdown(); /* may need to return on true */
1108 if (mouseover_item) {
1109 // show_page(mouseover_item);
1110 selected_item_mousedown = true;
1111 selected_item_mousedown_move_pend = true;
1112 return true;
1113 }
1114
1115 return false;
1116 }
1117
flist_init_friend_settings_page(void)1118 static void flist_init_friend_settings_page(void) {
1119 FRIEND *f = get_friend(right_mouse_item->id_number);
1120
1121 panel_friend_chat.disabled = true;
1122 panel_friend_video.disabled = true;
1123 panel_friend_settings.disabled = false;
1124
1125 edit_setstr(&edit_friend_pubkey, (char *)&f->id_str, TOX_PUBLIC_KEY_SIZE * 2);
1126
1127 maybe_i18nal_string_set_plain(&edit_friend_alias.empty_str, f->name, f->name_length);
1128 edit_setstr(&edit_friend_alias, f->alias, f->alias_length);
1129
1130 switch_friend_autoaccept_ft.switch_on = f->ft_autoaccept;
1131 }
1132
flist_init_group_settings_page(void)1133 static void flist_init_group_settings_page(void) {
1134 GROUPCHAT *g = get_group(right_mouse_item->id_number);
1135
1136 panel_group_chat.disabled = true;
1137 panel_group_video.disabled = true;
1138 panel_group_settings.disabled = false;
1139
1140 edit_setstr(&edit_group_topic, g->name, g->name_length);
1141
1142 dropdown_notify_groupchats.over = dropdown_notify_groupchats.selected = g->notify;
1143 }
1144
1145 typedef enum {
1146 SHOW_SETTINGS,
1147 SHOW_INLINE_VID,
1148 CLEAR_HISTOR,
1149 DELETE_FRIEND,
1150 } FLIST_CONTEXT_MENU;
1151
contextmenu_friend(FLIST_CONTEXT_MENU rcase)1152 static void contextmenu_friend(FLIST_CONTEXT_MENU rcase) {
1153 FRIEND *f = get_friend(right_mouse_item->id_number);
1154
1155 panel_friend_chat.disabled = false;
1156 panel_friend_video.disabled = true;
1157 panel_friend_settings.disabled = true;
1158 switch (rcase) {
1159 case SHOW_SETTINGS: {
1160 /* should be settings page */
1161 flist_init_friend_settings_page();
1162 break;
1163 }
1164 case SHOW_INLINE_VID: {
1165 /* Should be show inline video */
1166 panel_friend_chat.disabled = true;
1167 panel_friend_video.disabled = false;
1168 panel_friend_settings.disabled = true;
1169 settings.inline_video = true;
1170 f->video_inline = true;
1171 postmessage_utox(AV_CLOSE_WINDOW, f->number + 1, 0, NULL);
1172 break;
1173 }
1174 case CLEAR_HISTOR: {
1175 /* should be clean history */
1176 friend_history_clear(get_friend(right_mouse_item->id_number));
1177 break;
1178 }
1179 case DELETE_FRIEND: {
1180 /* Should be: delete friend */
1181 panel_friend_chat.disabled = true;
1182 panel_friend_confirm_deletion.disabled = false;
1183 break;
1184 }
1185 }
1186 }
1187
contextmenu_list_onselect(uint8_t i)1188 static void contextmenu_list_onselect(uint8_t i) {
1189 if (right_mouse_item) {
1190 switch (right_mouse_item->type) {
1191 case ITEM_FRIEND: {
1192 contextmenu_friend(i);
1193 return;
1194 }
1195 case ITEM_GROUP: {
1196 panel_group_chat.disabled = false;
1197 GROUPCHAT *g = get_group(right_mouse_item->id_number);
1198 if (i == 0) {
1199 flist_init_group_settings_page();
1200 } else if (i == 1) {
1201 if (right_mouse_item != selected_item) {
1202 show_page(right_mouse_item);
1203 }
1204
1205 char str[g->name_length + 7];
1206 strcpy(str, "/topic ");
1207 memcpy(str + 7, g->name, g->name_length);
1208 edit_setfocus(&edit_chat_msg_group);
1209 edit_paste(str, sizeof(str), 0);
1210 } else if (i == 2 && g->av_group) {
1211 g->muted = !g->muted;
1212 } else {
1213 flist_delete_rmouse_item();
1214 }
1215
1216 return;
1217 }
1218 case ITEM_FREQUEST: {
1219 FREQUEST *req = get_frequest(right_mouse_item->id_number);
1220 if (!req) {
1221 LOG_ERR("F-List", "Could not get friend request number: %u", right_mouse_item->id_number);
1222 return;
1223 }
1224
1225 if (i == 0) {
1226 postmessage_toxcore(TOX_FRIEND_ACCEPT, 0, 0, req);
1227 } else {
1228 flist_delete_rmouse_item();
1229 }
1230 return;
1231 }
1232 default: {
1233 LOG_TRACE("F-List", "blerg" );
1234 return;
1235 }
1236 }
1237 } else {
1238 if (i) {
1239 postmessage_toxcore(TOX_GROUP_CREATE, 0, 0, NULL);
1240 } else {
1241 show_page(&item_add);
1242 }
1243 }
1244 }
1245
flist_mright(void * UNUSED (n))1246 bool flist_mright(void *UNUSED(n)) {
1247 static UTOX_I18N_STR menu_friend[] = {
1248 STR_FRIEND_SETTINGS,
1249 STR_CALL_VIDEO_SHOW_INLINE,
1250 STR_CLEAR_HISTORY,
1251 STR_REMOVE_FRIEND
1252 };
1253
1254 static UTOX_I18N_STR menu_group_unmuted[] = { STR_GROUPCHAT_SETTINGS, STR_CHANGE_GROUP_TOPIC, STR_MUTE,
1255 STR_REMOVE_GROUP };
1256 static UTOX_I18N_STR menu_group_muted[] = { STR_GROUPCHAT_SETTINGS, STR_CHANGE_GROUP_TOPIC, STR_UNMUTE,
1257 STR_REMOVE_GROUP };
1258
1259 static UTOX_I18N_STR menu_group[] = { STR_GROUPCHAT_SETTINGS, STR_CHANGE_GROUP_TOPIC, STR_REMOVE_GROUP };
1260 static UTOX_I18N_STR menu_request[] = { STR_REQ_ACCEPT, STR_REQ_DECLINE };
1261
1262 if (mouseover_item) {
1263 right_mouse_item = mouseover_item;
1264 switch (mouseover_item->type) {
1265 case ITEM_FRIEND: {
1266 contextmenu_new(COUNTOF(menu_friend), menu_friend, contextmenu_list_onselect);
1267 show_page(mouseover_item);
1268 break;
1269 }
1270
1271 case ITEM_GROUP: {
1272 GROUPCHAT *g = get_group(mouseover_item->id_number);
1273 if (g->av_group) {
1274 if (g->muted) {
1275 contextmenu_new(COUNTOF(menu_group_muted), menu_group_muted, contextmenu_list_onselect);
1276 } else {
1277 contextmenu_new(COUNTOF(menu_group_unmuted), menu_group_unmuted, contextmenu_list_onselect);
1278 }
1279 } else {
1280 contextmenu_new(COUNTOF(menu_group), menu_group, contextmenu_list_onselect);
1281 }
1282 show_page(mouseover_item);
1283 break;
1284 }
1285
1286 case ITEM_GROUP_CREATE: {
1287 break;
1288 }
1289
1290 case ITEM_FREQUEST: {
1291 contextmenu_new(COUNTOF(menu_request), menu_request, contextmenu_list_onselect);
1292 break;
1293 }
1294
1295 default: {
1296 LOG_ERR("F-List", "MRIGHT on a flist entry that shouldn't exist!");
1297 break;
1298 }
1299 }
1300
1301 return true;
1302 } else if (mouse_in_list) {
1303 right_mouse_item = NULL; /* Unset right_mouse_item so that we don't interact with the incorrect context menu
1304 * I'm not sure if this belongs here or in flist_mmove, or maybe item_hit. */
1305 }
1306
1307 return false;
1308 }
1309
flist_mwheel(void * UNUSED (n),int UNUSED (height),double UNUSED (d),bool UNUSED (smooth))1310 bool flist_mwheel(void *UNUSED(n), int UNUSED(height), double UNUSED(d), bool UNUSED(smooth)) {
1311 return false;
1312 }
1313
flist_mup(void * UNUSED (n))1314 bool flist_mup(void *UNUSED(n)) {
1315 bool draw = false;
1316 tooltip_mup(); /* may need to return one true */
1317
1318 if (mouseover_item && selected_item_mousedown_move_pend == true) {
1319 show_page(mouseover_item);
1320 draw = true;
1321 }
1322
1323 if (selected_item_mousedown && abs(selected_item_dy) >= 5) {
1324 if (nitem && find_item_shown_index(nitem) != INT_MAX) {
1325 if (selected_item->type == ITEM_FRIEND) {
1326 if (nitem->type == ITEM_FRIEND) {
1327 ITEM temp = *selected_item;
1328 *selected_item = *nitem;
1329 *nitem = temp;
1330
1331 selected_item = nitem;
1332 }
1333
1334 if (nitem->type == ITEM_GROUP) {
1335 FRIEND *f = get_friend(selected_item->id_number);
1336 GROUPCHAT *g = get_group(nitem->id_number);
1337 if (f->online) {
1338 postmessage_toxcore(TOX_GROUP_SEND_INVITE, g->number, f->number, NULL);
1339 }
1340 }
1341 }
1342
1343 if (selected_item->type == ITEM_GROUP) {
1344 if (nitem->type == ITEM_FRIEND || nitem->type == ITEM_GROUP) {
1345 ITEM temp = *selected_item;
1346 *selected_item = *nitem;
1347 *nitem = temp;
1348
1349 selected_item = nitem;
1350 }
1351 }
1352
1353 nitem = NULL;
1354 }
1355
1356 draw = true;
1357 }
1358
1359 selected_item_mousedown = 0;
1360 selected_item_mousedown_move_pend = 0;
1361 selected_item_dy = 0;
1362
1363 return draw;
1364 }
1365
flist_mleave(void * UNUSED (n))1366 bool flist_mleave(void *UNUSED(n)) {
1367 if (mouseover_item) {
1368 mouseover_item = NULL;
1369 return true;
1370 }
1371
1372 return false;
1373 }
1374