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