xref: /netbsd-src/lib/libcurses/slk.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: slk.c,v 1.2 2017/01/30 17:15:52 roy Exp $	*/
2 
3 /*-
4  * Copyright (c) 2017 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Roy Marples.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #ifndef lint
34 __RCSID("$NetBSD: slk.c,v 1.2 2017/01/30 17:15:52 roy Exp $");
35 #endif				/* not lint */
36 
37 #include <ctype.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #ifdef HAVE_WCHAR
41 #include <wctype.h>
42 #endif
43 
44 #include "curses.h"
45 #include "curses_private.h"
46 
47 /* Terminals with real soft labels have NOT been tested.
48  * If you have such a device, please let us know so this comment
49  * can be adjusted. */
50 
51 /* POSIX says that each label can be up to 8 columns.
52  * However, our implementation can allow labels to expand beyond that. */
53 //#define	SLK_SIZE_DYNAMIC
54 #ifdef SLK_SIZE_DYNAMIC
55 #define	SLK_SIZE	MAX_SLK_LABEL
56 #else
57 #define	SLK_SIZE	MAX_SLK_COLS
58 #endif
59 
60 static int	 slk_fmt;	/* fmt of slk_init */
61 
62 /* Safe variants of public functions. */
63 static int	 __slk_attron(SCREEN *, const chtype);
64 static int	 __slk_attr_on(SCREEN *, const attr_t, void *);
65 static int	 __slk_attroff(SCREEN *, const chtype);
66 static int	 __slk_attr_off(SCREEN *, const attr_t, void *);
67 static int	 __slk_attrset(SCREEN *, const chtype);
68 static int	 __slk_attr_set(SCREEN *, const attr_t, short, void *opt);
69 static int	 __slk_color(SCREEN *, short);
70 static int	 __slk_clear(SCREEN *);
71 static char	*__slk_label(SCREEN *, int);
72 static int	 __slk_restore(SCREEN *);
73 static int	 __slk_set(SCREEN *, int, const char *, int);
74 static int	 __slk_touch(SCREEN *);
75 static int	 __slk_wset(SCREEN *, int, const wchar_t *, int);
76 
77 /* Internal engine parts. */
78 static int	 __slk_ripoffline(WINDOW *, int);
79 static int	 __slk_set_finalise(SCREEN *, int);
80 static int	 __slk_draw(SCREEN *, int);
81 static int	 __slk_redraw(SCREEN *);
82 
83 /*
84  * slk_init --
85  *	Init Soft Label Keys.
86  */
87 int
88 slk_init(int fmt)
89 {
90 
91 	switch(fmt) {
92 	case SLK_FMT_3_2_3:
93 	case SLK_FMT_4_4:
94 		break;
95 	default:
96 		return ERR;
97 	}
98 
99 	slk_fmt = fmt;
100 	/* Even if the terminal supports soft label keys directly,
101 	 * we need to reserve a line. */
102 	return ripoffline(-1, __slk_ripoffline);
103 }
104 
105 /*
106  * slk_attron --
107  *	Test and set attributes on ripped off slk window.
108  */
109 int
110 slk_attron(const chtype attr)
111 {
112 
113 	return __slk_attron(_cursesi_screen, attr);
114 }
115 
116 /*
117  * slk_attr_on --
118  *	Test and set wide attributes on ripped off slk window.
119  */
120 int
121 slk_attr_on(const attr_t attr, void *opt)
122 {
123 
124 	return __slk_attr_on(_cursesi_screen, attr, opt);
125 }
126 
127 /*
128  * slk_attroff --
129  *	Test and unset attributes on ripped off slk window.
130  */
131 int
132 slk_attroff(const chtype attr)
133 {
134 
135 	return __slk_attroff(_cursesi_screen, attr);
136 }
137 
138 /*
139  * slk_attr_off --
140  *	Test and unset wide attributes on ripped off slk window.
141  */
142 int
143 slk_attr_off(const attr_t attr, void *opt)
144 {
145 
146 	return __slk_attr_off(_cursesi_screen, attr, opt);
147 }
148 
149 /*
150  * slk_attrset --
151  *	Set attributes and color pair on ripped off slk window.
152  */
153 int
154 slk_attrset(const chtype attr)
155 {
156 
157 	return __slk_attrset(_cursesi_screen, attr);
158 }
159 
160 /*
161  * slk_attr_set --
162  *	Set wide attributes and color pair on ripped off slk window.
163  */
164 int
165 slk_attr_set(const attr_t attr, short pair, void *opt)
166 {
167 
168 	return __slk_attr_set(_cursesi_screen, attr, pair, opt);
169 }
170 
171 /*
172  * slk_clear --
173  *	Clear slk from the current screen.
174  */
175 int
176 slk_clear(void)
177 {
178 
179 	return __slk_clear(_cursesi_screen);
180 }
181 
182 /*
183  * slk_color --
184  *	Set color pair on ripped off slk window.
185  */
186 int
187 slk_color(short pair)
188 {
189 
190 	return __slk_color(_cursesi_screen, pair);
191 }
192 
193 /*
194  * slk_label --
195  *	Return a pointer to the saved label for key labnum.
196  */
197 char *
198 slk_label(int labnum)
199 {
200 
201 	return __slk_label(_cursesi_screen, labnum);
202 }
203 
204 /*
205  * slk_wnoutrefresh --
206  *	Add the contents of the ripped off slk window to the virtual window.
207  */
208 int
209 slk_noutrefresh(void)
210 {
211 
212 	return __slk_noutrefresh(_cursesi_screen);
213 }
214 
215 /*
216  * slk_refresh --
217  *	Force a refresh for the ripped off slk window.
218  */
219 int
220 slk_refresh(void)
221 {
222 
223 	if (slk_noutrefresh() == ERR)
224 		return ERR;
225 	return doupdate();
226 }
227 
228 /*
229  * slk_restore --
230  *	Retore slk to the screen after a slk_clear.
231  */
232 int
233 slk_restore(void)
234 {
235 
236 	return __slk_restore(_cursesi_screen);
237 }
238 
239 /*
240  * slk_set --
241  *	Sets the text of the label specified by labnum
242  *	and how it is displayed.
243  */
244 int
245 slk_set(int labnum, const char *label, int justify)
246 {
247 
248 	return __slk_set(_cursesi_screen, labnum, label, justify);
249 }
250 
251 /*
252  * slk_touch --
253  *	Sets the ripped off slk window as modified.
254  */
255 int
256 slk_touch(void)
257 {
258 
259 	return __slk_touch(_cursesi_screen);
260 }
261 
262 /*
263  * slk_wset --
264  *	Sets the wide text of the label specified by labnum
265  *	and how it is displayed.
266  */
267 int
268 slk_wset(int labnum, const wchar_t *label, int justify)
269 {
270 
271 	return __slk_wset(_cursesi_screen, labnum, label, justify);
272 }
273 
274 /*
275  * __slk_attron --
276  *	Test and set attributes on ripped off slk window.
277  */
278 static int
279 __slk_attron(SCREEN *screen, const chtype attr)
280 {
281 
282 	if (screen == NULL || screen->slk_window == NULL)
283 		return ERR;
284 	return wattron(screen->slk_window, attr);
285 }
286 
287 /*
288  * __slk_attr_on --
289  *	Test and set wide attributes on ripped off slk window.
290  */
291 static int
292 __slk_attr_on(SCREEN *screen, const attr_t attr, void *opt)
293 {
294 
295 	if (screen == NULL || screen->slk_window == NULL)
296 		return ERR;
297 	return wattr_on(screen->slk_window, attr, opt);
298 }
299 
300 /*
301  * __slk_attroff --
302  *	Test and unset attributes on ripped off slk window.
303  */
304 static int
305 __slk_attroff(SCREEN *screen, const chtype attr)
306 {
307 
308 	if (screen == NULL || screen->slk_window == NULL)
309 		return ERR;
310 	return wattroff(screen->slk_window, attr);
311 }
312 
313 /*
314  * __slk_attr_off --
315  *	Test and unset wide attributes on ripped off slk window.
316  */
317 static int
318 __slk_attr_off(SCREEN *screen, const attr_t attr, void *opt)
319 {
320 
321 	if (screen == NULL || screen->slk_window == NULL)
322 		return ERR;
323 	return wattr_off(screen->slk_window, attr, opt);
324 }
325 
326 /*
327  * __slk_attrset --
328  *	Set attributes and color pair on ripped off slk window.
329  */
330 static int
331 __slk_attrset(SCREEN *screen, const chtype attr)
332 {
333 
334 	if (screen == NULL || screen->slk_window == NULL)
335 		return ERR;
336 	return wattrset(screen->slk_window, attr);
337 }
338 
339 /*
340  * __slk_attr_set --
341  *	Set wide attributes and color pair on ripped off slk window.
342  */
343 static int
344 __slk_attr_set(SCREEN *screen, const attr_t attr, short pair, void *opt)
345 {
346 
347 	if (screen == NULL || screen->slk_window == NULL)
348 		return ERR;
349 	return wattr_set(screen->slk_window, attr, pair, opt);
350 }
351 
352 /*
353  * __slk_clear --
354  *	Clear slk from the current screen.
355  */
356 static int
357 __slk_clear(SCREEN *screen)
358 {
359 
360 	if (screen == NULL)
361 		return ERR;
362 	screen->slk_hidden = true;
363 	if (screen->is_term_slk) {
364 		if (t_label_off(screen->term) == NULL)
365 			return ERR;
366 		return ti_putp(screen->term,
367 		    ti_tiparm(screen->term, t_label_off(screen->term)));
368 	}
369 	if (screen->slk_window == NULL)
370 		return ERR;
371 	werase(screen->slk_window);
372 	return wrefresh(screen->slk_window);
373 }
374 
375 /*
376  * __slk_color --
377  *	Set color pair on ripped off slk window.
378  */
379 static int
380 __slk_color(SCREEN *screen, short pair)
381 {
382 
383 	if (screen == NULL || screen->slk_window == NULL)
384 		return ERR;
385 	return wcolor_set(screen->slk_window, pair, NULL);
386 }
387 
388 
389 /*
390  * __slk_label --
391  *	Return a pointer to the saved label for key labnum.
392  */
393 static char *
394 __slk_label(SCREEN *screen, int labnum)
395 {
396 
397 	if (screen == NULL || labnum < 1 || labnum > screen->slk_nlabels)
398 		return NULL;
399 	return screen->slk_labels[--labnum].text;
400 }
401 
402 /*
403  * __slk_wnoutrefresh --
404  *	Add the contents of the ripped off slk window to the virtual window.
405  */
406 int
407 __slk_noutrefresh(SCREEN *screen)
408 {
409 
410 	if (screen == NULL || screen->slk_window == NULL)
411 		return ERR;
412 	return wnoutrefresh(screen->slk_window);
413 }
414 
415 /*
416  * __slk_restore --
417  *	Retore slk to the screen after a slk_clear.
418  */
419 static int
420 __slk_restore(SCREEN *screen)
421 {
422 
423 	if (screen == NULL)
424 		return ERR;
425 	screen->slk_hidden = false;
426 	if (screen->is_term_slk) {
427 		if (t_label_on(screen->term) == NULL)
428 			return ERR;
429 		return ti_putp(screen->term,
430 		    ti_tiparm(screen->term, t_label_on(screen->term)));
431 	}
432 	if (screen->slk_window == NULL)
433 		return ERR;
434 	if (__slk_redraw(screen) == ERR)
435 		return ERR;
436 	return wrefresh(screen->slk_window);
437 }
438 
439 /*
440  * __slk_set --
441  *	Sets the text of the label specified by labnum
442  *	and how it is displayed.
443  */
444 static int
445 __slk_set(SCREEN *screen, int labnum, const char *label, int justify)
446 {
447 	struct __slk_label *l;
448 	const char *end;
449 	size_t len;
450 	char *text;
451 #ifdef HAVE_WCHAR
452 	wchar_t wc;
453 	size_t wc_len;
454 #endif
455 
456 	/* Check args. */
457 	if (screen == NULL || labnum < 1 || labnum > screen->slk_nlabels)
458 		return ERR;
459 	switch(justify) {
460 	case SLK_JUSTIFY_LEFT:
461 	case SLK_JUSTIFY_CENTER:
462 	case SLK_JUSTIFY_RIGHT:
463 		break;
464 	default:
465 		return ERR;
466 	}
467 	if (label == NULL)
468 		label = "";
469 
470 	/* Skip leading whitespace. */
471 	while(isspace((unsigned char)*label))
472 		label++;
473 	/* Grab end. */
474 	end = label;
475 
476 #ifdef HAVE_WCHAR
477 	len = 0;
478 	while (*end != '\0') {
479 		if ((wc_len = mbrtowc(0, end, strlen(end), &screen->sp)) == -1)
480 			return ERR;
481 		mbrtowc(&wc, end, wc_len, &screen->sp);
482 		if (!iswprint((wint_t)wc))
483 			break;
484 		len += wcwidth(wc);
485 		end += wc_len;
486 	}
487 #else
488 	while(isprint((unsigned char)*end))
489 		end++;
490 	len = end - label;
491 #endif
492 
493 	/* Take a backup, in-case we can grow the label. */
494 	if ((text = strndup(label, len)) == NULL)
495 		return ERR;
496 
497 	/* All checks out, assign. */
498 	l = &screen->slk_labels[--labnum]; /* internal zero based index */
499 	l->text = text;
500 	l->justify = justify;
501 
502 	__slk_set_finalise(screen, labnum);
503 	return OK;
504 }
505 
506 /*
507  * __slk_touch --
508  *	Sets the ripped off slk window as modified.
509  */
510 static int
511 __slk_touch(SCREEN *screen)
512 {
513 
514 	if (screen == NULL || screen->slk_window == NULL)
515 		return ERR;
516 	return touchwin(screen->slk_window);
517 }
518 
519 /*
520  * __slk_wset --
521  *	Sets the wide text of the label specified by labnum
522  *	and how it is displayed.
523  */
524 static int
525 __slk_wset(SCREEN *screen, int labnum, const wchar_t *label, int justify)
526 {
527 #ifdef HAVE_WCHAR
528 	const wchar_t *olabel;
529 	size_t len;
530 	char *str;
531 	int result = ERR;
532 
533 	if (screen == NULL)
534 		return ERR;
535 	olabel = label;
536 	if ((len = wcsrtombs(NULL, &olabel, 0, &screen->sp)) == -1)
537 		return ERR;
538 	len++; /* We need to store the NULL character. */
539 	if ((str = malloc(len)) == NULL)
540 		return ERR;
541 	olabel = label;
542 	if (wcsrtombs(str, &olabel, len, &screen->sp) == -1)
543 		goto out;
544 	result = __slk_set(screen, labnum, str, justify);
545 out:
546 	free(str);
547 	return result;
548 #else
549 	return ERR;
550 #endif
551 }
552 
553 
554 /*
555  * __slk_init --
556  *	Allocate structures.
557  */
558 int
559 __slk_init(SCREEN *screen)
560 {
561 
562 	__slk_free(screen);	/* safety */
563 
564 	screen->slk_format = slk_fmt;
565 	switch(screen->slk_format) {
566 	case SLK_FMT_3_2_3:
567 	case SLK_FMT_4_4:
568 		screen->slk_nlabels = 8;
569 		break;
570 	default:	/* impossible */
571 		return ERR;
572 	}
573 
574 	screen->slk_labels = calloc(screen->slk_nlabels,
575 				    sizeof(*screen->slk_labels));
576 	if (screen->slk_labels == NULL)
577 		return ERR;
578 
579 	screen->is_term_slk =
580 	    t_plab_norm(screen->term) != NULL &&
581 	    t_num_labels(screen->term) > 0;
582 	if (screen->is_term_slk) {
583 		__unripoffline(__slk_ripoffline);
584 		screen->slk_nlabels = t_num_labels(screen->term);
585 		screen->slk_label_len = t_label_width(screen->term);
586 		/* XXX label_height, label_format? */
587 	}
588 
589 	return OK;
590 }
591 
592 /*
593  * __slk_free --
594  *	Free allocates resources.
595  */
596 void
597 __slk_free(SCREEN *screen)
598 {
599 	int i;
600 
601 	if (screen->slk_window != NULL)
602 		delwin(screen->slk_window);
603 	for (i = 0; i < screen->slk_nlabels; i++)
604 		free(screen->slk_labels[i].text);
605 	free(screen->slk_labels);
606 }
607 
608 /*
609  * __slk_ripoffline --
610  *	ripoffline callback to accept a WINDOW to create our keys.
611  */
612 static int
613 __slk_ripoffline(WINDOW *window, int cols)
614 {
615 
616 	if (window == NULL)
617 		return ERR;
618 	window->screen->slk_window = window;
619 	wattron(window,
620 	    (t_no_color_video(window->screen->term) & 1) == 0
621 	    ? A_STANDOUT : A_REVERSE);
622 	__slk_resize(window->screen, cols);
623 	return OK;
624 }
625 
626 /*
627  * __slk_resize --
628  *	Size and position the labels in the ripped off slk window.
629  */
630 int
631 __slk_resize(SCREEN *screen, int cols)
632 {
633 	int x = 0;
634 	struct __slk_label *l;
635 
636 	if (screen == NULL)
637 		return ERR;
638 	if (screen->is_term_slk || screen->slk_nlabels == 0)
639 		return OK;
640 
641 	screen->slk_label_len = (cols / screen->slk_nlabels) - 1;
642 	if (screen->slk_label_len > SLK_SIZE)
643 		screen->slk_label_len = SLK_SIZE;
644 
645 	l = screen->slk_labels;
646 
647 	switch(screen->slk_format) {
648 	case SLK_FMT_3_2_3:
649 		/* Left 3 */
650 		(l++)->x = x;
651 		(l++)->x = (x += screen->slk_label_len + 1);
652 		(l++)->x = (x += screen->slk_label_len + 1);
653 
654 		/* Middle 2 */
655 		x = cols / 2;
656 		(l++)->x = x -(screen->slk_label_len + 1);
657 		(l++)->x = x + 1;
658 
659 		/* Right 3 */
660 		x = (cols - ((screen->slk_label_len + 1) * 3)) + 1;
661 		(l++)->x = x;
662 		(l++)->x = (x += screen->slk_label_len + 1);
663 		(l++)->x = (x += screen->slk_label_len + 1);
664 		break;
665 
666 	case SLK_FMT_4_4:
667 	{
668 		int i, half;
669 
670 		half = screen->slk_nlabels / 2;
671 		for (i = 0; i < screen->slk_nlabels; i++) {
672 			(l++)->x = x;
673 			x += screen->slk_label_len;
674 			/* Split labels in half */
675 			if (i == half - 1)
676 				x = cols - (screen->slk_label_len * half) + 1;
677 		}
678 		break;
679 	}
680 	}
681 
682 	/* Write text to the labels. */
683 	for (x = 0; x < screen->slk_nlabels; x++)
684 		__slk_set_finalise(screen, x);
685 
686 	return __slk_redraw(screen);
687 }
688 
689 /*
690  * __slk_set_finalise --
691  *	Does the grunt work of positioning and sizing the text in the label.
692  */
693 static int
694 __slk_set_finalise(SCREEN *screen, int labnum)
695 {
696 	struct __slk_label *l;
697 	size_t spc, len, x;
698 	char *p;
699 
700 	l = &screen->slk_labels[labnum];
701 	spc = screen->slk_label_len;
702 
703 #ifdef HAVE_WCHAR
704 	len = 0;
705 	if (l->text != NULL) {
706 		wchar_t wc;
707 
708 		p = l->text;
709 		while (*p != '\0') {
710 			if ((x = mbrtowc(0, p, strlen(p), &screen->sp)) == -1)
711 				return ERR;
712 			mbrtowc(&wc, p, x, &screen->sp);
713 			if (len + wcwidth(wc) > spc)
714 				break;
715 			len += wcwidth(wc);
716 			p += x;
717 		}
718 	}
719 #else
720 	len = l->text == NULL ? 0 : strlen(l->text);
721 	if (len > spc)
722 		len = spc;
723 #endif
724 
725 	switch(l->justify) {
726 	case SLK_JUSTIFY_LEFT:
727 		x = 0;
728 		break;
729 	case SLK_JUSTIFY_CENTER:
730 		x = (spc - len) / 2;
731 		if (x + len > spc)
732 			x--;
733 		break;
734 	case SLK_JUSTIFY_RIGHT:
735 		x = spc - len;
736 		break;
737 	default:
738 		return ERR; /* impossible */
739 	}
740 
741 	p = l->label;
742 	if (x != 0) {
743 		memset(p, ' ', x);
744 		p += x;
745 		spc -= x;
746 	}
747 	if (len != 0) {
748 		memcpy(p, l->text, len);
749 		p += len;
750 		spc -= len;
751 	}
752 	if (spc != 0) {
753 		memset(p, ' ', spc);
754 		p += spc;
755 	}
756 	*p = '\0'; /* Terminate for plab_norm. */
757 
758 	return __slk_draw(screen, labnum);
759 }
760 
761 /*
762  * __slk_draw --
763  *	Draws the specified key.
764  */
765 static int
766 __slk_draw(SCREEN *screen, int labnum)
767 {
768 	const struct __slk_label *l;
769 
770 	if (screen->slk_hidden)
771 		return OK;
772 
773 	l = &screen->slk_labels[labnum];
774 	if (screen->is_term_slk)
775 		return ti_putp(screen->term,
776 		    ti_tiparm(screen->term,
777 		    t_plab_norm(screen->term), labnum + 1, l->label));
778 	else if (screen->slk_window != NULL)
779 		return mvwaddnstr(screen->slk_window, 0, l->x,
780 		    l->label, screen->slk_label_len);
781 	else
782 		return ERR;
783 }
784 
785 /*
786  * __slk_draw --
787  *	Draws all the keys.
788  */
789 static int
790 __slk_redraw(SCREEN *screen)
791 {
792 	int i, result = OK;
793 
794 	for (i = 0; i < screen->slk_nlabels; i++) {
795 		if (__slk_draw(screen, i) == ERR)
796 			result = ERR;
797 	}
798 	return result;
799 }
800