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