/* $NetBSD: slk.c,v 1.8 2019/07/28 00:51:59 uwe Exp $ */ /*- * Copyright (c) 2017 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Roy Marples. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #ifndef lint __RCSID("$NetBSD: slk.c,v 1.8 2019/07/28 00:51:59 uwe Exp $"); #endif /* not lint */ #include #include #include #ifdef HAVE_WCHAR #include #endif #include "curses.h" #include "curses_private.h" /* Terminals with real soft labels have NOT been tested. * If you have such a device, please let us know so this comment * can be adjusted. */ /* POSIX says that each label can be up to 8 columns. * However, our implementation can allow labels to expand beyond that. */ //#define SLK_SIZE_DYNAMIC #ifdef SLK_SIZE_DYNAMIC #define SLK_SIZE MAX_SLK_LABEL #else #define SLK_SIZE MAX_SLK_COLS #endif static int slk_fmt = SLK_FMT_INVAL; /* fmt of slk_init */ /* Safe variants of public functions. */ static int __slk_attroff(SCREEN *, const chtype); static int __slk_attron(SCREEN *, const chtype); static int __slk_attrset(SCREEN *, const chtype); #ifdef HAVE_WCHAR static int __slk_attr_off(SCREEN *, const attr_t, void *); static int __slk_attr_on(SCREEN *, const attr_t, void *); static int __slk_attr_set(SCREEN *, const attr_t, short, void *opt); static int __slk_color(SCREEN *, short); #endif static int __slk_clear(SCREEN *); static char *__slk_label(SCREEN *, int); static int __slk_restore(SCREEN *); static int __slk_set(SCREEN *, int, const char *, int); static int __slk_touch(SCREEN *); #ifdef HAVE_WCHAR static int __slk_wset(SCREEN *, int, const wchar_t *, int); #endif /* Internal engine parts. */ static int __slk_ripoffline(WINDOW *, int); static int __slk_set_finalise(SCREEN *, int); static int __slk_draw(SCREEN *, int); static int __slk_redraw(SCREEN *); /* * slk_init -- * Init Soft Label Keys. */ int slk_init(int fmt) { switch(fmt) { case SLK_FMT_3_2_3: case SLK_FMT_4_4: break; default: return ERR; } slk_fmt = fmt; /* Even if the terminal supports soft label keys directly, * we need to reserve a line. */ return ripoffline(-1, __slk_ripoffline); } /* * slk_attron -- * Test and set attributes on ripped off slk window. */ int slk_attron(const chtype attr) { return __slk_attron(_cursesi_screen, attr); } #ifdef HAVE_WCHAR /* * slk_attr_on -- * Test and set wide attributes on ripped off slk window. */ int slk_attr_on(const attr_t attr, void *opt) { return __slk_attr_on(_cursesi_screen, attr, opt); } #endif /* HAVE_WCHAR */ /* * slk_attroff -- * Test and unset attributes on ripped off slk window. */ int slk_attroff(const chtype attr) { return __slk_attroff(_cursesi_screen, attr); } #ifdef HAVE_WCHAR /* * slk_attr_off -- * Test and unset wide attributes on ripped off slk window. */ int slk_attr_off(const attr_t attr, void *opt) { return __slk_attr_off(_cursesi_screen, attr, opt); } #endif /* HAVE_WCHAR */ /* * slk_attrset -- * Set attributes and color pair on ripped off slk window. */ int slk_attrset(const chtype attr) { return __slk_attrset(_cursesi_screen, attr); } #ifdef HAVE_WCHAR /* * slk_attr_set -- * Set wide attributes and color pair on ripped off slk window. */ int slk_attr_set(const attr_t attr, short pair, void *opt) { return __slk_attr_set(_cursesi_screen, attr, pair, opt); } #endif /* HAVE_WCHAR */ /* * slk_clear -- * Clear slk from the current screen. */ int slk_clear(void) { return __slk_clear(_cursesi_screen); } #ifdef HAVE_WCHAR /* * slk_color -- * Set color pair on ripped off slk window. */ int slk_color(short pair) { return __slk_color(_cursesi_screen, pair); } #endif /* HAVE_WCHAR */ /* * slk_label -- * Return a pointer to the saved label for key labnum. */ char * slk_label(int labnum) { return __slk_label(_cursesi_screen, labnum); } /* * slk_wnoutrefresh -- * Add the contents of the ripped off slk window to the virtual window. */ int slk_noutrefresh(void) { return __slk_noutrefresh(_cursesi_screen); } /* * slk_refresh -- * Force a refresh for the ripped off slk window. */ int slk_refresh(void) { if (slk_noutrefresh() == ERR) return ERR; return doupdate(); } /* * slk_restore -- * Retore slk to the screen after a slk_clear. */ int slk_restore(void) { return __slk_restore(_cursesi_screen); } /* * slk_set -- * Sets the text of the label specified by labnum * and how it is displayed. */ int slk_set(int labnum, const char *label, int justify) { return __slk_set(_cursesi_screen, labnum, label, justify); } /* * slk_touch -- * Sets the ripped off slk window as modified. */ int slk_touch(void) { return __slk_touch(_cursesi_screen); } #ifdef HAVE_WCHAR /* * slk_wset -- * Sets the wide text of the label specified by labnum * and how it is displayed. */ int slk_wset(int labnum, const wchar_t *label, int justify) { return __slk_wset(_cursesi_screen, labnum, label, justify); } #endif /* HAVE_WCHAR */ /* * __slk_attron -- * Test and set attributes on ripped off slk window. */ static int __slk_attron(SCREEN *screen, const chtype attr) { if (screen == NULL || screen->slk_window == NULL) return ERR; return wattron(screen->slk_window, attr); } #ifdef HAVE_WCHAR /* * __slk_attr_on -- * Test and set wide attributes on ripped off slk window. */ static int __slk_attr_on(SCREEN *screen, const attr_t attr, void *opt) { if (screen == NULL || screen->slk_window == NULL) return ERR; return wattr_on(screen->slk_window, attr, opt); } #endif /* HAVE_WCHAR */ /* * __slk_attroff -- * Test and unset attributes on ripped off slk window. */ static int __slk_attroff(SCREEN *screen, const chtype attr) { if (screen == NULL || screen->slk_window == NULL) return ERR; return wattroff(screen->slk_window, attr); } #ifdef HAVE_WCHAR /* * __slk_attr_off -- * Test and unset wide attributes on ripped off slk window. */ static int __slk_attr_off(SCREEN *screen, const attr_t attr, void *opt) { if (screen == NULL || screen->slk_window == NULL) return ERR; return wattr_off(screen->slk_window, attr, opt); } #endif /* HAVE_WCHAR */ /* * __slk_attrset -- * Set attributes and color pair on ripped off slk window. */ static int __slk_attrset(SCREEN *screen, const chtype attr) { if (screen == NULL || screen->slk_window == NULL) return ERR; return wattrset(screen->slk_window, attr); } #ifdef HAVE_WCHAR /* * __slk_attr_set -- * Set wide attributes and color pair on ripped off slk window. */ static int __slk_attr_set(SCREEN *screen, const attr_t attr, short pair, void *opt) { if (screen == NULL || screen->slk_window == NULL) return ERR; return wattr_set(screen->slk_window, attr, pair, opt); } #endif /* HAVE_WCHAR */ /* * __slk_clear -- * Clear slk from the current screen. */ static int __slk_clear(SCREEN *screen) { if (screen == NULL) return ERR; screen->slk_hidden = true; if (screen->is_term_slk) { if (t_label_off(screen->term) == NULL) return ERR; return ti_putp(screen->term, ti_tiparm(screen->term, t_label_off(screen->term))); } if (screen->slk_window == NULL) return ERR; werase(screen->slk_window); return wrefresh(screen->slk_window); } #ifdef HAVE_WCHAR /* * __slk_color -- * Set color pair on ripped off slk window. */ static int __slk_color(SCREEN *screen, short pair) { if (screen == NULL || screen->slk_window == NULL) return ERR; return wcolor_set(screen->slk_window, pair, NULL); } #endif /* HAVE_WCHAR */ /* * __slk_label -- * Return a pointer to the saved label for key labnum. */ static char * __slk_label(SCREEN *screen, int labnum) { if (screen == NULL || labnum < 1 || labnum > screen->slk_nlabels) return NULL; return screen->slk_labels[--labnum].text; } /* * __slk_wnoutrefresh -- * Add the contents of the ripped off slk window to the virtual window. */ int __slk_noutrefresh(SCREEN *screen) { if (screen == NULL || screen->slk_window == NULL) return ERR; return wnoutrefresh(screen->slk_window); } /* * __slk_restore -- * Retore slk to the screen after a slk_clear. */ static int __slk_restore(SCREEN *screen) { if (screen == NULL) return ERR; screen->slk_hidden = false; if (screen->is_term_slk) { if (t_label_on(screen->term) == NULL) return ERR; return ti_putp(screen->term, ti_tiparm(screen->term, t_label_on(screen->term))); } if (screen->slk_window == NULL) return ERR; if (__slk_redraw(screen) == ERR) return ERR; return wrefresh(screen->slk_window); } /* * __slk_set -- * Sets the text of the label specified by labnum * and how it is displayed. */ static int __slk_set(SCREEN *screen, int labnum, const char *label, int justify) { struct __slk_label *l; const char *end; size_t len; char *text; #ifdef HAVE_WCHAR wchar_t wc; size_t wc_len; #endif /* Check args. */ if (screen == NULL || labnum < 1 || labnum > screen->slk_nlabels) return ERR; switch(justify) { case SLK_JUSTIFY_LEFT: case SLK_JUSTIFY_CENTER: case SLK_JUSTIFY_RIGHT: break; default: return ERR; } if (label == NULL) label = ""; /* Skip leading whitespace. */ while(isspace((unsigned char)*label)) label++; /* Grab end. */ end = label; #ifdef HAVE_WCHAR size_t endlen = strlen(end); while (*end != '\0') { wc_len = mbrtowc(&wc, end, endlen, &screen->sp); if ((ssize_t)wc_len < 0) return ERR; if (!iswprint((wint_t)wc)) break; end += wc_len; endlen -= wc_len; } #else while(isprint((unsigned char)*end)) end++; #endif len = end - label; /* Take a backup, in-case we can grow the label. */ if ((text = strndup(label, len)) == NULL) return ERR; /* All checks out, assign. */ l = &screen->slk_labels[--labnum]; /* internal zero based index */ l->text = text; l->justify = justify; __slk_set_finalise(screen, labnum); return OK; } /* * __slk_touch -- * Sets the ripped off slk window as modified. */ static int __slk_touch(SCREEN *screen) { if (screen == NULL || screen->slk_window == NULL) return ERR; return touchwin(screen->slk_window); } #ifdef HAVE_WCHAR /* * __slk_wset -- * Sets the wide text of the label specified by labnum * and how it is displayed. */ static int __slk_wset(SCREEN *screen, int labnum, const wchar_t *label, int justify) { const wchar_t *olabel; size_t len; char *str; int result = ERR; if (screen == NULL) return ERR; olabel = label; if ((len = wcsrtombs(NULL, &olabel, 0, &screen->sp)) == -1) return ERR; len++; /* We need to store the NULL character. */ if ((str = malloc(len)) == NULL) return ERR; olabel = label; if (wcsrtombs(str, &olabel, len, &screen->sp) == -1) goto out; result = __slk_set(screen, labnum, str, justify); out: free(str); return result; } #endif /* HAVE_WCHAR */ /* * __slk_init -- * Allocate structures. */ int __slk_init(SCREEN *screen) { __slk_free(screen); /* safety */ screen->slk_format = slk_fmt; if (slk_fmt == SLK_FMT_INVAL) return OK; slk_fmt = SLK_FMT_INVAL; switch(screen->slk_format) { case SLK_FMT_3_2_3: case SLK_FMT_4_4: screen->slk_nlabels = 8; break; default: /* impossible */ return ERR; } screen->slk_labels = calloc(screen->slk_nlabels, sizeof(*screen->slk_labels)); if (screen->slk_labels == NULL) return ERR; screen->is_term_slk = t_plab_norm(screen->term) != NULL && t_num_labels(screen->term) > 0; if (screen->is_term_slk) { __unripoffline(__slk_ripoffline); screen->slk_nlabels = t_num_labels(screen->term); screen->slk_label_len = t_label_width(screen->term); /* XXX label_height, label_format? */ } return OK; } /* * __slk_free -- * Free allocates resources. */ void __slk_free(SCREEN *screen) { int i; if (screen->slk_window != NULL) delwin(screen->slk_window); for (i = 0; i < screen->slk_nlabels; i++) free(screen->slk_labels[i].text); free(screen->slk_labels); } /* * __slk_ripoffline -- * ripoffline callback to accept a WINDOW to create our keys. */ static int __slk_ripoffline(WINDOW *window, int cols) { if (window == NULL) return ERR; window->screen->slk_window = window; wattron(window, (t_no_color_video(window->screen->term) & 1) == 0 ? A_STANDOUT : A_REVERSE); __slk_resize(window->screen, cols); return OK; } /* * __slk_resize -- * Size and position the labels in the ripped off slk window. */ int __slk_resize(SCREEN *screen, int cols) { int x = 0; struct __slk_label *l; if (screen == NULL) return ERR; if (screen->is_term_slk || screen->slk_nlabels == 0) return OK; screen->slk_label_len = (cols / screen->slk_nlabels) - 1; if (screen->slk_label_len > SLK_SIZE) screen->slk_label_len = SLK_SIZE; l = screen->slk_labels; switch(screen->slk_format) { case SLK_FMT_3_2_3: /* Left 3 */ (l++)->x = x; (l++)->x = (x += screen->slk_label_len + 1); (l++)->x = (x += screen->slk_label_len + 1); /* Middle 2 */ x = cols / 2; (l++)->x = x -(screen->slk_label_len + 1); (l++)->x = x + 1; /* Right 3 */ x = (cols - ((screen->slk_label_len + 1) * 3)) + 1; (l++)->x = x; (l++)->x = (x += screen->slk_label_len + 1); (l++)->x = (x += screen->slk_label_len + 1); break; case SLK_FMT_4_4: { int i, half; half = screen->slk_nlabels / 2; for (i = 0; i < screen->slk_nlabels; i++) { (l++)->x = x; x += screen->slk_label_len; /* Split labels in half */ if (i == half - 1) x = cols - (screen->slk_label_len * half) + 1; } break; } } /* Write text to the labels. */ for (x = 0; x < screen->slk_nlabels; x++) __slk_set_finalise(screen, x); return __slk_redraw(screen); } /* * __slk_set_finalise -- * Does the grunt work of positioning and sizing the text in the label. */ static int __slk_set_finalise(SCREEN *screen, int labnum) { struct __slk_label *l; size_t spc, len, width, x; char *p; l = &screen->slk_labels[labnum]; spc = screen->slk_label_len; #ifdef HAVE_WCHAR len = 0; width = 0; if (l->text != NULL) { size_t plen; p = l->text; plen = strlen(l->text); while (*p != '\0') { size_t mblen; wchar_t wc; int w; mblen = mbrtowc(&wc, p, plen, &screen->sp); if ((ssize_t)mblen < 0) return ERR; w = wcwidth(wc); if (width + w > spc) break; width += w; len += mblen; p += mblen; plen -= mblen; } } #else len = l->text == NULL ? 0 : strlen(l->text); if (len > spc) len = spc; width = len; #endif switch(l->justify) { case SLK_JUSTIFY_LEFT: x = 0; break; case SLK_JUSTIFY_CENTER: x = (spc - width) / 2; if (x + width > spc) x--; break; case SLK_JUSTIFY_RIGHT: x = spc - width; break; default: return ERR; /* impossible */ } p = l->label; if (x != 0) { memset(p, ' ', x); p += x; spc -= x; } if (len != 0) { memcpy(p, l->text, len); p += len; spc -= width; } if (spc != 0) { memset(p, ' ', spc); p += spc; } *p = '\0'; /* Terminate for plab_norm. */ return __slk_draw(screen, labnum); } /* * __slk_draw -- * Draws the specified key. */ static int __slk_draw(SCREEN *screen, int labnum) { const struct __slk_label *l; if (screen->slk_hidden) return OK; l = &screen->slk_labels[labnum]; if (screen->is_term_slk) return ti_putp(screen->term, ti_tiparm(screen->term, t_plab_norm(screen->term), labnum + 1, l->label)); else if (screen->slk_window != NULL) return mvwaddnstr(screen->slk_window, 0, l->x, l->label, screen->slk_label_len); else return ERR; } /* * __slk_draw -- * Draws all the keys. */ static int __slk_redraw(SCREEN *screen) { int i, result = OK; for (i = 0; i < screen->slk_nlabels; i++) { if (__slk_draw(screen, i) == ERR) result = ERR; } return result; }