Line data Source code
1 : /* SPDX-License-Identifier: GPL-3.0-or-later */
2 : /* Copyright 2026 Peter Csaszar */
3 :
4 : /**
5 : * @file tui/dialog_pane.c
6 : * @brief Dialog list view-model implementation (US-11 v2).
7 : */
8 :
9 : #include "tui/dialog_pane.h"
10 :
11 : #include <stdio.h>
12 : #include <string.h>
13 :
14 24 : static char kind_prefix(DialogPeerKind k) {
15 24 : switch (k) {
16 22 : case DIALOG_PEER_USER: return 'u';
17 1 : case DIALOG_PEER_CHAT: return 't'; /* "team" / group chat */
18 1 : case DIALOG_PEER_CHANNEL: return 'c';
19 0 : default: return '?';
20 : }
21 : }
22 :
23 35 : void dialog_pane_init(DialogPane *dp) {
24 35 : if (!dp) return;
25 35 : memset(dp, 0, sizeof(*dp));
26 35 : list_view_init(&dp->lv);
27 : }
28 :
29 27 : void dialog_pane_set_entries(DialogPane *dp,
30 : const DialogEntry *src, int n) {
31 27 : if (!dp) return;
32 27 : if (n < 0) n = 0;
33 27 : if (n > DIALOG_PANE_MAX) n = DIALOG_PANE_MAX;
34 27 : if (src && n > 0) {
35 22 : memcpy(dp->entries, src, (size_t)n * sizeof(DialogEntry));
36 : }
37 27 : dp->count = n;
38 : /* Always reset selection to the top after a refresh so the user lands
39 : * on the most-recent dialog rather than a now-invalid index. */
40 27 : dp->lv.selected = (n > 0) ? 0 : -1;
41 27 : dp->lv.scroll_top = 0;
42 27 : list_view_set_count(&dp->lv, n);
43 : }
44 :
45 10 : int dialog_pane_refresh(DialogPane *dp,
46 : const ApiConfig *cfg,
47 : MtProtoSession *s, Transport *t) {
48 10 : if (!dp || !cfg || !s || !t) return -1;
49 10 : DialogEntry tmp[DIALOG_PANE_MAX] = {0};
50 10 : int count = 0;
51 10 : if (domain_get_dialogs(cfg, s, t, DIALOG_PANE_MAX, 0, tmp, &count, NULL) != 0)
52 0 : return -1;
53 10 : dialog_pane_set_entries(dp, tmp, count);
54 10 : return 0;
55 : }
56 :
57 9 : const DialogEntry *dialog_pane_selected(const DialogPane *dp) {
58 9 : if (!dp || dp->count == 0) return NULL;
59 7 : int idx = dp->lv.selected;
60 7 : if (idx < 0 || idx >= dp->count) return NULL;
61 7 : return &dp->entries[idx];
62 : }
63 :
64 : /* Format one dialog entry as a single line. Returns bytes written
65 : * (excluding NUL). Produces strings like:
66 : * u Alice (plain DM, 0 unread)
67 : * c [12] Tech channel (channel with 12 unread)
68 : */
69 24 : static int format_row(const DialogEntry *e, char *out, size_t cap) {
70 24 : char prefix = kind_prefix(e->kind);
71 24 : const char *title = e->title[0] ? e->title : "(no title)";
72 24 : if (e->unread_count > 0) {
73 8 : return snprintf(out, cap, "%c [%d] %s",
74 8 : prefix, e->unread_count, title);
75 : }
76 16 : return snprintf(out, cap, "%c %s", prefix, title);
77 : }
78 :
79 23 : void dialog_pane_render(const DialogPane *dp,
80 : const Pane *pane, Screen *screen,
81 : int focused) {
82 23 : if (!dp || !pane || !screen || !pane_is_valid(pane)) return;
83 20 : pane_clear(pane, screen);
84 :
85 20 : if (dp->count == 0) {
86 7 : const char *msg = "(no dialogs)";
87 7 : int mid_row = pane->rows / 2;
88 7 : int col = (pane->cols - (int)strlen(msg)) / 2;
89 7 : if (col < 0) col = 0;
90 7 : pane_put_str(pane, screen, mid_row, col, msg, SCREEN_ATTR_DIM);
91 7 : return;
92 : }
93 :
94 13 : int first = dp->lv.scroll_top;
95 13 : int last = first + dp->lv.rows_visible;
96 13 : if (last > dp->count) last = dp->count;
97 :
98 37 : for (int i = first; i < last; i++) {
99 24 : const DialogEntry *e = &dp->entries[i];
100 : char line[192];
101 24 : format_row(e, line, sizeof(line));
102 24 : uint8_t attrs = 0;
103 24 : if (e->unread_count > 0) attrs |= SCREEN_ATTR_BOLD;
104 24 : if (focused && i == dp->lv.selected) {
105 8 : attrs |= SCREEN_ATTR_REVERSE;
106 : /* Paint the full row so the highlight extends to the right edge
107 : * even when the title is short. */
108 8 : pane_fill(pane, screen, i - first, 0, pane->cols, attrs);
109 : }
110 24 : pane_put_str(pane, screen, i - first, 0, line, attrs);
111 : }
112 : }
|