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 3 : static char kind_prefix(DialogPeerKind k) {
15 3 : switch (k) {
16 3 : case DIALOG_PEER_USER: return 'u';
17 0 : case DIALOG_PEER_CHAT: return 't'; /* "team" / group chat */
18 0 : case DIALOG_PEER_CHANNEL: return 'c';
19 0 : default: return '?';
20 : }
21 : }
22 :
23 3 : void dialog_pane_init(DialogPane *dp) {
24 3 : if (!dp) return;
25 3 : memset(dp, 0, sizeof(*dp));
26 3 : list_view_init(&dp->lv);
27 : }
28 :
29 3 : void dialog_pane_set_entries(DialogPane *dp,
30 : const DialogEntry *src, int n) {
31 3 : if (!dp) return;
32 3 : if (n < 0) n = 0;
33 3 : if (n > DIALOG_PANE_MAX) n = DIALOG_PANE_MAX;
34 3 : if (src && n > 0) {
35 3 : memcpy(dp->entries, src, (size_t)n * sizeof(DialogEntry));
36 : }
37 3 : 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 3 : dp->lv.selected = (n > 0) ? 0 : -1;
41 3 : dp->lv.scroll_top = 0;
42 3 : list_view_set_count(&dp->lv, n);
43 : }
44 :
45 3 : int dialog_pane_refresh(DialogPane *dp,
46 : const ApiConfig *cfg,
47 : MtProtoSession *s, Transport *t) {
48 3 : if (!dp || !cfg || !s || !t) return -1;
49 3 : DialogEntry tmp[DIALOG_PANE_MAX] = {0};
50 3 : int count = 0;
51 3 : if (domain_get_dialogs(cfg, s, t, DIALOG_PANE_MAX, 0, tmp, &count, NULL) != 0)
52 0 : return -1;
53 3 : dialog_pane_set_entries(dp, tmp, count);
54 3 : return 0;
55 : }
56 :
57 2 : const DialogEntry *dialog_pane_selected(const DialogPane *dp) {
58 2 : if (!dp || dp->count == 0) return NULL;
59 2 : int idx = dp->lv.selected;
60 2 : if (idx < 0 || idx >= dp->count) return NULL;
61 2 : 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 3 : static int format_row(const DialogEntry *e, char *out, size_t cap) {
70 3 : char prefix = kind_prefix(e->kind);
71 3 : const char *title = e->title[0] ? e->title : "(no title)";
72 3 : if (e->unread_count > 0) {
73 3 : return snprintf(out, cap, "%c [%d] %s",
74 3 : prefix, e->unread_count, title);
75 : }
76 0 : return snprintf(out, cap, "%c %s", prefix, title);
77 : }
78 :
79 3 : void dialog_pane_render(const DialogPane *dp,
80 : const Pane *pane, Screen *screen,
81 : int focused) {
82 3 : if (!dp || !pane || !screen || !pane_is_valid(pane)) return;
83 3 : pane_clear(pane, screen);
84 :
85 3 : if (dp->count == 0) {
86 0 : const char *msg = "(no dialogs)";
87 0 : int mid_row = pane->rows / 2;
88 0 : int col = (pane->cols - (int)strlen(msg)) / 2;
89 0 : if (col < 0) col = 0;
90 0 : pane_put_str(pane, screen, mid_row, col, msg, SCREEN_ATTR_DIM);
91 0 : return;
92 : }
93 :
94 3 : int first = dp->lv.scroll_top;
95 3 : int last = first + dp->lv.rows_visible;
96 3 : if (last > dp->count) last = dp->count;
97 :
98 6 : for (int i = first; i < last; i++) {
99 3 : const DialogEntry *e = &dp->entries[i];
100 : char line[192];
101 3 : format_row(e, line, sizeof(line));
102 3 : uint8_t attrs = 0;
103 3 : if (e->unread_count > 0) attrs |= SCREEN_ATTR_BOLD;
104 3 : if (focused && i == dp->lv.selected) {
105 2 : 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 2 : pane_fill(pane, screen, i - first, 0, pane->cols, attrs);
109 : }
110 3 : pane_put_str(pane, screen, i - first, 0, line, attrs);
111 : }
112 : }
|