screen-redraw.c revision 1.1.1.5.2.1 1 /* $OpenBSD$ */
2
3 /*
4 * Copyright (c) 2007 Nicholas Marriott <nicm (at) users.sourceforge.net>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <sys/types.h>
20
21 #include <string.h>
22
23 #include "tmux.h"
24
25 static int screen_redraw_cell_border1(struct window_pane *, u_int, u_int);
26 static int screen_redraw_cell_border(struct client *, u_int, u_int);
27 static int screen_redraw_check_cell(struct client *, u_int, u_int, int,
28 struct window_pane **);
29 static int screen_redraw_check_is(u_int, u_int, int, int, struct window *,
30 struct window_pane *, struct window_pane *);
31
32 static int screen_redraw_make_pane_status(struct client *, struct window *,
33 struct window_pane *);
34 static void screen_redraw_draw_pane_status(struct client *, int);
35
36 static void screen_redraw_draw_borders(struct client *, int, int, u_int);
37 static void screen_redraw_draw_panes(struct client *, u_int);
38 static void screen_redraw_draw_status(struct client *, u_int);
39 static void screen_redraw_draw_number(struct client *, struct window_pane *,
40 u_int);
41
42 #define CELL_INSIDE 0
43 #define CELL_LEFTRIGHT 1
44 #define CELL_TOPBOTTOM 2
45 #define CELL_TOPLEFT 3
46 #define CELL_TOPRIGHT 4
47 #define CELL_BOTTOMLEFT 5
48 #define CELL_BOTTOMRIGHT 6
49 #define CELL_TOPJOIN 7
50 #define CELL_BOTTOMJOIN 8
51 #define CELL_LEFTJOIN 9
52 #define CELL_RIGHTJOIN 10
53 #define CELL_JOIN 11
54 #define CELL_OUTSIDE 12
55
56 #define CELL_BORDERS " xqlkmjwvtun~"
57
58 #define CELL_STATUS_OFF 0
59 #define CELL_STATUS_TOP 1
60 #define CELL_STATUS_BOTTOM 2
61
62 /* Check if cell is on the border of a particular pane. */
63 static int
64 screen_redraw_cell_border1(struct window_pane *wp, u_int px, u_int py)
65 {
66 /* Inside pane. */
67 if (px >= wp->xoff && px < wp->xoff + wp->sx &&
68 py >= wp->yoff && py < wp->yoff + wp->sy)
69 return (0);
70
71 /* Left/right borders. */
72 if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= wp->yoff + wp->sy) {
73 if (wp->xoff != 0 && px == wp->xoff - 1)
74 return (1);
75 if (px == wp->xoff + wp->sx)
76 return (2);
77 }
78
79 /* Top/bottom borders. */
80 if ((wp->xoff == 0 || px >= wp->xoff - 1) && px <= wp->xoff + wp->sx) {
81 if (wp->yoff != 0 && py == wp->yoff - 1)
82 return (3);
83 if (py == wp->yoff + wp->sy)
84 return (4);
85 }
86
87 /* Outside pane. */
88 return (-1);
89 }
90
91 /* Check if a cell is on the pane border. */
92 static int
93 screen_redraw_cell_border(struct client *c, u_int px, u_int py)
94 {
95 struct window *w = c->session->curw->window;
96 struct window_pane *wp;
97 int retval;
98
99 /* Check all the panes. */
100 TAILQ_FOREACH(wp, &w->panes, entry) {
101 if (!window_pane_visible(wp))
102 continue;
103 if ((retval = screen_redraw_cell_border1(wp, px, py)) != -1)
104 return (!!retval);
105 }
106
107 return (0);
108 }
109
110 /* Check if cell inside a pane. */
111 static int
112 screen_redraw_check_cell(struct client *c, u_int px, u_int py, int pane_status,
113 struct window_pane **wpp)
114 {
115 struct window *w = c->session->curw->window;
116 struct window_pane *wp;
117 int borders;
118 u_int right, line;
119
120 *wpp = NULL;
121
122 if (px > w->sx || py > w->sy)
123 return (CELL_OUTSIDE);
124
125 if (pane_status != CELL_STATUS_OFF) {
126 TAILQ_FOREACH(wp, &w->panes, entry) {
127 if (!window_pane_visible(wp))
128 continue;
129
130 if (pane_status == CELL_STATUS_TOP)
131 line = wp->yoff - 1;
132 else
133 line = wp->yoff + wp->sy;
134 right = wp->xoff + 2 + wp->status_size - 1;
135
136 if (py == line && px >= wp->xoff + 2 && px <= right)
137 return (CELL_INSIDE);
138 }
139 }
140
141 TAILQ_FOREACH(wp, &w->panes, entry) {
142 if (!window_pane_visible(wp))
143 continue;
144 *wpp = wp;
145
146 /* If outside the pane and its border, skip it. */
147 if ((wp->xoff != 0 && px < wp->xoff - 1) ||
148 px > wp->xoff + wp->sx ||
149 (wp->yoff != 0 && py < wp->yoff - 1) ||
150 py > wp->yoff + wp->sy)
151 continue;
152
153 /* If definitely inside, return so. */
154 if (!screen_redraw_cell_border(c, px, py))
155 return (CELL_INSIDE);
156
157 /*
158 * Construct a bitmask of whether the cells to the left (bit
159 * 4), right, top, and bottom (bit 1) of this cell are borders.
160 */
161 borders = 0;
162 if (px == 0 || screen_redraw_cell_border(c, px - 1, py))
163 borders |= 8;
164 if (px <= w->sx && screen_redraw_cell_border(c, px + 1, py))
165 borders |= 4;
166 if (pane_status == CELL_STATUS_TOP) {
167 if (py != 0 && screen_redraw_cell_border(c, px, py - 1))
168 borders |= 2;
169 } else {
170 if (py == 0 || screen_redraw_cell_border(c, px, py - 1))
171 borders |= 2;
172 }
173 if (py <= w->sy && screen_redraw_cell_border(c, px, py + 1))
174 borders |= 1;
175
176 /*
177 * Figure out what kind of border this cell is. Only one bit
178 * set doesn't make sense (can't have a border cell with no
179 * others connected).
180 */
181 switch (borders) {
182 case 15: /* 1111, left right top bottom */
183 return (CELL_JOIN);
184 case 14: /* 1110, left right top */
185 return (CELL_BOTTOMJOIN);
186 case 13: /* 1101, left right bottom */
187 return (CELL_TOPJOIN);
188 case 12: /* 1100, left right */
189 return (CELL_TOPBOTTOM);
190 case 11: /* 1011, left top bottom */
191 return (CELL_RIGHTJOIN);
192 case 10: /* 1010, left top */
193 return (CELL_BOTTOMRIGHT);
194 case 9: /* 1001, left bottom */
195 return (CELL_TOPRIGHT);
196 case 7: /* 0111, right top bottom */
197 return (CELL_LEFTJOIN);
198 case 6: /* 0110, right top */
199 return (CELL_BOTTOMLEFT);
200 case 5: /* 0101, right bottom */
201 return (CELL_TOPLEFT);
202 case 3: /* 0011, top bottom */
203 return (CELL_LEFTRIGHT);
204 }
205 }
206
207 return (CELL_OUTSIDE);
208 }
209
210 /* Check if the border of a particular pane. */
211 static int
212 screen_redraw_check_is(u_int px, u_int py, int type, int pane_status,
213 struct window *w, struct window_pane *wantwp, struct window_pane *wp)
214 {
215 int border;
216
217 /* Is this off the active pane border? */
218 border = screen_redraw_cell_border1(wantwp, px, py);
219 if (border == 0 || border == -1)
220 return (0);
221 if (pane_status == CELL_STATUS_TOP && border == 4)
222 return (0);
223 if (pane_status == CELL_STATUS_BOTTOM && border == 3)
224 return (0);
225
226 /* If there are more than two panes, that's enough. */
227 if (window_count_panes(w) != 2)
228 return (1);
229
230 /* Else if the cell is not a border cell, forget it. */
231 if (wp == NULL || (type == CELL_OUTSIDE || type == CELL_INSIDE))
232 return (1);
233
234 /* With status lines mark the entire line. */
235 if (pane_status != CELL_STATUS_OFF)
236 return (1);
237
238 /* Check if the pane covers the whole width. */
239 if (wp->xoff == 0 && wp->sx == w->sx) {
240 /* This can either be the top pane or the bottom pane. */
241 if (wp->yoff == 0) { /* top pane */
242 if (wp == wantwp)
243 return (px <= wp->sx / 2);
244 return (px > wp->sx / 2);
245 }
246 return (0);
247 }
248
249 /* Check if the pane covers the whole height. */
250 if (wp->yoff == 0 && wp->sy == w->sy) {
251 /* This can either be the left pane or the right pane. */
252 if (wp->xoff == 0) { /* left pane */
253 if (wp == wantwp)
254 return (py <= wp->sy / 2);
255 return (py > wp->sy / 2);
256 }
257 return (0);
258 }
259
260 return (1);
261 }
262
263 /* Update pane status. */
264 static int
265 screen_redraw_make_pane_status(struct client *c, struct window *w,
266 struct window_pane *wp)
267 {
268 struct grid_cell gc;
269 const char *fmt;
270 struct format_tree *ft;
271 char *out;
272 size_t outlen;
273 struct screen_write_ctx ctx;
274 struct screen old;
275
276 if (wp == w->active)
277 style_apply(&gc, w->options, "pane-active-border-style");
278 else
279 style_apply(&gc, w->options, "pane-border-style");
280
281 fmt = options_get_string(w->options, "pane-border-format");
282
283 ft = format_create(NULL, FORMAT_PANE|wp->id, 0);
284 format_defaults(ft, c, NULL, NULL, wp);
285
286 memcpy(&old, &wp->status_screen, sizeof old);
287 screen_init(&wp->status_screen, wp->sx, 1, 0);
288 wp->status_screen.mode = 0;
289
290 out = format_expand(ft, fmt);
291 outlen = screen_write_cstrlen("%s", out);
292 if (outlen > wp->sx - 4)
293 outlen = wp->sx - 4;
294 screen_resize(&wp->status_screen, outlen, 1, 0);
295
296 screen_write_start(&ctx, NULL, &wp->status_screen);
297 screen_write_cursormove(&ctx, 0, 0);
298 screen_write_clearline(&ctx, 8);
299 screen_write_cnputs(&ctx, outlen, &gc, "%s", out);
300 screen_write_stop(&ctx);
301
302 format_free(ft);
303
304 wp->status_size = outlen;
305
306 if (grid_compare(wp->status_screen.grid, old.grid) == 0) {
307 screen_free(&old);
308 return (0);
309 }
310 screen_free(&old);
311 return (1);
312 }
313
314 /* Draw pane status. */
315 static void
316 screen_redraw_draw_pane_status(struct client *c, int pane_status)
317 {
318 struct window *w = c->session->curw->window;
319 struct options *oo = c->session->options;
320 struct tty *tty = &c->tty;
321 struct window_pane *wp;
322 int spos;
323 u_int yoff;
324
325 spos = options_get_number(oo, "status-position");
326 TAILQ_FOREACH(wp, &w->panes, entry) {
327 if (!window_pane_visible(wp))
328 continue;
329 if (pane_status == CELL_STATUS_TOP)
330 yoff = wp->yoff - 1;
331 else
332 yoff = wp->yoff + wp->sy;
333 if (spos == 0)
334 yoff += 1;
335
336 tty_draw_line(tty, NULL, &wp->status_screen, 0, wp->xoff + 2,
337 yoff);
338 }
339 tty_cursor(tty, 0, 0);
340 }
341
342 /* Update status line and change flags if unchanged. */
343 void
344 screen_redraw_update(struct client *c)
345 {
346 struct window *w = c->session->curw->window;
347 struct window_pane *wp;
348 struct options *wo = w->options;
349 int redraw;
350
351 if (c->message_string != NULL)
352 redraw = status_message_redraw(c);
353 else if (c->prompt_string != NULL)
354 redraw = status_prompt_redraw(c);
355 else
356 redraw = status_redraw(c);
357 if (!redraw)
358 c->flags &= ~CLIENT_STATUS;
359
360 if (options_get_number(wo, "pane-border-status") != CELL_STATUS_OFF) {
361 redraw = 0;
362 TAILQ_FOREACH(wp, &w->panes, entry) {
363 if (screen_redraw_make_pane_status(c, w, wp))
364 redraw = 1;
365 }
366 if (redraw)
367 c->flags |= CLIENT_BORDERS;
368 }
369 }
370
371 /* Redraw entire screen. */
372 void
373 screen_redraw_screen(struct client *c, int draw_panes, int draw_status,
374 int draw_borders)
375 {
376 struct options *oo = c->session->options;
377 struct tty *tty = &c->tty;
378 struct window *w = c->session->curw->window;
379 struct options *wo = w->options;
380 u_int top;
381 int status, pane_status, spos;
382
383 /* Suspended clients should not be updated. */
384 if (c->flags & CLIENT_SUSPENDED)
385 return;
386
387 /* Get status line, er, status. */
388 spos = options_get_number(oo, "status-position");
389 if (c->message_string != NULL || c->prompt_string != NULL)
390 status = 1;
391 else
392 status = options_get_number(oo, "status");
393 top = 0;
394 if (status && spos == 0)
395 top = 1;
396 if (!status)
397 draw_status = 0;
398
399 /* Draw the elements. */
400 if (draw_borders) {
401 pane_status = options_get_number(wo, "pane-border-status");
402 screen_redraw_draw_borders(c, status, pane_status, top);
403 if (pane_status != CELL_STATUS_OFF)
404 screen_redraw_draw_pane_status(c, pane_status);
405 }
406 if (draw_panes)
407 screen_redraw_draw_panes(c, top);
408 if (draw_status)
409 screen_redraw_draw_status(c, top);
410 tty_reset(tty);
411 }
412
413 /* Draw a single pane. */
414 void
415 screen_redraw_pane(struct client *c, struct window_pane *wp)
416 {
417 u_int i, yoff;
418
419 if (!window_pane_visible(wp))
420 return;
421
422 yoff = wp->yoff;
423 if (status_at_line(c) == 0)
424 yoff++;
425
426 log_debug("%s: redraw pane %%%u (at %u,%u)", c->name, wp->id,
427 wp->xoff, yoff);
428
429 for (i = 0; i < wp->sy; i++)
430 tty_draw_pane(&c->tty, wp, i, wp->xoff, yoff);
431 tty_reset(&c->tty);
432 }
433
434 /* Draw the borders. */
435 static void
436 screen_redraw_draw_borders(struct client *c, int status, int pane_status,
437 u_int top)
438 {
439 struct session *s = c->session;
440 struct window *w = s->curw->window;
441 struct options *oo = &w->options;
442 struct tty *tty = &c->tty;
443 struct window_pane *wp;
444 struct grid_cell m_active_gc, active_gc, m_other_gc, other_gc;
445 struct grid_cell msg_gc;
446 u_int i, j, type, msgx = 0, msgy = 0;
447 int active, small, flags;
448 char msg[256];
449 const char *tmp;
450 size_t msglen = 0;
451
452 small = (tty->sy - status + top > w->sy) || (tty->sx > w->sx);
453 if (small) {
454 flags = w->flags & (WINDOW_FORCEWIDTH|WINDOW_FORCEHEIGHT);
455 if (flags == (WINDOW_FORCEWIDTH|WINDOW_FORCEHEIGHT))
456 tmp = "force-width, force-height";
457 else if (flags == WINDOW_FORCEWIDTH)
458 tmp = "force-width";
459 else if (flags == WINDOW_FORCEHEIGHT)
460 tmp = "force-height";
461 else
462 tmp = "a smaller client";
463 xsnprintf(msg, sizeof msg, "(size %ux%u from %s)",
464 w->sx, w->sy, tmp);
465 msglen = strlen(msg);
466
467 if (tty->sy - 1 - status + top > w->sy && tty->sx >= msglen) {
468 msgx = tty->sx - msglen;
469 msgy = tty->sy - 1 - status + top;
470 } else if (tty->sx - w->sx > msglen) {
471 msgx = tty->sx - msglen;
472 msgy = tty->sy - 1 - status + top;
473 } else
474 small = 0;
475 }
476
477 style_apply(&other_gc, oo, "pane-border-style");
478 style_apply(&active_gc, oo, "pane-active-border-style");
479 active_gc.attr = other_gc.attr = GRID_ATTR_CHARSET;
480
481 memcpy(&m_other_gc, &other_gc, sizeof m_other_gc);
482 m_other_gc.attr ^= GRID_ATTR_REVERSE;
483 memcpy(&m_active_gc, &active_gc, sizeof m_active_gc);
484 m_active_gc.attr ^= GRID_ATTR_REVERSE;
485
486 for (j = 0; j < tty->sy - status; j++) {
487 for (i = 0; i < tty->sx; i++) {
488 type = screen_redraw_check_cell(c, i, j, pane_status,
489 &wp);
490 if (type == CELL_INSIDE)
491 continue;
492 if (type == CELL_OUTSIDE && small &&
493 i > msgx && j == msgy)
494 continue;
495 active = screen_redraw_check_is(i, j, type, pane_status,
496 w, w->active, wp);
497 if (server_is_marked(s, s->curw, marked_pane.wp) &&
498 screen_redraw_check_is(i, j, type, pane_status, w,
499 marked_pane.wp, wp)) {
500 if (active)
501 tty_attributes(tty, &m_active_gc, NULL);
502 else
503 tty_attributes(tty, &m_other_gc, NULL);
504 } else if (active)
505 tty_attributes(tty, &active_gc, NULL);
506 else
507 tty_attributes(tty, &other_gc, NULL);
508 tty_cursor(tty, i, top + j);
509 tty_putc(tty, CELL_BORDERS[type]);
510 }
511 }
512
513 if (small) {
514 memcpy(&msg_gc, &grid_default_cell, sizeof msg_gc);
515 tty_attributes(tty, &msg_gc, NULL);
516 tty_cursor(tty, msgx, msgy);
517 tty_puts(tty, msg);
518 }
519 }
520
521 /* Draw the panes. */
522 static void
523 screen_redraw_draw_panes(struct client *c, u_int top)
524 {
525 struct window *w = c->session->curw->window;
526 struct tty *tty = &c->tty;
527 struct window_pane *wp;
528 u_int i;
529
530 TAILQ_FOREACH(wp, &w->panes, entry) {
531 if (!window_pane_visible(wp))
532 continue;
533 for (i = 0; i < wp->sy; i++)
534 tty_draw_pane(tty, wp, i, wp->xoff, top + wp->yoff);
535 if (c->flags & CLIENT_IDENTIFY)
536 screen_redraw_draw_number(c, wp, top);
537 }
538 }
539
540 /* Draw the status line. */
541 static void
542 screen_redraw_draw_status(struct client *c, u_int top)
543 {
544 struct tty *tty = &c->tty;
545
546 if (top)
547 tty_draw_line(tty, NULL, &c->status, 0, 0, 0);
548 else
549 tty_draw_line(tty, NULL, &c->status, 0, 0, tty->sy - 1);
550 }
551
552 /* Draw number on a pane. */
553 static void
554 screen_redraw_draw_number(struct client *c, struct window_pane *wp, u_int top)
555 {
556 struct tty *tty = &c->tty;
557 struct session *s = c->session;
558 struct options *oo = &s->options;
559 struct window *w = wp->window;
560 struct grid_cell gc;
561 u_int idx, px, py, i, j, xoff, yoff;
562 int colour, active_colour;
563 char buf[16], *ptr;
564 size_t len;
565
566 if (window_pane_index(wp, &idx) != 0)
567 fatalx("index not found");
568 len = xsnprintf(buf, sizeof buf, "%u", idx);
569
570 if (wp->sx < len)
571 return;
572 colour = options_get_number(oo, "display-panes-colour");
573 active_colour = options_get_number(oo, "display-panes-active-colour");
574
575 px = wp->sx / 2; py = wp->sy / 2;
576 xoff = wp->xoff; yoff = wp->yoff;
577
578 if (top)
579 yoff++;
580
581 if (wp->sx < len * 6 || wp->sy < 5) {
582 tty_cursor(tty, xoff + px - len / 2, yoff + py);
583 goto draw_text;
584 }
585
586 px -= len * 3;
587 py -= 2;
588
589 memcpy(&gc, &grid_default_cell, sizeof gc);
590 if (w->active == wp)
591 gc.bg = active_colour;
592 else
593 gc.bg = colour;
594 gc.flags |= GRID_FLAG_NOPALETTE;
595
596 tty_attributes(tty, &gc, wp);
597 for (ptr = buf; *ptr != '\0'; ptr++) {
598 if (*ptr < '0' || *ptr > '9')
599 continue;
600 idx = *ptr - '0';
601
602 for (j = 0; j < 5; j++) {
603 for (i = px; i < px + 5; i++) {
604 tty_cursor(tty, xoff + i, yoff + py + j);
605 if (window_clock_table[idx][j][i - px])
606 tty_putc(tty, ' ');
607 }
608 }
609 px += 6;
610 }
611
612 len = xsnprintf(buf, sizeof buf, "%ux%u", wp->sx, wp->sy);
613 if (wp->sx < len || wp->sy < 6)
614 return;
615 tty_cursor(tty, xoff + wp->sx - len, yoff);
616
617 draw_text:
618 memcpy(&gc, &grid_default_cell, sizeof gc);
619 if (w->active == wp)
620 gc.fg = active_colour;
621 else
622 gc.fg = colour;
623 gc.flags |= GRID_FLAG_NOPALETTE;
624
625 tty_attributes(tty, &gc, wp);
626 tty_puts(tty, buf);
627
628 tty_cursor(tty, 0, 0);
629 }
630