]> git.neil.brown.name Git - edlib.git/blob - lib-messageline.c
TODO: clean out done items.
[edlib.git] / lib-messageline.c
1 /*
2  * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * trim a line off the bottom of a pane and capture messages
6  * to go there.  They disappear on the next keystroke.
7  *
8  * Later it might be good to allow boarderless popups to appear here.
9  *
10  * The message displayed is:
11  *  a 'modal' message until a keystroke, or
12  *  a normal message which remains until it has been visible without a modal for
13  *     seven seconds with keystrokes, or 30 seconds without keystrokes, or
14  *  a 'default' message ... which is hardly used, or
15  *  a the current time
16  *
17  * Refreshed about every 15 seconds, so timestamp can be a little out of date
18  * but not much.
19  */
20
21 #include <unistd.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <time.h>
25
26 #define PANE_DATA_TYPE struct mlinfo
27 #include "core.h"
28
29 struct mlinfo {
30         char *message;
31         char *modal;    /* message displays a mode, and must
32                          * remain exactly until a keystroke
33                          */
34         struct pane *line safe, *child;
35         struct pane *log;
36         int hidden;
37         time_t last_message; /* message should stay for at least 10 seconds */
38 };
39 #include "core-pane.h"
40
41 static struct pane *do_messageline_attach(struct pane *p safe);
42 static struct map *messageline_map;
43 DEF_LOOKUP_CMD(messageline_handle, messageline_map);
44
45 DEF_CMD(messageline_clone)
46 {
47         struct pane *p = do_messageline_attach(ci->focus);
48
49         pane_clone_children(ci->home, p);
50         return 1;
51 }
52
53 DEF_CMD(messageline_border)
54 {
55         struct mlinfo *mli = ci->home->data;
56         if (ci->num > 0)
57                 mli->hidden = 0;
58         else
59                 mli->hidden = 1;
60         /* trigger a resize of children */
61         pane_damaged(ci->home, DAMAGED_SIZE);
62         return Efallthrough; /* Allow other panes to remove other borders */
63 }
64
65 DEF_CMD(messageline_msg)
66 {
67         struct mlinfo *mli = ci->home->data;
68
69         if (ci->str && (strcmp(ci->key, "Message:default") != 0 ||
70                         mli->message == NULL)) {
71                 if (!mli->message) {
72                         call("Window:request:Keystroke-notify", ci->home);
73                         call("Window:request:Mouse-event-notify", ci->home);
74                 }
75                 if (strcmp(ci->key, "Message:modal") == 0) {
76                         free(mli->modal);
77                         if (ci->str[0])
78                                 mli->modal = strdup(ci->str);
79                         else
80                                 mli->modal = NULL;
81                 } else {
82                         free(mli->message);
83                         if (ci->str[0])
84                                 mli->message = strdup(ci->str);
85                         else
86                                 mli->message = NULL;
87                         /* x==0 check ensures we only append message once when
88                          * it comes in via a broadcast notification
89                          */
90                         if (ci->x == 0 && mli->log && ci->str[0])
91                                 call("doc:log:append", mli->log,
92                                      0, NULL, ci->str);
93                 }
94                 time(&mli->last_message);
95                 pane_damaged(ci->home, DAMAGED_VIEW);
96         }
97         if (strcmp(ci->key, "Message:broadcast") == 0)
98                 return 1; /* Acknowledge message */
99         else
100                 return Efallthrough; /* allow other handlers */
101 }
102
103 DEF_CMD(messageline_abort)
104 {
105         struct mlinfo *mli = ci->home->data;
106
107         if (!mli->message) {
108                 call("Window:request:Keystroke-notify", ci->home);
109                 call("Window:request:Mouse-event-notify", ci->home);
110         }
111         free(mli->message);
112         mli->message = strdup("ABORTED");
113         free(mli->modal);
114         mli->modal = NULL;
115         time(&mli->last_message);
116         pane_damaged(ci->home, DAMAGED_VIEW);
117         return Efallthrough;
118 }
119
120 DEF_CMD(messageline_refresh_size)
121 {
122         struct mlinfo *mli = ci->home->data;
123         struct pane *p = mli->line;
124
125         if (mli->hidden) {
126                 pane_resize(p, 0, ci->home->h,
127                             ci->home->w, ci->home->h / 3);
128                 if (mli->child)
129                         pane_resize(mli->child, 0, 0,
130                                     ci->home->w, ci->home->h);
131         } else {
132                 pane_resize(p, p->x, p->y, ci->home->w, ci->home->h/3);
133                 call("render-line:measure", p, -1);
134                 pane_resize(p, p->x, ci->home->h - p->h,
135                             ci->home->w, p->h);
136                 if (mli->child && ci->home->h > p->h)
137                         pane_resize(mli->child, 0, 0,
138                                     ci->home->w,
139                                     ci->home->h - p->h);
140         }
141         pane_damaged(p, DAMAGED_REFRESH);
142         return 1;
143 }
144
145 DEF_CMD(messageline_child_notify)
146 {
147         struct mlinfo *mli = ci->home->data;
148         if (ci->focus->z)
149                 /* Ignore */
150                 return 1;
151         if (ci->num < 0) {
152                 if (ci->home->focus == ci->focus)
153                         ci->home->focus = NULL;
154                 mli->child = NULL;
155         } else {
156                 if (mli->child)
157                         pane_close(mli->child);
158                 mli->child = ci->focus;
159                 ci->home->focus = ci->focus;
160         }
161         return 1;
162 }
163
164 DEF_CMD(messageline_notify)
165 {
166         /* Keystroke notification clears the message line */
167         struct mlinfo *mli = ci->home->data;
168         int wait_time = 7;
169
170         if (edlib_testing(ci->home))
171                 wait_time = 0;
172
173         if (mli->modal) {
174                 free(mli->modal);
175                 mli->modal = NULL;
176                 if (mli->message)
177                         mli->last_message = time(NULL);
178                 pane_damaged(ci->home, DAMAGED_VIEW);
179         }
180         if (mli->message &&
181             time(NULL) >= mli->last_message + wait_time) {
182                 free(mli->message);
183                 mli->message = NULL;
184                 pane_damaged(ci->home, DAMAGED_VIEW);
185         }
186         if (!mli->message && !mli->modal) {
187                 pane_drop_notifiers(ci->home, "Keystroke-notify");
188                 pane_drop_notifiers(ci->home, "Mouse-event-notify");
189         }
190         return 1;
191 }
192
193 static void pane_str(struct pane *p safe, char *s, char *attr)
194 {
195         struct mlinfo *mli = p->parent->data;
196         char *l = strconcat(p, SOH, attr, STX, s, ETX);
197         call("render-line:set", p, -1, NULL, l);
198         /* Allow message line to use up to 1/3 of total height */
199         pane_resize(p, p->x, p->y, p->w, p->parent->h/3);
200         call("render-line:measure", p, -1);
201         if (!mli->hidden) {
202                 pane_resize(p, p->x, p->parent->h - p->h, p->w, p->h);
203                 if (mli->child) {
204                         struct pane *c = mli->child;
205                         pane_resize(c, 0, 0, c->w, p->parent->h - p->h);
206                 }
207         }
208 }
209
210 DEF_CMD(messageline_refresh)
211 {
212         struct mlinfo *mli = ci->home->data;
213
214         if (mli->message && !mli->modal &&
215             time(NULL) >= mli->last_message + 30) {
216                 free(mli->message);
217                 mli->message = NULL;
218                 pane_drop_notifiers(ci->home, "Keystroke-notify");
219                 pane_drop_notifiers(ci->home, "Mouse-event-notify");
220         }
221         if (mli->modal)
222                 pane_str(mli->line, mli->modal, "bold,fg:magenta-60,bg:white");
223         else if (mli->message)
224                 pane_str(mli->line, mli->message, "bold,fg:red,bg:cyan");
225         else {
226                 char buf[80];
227                 time_t t;
228                 struct tm *tm;
229
230                 t = time(NULL);
231                 if (edlib_testing(ci->home))
232                         t = 1581382278;
233                 tm = localtime(&t);
234                 if (tm)
235                         strftime(buf, sizeof(buf), "%H:%M %d-%b-%Y", tm);
236                 else
237                         buf[0] = 0;
238                 pane_str(mli->line, buf, "bold,fg:blue,rtab");
239         }
240         return 1;
241 }
242
243 DEF_CMD(force_refresh)
244 {
245         pane_damaged(ci->home, DAMAGED_VIEW);
246         return 1;
247 }
248
249 static struct pane *do_messageline_attach(struct pane *p safe)
250 {
251         struct mlinfo *mli;
252         struct pane *ret, *mlp;
253
254         ret = pane_register(p, 0, &messageline_handle.c);
255         if (!ret)
256                 return NULL;
257         mli = ret->data;
258         call("editor:request:Message:broadcast", ret);
259         /* z=1 to avoid clone_children affecting it */
260         mlp = call_ret(pane, "attach-renderline", ret, 1);
261         if (!mlp) {
262                 pane_close(ret);
263                 return NULL;
264         }
265         /* Support wrapping */
266         attr_set_str(&mlp->attrs, "render:wrap", "yes");
267         pane_damaged(ret, DAMAGED_VIEW);
268         mli->line = mlp;
269         pane_take_focus(ret);
270         if (!edlib_testing(p))
271                 /* This can introduce unwanted variablitiy in tests */
272                 call_comm("event:timer", ret, &force_refresh, 15000);
273
274         mli->log = call_ret(pane, "docs:byname", p, 0, NULL, "*Messages*");
275         if (!mli->log)
276                 mli->log = call_ret(pane, "log:create", ret, 0, NULL,
277                                     "*Messages*");
278
279         return ret;
280 }
281
282 DEF_CMD(messageline_attach)
283 {
284         struct pane *ret;
285
286         ret = do_messageline_attach(ci->focus);
287         if (!ret)
288                 return Efail;
289         return comm_call(ci->comm2, "callback:attach", ret);
290 }
291
292 void edlib_init(struct pane *ed safe)
293 {
294         call_comm("global-set-command", ed, &messageline_attach, 0, NULL,
295                   "attach-messageline");
296
297         if (messageline_map)
298                 return;
299         messageline_map = key_alloc();
300         key_add(messageline_map, "Clone", &messageline_clone);
301         key_add(messageline_map, "Window:border", &messageline_border);
302         key_add(messageline_map, "Message", &messageline_msg);
303         key_add(messageline_map, "Message:modal", &messageline_msg);
304         key_add(messageline_map, "Message:default", &messageline_msg);
305         key_add(messageline_map, "Message:broadcast", &messageline_msg);
306         key_add(messageline_map, "Abort", &messageline_abort);
307         key_add(messageline_map, "Refresh:size", &messageline_refresh_size);
308         key_add(messageline_map, "Child-Notify",&messageline_child_notify);
309         key_add(messageline_map, "Keystroke-notify", &messageline_notify);
310         key_add(messageline_map, "Mouse-event-notify", &messageline_notify);
311         key_add(messageline_map, "Refresh:view", &messageline_refresh);
312 }