1 #include "messages.h"
2 
3 #include "chatlog.h"
4 #include "file_transfers.h"
5 #include "filesys.h"
6 #include "flist.h"
7 #include "friend.h"
8 #include "groups.h"
9 #include "debug.h"
10 #include "macros.h"
11 #include "self.h"
12 #include "settings.h"
13 #include "text.h"
14 #include "theme.h"
15 #include "tox.h"
16 #include "utox.h"
17 
18 #include "ui/contextmenu.h"
19 #include "ui/draw.h"
20 #include "ui/scrollable.h"
21 #include "ui/svg.h"
22 #include "ui/text.h"
23 
24 #include "layout/friend.h"
25 #include "layout/group.h"
26 
27 #include "native/clipboard.h"
28 // TODO including native .h files should never be needed, refactor filesys.h to provide necessary API
29 #include "native/filesys.h"
30 #include "native/image.h"
31 #include "native/keyboard.h"
32 #include "native/os.h"
33 
34 #include <stdlib.h>
35 #include <string.h>
36 
37 #define UTOX_MAX_BACKLOG_MESSAGES 256
38 
39 pthread_mutex_t messages_lock;
40 
41 /** Appends a messages from self or friend to the message list;
42  * will realloc or trim messages as needed;
43  *
44  * also handles auto scrolling selections with messages
45  *
46  * accepts: MESSAGES *pointer, MESSAGE *pointer, MSG_DATA *pointer
47  */
48 
get_time_width()49 static int get_time_width() {
50     return SCALE(settings.use_long_time_msg ? TIME_WIDTH_LONG : TIME_WIDTH);
51 }
52 
msgheight(MSG_HEADER * msg,int width)53 static int msgheight(MSG_HEADER *msg, int width) {
54     switch (msg->msg_type) {
55         case MSG_TYPE_NULL: {
56             LOG_ERR("Messages", "Invalid message type in msgheight.");
57             return 0;
58         }
59 
60         case MSG_TYPE_TEXT:
61         case MSG_TYPE_ACTION_TEXT:
62         case MSG_TYPE_NOTICE:
63         case MSG_TYPE_NOTICE_DAY_CHANGE: {
64             int  theight = text_height(abs(width - SCALE(MESSAGES_X) - get_time_width()), font_small_lineheight,
65                                        msg->via.txt.msg, msg->via.txt.length);
66             return (theight == 0) ? 0 : theight + MESSAGES_SPACING;
67         }
68 
69         case MSG_TYPE_IMAGE: {
70             uint32_t maxwidth = width - SCALE(MESSAGES_X) - get_time_width();
71             if (msg->via.img.zoom || msg->via.img.w <= maxwidth) {
72                 return msg->via.img.h + MESSAGES_SPACING;
73             }
74 
75             return msg->via.img.h * maxwidth / msg->via.img.w + MESSAGES_SPACING;
76         }
77 
78         case MSG_TYPE_FILE: {
79             return FILE_TRANSFER_BOX_HEIGHT + MESSAGES_SPACING;
80         }
81     }
82 
83     return 0;
84 }
85 
msgheight_group(MSG_HEADER * msg,int width)86 static int msgheight_group(MSG_HEADER *msg, int width) {
87     switch (msg->msg_type) {
88         case MSG_TYPE_TEXT:
89         case MSG_TYPE_ACTION_TEXT:
90         case MSG_TYPE_NOTICE:
91         case MSG_TYPE_NOTICE_DAY_CHANGE: {
92             int theight = text_height(abs(width - SCALE(MESSAGES_X) - get_time_width()), font_small_lineheight,
93                                       msg->via.grp.msg, msg->via.grp.length);
94             return (theight == 0) ? 0 : theight + MESSAGES_SPACING;
95         }
96 
97         default: {
98             LOG_TRACE("Messages", "Error, can't set this group message height" );
99         }
100     }
101 
102     return 0;
103 }
104 
message_setheight(MESSAGES * m,MSG_HEADER * msg)105 static int message_setheight(MESSAGES *m, MSG_HEADER *msg) {
106     if (m->width == 0) {
107         return 0;
108     }
109 
110     setfont(FONT_TEXT);
111 
112     if (m->is_groupchat) {
113         msg->height    = msgheight_group(msg, m->width);
114     } else {
115         msg->height = msgheight(msg, m->width);
116     }
117 
118     return msg->height;
119 }
120 
message_updateheight(MESSAGES * m,MSG_HEADER * msg)121 static void message_updateheight(MESSAGES *m, MSG_HEADER *msg) {
122     if (m->width == 0) {
123         return;
124     }
125 
126     setfont(FONT_TEXT);
127 
128     m->height   -= msg->height;
129     msg->height  = message_setheight(m, msg);
130     m->height   += msg->height;
131 }
132 
message_add(MESSAGES * m,MSG_HEADER * msg)133 static uint32_t message_add(MESSAGES *m, MSG_HEADER *msg) {
134     pthread_mutex_lock(&messages_lock);
135 
136     if (m->number < UTOX_MAX_BACKLOG_MESSAGES) {
137         if (!m->data || m->extra <= 0) {
138             if (m->data) {
139                 m->data = realloc(m->data, (m->number + 10) * sizeof(void *));
140                 m->extra += 10;
141             } else {
142                 m->number = 0;
143                 m->data = calloc(20, sizeof(void *));
144                 m->extra = 20;
145             }
146 
147             if (!m->data) {
148                 LOG_FATAL_ERR(EXIT_MALLOC, "Messages", "\n\n\nFATAL ERROR TRYING TO REALLOC FOR MESSAGES.\nTHIS IS A BUG, PLEASE REPORT!\n\n\n");
149             }
150         }
151         m->data[m->number++] = msg;
152         m->extra--;
153     } else {
154         m->height -= m->data[0]->height;
155         message_free(m->data[0]);
156         memmove(m->data, m->data + 1, (UTOX_MAX_BACKLOG_MESSAGES - 1) * sizeof(MSG_HEADER *));
157         m->data[UTOX_MAX_BACKLOG_MESSAGES - 1] = msg;
158 
159         // Scroll selection up so that it stays over the same messages.
160         if (m->sel_start_msg != UINT32_MAX) {
161             if (0 < m->sel_start_msg) {
162                 m->sel_start_msg--;
163             } else {
164                 m->sel_start_position = 0;
165             }
166         }
167 
168         if (m->sel_end_msg != UINT32_MAX) {
169             if (0 < m->sel_end_msg) {
170                 m->sel_end_msg--;
171             } else {
172                 m->sel_end_position = 0;
173             }
174         }
175 
176         if (m->cursor_down_msg != UINT32_MAX) {
177             if (0 < m->cursor_down_msg) {
178                 m->cursor_down_msg--;
179             } else {
180                 m->cursor_down_position = 0;
181             }
182         }
183         if (m->cursor_over_msg != UINT32_MAX) {
184             if (0 < m->cursor_over_msg) {
185                 m->cursor_over_msg--;
186             } else {
187                 m->cursor_over_position = 0;
188             }
189         }
190     }
191 
192     message_updateheight(m, msg);
193 
194     if (m->is_groupchat) {
195         const GROUPCHAT *groupchat = flist_get_groupchat();
196         if (groupchat && groupchat == get_group(m->id)) {
197             m->panel.content_scroll->content_height = m->height;
198         }
199     } else {
200         const FRIEND *friend = flist_get_friend();
201         if (friend && friend == get_friend(m->id)) {
202             m->panel.content_scroll->content_height = m->height;
203         }
204     }
205 
206     pthread_mutex_unlock(&messages_lock);
207     return m->number;
208 }
209 
msg_add_day_notice(MESSAGES * m,time_t last,time_t next)210 static bool msg_add_day_notice(MESSAGES *m, time_t last, time_t next) {
211     /* The tm struct is shared, we have to do it this way */
212     int ltime_year = 0, ltime_mon = 0, ltime_day = 0;
213 
214     struct tm *msg_time = localtime(&last);
215 
216     ltime_year = msg_time->tm_year;
217     ltime_mon  = msg_time->tm_mon;
218     ltime_day  = msg_time->tm_mday;
219     msg_time   = localtime(&next);
220 
221     if (ltime_year >= msg_time->tm_year
222         && (ltime_year != msg_time->tm_year || ltime_mon >= msg_time->tm_mon)
223         && (ltime_year != msg_time->tm_year || ltime_mon != msg_time->tm_mon || ltime_day >= msg_time->tm_mday))
224     {
225         return false;
226     }
227 
228     MSG_HEADER *msg = calloc(1, sizeof(MSG_HEADER));
229     if (!msg) {
230         LOG_FATAL_ERR(EXIT_MALLOC, "Messages", "Couldn't allocate memory for day notice.");
231     }
232 
233     time(&msg->time);
234     msg->our_msg       = 0;
235     msg->msg_type      = MSG_TYPE_NOTICE_DAY_CHANGE;
236 
237     msg->via.notice_day.msg    = calloc(1, 256);
238     if (!msg->via.notice_day.msg) {
239         LOG_FATAL_ERR(EXIT_MALLOC, "Messages", "Couldn't allocate memory for day notice.");
240     }
241     msg->via.notice_day.length = strftime((char *)msg->via.notice_day.msg, 256,
242                                    "Day has changed to %A %B %d %Y", msg_time);
243     if (0 == msg->via.notice_day.length) {
244         LOG_ERR("Messages", "Couldn't compose day notice message.");
245         free(msg->via.notice_day.msg);
246         free(msg);
247         return false;
248     }
249 
250     message_add(m, msg);
251     return true;
252 }
253 
254 /* TODO leaving this here is a little hacky, but it was the fastest way
255  * without considering if I should expose messages_add */
message_add_group(MESSAGES * m,MSG_HEADER * msg)256 uint32_t message_add_group(MESSAGES *m, MSG_HEADER *msg) {
257     return message_add(m, msg);
258 }
259 
260 /* TODO This function and message_add_type_action() are essentially pasta. */
message_add_type_text(MESSAGES * m,bool auth,const char * msgtxt,uint16_t length,bool log,bool send)261 uint32_t message_add_type_text(MESSAGES *m, bool auth, const char *msgtxt, uint16_t length, bool log, bool send) {
262     FRIEND *f = get_friend(m->id);
263     if (!f) {
264         LOG_ERR("Messages", "Could not get friend with id: %u", m->id);
265         return UINT32_MAX;
266     }
267 
268     MSG_HEADER *msg = calloc(1, sizeof(MSG_HEADER));
269     if (!msg) {
270         LOG_FATAL_ERR(EXIT_MALLOC, "Messages", "Could not allocate memory for a message.");
271     }
272 
273     msg->via.txt.length = length;
274     msg->via.txt.msg    = calloc(1, length);
275     if (!msg->via.txt.msg) {
276         LOG_FATAL_ERR(EXIT_MALLOC, "Messages", "Could not allocate memory for message.");
277     }
278     memcpy(msg->via.txt.msg, msgtxt, length);
279 
280     time(&msg->time);
281     msg->our_msg  = auth;
282     msg->msg_type = MSG_TYPE_TEXT;
283 
284     if (auth) {
285         msg->via.txt.author_length = self.name_length;
286         if (!send) {
287             msg->receipt      = 0;
288             msg->receipt_time = 1;
289         }
290     } else {
291         msg->via.txt.author_length = f->name_length;
292     }
293 
294     if (m->data && m->number) {
295         MSG_HEADER *day_msg = m->data[m->number ? m->number - 1 : 0];
296         msg_add_day_notice(m, day_msg->time, msg->time);
297     }
298 
299     if (log) {
300         message_log_to_disk(m, msg);
301     }
302 
303     if (auth && send) {
304         postmessage_toxcore(TOX_SEND_MESSAGE, m->id, length, msg);
305     }
306 
307     return message_add(m, msg);
308 }
309 
message_add_type_action(MESSAGES * m,bool auth,const char * msgtxt,uint16_t length,bool log,bool send)310 uint32_t message_add_type_action(MESSAGES *m, bool auth, const char *msgtxt, uint16_t length, bool log, bool send) {
311     FRIEND *f = get_friend(m->id);
312     if (!f) {
313         LOG_ERR("Messages", "Could not get friend with number: %u", m->id);
314         return UINT32_MAX;
315     }
316 
317     MSG_HEADER *msg = calloc(1, sizeof(MSG_HEADER));
318     if (!msg) {
319         LOG_FATAL_ERR(EXIT_MALLOC, "Messages", "Could not get the message header.");
320     }
321 
322     msg->via.action.length = length;
323     msg->via.action.msg = calloc(1, length);
324     if (!msg->via.action.msg) {
325         LOG_FATAL_ERR(EXIT_MALLOC, "Messages", "Could not allocate memory for message.");
326     }
327     memcpy(msg->via.action.msg, msgtxt, length);
328 
329     time(&msg->time);
330     msg->our_msg  = auth;
331     msg->msg_type = MSG_TYPE_ACTION_TEXT;
332 
333     if (auth) {
334         msg->via.txt.author_length = self.name_length;
335         if (!send) {
336             msg->receipt      = 0;
337             msg->receipt_time = 1;
338         }
339     } else {
340         msg->via.txt.author_length = f->name_length;
341     }
342 
343     if (log) {
344         message_log_to_disk(m, msg);
345     }
346 
347     if (auth && send) {
348         postmessage_toxcore(TOX_SEND_ACTION, f->number, length, msg);
349     }
350 
351     return message_add(m, msg);
352 }
353 
message_add_type_notice(MESSAGES * m,const char * msgtxt,uint16_t length,bool log)354 uint32_t message_add_type_notice(MESSAGES *m, const char *msgtxt, uint16_t length, bool log) {
355     MSG_HEADER *msg = calloc(1, sizeof(MSG_HEADER));
356     if (!msg) {
357         LOG_FATAL_ERR(EXIT_MALLOC, "Messages", "Couldn't allocate memory for notice.");
358     }
359 
360     msg->via.notice.length = length;
361     msg->via.notice.msg = calloc(1, length);
362     if (!msg->via.notice.msg) {
363         LOG_FATAL_ERR(EXIT_MALLOC, "Messages", "Couldn't allocate memory for notice.");
364     }
365     memcpy(msg->via.notice.msg, msgtxt, length);
366 
367     time(&msg->time);
368     msg->our_msg       = 0;
369     msg->msg_type      = MSG_TYPE_NOTICE;
370     msg->via.txt.author_length = self.name_length;
371     msg->receipt_time  = time(NULL);
372 
373     if (log) {
374         message_log_to_disk(m, msg);
375     }
376 
377     return message_add(m, msg);
378 }
379 
message_add_type_image(MESSAGES * m,bool auth,NATIVE_IMAGE * img,uint16_t width,uint16_t height,bool UNUSED (log))380 uint32_t message_add_type_image(MESSAGES *m, bool auth, NATIVE_IMAGE *img, uint16_t width, uint16_t height, bool UNUSED(log)) {
381     if (!NATIVE_IMAGE_IS_VALID(img)) {
382         return 0;
383     }
384 
385     MSG_HEADER *msg = calloc(1, sizeof(MSG_HEADER));
386     if (!msg) {
387         LOG_FATAL_ERR(EXIT_MALLOC, "Messages", "Could not allocate memory for message header.");
388     }
389 
390     time(&msg->time);
391     msg->our_msg  = auth;
392     msg->msg_type = MSG_TYPE_IMAGE;
393 
394     msg->via.img.w        = width;
395     msg->via.img.h        = height;
396     msg->via.img.zoom     = 0;
397     msg->via.img.image    = img;
398     msg->via.img.position = 0.0;
399 
400     return message_add(m, msg);
401 }
402 
403 /* TODO FIX THIS SECTION TO MATCH ABOVE! */
404 /* Called by new file transfer to add a new message to the msg list */
message_add_type_file(MESSAGES * m,uint32_t file_number,bool incoming,bool image,uint8_t status,const uint8_t * name,size_t name_size,size_t target_size,size_t current_size)405 MSG_HEADER *message_add_type_file(MESSAGES *m, uint32_t file_number, bool incoming, bool image, uint8_t status,
406                                 const uint8_t *name, size_t name_size, size_t target_size, size_t current_size)
407 {
408     MSG_HEADER *msg = calloc(1, sizeof(MSG_HEADER));
409     if (!msg) {
410         LOG_FATAL_ERR(EXIT_MALLOC, "Messages", "Could not allocate memory for message header.");
411     }
412 
413     time(&msg->time);
414     msg->our_msg     = !incoming;
415     msg->msg_type    = MSG_TYPE_FILE;
416 
417     msg->via.ft.file_status = status;
418 
419     msg->via.ft.file_number = file_number;
420 
421     msg->via.ft.size       = target_size;
422     msg->via.ft.progress   = current_size;
423     msg->via.ft.speed      = 0;
424     msg->via.ft.inline_png = image;
425 
426     msg->via.ft.name_length = name_size;
427     msg->via.ft.name = calloc(1, name_size + 1);
428     if (!msg->via.ft.name) {
429         LOG_FATAL_ERR(EXIT_MALLOC, "Messages", "Could not allocate memory for the file name.");
430     }
431     memcpy(msg->via.ft.name, name, msg->via.ft.name_length);
432 
433     if (image) {
434         msg->via.ft.path = NULL;
435     } else { // It's a file
436         msg->via.ft.path = calloc(1, UTOX_FILE_NAME_LENGTH);
437         if (!msg->via.ft.path) {
438             LOG_FATAL_ERR(EXIT_MALLOC, "Messages", "Could not allocate memory for the file path.");
439         }
440     }
441 
442     message_add(m, msg);
443 
444     return msg;
445 }
446 
message_log_to_disk(MESSAGES * m,MSG_HEADER * msg)447 bool message_log_to_disk(MESSAGES *m, MSG_HEADER *msg) {
448     if (m->is_groupchat) {
449         /* We don't support logging groupchats yet */
450         return false;
451     }
452 
453     if (!settings.logging_enabled) {
454         return false;
455     }
456 
457     FRIEND *f = get_friend(m->id);
458     if (!f) {
459         LOG_ERR("Messages", "Could not get friend with number: %u", m->id);
460         return false;
461     }
462 
463     if (f->skip_msg_logging) {
464         return false;
465     }
466 
467     LOG_FILE_MSG_HEADER header;
468     memset(&header, 0, sizeof(header));
469 
470     switch (msg->msg_type) {
471         case MSG_TYPE_TEXT:
472         case MSG_TYPE_ACTION_TEXT:
473         case MSG_TYPE_NOTICE: {
474             size_t author_length;
475             char   *author;
476             if (msg->our_msg) {
477                 author_length = self.name_length;
478                 author        = self.name;
479             } else {
480                 author_length = f->name_length;
481                 author        = f->name;
482             }
483 
484             header.log_version   = LOGFILE_SAVE_VERSION;
485             header.time          = msg->time;
486             header.author_length = author_length;
487             header.msg_length    = msg->via.txt.length;
488             header.author        = msg->our_msg;
489             header.receipt       = !!msg->receipt_time; // bool only
490             header.msg_type      = msg->msg_type;
491 
492             size_t length = sizeof(header) + msg->via.txt.length + author_length + 1; /* extra \n char*/
493             uint8_t *data = calloc(1, length);
494             if (!data) {
495                 LOG_FATAL_ERR(EXIT_MALLOC, "Messages", "Can't calloc for chat logging data. size:%lu", length);
496             }
497             memcpy(data, &header, sizeof(header));
498             memcpy(data + sizeof(header), author, author_length);
499             memcpy(data + sizeof(header) + author_length, msg->via.txt.msg, msg->via.txt.length);
500             strcpy2(data + length - 1, "\n");
501 
502             msg->disk_offset = utox_save_chatlog(f->id_str, data, length);
503 
504             free(data);
505             return true;
506         }
507         default: {
508             LOG_NOTE("Messages", "uTox Logging:\tUnsupported message type %i", msg->msg_type);
509         }
510     }
511     return false;
512 }
513 
messages_read_from_log(uint32_t friend_number)514 bool messages_read_from_log(uint32_t friend_number) {
515     size_t    actual_count = 0;
516 
517     FRIEND *f = get_friend(friend_number);
518     if (!f) {
519         LOG_ERR("Messages", "Could not get friend with number: %u", friend_number);
520         return false;
521     }
522 
523     MSG_HEADER **data = utox_load_chatlog(f->id_str, &actual_count, UTOX_MAX_BACKLOG_MESSAGES, 0);
524     if (!data) {
525         if (actual_count > 0) {
526             LOG_ERR("Messages", "uTox Logging:\tFound chat log entries, but couldn't get any data. This is a problem.");
527         }
528         return false;
529     }
530 
531     MSG_HEADER **p = data;
532     MSG_HEADER *msg;
533     time_t last = 0;
534     while (actual_count--) {
535         msg = *p++;
536         if (!msg) {
537             continue;
538         }
539 
540         if (msg_add_day_notice(&f->msg, last, msg->time)) {
541             last = msg->time;
542         }
543         message_add(&f->msg, msg);
544     }
545 
546     free(data);
547     return true;
548 }
549 
messages_send_from_queue(MESSAGES * m,uint32_t friend_number)550 void messages_send_from_queue(MESSAGES *m, uint32_t friend_number) {
551     uint32_t start    = m->number;
552     uint8_t  seek_num = 3; /* this magic number is the number of messages we'll skip looking for the first unsent */
553 
554     pthread_mutex_lock(&messages_lock);
555 
556     int queue_count = 0;
557     /* seek back to find first queued message
558      * I hate this nest too, but it's readable */
559     while (start) {
560         --start;
561 
562         if (++queue_count > 25) {
563             break;
564         }
565 
566         if (m->data[start]) {
567             MSG_HEADER *msg = m->data[start];
568             if (msg->msg_type == MSG_TYPE_TEXT || msg->msg_type == MSG_TYPE_ACTION_TEXT) {
569                 if (msg->our_msg) {
570                     if (msg->receipt_time) {
571                         if (!seek_num--) {
572                             break;
573                         }
574                     }
575                 }
576             }
577         }
578     }
579 
580     int sent_count = 0;
581     /* start sending messages, hopefully in order */
582     while (start < m->number && sent_count <= 25) {
583         if (m->data[start]) {
584             MSG_HEADER *msg = m->data[start];
585             if (msg->msg_type == MSG_TYPE_TEXT || msg->msg_type == MSG_TYPE_ACTION_TEXT) {
586                 if (msg->our_msg && !msg->receipt_time) {
587                     postmessage_toxcore((msg->msg_type == MSG_TYPE_TEXT ? TOX_SEND_MESSAGE : TOX_SEND_ACTION),
588                                         friend_number, msg->via.txt.length, msg);
589                     ++sent_count;
590                 }
591             }
592         }
593         ++start;
594     }
595     pthread_mutex_unlock(&messages_lock);
596 }
597 
messages_clear_receipt(MESSAGES * m,uint32_t receipt_number)598 void messages_clear_receipt(MESSAGES *m, uint32_t receipt_number) {
599     pthread_mutex_lock(&messages_lock);
600 
601     uint32_t start = m->number;
602     while (start--) {
603         if (!m->data[start]) {
604             continue;
605         }
606 
607         MSG_HEADER *msg = m->data[start];
608         if (msg->msg_type != MSG_TYPE_TEXT &&
609             msg->msg_type != MSG_TYPE_ACTION_TEXT) {
610             continue;
611         }
612 
613         if (msg->receipt != receipt_number) {
614             continue;
615         }
616 
617         msg->receipt = -1;
618         time(&msg->receipt_time);
619 
620         LOG_FILE_MSG_HEADER header;
621         memset(&header, 0, sizeof(header));
622 
623         header.log_version   = LOGFILE_SAVE_VERSION;
624         header.time          = msg->time;
625         header.author_length = msg->via.txt.author_length;
626         header.msg_length    = msg->via.txt.length;
627         header.author        = 1;
628         header.receipt       = 1;
629         header.msg_type      = msg->msg_type;
630 
631         size_t length = sizeof(header);
632         uint8_t *data = calloc(1, length);
633         if (!data) {
634             LOG_FATAL_ERR(EXIT_MALLOC, "Messages", "Couldn't allocate memory for message.");
635         }
636         memcpy(data, &header, length);
637 
638         char *hex = get_friend(m->id)->id_str;
639         if (msg->disk_offset) {
640             LOG_TRACE("Messages", "Updating message -> disk_offset is %lu" , msg->disk_offset);
641             utox_update_chatlog(hex, msg->disk_offset, data, length);
642         } else if (msg->disk_offset == 0 && start <= 1 && receipt_number == 1) {
643             /* This could get messy if receipt is 1, msg position is 0, and the offset is actually wrong,
644              * But I couldn't come up with any other way to verify the rare case of a bad offset
645              * start <= 1 to offset for the day change notification                                    */
646             LOG_TRACE("Messages", "Updating first message -> disk_offset is %lu" , msg->disk_offset);
647             utox_update_chatlog(hex, msg->disk_offset, data, length);
648         } else {
649             LOG_ERR("Messages",
650                     "Messages:\tUnable to update this message...\n"
651                     "\t\tmsg->disk_offset %lu && m->number %u receipt_number %u \n",
652                     msg->disk_offset, m->number, receipt_number);
653         }
654         free(data);
655 
656         postmessage_utox(FRIEND_MESSAGE_UPDATE, 0, 0, NULL); /* Used to redraw the screen */
657         pthread_mutex_unlock(&messages_lock);
658         return;
659     }
660 
661     LOG_ERR("Messages", "Received a receipt for a message we don't have a record of. %u", receipt_number);
662     pthread_mutex_unlock(&messages_lock);
663 }
664 
messages_draw_timestamp(int x,int y,const time_t * time)665 static void messages_draw_timestamp(int x, int y, const time_t *time) {
666     struct tm *ltime = localtime(time);
667 
668     char     timestr[9];
669     uint16_t len;
670 
671 
672     if (settings.use_long_time_msg) {
673         snprintf(timestr, sizeof(timestr), "%.2u:%.2u:%.2u", ltime->tm_hour, ltime->tm_min, ltime->tm_sec);
674         x -= textwidth("24:60:00", sizeof "24:60:00" - 1);
675     } else {
676         snprintf(timestr, sizeof(timestr), "%u:%.2u", ltime->tm_hour, ltime->tm_min);
677         x -= textwidth("24:60", sizeof "24:60" - 1);
678     }
679     len = strnlen(timestr, sizeof(timestr) - 1);
680 
681     setcolor(COLOR_MAIN_TEXT_SUBTEXT);
682     setfont(FONT_MISC);
683     drawtext(x - MESSAGES_SPACING, y, timestr, len);
684 }
685 
messages_draw_author(int x,int y,int w,char * name,uint32_t length,uint32_t color)686 static void messages_draw_author(int x, int y, int w, char *name, uint32_t length, uint32_t color) {
687     setcolor(color);
688     setfont(FONT_TEXT);
689     drawtextwidth_right(x, w, y, name, length);
690 }
691 
messages_draw_text(const char * msg,size_t length,uint32_t msg_height,uint8_t msg_type,bool author,bool receipt,uint16_t highlight_start,uint16_t highlight_end,int x,int y,int w,int UNUSED (h))692 static int messages_draw_text(const char *msg, size_t length, uint32_t msg_height, uint8_t msg_type, bool author,
693                               bool receipt, uint16_t highlight_start, uint16_t highlight_end,
694                               int x, int y, int w, int UNUSED(h))
695 {
696     switch (msg_type) {
697         case MSG_TYPE_TEXT: {
698             if (author) {
699                 if (receipt) {
700                     setcolor(COLOR_MSG_USER);
701                 } else {
702                     setcolor(COLOR_MSG_USER_PEND);
703                 }
704             } else {
705                 setcolor(COLOR_MSG_CONTACT);
706             }
707             break;
708         }
709         case MSG_TYPE_NOTICE:
710         case MSG_TYPE_NOTICE_DAY_CHANGE:
711         case MSG_TYPE_ACTION_TEXT: {
712             setcolor(COLOR_MAIN_TEXT_ACTION);
713             break;
714         }
715     }
716 
717     setfont(FONT_TEXT);
718 
719     int ny = utox_draw_text_multiline_within_box(x, y, w + x, MAIN_TOP, y + msg_height, font_small_lineheight, msg,
720                                                  length, highlight_start, highlight_end, 0, 0, 1);
721 
722     if (ny < y || (uint32_t)(ny - y) + MESSAGES_SPACING != msg_height) {
723         LOG_TRACE("Messages", "Text Draw Error:\ty %i | ny %i | mheight %u | width %i " , y, ny, msg_height, w);
724     }
725 
726     return ny;
727 }
728 
729 /* draws an inline image at rect (x,y,width,height)
730  *  maxwidth is maximum width the image can take in
731  *  zoom is whether the image is currently zoomed in
732  *  position is the y position along the image the player has scrolled */
messages_draw_image(MSG_IMG * img,int x,int y,uint32_t maxwidth)733 static int messages_draw_image(MSG_IMG *img, int x, int y, uint32_t maxwidth) {
734     image_set_filter(img->image, FILTER_BILINEAR);
735 
736     if (!img->zoom && img->w > maxwidth) {
737         image_set_scale(img->image, (double)maxwidth / img->w);
738 
739         draw_image(img->image, x, y, maxwidth, img->h * maxwidth / img->w, 0, 0);
740 
741         image_set_scale(img->image, 1.0);
742     } else if (img->w > maxwidth) {
743         draw_image(img->image, x, y, maxwidth, img->h, (int)((double)(img->w - maxwidth) * img->position), 0);
744     } else {
745         draw_image(img->image, x, y, img->w, img->h, 0, 0);
746     }
747 
748     return (img->zoom || img->w <= maxwidth) ? img->h : img->h * maxwidth / img->w;
749 }
750 
751 /* Draw macros added, to reduce future line edits. */
752 #define DRAW_FT_RECT(color) draw_rect_fill(dx, y, d_width, FILE_TRANSFER_BOX_HEIGHT, color)
753 
754 #define DRAW_FT_PROG(color) draw_rect_fill(dx, y, prog_bar, FILE_TRANSFER_BOX_HEIGHT, color)
755 
756 #define DRAW_FT_CAP(bg, fg)                                                                                 \
757     do {                                                                                                    \
758         drawalpha(BM_FT_CAP, dx - room_for_clip, y, BM_FT_CAP_WIDTH, BM_FTB_HEIGHT, bg);                    \
759         drawalpha(BM_FILE, dx - room_for_clip + SCALE(4), y + SCALE(4), BM_FILE_WIDTH, BM_FILE_HEIGHT, fg); \
760     } while (0)
761 
762 /* Always first */
763 #define DRAW_FT_NO_BTN()                                                                        \
764     do {                                                                                        \
765         drawalpha(BM_FTB1, btnx, tbtn_bg_y, btn_bg_w, tbtn_bg_h,                                \
766                   (mouse_left_btn ? COLOR_BTN_DANGER_BKGRND_HOVER : COLOR_BTN_SUCCESS_BKGRND)); \
767         drawalpha(BM_NO, btnx + ((btn_bg_w - btnw) / 2), tbtn_y, btnw, btnh,                    \
768                   (mouse_left_btn ? COLOR_BTN_DANGER_TEXT_HOVER : COLOR_BTN_DANGER_TEXT));      \
769     } while (0)
770 
771 /* Always last */
772 #define DRAW_FT_YES_BTN()                                                                           \
773     do {                                                                                            \
774         drawalpha(BM_FTB2, btnx + btn_bg_w + SCALE(2), tbtn_bg_y, btn_bg_w, tbtn_bg_h,              \
775                   (mouse_rght_btn ? COLOR_BTN_SUCCESS_BKGRND_HOVER : COLOR_BTN_SUCCESS_BKGRND));    \
776         drawalpha(BM_YES, btnx + btn_bg_w + SCALE(2) + ((btn_bg_w - btnw) / 2), tbtn_y, btnw, btnh, \
777                   (mouse_rght_btn ? COLOR_BTN_SUCCESS_TEXT_HOVER : COLOR_BTN_SUCCESS_TEXT));        \
778     } while (0)
779 
780 #define DRAW_FT_PAUSE_BTN()                                                                           \
781     do {                                                                                              \
782         drawalpha(BM_FTB2, btnx + btn_bg_w + SCALE(2), tbtn_bg_y, btn_bg_w, tbtn_bg_h,                \
783                   (mouse_rght_btn ? COLOR_BTN_SUCCESS_BKGRND_HOVER : COLOR_BTN_SUCCESS_BKGRND));      \
784         drawalpha(BM_PAUSE, btnx + btn_bg_w + SCALE(2) + ((btn_bg_w - btnw) / 2), tbtn_y, btnw, btnh, \
785                   (mouse_rght_btn ? COLOR_BTN_SUCCESS_TEXT_HOVER : COLOR_BTN_SUCCESS_TEXT));          \
786     } while (0)
787 
788 #define DRAW_FT_RESUME_BTN()                                                                           \
789     do {                                                                                               \
790         drawalpha(BM_FTB2, btnx + btn_bg_w + SCALE(2), tbtn_bg_y, btn_bg_w, tbtn_bg_h,                 \
791                   (mouse_rght_btn ? COLOR_BTN_SUCCESS_BKGRND_HOVER : COLOR_BTN_SUCCESS_BKGRND));       \
792         drawalpha(BM_RESUME, btnx + btn_bg_w + SCALE(2) + ((btn_bg_w - btnw) / 2), tbtn_y, btnw, btnh, \
793                   (mouse_rght_btn ? COLOR_BTN_SUCCESS_TEXT_HOVER : COLOR_BTN_SUCCESS_TEXT));           \
794     } while (0)
795 
796 #define DRAW_FT_TEXT_RIGHT(str, len)                   \
797     do {                                               \
798         wbound -= (textwidth(str, len) + (SCALE(12))); \
799         drawtext(wbound, y + SCALE(8), str, len);      \
800     } while (0)
801 
802 #define DRAW_FT_ALPH_RIGHT(bm, col)                     \
803     do {                                                \
804         wbound -= btnw + (SCALE(12));                   \
805         drawalpha(bm, wbound, tbtn_y, btnw, btnh, col); \
806     } while (0)
807 
808 #define DRAWSTR_FT_RIGHT(t) DRAW_FT_TEXT_RIGHT(S(t), SLEN(t))
809 
810 
messages_draw_filetransfer(MESSAGES * m,MSG_FILE * file,uint32_t i,int x,int y,int w,int UNUSED (h))811 static void messages_draw_filetransfer(MESSAGES *m, MSG_FILE *file, uint32_t i, int x, int y, int w, int UNUSED(h)) {
812     // Used in macros.
813     int room_for_clip = BM_FT_CAP_WIDTH + SCALE(2);
814     int dx            = x + SCALE(MESSAGES_X) + room_for_clip;
815     int d_width       = w - SCALE(MESSAGES_X) - get_time_width() - room_for_clip;
816     /* Mouse Positions */
817     bool mo             = (m->cursor_over_msg == i);
818     bool mouse_over     = (mo && m->cursor_over_position) ? 1 : 0;
819     bool mouse_rght_btn = (mo && m->cursor_over_position == 2) ? 1 : 0;
820     bool mouse_left_btn = (mo && m->cursor_over_position == 1) ? 1 : 0;
821 
822     /* Button Background */
823     int btn_bg_w = BM_FTB_WIDTH;
824     /* Button Background heights */
825     int tbtn_bg_y = y;
826     int tbtn_bg_h = BM_FTB_HEIGHT;
827     /* Top button info */
828     int btnx   = dx + d_width - (btn_bg_w * 2) - SCALE(2);
829     int tbtn_y = y + SCALE(8);
830     int btnw   = BM_FB_WIDTH;
831     int btnh   = BM_FB_HEIGHT;
832 
833     long double file_percent = (double)file->progress / (double)file->size;
834     if (file->progress > file->size) {
835         file->progress = file->size;
836         file_percent = 1.0;
837     }
838 
839     char ft_text[file->name_length + 128];
840     size_t ft_text_length;
841 
842     snprintf(ft_text, sizeof(ft_text), "%.*s ", (int)file->name_length, file->name);
843     ft_text_length = strnlen(ft_text, sizeof(ft_text) - 1);
844     ft_text_length += sprint_humanread_bytes(ft_text + ft_text_length, sizeof(ft_text) - ft_text_length, file->size);
845 
846     setfont(FONT_MISC);
847     setcolor(COLOR_BKGRND_MAIN);
848 
849     int wbound = dx + d_width - SCALE(6);
850 
851     switch (file->file_status) {
852         case FILE_TRANSFER_STATUS_NONE:
853         case FILE_TRANSFER_STATUS_ACTIVE:
854         case FILE_TRANSFER_STATUS_PAUSED_US:
855         case FILE_TRANSFER_STATUS_PAUSED_BOTH:
856         case FILE_TRANSFER_STATUS_PAUSED_THEM: {
857             int ftb_allowance = (BM_FTB_WIDTH * 2) + (SCALE(4));
858             d_width -= ftb_allowance;
859             wbound -= ftb_allowance;
860             break;
861         }
862 
863         default: {
864             // we'll round the corner even without buttons.
865             d_width -= btn_bg_w;
866             break;
867         }
868     }
869 
870     // progress rectangle
871     uint32_t prog_bar = (file->size == 0) ? 0 : ((long double)d_width * file_percent);
872 
873     switch (file->file_status) {
874         case FILE_TRANSFER_STATUS_COMPLETED: {
875             /* If mouse over use hover color */
876             uint32_t text       = mouse_over ? COLOR_BTN_SUCCESS_TEXT_HOVER : COLOR_BTN_SUCCESS_TEXT,
877                      background = mouse_over ? COLOR_BTN_SUCCESS_BKGRND_HOVER : COLOR_BTN_SUCCESS_BKGRND;
878 
879             setcolor(text);
880             DRAW_FT_CAP(background, text);
881             DRAW_FT_RECT(background);
882             drawalpha(BM_FTB2, dx + d_width, tbtn_bg_y, btn_bg_w, tbtn_bg_h, background);
883 
884             if (file->inline_png) {
885                 DRAWSTR_FT_RIGHT(CLICKTOSAVE);
886             } else {
887                 DRAWSTR_FT_RIGHT(CLICKTOOPEN);
888             }
889             DRAW_FT_ALPH_RIGHT(BM_YES, text);
890             break;
891         }
892 
893         case FILE_TRANSFER_STATUS_KILLED: {
894             setcolor(COLOR_BTN_DANGER_TEXT);
895             DRAW_FT_CAP(COLOR_BTN_DANGER_BACKGROUND, COLOR_BTN_DANGER_TEXT);
896             DRAW_FT_RECT(COLOR_BTN_DANGER_BACKGROUND);
897             drawalpha(BM_FTB2, dx + d_width, tbtn_bg_y, btn_bg_w, tbtn_bg_h, COLOR_BTN_DANGER_BACKGROUND);
898 
899             DRAWSTR_FT_RIGHT(TRANSFER_CANCELLED);
900             DRAW_FT_ALPH_RIGHT(BM_NO, COLOR_BTN_DANGER_TEXT);
901             break;
902         }
903 
904         case FILE_TRANSFER_STATUS_BROKEN: {
905             setcolor(COLOR_BTN_DANGER_TEXT);
906             DRAW_FT_CAP(COLOR_BTN_DANGER_BACKGROUND, COLOR_BTN_DANGER_TEXT);
907             DRAW_FT_RECT(COLOR_BTN_DANGER_BACKGROUND);
908             drawalpha(BM_FTB2, dx + d_width, tbtn_bg_y, btn_bg_w, tbtn_bg_h, COLOR_BTN_DANGER_BACKGROUND);
909 
910             DRAWSTR_FT_RIGHT(TRANSFER_BROKEN);
911             DRAW_FT_ALPH_RIGHT(BM_NO, COLOR_BTN_DANGER_TEXT);
912             break;
913         }
914 
915         case FILE_TRANSFER_STATUS_NONE: {
916             /* ↑ used for incoming transfers */
917             setcolor(COLOR_BTN_DISABLED_TRANSFER);
918             DRAW_FT_CAP(COLOR_BTN_DISABLED_BKGRND, COLOR_BTN_DISABLED_TRANSFER);
919             DRAW_FT_RECT(COLOR_BTN_DISABLED_BKGRND);
920 
921             DRAW_FT_NO_BTN();
922             DRAW_FT_YES_BTN();
923 
924             DRAW_FT_PROG(COLOR_BTN_DISABLED_FORGRND);
925             break;
926         }
927 
928         case FILE_TRANSFER_STATUS_ACTIVE: {
929             setcolor(COLOR_BTN_INPROGRESS_TEXT);
930             DRAW_FT_CAP(COLOR_BTN_INPROGRESS_BKGRND, COLOR_BTN_INPROGRESS_TEXT);
931             DRAW_FT_RECT(COLOR_BTN_INPROGRESS_BKGRND);
932 
933             DRAW_FT_NO_BTN();
934             DRAW_FT_PAUSE_BTN();
935 
936             char speed[32] = {0};
937             size_t speed_len;
938             speed_len = sprint_humanread_bytes(speed, sizeof(speed), file->speed);
939             snprintf(speed + speed_len, sizeof(speed) - speed_len, "/s %lus",
940                      file->speed ? (file->size - file->progress) / file->speed : 0);
941             speed_len = strnlen(speed, sizeof(speed) - 1);
942 
943             DRAW_FT_TEXT_RIGHT(speed, speed_len);
944             DRAW_FT_PROG(COLOR_BTN_INPROGRESS_FORGRND);
945             break;
946         }
947 
948         case FILE_TRANSFER_STATUS_PAUSED_US:
949         case FILE_TRANSFER_STATUS_PAUSED_BOTH:
950         case FILE_TRANSFER_STATUS_PAUSED_THEM: {
951             setcolor(COLOR_BTN_DISABLED_TRANSFER);
952 
953             DRAW_FT_CAP(COLOR_BTN_DISABLED_BKGRND, COLOR_BTN_DISABLED_TRANSFER);
954             DRAW_FT_RECT(COLOR_BTN_DISABLED_BKGRND);
955 
956             DRAW_FT_NO_BTN();
957 
958             if (file->file_status == FILE_TRANSFER_STATUS_PAUSED_BOTH
959                 || file->file_status == FILE_TRANSFER_STATUS_PAUSED_US) {
960                 /* Paused by at least us */
961                 DRAW_FT_RESUME_BTN();
962             } else {
963                 /* Paused only by them */
964                 DRAW_FT_PAUSE_BTN();
965             }
966 
967             DRAW_FT_PROG(COLOR_BTN_DISABLED_FORGRND);
968             break;
969         }
970     }
971 
972     setfont(FONT_TEXT);
973     drawtextrange(dx + SCALE(10), wbound - SCALE(10), y + SCALE(6), ft_text, ft_text_length);
974 }
975 
976 /* This is a bit hacky, and likely would benefit from being moved to a whole new section including separating
977  * group messages/functions from friend messages and functions from inside ui.c.
978  *
979  * Ideally group and friend messages wouldn't even need to know about each other.   */
messages_draw_group(MESSAGES * m,MSG_HEADER * msg,uint32_t curr_msg_i,int x,int y,int width,int height)980 static int messages_draw_group(MESSAGES *m, MSG_HEADER *msg, uint32_t curr_msg_i, int x, int y, int width, int height) {
981     uint32_t h1 = UINT32_MAX, h2 = UINT32_MAX;
982     if ((m->sel_start_msg > curr_msg_i && m->sel_end_msg > curr_msg_i)
983         || (m->sel_start_msg < curr_msg_i && m->sel_end_msg < curr_msg_i)) {
984         /* Out side the highlight area */
985         h1 = UINT32_MAX;
986         h2 = UINT32_MAX;
987     } else {
988         if (m->sel_start_msg < curr_msg_i) {
989             h1 = 0;
990         } else {
991             h1 = m->sel_start_position;
992         }
993 
994         if (m->sel_end_msg > curr_msg_i) {
995             h2 = msg->via.grp.length;
996         } else {
997             h2 = m->sel_end_position;
998         }
999     }
1000 
1001     /* error check */
1002     if ((m->sel_start_msg == m->sel_end_msg && m->sel_start_position == m->sel_end_position) || h1 == h2) {
1003         h1 = UINT32_MAX;
1004         h2 = UINT32_MAX;
1005     }
1006 
1007     messages_draw_author(x, y, SCALE(MESSAGES_X - NAME_OFFSET), msg->via.grp.author, msg->via.grp.author_length, msg->via.grp.author_color);
1008     messages_draw_timestamp(x + width, y, &msg->time);
1009     return messages_draw_text(msg->via.grp.msg, msg->via.grp.length, msg->height, msg->msg_type, msg->our_msg, 1,
1010                               h1, h2, x + SCALE(MESSAGES_X), y, width - get_time_width() - SCALE(MESSAGES_X), height)
1011            + MESSAGES_SPACING;
1012 }
1013 
messages_time_change(MESSAGES * m,MSG_HEADER * msg,size_t index,int x,int y,int width,int height)1014 static int messages_time_change(MESSAGES *m, MSG_HEADER *msg, size_t index, int x, int y, int width, int height) {
1015     uint32_t h1 = UINT32_MAX, h2 = UINT32_MAX;
1016 
1017     if ((m->sel_start_msg > index && m->sel_end_msg > index)
1018         || (m->sel_start_msg < index && m->sel_end_msg < index)) {
1019         /* Out side the highlight area */
1020         h1 = UINT32_MAX;
1021         h2 = UINT32_MAX;
1022     } else {
1023         if (m->sel_start_msg < index) {
1024             h1 = 0;
1025         } else {
1026             h1 = m->sel_start_position;
1027         }
1028 
1029         if (m->sel_end_msg > index) {
1030             h2 = msg->via.notice.length;
1031         } else {
1032             h2 = m->sel_end_position;
1033         }
1034     }
1035 
1036                 /* error check */
1037     if ((m->sel_start_msg == m->sel_end_msg && m->sel_start_position == m->sel_end_position) || h1 == h2) {
1038         h1 = UINT32_MAX;
1039         h2 = UINT32_MAX;
1040     }
1041 
1042     /* text.c is super broken, so we have to be hacky here */
1043     if (h2 != msg->via.notice.length) {
1044         if (m->sel_end_msg != index) {
1045             h2 = msg->via.notice.length - h2;
1046         } else {
1047             h2 -= h1;
1048         }
1049     }
1050 
1051     return messages_draw_text(msg->via.notice.msg, msg->via.notice.length, msg->height,
1052                               msg->msg_type, msg->our_msg, msg->receipt_time,
1053                               h1, h2, x + SCALE(MESSAGES_X), y, width - get_time_width() - SCALE(MESSAGES_X), height);
1054 }
1055 
1056 /** Formats all messages from self and friends, and then call draw functions
1057  * to write them to the UI.
1058  *
1059  * accepts: messages struct *pointer, int x,y positions, int width,height
1060  */
messages_draw(PANEL * panel,int x,int y,int width,int height)1061 void messages_draw(PANEL *panel, int x, int y, int width, int height) {
1062     if (width - SCALE(MESSAGES_X) - get_time_width() <= 0) {
1063         return;
1064     }
1065 
1066     pthread_mutex_lock(&messages_lock);
1067 
1068     MESSAGES *m = panel->object;
1069     // Do not draw author name next to every message
1070     uint8_t lastauthor = 0xFF;
1071 
1072     // Message iterator
1073     MSG_HEADER **p = m->data;
1074     uint32_t n = m->number;
1075 
1076     if (m->width != width) {
1077         m->width = width;
1078         messages_updateheight(m, width - SCALE(MESSAGES_X) + get_time_width());
1079         y -= scroll_gety(panel->content_scroll, height);
1080     }
1081 
1082     // Go through messages
1083     for (size_t curr_msg_i = 0; curr_msg_i != n; curr_msg_i++) {
1084         MSG_HEADER *msg = *p++;
1085 
1086         /* Decide if we should even bother drawing this message. */
1087         if (msg->height == 0) {
1088             /* Empty message */
1089             pthread_mutex_unlock(&messages_lock);
1090             return;
1091         } else if (y + msg->height <= (unsigned)SCALE(MAIN_TOP)) {
1092             /* message is exclusively above the viewing window */
1093             y += msg->height;
1094             continue;
1095         } else if (y >= height + SCALE(100)) { // NOTE: should not be constant 100
1096             /* Message is exclusively below the viewing window */
1097             break;
1098         }
1099 
1100         // Draw the names for groups or friends
1101         if (m->is_groupchat) {
1102             y = messages_draw_group(m, msg, curr_msg_i, x, y, width, height);
1103             continue;
1104         } else {
1105             bool draw_author = true;
1106             switch (msg->msg_type) {
1107                 case MSG_TYPE_NULL: {
1108                     // This shouldn't happen.
1109                     LOG_ERR("Messages", "Invalid message type in messages_draw.");
1110                     break;
1111                 }
1112 
1113                 case MSG_TYPE_ACTION_TEXT: {
1114                     // Always draw name next to action message
1115                     lastauthor = ~0;
1116                     break;
1117                 }
1118 
1119                 case MSG_TYPE_TEXT:
1120                 case MSG_TYPE_IMAGE:
1121                 case MSG_TYPE_FILE: {
1122                     draw_author = true;
1123                     break;
1124                 }
1125 
1126                 case MSG_TYPE_NOTICE:
1127                 case MSG_TYPE_NOTICE_DAY_CHANGE: {
1128                     draw_author = false;
1129                     break;
1130                 }
1131             }
1132 
1133             if (draw_author) {
1134                 if (msg->our_msg != lastauthor || y < SCALE(MAIN_TOP) + font_small_lineheight) {
1135                     int msg_y = y;
1136 
1137                     // If previous author label is invisible (i.e. above top side of the messages window)
1138                     // than clear its old place by drawing a rectangle with background colour.
1139                     // After that we are able to draw a new author label at the same place.
1140                     if (y < SCALE(MAIN_TOP) + font_small_lineheight) {
1141                         msg_y = SCALE(MAIN_TOP);
1142 
1143                         // MAIN_TOP + 1 because otherwise it cuts off one pixel from TOP FRAME somehow
1144                         draw_rect_fill(x, SCALE(MAIN_TOP) + 1, SCALE(MESSAGES_X), font_small_lineheight, COLOR_BKGRND_MAIN);
1145                     }
1146 
1147                     FRIEND *f = get_friend(m->id);
1148                     if (msg->our_msg) {
1149                         messages_draw_author(x, msg_y, SCALE(MESSAGES_X - NAME_OFFSET), self.name, self.name_length,
1150                                              COLOR_MAIN_TEXT_SUBTEXT);
1151                     } else if (f->alias) {
1152                         messages_draw_author(x, msg_y, SCALE(MESSAGES_X - NAME_OFFSET), f->alias, f->alias_length,
1153                                              COLOR_MAIN_TEXT_CHAT);
1154                     } else {
1155                         messages_draw_author(x, msg_y, SCALE(MESSAGES_X - NAME_OFFSET), f->name, f->name_length,
1156                                              COLOR_MAIN_TEXT_CHAT);
1157                     }
1158                     lastauthor = msg->our_msg;
1159                 }
1160             }
1161         }
1162 
1163         // Draw message contents
1164         switch (msg->msg_type) {
1165             case MSG_TYPE_NULL: {
1166                 LOG_ERR("Messages", "Error msg type is null");
1167                 break;
1168             }
1169 
1170             case MSG_TYPE_TEXT:
1171             case MSG_TYPE_ACTION_TEXT:
1172             case MSG_TYPE_NOTICE: {
1173                 // Draw timestamps
1174                 messages_draw_timestamp(x + width, y, &msg->time);
1175                 y = messages_time_change(m, msg, curr_msg_i, x, y, width, height);
1176                 break;
1177             }
1178             case MSG_TYPE_NOTICE_DAY_CHANGE: {
1179                 y = messages_time_change(m, msg, curr_msg_i, x, y, width, height);
1180                 break;
1181             }
1182 
1183             // Draw image
1184             case MSG_TYPE_IMAGE: {
1185                 y += messages_draw_image(&msg->via.img, x + SCALE(MESSAGES_X), y, width - SCALE(MESSAGES_X) - get_time_width());
1186                 break;
1187             }
1188 
1189             // Draw file transfer
1190             case MSG_TYPE_FILE: {
1191                 messages_draw_filetransfer(m, &msg->via.ft, curr_msg_i, x, y, width, height);
1192                 y += FILE_TRANSFER_BOX_HEIGHT;
1193                 break;
1194             }
1195         }
1196 
1197         y += MESSAGES_SPACING;
1198     }
1199 
1200     pthread_mutex_unlock(&messages_lock);
1201 }
1202 
messages_mmove_text(MESSAGES * m,int width,int mx,int my,int dy,char * message,uint32_t msg_height,uint16_t msg_length)1203 static bool messages_mmove_text(MESSAGES *m, int width, int mx, int my, int dy, char *message, uint32_t msg_height,
1204                                 uint16_t msg_length)
1205 {
1206     if (mx < width - get_time_width()) {
1207         cursor = CURSOR_TEXT;
1208     }
1209 
1210     m->cursor_over_position = hittextmultiline(mx - SCALE(MESSAGES_X), width - SCALE(MESSAGES_X) - get_time_width(), (my < 0 ? 0 : my),
1211                                                msg_height, font_small_lineheight, message, msg_length, 1);
1212 
1213     if (my < 0 || my >= dy || mx < SCALE(MESSAGES_X) || m->cursor_over_position == msg_length) {
1214         m->cursor_over_uri = UINT32_MAX;
1215         return 0;
1216     }
1217 
1218     bool prev_cursor_down_uri = m->cursor_down_uri;
1219 
1220     if (m->cursor_over_uri != UINT32_MAX) {
1221         m->cursor_down_uri = 0;
1222         m->cursor_over_uri = UINT32_MAX;
1223     }
1224 
1225     /* Seek back to the last word/line break */
1226     char *str = message + m->cursor_over_position;
1227     while (str != message) {
1228         str--;
1229         if (*str == ' ' || *str == '\n') {
1230             str++;
1231             break;
1232         }
1233     }
1234 
1235     /* Check if it's a URI we handle TODO: handle moar! */
1236     char *end = message + msg_length;
1237     while (str != end && *str != ' ' && *str != '\n') {
1238         if (str == message || *(str - 1) == '\n' || *(str - 1) == ' ') {
1239             if (m->cursor_over_uri == UINT32_MAX && end - str >= 7 && (strncmp(str, "http://", 7) == 0)) {
1240                 cursor             = CURSOR_HAND;
1241                 m->cursor_over_uri = str - message;
1242             } else if (m->cursor_over_uri == UINT32_MAX && end - str >= 8 && (strncmp(str, "https://", 8) == 0)) {
1243                 cursor             = CURSOR_HAND;
1244                 m->cursor_over_uri = str - message;
1245             } else if (m->cursor_over_uri == UINT32_MAX && end - str >= 4 && (strncmp(str, "tox:", 4) == 0)) {
1246                 cursor             = CURSOR_HAND;
1247                 m->cursor_over_uri = str - message;
1248             }
1249         }
1250         str++;
1251     }
1252 
1253     if (m->cursor_over_uri != UINT32_MAX) {
1254         m->urllen          = (str - message) - m->cursor_over_uri;
1255         m->cursor_down_uri = prev_cursor_down_uri;
1256         LOG_TRACE("Messages", "urllen %u" , m->urllen);
1257     }
1258 
1259     return 0;
1260 }
1261 
messages_mmove_image(MSG_IMG * image,uint32_t max_width,int mx,int my)1262 static bool messages_mmove_image(MSG_IMG *image, uint32_t max_width, int mx, int my) {
1263     if (image->w > max_width) {
1264         mx -= SCALE(MESSAGES_X);
1265         int w = image->w > max_width ? max_width : image->w;
1266         int h = (image->zoom || image->w <= max_width) ? image->h : image->h * max_width / image->w;
1267 
1268         if (mx >= 0 && my >= 0 && mx < w && my < h) {
1269             cursor = CURSOR_ZOOM_IN + image->zoom;
1270             return 1;
1271         }
1272     }
1273     return 0;
1274 }
1275 
messages_mmove_filetransfer(int mx,int my,int width)1276 static uint8_t messages_mmove_filetransfer(int mx, int my, int width) {
1277     mx -= SCALE(10); /* Why? */
1278     if (mx >= 0 && mx < width && my >= 0 && my < FILE_TRANSFER_BOX_HEIGHT) {
1279         if (mx >= width - get_time_width() - (BM_FTB_WIDTH * 2) - SCALE(2) - SCROLL_WIDTH
1280             && mx <= width - get_time_width() - SCROLL_WIDTH) {
1281             if (mx >= width - get_time_width() - BM_FTB_WIDTH - SCROLL_WIDTH) {
1282                 // mouse is over the right button (pause / accept)
1283                 return 2;
1284             } else {
1285                 // mouse is over the left button  (cancel)
1286                 return 1;
1287             }
1288         }
1289         return 3;
1290     }
1291     return 0;
1292 }
1293 
messages_mmove(PANEL * panel,int UNUSED (px),int UNUSED (py),int width,int UNUSED (height),int mx,int my,int dx,int UNUSED (dy))1294 bool messages_mmove(PANEL *panel, int UNUSED(px), int UNUSED(py), int width, int UNUSED(height), int mx, int my, int dx,
1295                     int UNUSED(dy))
1296 {
1297     MESSAGES *m = panel->object;
1298 
1299     m->cursor_over_time = inrect(mx, my, width - get_time_width(), 0, get_time_width(), m->height);
1300 
1301     if (m->cursor_down_msg < m->number) {
1302         uint32_t maxwidth = width - SCALE(MESSAGES_X) - get_time_width();
1303         MSG_HEADER *msg = m->data[m->cursor_down_msg];
1304         if ((msg->msg_type == MSG_TYPE_IMAGE) && (msg->via.img.w > maxwidth)) {
1305             msg->via.img.position -= (double)dx / (double)(msg->via.img.w - maxwidth);
1306             if (msg->via.img.position > 1.0) {
1307                 msg->via.img.position = 1.0;
1308             } else if (msg->via.img.position < 0.0) {
1309                 msg->via.img.position = 0.0;
1310             }
1311             cursor = CURSOR_ZOOM_OUT;
1312             return true;
1313         }
1314     }
1315 
1316     if (mx < 0 || my < 0 || my > m->height) {
1317         if (m->cursor_over_msg != UINT32_MAX) {
1318             m->cursor_over_msg = UINT32_MAX;
1319             return true;
1320         }
1321         return false;
1322     }
1323 
1324     setfont(FONT_TEXT);
1325 
1326     MSG_HEADER **p = m->data;
1327     uint32_t i = 0;
1328     bool     need_redraw = false;
1329 
1330     while (i < m->number) {
1331         MSG_HEADER *msg = *p++;
1332 
1333         int dy = msg->height; /* dy is the wrong name here, you should change it! */
1334 
1335         if (my >= 0 && my < dy) {
1336             m->cursor_over_msg = i;
1337 
1338             switch (msg->msg_type) {
1339                 case MSG_TYPE_NULL: {
1340                     LOG_ERR("Messages", "Invalid message type in messages_mmove.");
1341                     return false;
1342                 }
1343 
1344                 case MSG_TYPE_TEXT:
1345                 case MSG_TYPE_ACTION_TEXT:
1346                 case MSG_TYPE_NOTICE:
1347                 case MSG_TYPE_NOTICE_DAY_CHANGE: {
1348                     if (m->is_groupchat) {
1349                         messages_mmove_text(m, width, mx, my, dy, msg->via.grp.msg,
1350                                             msg->height, msg->via.grp.length);
1351                     } else {
1352                         messages_mmove_text(m, width, mx, my, dy, msg->via.txt.msg,
1353                                             msg->height, msg->via.txt.length);
1354                     }
1355                     if (m->cursor_down_msg != UINT32_MAX
1356                         && (m->cursor_down_position != m->cursor_over_position
1357                             || m->cursor_down_msg != m->cursor_over_msg))
1358                     {
1359                         m->selecting_text = 1;
1360                     }
1361                     break;
1362                 }
1363 
1364                 case MSG_TYPE_IMAGE: {
1365                     m->cursor_over_position = messages_mmove_image(&msg->via.img, (width - SCALE(MESSAGES_X) - get_time_width()), mx, my);
1366                     break;
1367                 }
1368 
1369                 case MSG_TYPE_FILE: {
1370                     m->cursor_over_position = messages_mmove_filetransfer(mx, my, width);
1371                     if (m->cursor_over_position) {
1372                         need_redraw = true;
1373                     }
1374                     break;
1375                 }
1376             }
1377 
1378             if (i != m->cursor_over_msg && m->cursor_over_msg != UINT32_MAX
1379                 && (msg->msg_type == MSG_TYPE_FILE || m->data[m->cursor_over_msg]->msg_type == MSG_TYPE_FILE)) {
1380                 need_redraw = true; // Redraw file on hover-in/out.
1381             }
1382 
1383             if (m->selecting_text) {
1384                 need_redraw = true;
1385 
1386                 if (m->cursor_down_msg != m->cursor_over_msg || m->cursor_down_position <= m->cursor_over_position) {
1387                     m->sel_start_position = m->cursor_down_position;
1388                     m->sel_end_position   = m->cursor_over_position;
1389                 } else {
1390                     m->sel_start_position = m->cursor_over_position;
1391                     m->sel_end_position   = m->cursor_down_position;
1392                 }
1393 
1394                 if (m->cursor_down_msg <= m->cursor_over_msg) {
1395                     m->sel_start_msg = m->cursor_down_msg;
1396                     m->sel_end_msg   = m->cursor_over_msg;
1397                 } else {
1398                     m->sel_start_msg      = m->cursor_over_msg;
1399                     m->sel_end_msg        = m->cursor_down_msg;
1400                     m->sel_start_position = m->cursor_over_position;
1401                     m->sel_end_position   = m->cursor_down_position;
1402                 }
1403             }
1404 
1405             return need_redraw;
1406         }
1407 
1408         my -= dy;
1409 
1410         i++;
1411     }
1412 
1413     return false;
1414 }
1415 
messages_mdown(PANEL * panel)1416 bool messages_mdown(PANEL *panel) {
1417     MESSAGES *m        = panel->object;
1418     m->cursor_down_msg = UINT32_MAX;
1419 
1420     if (m->cursor_over_msg != UINT32_MAX) {
1421         MSG_HEADER *msg = m->data[m->cursor_over_msg];
1422         switch (msg->msg_type) {
1423             case MSG_TYPE_NULL: {
1424                 LOG_ERR("Messages", "Invalid message type in messages_mdown.");
1425                 return false;
1426             }
1427 
1428             case MSG_TYPE_TEXT:
1429             case MSG_TYPE_ACTION_TEXT:
1430             case MSG_TYPE_NOTICE:
1431             case MSG_TYPE_NOTICE_DAY_CHANGE: {
1432                 if (m->cursor_over_uri != UINT32_MAX) {
1433                     m->cursor_down_uri = m->cursor_over_uri;
1434                     LOG_TRACE("Messages", "mdn dURI %u, oURI %u" , m->cursor_down_uri, m->cursor_over_uri);
1435                 }
1436 
1437                 m->sel_start_msg = m->sel_end_msg = m->cursor_down_msg = m->cursor_over_msg;
1438                 m->sel_start_position = m->sel_end_position = m->cursor_down_position = m->cursor_over_position;
1439                 break;
1440             }
1441 
1442             case MSG_TYPE_IMAGE: {
1443                 if (m->cursor_over_position) {
1444                     if (!msg->via.img.zoom) {
1445                         msg->via.img.zoom = 1;
1446                         message_updateheight(m, msg);
1447                     } else {
1448                         m->cursor_down_msg = m->cursor_over_msg;
1449                     }
1450                 }
1451                 break;
1452             }
1453 
1454             case MSG_TYPE_FILE: {
1455                 if (m->cursor_over_position == 0) {
1456                     break;
1457                 }
1458 
1459                 FRIEND *f = get_friend(m->id);
1460                 FILE_TRANSFER *ft;
1461                 uint32_t ft_number = msg->via.ft.file_number;
1462                 if (ft_number >= (1 << 16)) {
1463                     ft = &f->ft_incoming[(ft_number >> 16) - 1]; // TODO, abstraction needed
1464                 } else {
1465                     ft = &f->ft_outgoing[ft_number]; // TODO, abstraction needed
1466                 }
1467 
1468                 if (msg->via.ft.file_status == FILE_TRANSFER_STATUS_COMPLETED) {
1469                     if (m->cursor_over_position) {
1470                         if (msg->via.ft.inline_png) {
1471                             file_save_inline_image_png(msg);
1472                         } else {
1473                             openurl((char *)msg->via.ft.path);
1474                         }
1475                     }
1476 
1477                     return true;
1478                 }
1479 
1480                 if (m->cursor_over_position == 2) { // Right button, should be accept/pause/resume
1481                     if (!msg->our_msg && msg->via.ft.file_status == FILE_TRANSFER_STATUS_NONE) {
1482                         native_select_dir_ft(m->id, msg->via.ft.file_number, ft);
1483                         return true;
1484                     }
1485 
1486                     if (msg->via.ft.file_status == FILE_TRANSFER_STATUS_ACTIVE) {
1487                         postmessage_toxcore(TOX_FILE_PAUSE, m->id, msg->via.ft.file_number, ft);
1488                     } else {
1489                         postmessage_toxcore(TOX_FILE_RESUME, m->id, msg->via.ft.file_number, ft);
1490                     }
1491                 } else if (m->cursor_over_position == 1) { // Should be cancel
1492                     postmessage_toxcore(TOX_FILE_CANCEL, m->id, msg->via.ft.file_number, ft);
1493                 }
1494 
1495                 return true;
1496             }
1497         }
1498 
1499         return true;
1500     } else if (m->sel_start_msg != m->sel_end_msg || m->sel_start_position != m->sel_end_position) {
1501         m->sel_start_msg      = 0;
1502         m->sel_end_msg        = 0;
1503         m->sel_start_position = 0;
1504         m->sel_end_position   = 0;
1505 
1506         return true;
1507     }
1508 
1509     return false;
1510 }
1511 
messages_dclick(PANEL * panel,bool triclick)1512 bool messages_dclick(PANEL *panel, bool triclick) {
1513     MESSAGES *m = panel->object;
1514 
1515     if (m->cursor_over_time) {
1516         settings.use_long_time_msg = !settings.use_long_time_msg;
1517         return true;
1518     }
1519 
1520     if (m->cursor_over_msg == UINT32_MAX) {
1521         return false;
1522     }
1523 
1524     MSG_HEADER *msg = m->data[m->cursor_over_msg];
1525     switch (msg->msg_type) {
1526         case MSG_TYPE_NULL: {
1527             LOG_ERR("Messages", "Invalid message type in messages_dclick.");
1528             return false;
1529         }
1530 
1531         case MSG_TYPE_FILE:
1532         case MSG_TYPE_NOTICE:
1533         case MSG_TYPE_NOTICE_DAY_CHANGE: {
1534             return false;
1535         }
1536 
1537         case MSG_TYPE_TEXT:
1538         case MSG_TYPE_ACTION_TEXT: {
1539             m->sel_start_msg = m->sel_end_msg = m->cursor_over_msg;
1540 
1541             uint16_t i = m->cursor_over_position;
1542             while (i != 0 && msg->via.txt.msg[i - 1] != '\n'
1543                    /*  If it's a dclick, also set ' ' as boundary, else do nothing. */
1544                    && (!triclick ? (msg->via.txt.msg[i - 1] != ' ') : 1)) {
1545                 i -= utf8_unlen(msg->via.txt.msg + i);
1546             }
1547             m->sel_start_position = i;
1548             i = m->cursor_over_position;
1549             while (i != msg->via.txt.length && msg->via.txt.msg[i] != '\n'
1550                    /*  If it's a dclick, also set ' ' as boundary, else do nothing. */
1551                    && (!triclick ? (msg->via.txt.msg[i] != ' ') : 1)) {
1552                 i += utf8_len(msg->via.txt.msg + i);
1553             }
1554             m->sel_end_position = i;
1555 
1556             uint32_t diff = m->sel_end_position - m->sel_start_position;
1557             setselection(msg->via.txt.msg + m->sel_start_position, diff);
1558 
1559             return true;
1560         }
1561 
1562         case MSG_TYPE_IMAGE: {
1563             if (m->cursor_over_position) {
1564                 if (msg->via.img.zoom) {
1565                     msg->via.img.zoom = 0;
1566                     message_updateheight(m, msg);
1567                 }
1568             }
1569 
1570             return true;
1571         }
1572     }
1573 
1574     return false;
1575 }
1576 
contextmenu_messages_onselect(uint8_t i)1577 static void contextmenu_messages_onselect(uint8_t i) {
1578     copy(!!i); /* if not 0 force a 1 */
1579 }
1580 
messages_mright(PANEL * panel)1581 bool messages_mright(PANEL *panel) {
1582     const MESSAGES *m = panel->object;
1583 
1584     if (m->cursor_over_msg == UINT32_MAX) {
1585         return false;
1586     }
1587 
1588     const MSG_HEADER *msg = m->data[m->cursor_over_msg];
1589 
1590     switch (msg->msg_type) {
1591         case MSG_TYPE_NULL: {
1592             LOG_ERR("Messages", "Invalid message type in messages_mdown.");
1593             return false;
1594         }
1595 
1596         case MSG_TYPE_TEXT:
1597         case MSG_TYPE_ACTION_TEXT: {
1598             static UTOX_I18N_STR menu_copy[] = { STR_COPY, STR_COPY_WITH_NAMES };
1599             contextmenu_new(COUNTOF(menu_copy), menu_copy, contextmenu_messages_onselect);
1600             return true;
1601         }
1602 
1603         case MSG_TYPE_NOTICE:
1604         case MSG_TYPE_NOTICE_DAY_CHANGE:
1605         case MSG_TYPE_IMAGE:
1606         case MSG_TYPE_FILE: {
1607             return false;
1608         }
1609     }
1610 
1611     LOG_FATAL_ERR(EXIT_FAILURE, "Messages", "Congratulations, you've reached dead code. Please report this.");
1612 }
1613 
messages_mwheel(PANEL * UNUSED (panel),int UNUSED (height),double UNUSED (d),bool UNUSED (smooth))1614 bool messages_mwheel(PANEL *UNUSED(panel), int UNUSED(height), double UNUSED(d), bool UNUSED(smooth)) {
1615     return false;
1616 }
1617 
messages_mup(PANEL * panel)1618 bool messages_mup(PANEL *panel) {
1619     MESSAGES *m = panel->object;
1620 
1621     if (!m->data) {
1622         return false;
1623     }
1624 
1625     if (m->cursor_over_msg != UINT32_MAX) {
1626         MSG_HEADER *msg = m->data[m->cursor_over_msg];
1627         if (msg->msg_type == MSG_TYPE_TEXT) {
1628             if (m->cursor_over_uri != UINT32_MAX
1629                 && m->cursor_down_uri == m->cursor_over_uri
1630                 && m->cursor_over_position >= m->cursor_over_uri
1631                 && m->cursor_over_position <= m->cursor_over_uri + m->urllen - 1 /* - 1 Don't open on white space */
1632                 && !m->selecting_text)
1633             {
1634                 LOG_TRACE("Messages", "mup dURI %u, oURI %u" , m->cursor_down_uri, m->cursor_over_uri);
1635                 char url[m->urllen + 1];
1636                 memcpy(url, msg->via.txt.msg + m->cursor_over_uri, m->urllen * sizeof(char));
1637                 url[m->urllen] = 0;
1638                 openurl(url);
1639                 m->cursor_down_uri = 0;
1640             }
1641         }
1642     }
1643 
1644     if (m->selecting_text) {
1645         const uint32_t max_selection_size = UINT16_MAX + 1;
1646         char *sel = calloc(1, max_selection_size);
1647         if (!sel) {
1648             LOG_FATAL_ERR(EXIT_MALLOC, "Messages", "Couldn't allocate memory for selection.");
1649         }
1650         setselection(sel, messages_selection(panel, sel, max_selection_size, 0));
1651         free(sel);
1652 
1653         m->selecting_text = 0;
1654     }
1655 
1656     m->cursor_down_msg = UINT32_MAX;
1657 
1658     return false;
1659 }
1660 
messages_mleave(PANEL * UNUSED (m))1661 bool messages_mleave(PANEL *UNUSED(m)) {
1662     return false;
1663 }
1664 
messages_selection(PANEL * panel,char * buffer,uint32_t len,bool names)1665 int messages_selection(PANEL *panel, char *buffer, uint32_t len, bool names) {
1666     MESSAGES *m = panel->object;
1667 
1668     if (m->number == 0) {
1669         return 0;
1670     }
1671 
1672     uint32_t i = m->sel_start_msg, n = m->sel_end_msg + 1;
1673     MSG_HEADER **dp = &m->data[i];
1674 
1675     char *p = buffer;
1676 
1677     while (i != UINT32_MAX && i != n) {
1678         const MSG_HEADER *msg = *dp++;
1679 
1680         if (names && (i != m->sel_start_msg || m->sel_start_position == 0)) {
1681             if (m->is_groupchat) {
1682                 memcpy(p, msg->via.grp.author, msg->via.grp.author_length);
1683                 p += msg->via.grp.author_length;
1684                 len -= msg->via.grp.author_length;
1685             } else {
1686                 const FRIEND *f = get_friend(m->id);
1687 
1688                 if (!msg->our_msg) {
1689                     if (len <= f->name_length) {
1690                         break;
1691                     }
1692 
1693                     memcpy(p, f->name, f->name_length);
1694                     p += f->name_length;
1695                     len -= f->name_length;
1696                 } else {
1697                     if (len <= self.name_length) {
1698                         break;
1699                     }
1700 
1701                     memcpy(p, self.name, self.name_length);
1702                     p += self.name_length;
1703                     len -= self.name_length;
1704                 }
1705             }
1706 
1707             if (len <= 2) {
1708                 break;
1709             }
1710 
1711             strcpy2(p, ": ");
1712             p += 2;
1713             len -= 2;
1714         }
1715 
1716         switch (msg->msg_type) {
1717             case MSG_TYPE_NULL: {
1718                 LOG_ERR("Messages", "Invalid message type in messages_selection.");
1719                 return 0;
1720             }
1721 
1722             case MSG_TYPE_TEXT:
1723             case MSG_TYPE_ACTION_TEXT: {
1724                 char *data;
1725                 uint16_t length;
1726                 if (i == m->sel_start_msg) {
1727                     if (i == m->sel_end_msg) {
1728                         data   = msg->via.txt.msg + m->sel_start_position;
1729                         length = m->sel_end_position - m->sel_start_position;
1730                     } else {
1731                         data   = msg->via.txt.msg + m->sel_start_position;
1732                         length = msg->via.txt.length - m->sel_start_position;
1733                     }
1734                 } else if (i == m->sel_end_msg) {
1735                     data   = msg->via.txt.msg;
1736                     length = m->sel_end_position;
1737                 } else {
1738                     data   = msg->via.txt.msg;
1739                     length = msg->via.txt.length;
1740                 }
1741 
1742                 if (len <= length) {
1743                     *p = 0;
1744                     return p - buffer;
1745                 }
1746 
1747                 memcpy(p, data, length);
1748                 p += length;
1749                 len -= length;
1750                 break;
1751             }
1752 
1753             case MSG_TYPE_IMAGE:
1754             case MSG_TYPE_FILE:
1755             case MSG_TYPE_NOTICE:
1756             case MSG_TYPE_NOTICE_DAY_CHANGE: {
1757                 // Do nothing.
1758                 break;
1759             }
1760         }
1761 
1762         i++;
1763 
1764         if (i != n) {
1765             #ifdef __WIN32__
1766                 if (len <= 2) {
1767                     break;
1768                 }
1769                 *p++ = '\r';
1770                 *p++ = '\n';
1771                 len -= 2;
1772             #else
1773                 if (len <= 1) {
1774                     break;
1775                 }
1776                 *p++ = '\n';
1777                 len--;
1778             #endif
1779         }
1780     }
1781 
1782     return p - buffer;
1783 }
1784 
messages_updateheight(MESSAGES * m,int width)1785 void messages_updateheight(MESSAGES *m, int width) {
1786     if (!m->data || !width) {
1787         return;
1788     }
1789 
1790     setfont(FONT_TEXT);
1791 
1792     uint32_t height = 0;
1793     for (uint32_t i = 0; i < m->number; ++i) {
1794         height += message_setheight(m, (void *)m->data[i]);
1795     }
1796     m->panel.content_scroll->content_height = m->height = height;
1797 }
1798 
messages_char(uint32_t ch)1799 bool messages_char(uint32_t ch) {
1800     MESSAGES *m;
1801 
1802     if (flist_get_friend()) {
1803         m = messages_friend.object;
1804     } else if (flist_get_groupchat()) {
1805         m = messages_group.object;
1806     } else {
1807         LOG_TRACE("Messages", "Can't type to nowhere");
1808         return false;
1809     }
1810 
1811     switch (ch) {
1812         // TODO: probably need to fix this section :< m->panel.content scroll is likely to be wrong.
1813         case KEY_PAGEUP: {
1814             SCROLLABLE *scroll = m->panel.content_scroll;
1815             scroll->d -= 0.25; // TODO: Change to a full chat-screen height.
1816             if (scroll->d < 0.0) {
1817                 scroll->d = 0.0;
1818             }
1819 
1820             return true;
1821         }
1822 
1823         case KEY_PAGEDOWN: {
1824             SCROLLABLE *scroll = m->panel.content_scroll;
1825             scroll->d += 0.25; // TODO: Change to a full chat-screen height.
1826             if (scroll->d > 1.0) {
1827                 scroll->d = 1.0;
1828             }
1829 
1830             return true;
1831         }
1832 
1833         case KEY_HOME: {
1834             m->panel.content_scroll->d = 0.0;
1835             return true;
1836         }
1837 
1838         case KEY_END: {
1839             m->panel.content_scroll->d = 1.0;
1840             return true;
1841         }
1842     }
1843 
1844     return false;
1845 }
1846 
messages_init(MESSAGES * m,uint32_t friend_number)1847 void messages_init(MESSAGES *m, uint32_t friend_number) {
1848     if (m->data) {
1849         messages_clear_all(m);
1850     }
1851 
1852     pthread_mutex_lock(&messages_lock);
1853 
1854     memset(m, 0, sizeof(*m));
1855 
1856     m->id    = friend_number;
1857     m->extra = 20;
1858     m->data  = calloc(20, sizeof(void *));
1859     if (!m->data) {
1860         LOG_FATAL_ERR(EXIT_MALLOC, "Messages", "\n\n\nFATAL ERROR TRYING TO CALLOC FOR MESSAGES.\nTHIS IS A BUG, PLEASE REPORT!\n\n\n");
1861     }
1862 
1863     pthread_mutex_unlock(&messages_lock);
1864 }
1865 
message_free(MSG_HEADER * msg)1866 void message_free(MSG_HEADER *msg) {
1867     // The group messages are free()d in groups.c (group_free(GROUPCHAT *g))
1868     switch (msg->msg_type) {
1869         case MSG_TYPE_NULL: {
1870             LOG_ERR("Messages", "Invalid message type in message_free.");
1871             break;
1872         }
1873 
1874         case MSG_TYPE_IMAGE: {
1875             image_free(msg->via.img.image);
1876             break;
1877         }
1878 
1879         case MSG_TYPE_FILE: {
1880             free(msg->via.ft.name);
1881             free(msg->via.ft.path);
1882             free(msg->via.ft.data);
1883             break;
1884         }
1885 
1886         case MSG_TYPE_NOTICE_DAY_CHANGE: {
1887             free(msg->via.notice_day.msg);
1888             break;
1889         }
1890 
1891         case MSG_TYPE_TEXT: {
1892             free(msg->via.txt.msg);
1893             break;
1894         }
1895 
1896         case MSG_TYPE_ACTION_TEXT: {
1897             free(msg->via.action.msg);
1898             break;
1899         }
1900 
1901         case MSG_TYPE_NOTICE: {
1902             free(msg->via.notice.msg);
1903             break;
1904         }
1905     }
1906 
1907     free(msg);
1908 }
1909 
messages_clear_all(MESSAGES * m)1910 void messages_clear_all(MESSAGES *m) {
1911     pthread_mutex_lock(&messages_lock);
1912 
1913     for (uint32_t i = 0; i < m->number; i++) {
1914         message_free(m->data[i]);
1915     }
1916 
1917     free(m->data);
1918     m->data   = NULL;
1919     m->number = 0;
1920     m->extra  = 0;
1921     m->height = 0;
1922 
1923     m->sel_start_msg = m->sel_end_msg = m->sel_start_position = m->sel_end_position = 0;
1924 
1925     pthread_mutex_unlock(&messages_lock);
1926 }
1927