From: mlichvar Date: Fri, 25 Aug 2006 13:11:37 +0000 (+0000) Subject: sync with 0.52.0 X-Git-Tag: r0-52-0 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=feef2cb5b7617e73af2232ed7d61f56b2d22c169;p=thirdparty%2Fnewt.git sync with 0.52.0 --- diff --git a/Makefile.in b/Makefile.in index 4583d72..6c32cd7 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,18 +1,18 @@ LIBS = -lslang -lm -ldl #-lefence -SHLIBS = -lslang -lm -dl -lc +SHLIBS = -lslang -lm -dl -lc +LIBTCL = -ltcl8.4 GPM_SUPPORT=@gpm_support@ +CFLAGS = $(RPM_OPT_FLAGS) -Wall -D_GNU_SOURCE -g -O2 -DUTF8 -fPIC -I/usr/include/slang -CFLAGS = $(RPM_OPT_FLAGS) -Wall -I/usr/include/slang -D_GNU_SOURCE -g -O2 -DUTF8 - +SHLIBFLAGS= -Wl,-O1 -Wl,--version-script,newt.0.52.ver VERSION = @VERSION@ CVSTAG = r$(subst .,-,$(VERSION)) SONAME = @SONAME@ -PYTHONVERS = $(shell ls /usr/include/python*/Python.h | sed "s|/usr/include/||g"| sed "s|/Python.h||g") +PYTHONVERS = python2.4 -WHIPTCLSO= -#WHIPTCLSO=whiptcl.so +WHIPTCLSO=whiptcl.so PROGS = test whiptail $(WHIPTCLSO) testgrid testtree showchars showkey TESTOBJS = test.o @@ -46,7 +46,10 @@ else TARGET=depend $(PROGS) endif -all: $(TARGET) _snackmodule.so +all: $(TARGET) _snackmodule.so po/po-stamp + +po/po-stamp: + $(MAKE) -C po po-stamp test: $(TESTOBJS) $(LIBNEWT) $(CC) -g -o test $(TESTOBJS) $(LIBNEWT) $(LIBS) -static @@ -68,20 +71,23 @@ _snackmodule.so: snackmodule.c $(LIBNEWTSH) if [ ! -f "$$ver/_snackmodule.so" -o $(LIBNEWTSH) -nt "$$ver/_snackmodule.so" ]; then \ mkdir -p $$ver ;\ $(CC) $(CFLAGS) -I/usr/include/$$ver -fPIC -c -o $$ver/snackmodule.o snackmodule.c ;\ - $(CC) --shared $(SHCFLAGS) -o $$ver/_snackmodule.so $$ver/snackmodule.o -L . $(LIBNEWTSH) ;\ + $(CC) --shared $(SHCFLAGS) -o $$ver/_snackmodule.so $$ver/snackmodule.o -L . -lnewt -lslang ;\ fi ; \ done whiptail: $(NDIALOGOBJS) $(LIBNEWTSH) - $(CC) -g -o whiptail $(NDIALOGOBJS) -L . $(LIBNEWTSH) $(LIBS) -lpopt + $(CC) -g -o whiptail $(NDIALOGOBJS) -L . -lnewt $(LIBS) -lpopt whiptcl.so: $(WHIPTCLOBJS) $(LIBNEWTSH) - gcc -shared $(SHCFLAGS) -o whiptcl.so $(WHIPTCLOBJS) -L . $(LIBNEWTSH) -ltcl -lslang -lpopt -lm + $(CC) -shared $(SHCFLAGS) -o whiptcl.so $(WHIPTCLOBJS) -L . -lnewt $(LIBTCL) -lslang -lpopt -lm # Ensure dialogboxes is compiled -fPIC dialogboxes.o: dialogboxes.c $(CC) $(CFLAGS) $(SHCFLAGS) -c dialogboxes.c - + +whiptcl.o: whiptcl.c + $(CC) $(SHCFLAGS) $(CFLAGS) -c whiptcl.c + $(LIBNEWT): $(LIBOBJS) ar rv $@ $^ @@ -105,7 +111,8 @@ $(SHAREDDIR): sharedlib: $(LIBNEWTSH) $(LIBNEWTSH): $(SHAREDDIR) $(SHAREDOBJS) - $(CC) -shared -o $(LIBNEWTSH) -Wl,-soname,$(LIBNEWTSONAME) $(SHAREDOBJS) $(SHLIBS) + $(CC) -shared -o $(LIBNEWTSH) $(SHLIBFLAGS) -Wl,-soname,$(LIBNEWTSONAME) $(SHAREDOBJS) $(SHLIBS) + ln -s $(LIBNEWTSH) libnewt.so $(SHAREDDIR)/%.o : %.c $(CC) $(SHCFLAGS) -c $(CFLAGS) -o $@ $< diff --git a/button.c b/button.c index 467efdb..01ae1f5 100644 --- a/button.c +++ b/button.c @@ -104,13 +104,13 @@ static void buttonDrawIt(newtComponent co, int active, int pushed) { SLsmg_set_color(NEWT_COLORSET_BUTTON); if (bu->compact) { - if (active) + if (!active) SLsmg_set_color(NEWT_COLORSET_COMPACTBUTTON); else SLsmg_set_color(NEWT_COLORSET_BUTTON); newtGotorc(co->top+ pushed, co->left + 1 + pushed); SLsmg_write_char('<'); - SLsmg_write_string(bu->text); + write_string_int(bu->text, NULL); SLsmg_write_char('>'); } else { if (pushed) { @@ -140,7 +140,7 @@ static void buttonDrawText(newtComponent co, int active, int pushed) { newtGotorc(co->top + 1 + pushed, co->left + 1 + pushed); SLsmg_write_char(' '); - SLsmg_write_string(bu->text); + write_string_int(bu->text, NULL); SLsmg_write_char(' '); } diff --git a/checkbox.c b/checkbox.c index c753374..d523243 100644 --- a/checkbox.c +++ b/checkbox.c @@ -79,7 +79,7 @@ newtComponent newtRadioGetCurrent(newtComponent setMember) { char newtCheckboxGetValue(newtComponent co) { struct checkbox * cb = co->data; - return *cb->result; + return cb->value; } void newtCheckboxSetValue(newtComponent co, char value) { @@ -141,6 +141,12 @@ void newtCheckboxSetFlags(newtComponent co, int flags, enum newtFlagsSense sense cb->flags = newtSetFlags(cb->flags, flags, sense); + // If the flag just sets a property (eg. NEWT_FLAG_RETURNEXIT), + // don't redraw, etc. as the component might be 'hidden' and not to + // be drawn (eg. in a scrolled list) + if (flags == NEWT_FLAG_RETURNEXIT) + return; + if (!(cb->flags & NEWT_FLAG_DISABLED)) co->takesFocus = 1; else @@ -181,7 +187,7 @@ static void cbDraw(newtComponent c) { break; } - SLsmg_write_string(cb->text); + write_string_int(cb->text, NULL); if (cb->hasFocus) SLsmg_set_color(cb->active); @@ -242,7 +248,10 @@ struct eventResult cbEvent(newtComponent co, struct event ev) { er.result = ER_IGNORED; } } else if(ev.u.key == NEWT_KEY_ENTER) { - er.result = ER_IGNORED; + if (cb->flags & NEWT_FLAG_RETURNEXIT) + er.result = ER_EXITFORM; + else + er.result = ER_IGNORED; } else { er.result = ER_IGNORED; } diff --git a/checkboxtree.c b/checkboxtree.c index 815cc52..b4c6d8f 100644 --- a/checkboxtree.c +++ b/checkboxtree.c @@ -443,7 +443,7 @@ static void ctDraw(newtComponent co) { struct items ** item; int i, j; char * spaces; - int currRow = 0; + int currRow; if (!co->isMapped) return ; @@ -482,12 +482,13 @@ static void ctDraw(newtComponent co) { } else { char tmp[5]; snprintf(tmp,5,"[%c] ",ct->seq[(*item)->selected]); + /* BIDI: no need to use _int funcs here: only ASCII characters */ SLsmg_write_string(tmp); } } - SLsmg_write_nstring((*item)->text, co->width - 4 - - (3 * (*item)->depth)); + write_nstring_int((*item)->text, co->width - 4 - + (3 * (*item)->depth), NULL); SLsmg_set_color(NEWT_COLORSET_LISTBOX); diff --git a/config.h.in b/config.h.in index 9fbcdf4..db81012 100644 --- a/config.h.in +++ b/config.h.in @@ -1,11 +1,21 @@ -/* config.h.in. Generated automatically from configure.in by autoheader. */ +/* config.h.in. Generated automatically from configure.in by autoheader 2.13. */ /* Define if you have the header file. */ #undef HAVE_ALLOCA_H +/* Define if you have the header file. */ +#undef HAVE_DLFCN_H + +/* Define if you have the header file. */ +#undef HAVE_FRIBIDI_FRIBIDI_H + /* Define if you have the header file. */ #undef HAVE_SYS_SELECT_H /* Define to 1 if GPM support is enabled */ #undef USE_GPM +#define PACKAGE "newt" +#define ENABLE_NLS 1 + +#define VERSION "0.51.6" diff --git a/configure.in b/configure.in index 42861ca..eaeb27c 100644 --- a/configure.in +++ b/configure.in @@ -4,8 +4,8 @@ AC_INIT(newt_pr.h) AC_CONFIG_HEADER(config.h) PACKAGE=newt -VERSION=$(awk '/^%define version/ {print $3}' $srcdir/newt.spec) -SONAME=0.51 +VERSION=0.52.0 +SONAME=0.52 AC_SUBST(PACKAGE) AC_SUBST(VERSION) AC_SUBST(SONAME) @@ -13,7 +13,7 @@ AC_PROG_CC AC_PROG_INSTALL AC_PROG_LN_S -AC_CHECK_HEADERS(sys/select.h alloca.h) +AC_CHECK_HEADERS(sys/select.h alloca.h fribidi/fribidi.h dlfcn.h) AC_ARG_WITH(gpm-support, [ --with-gpm-support Compile with GPM support]) diff --git a/dialogboxes.c b/dialogboxes.c index d2b2209..70d367d 100644 --- a/dialogboxes.c +++ b/dialogboxes.c @@ -1,11 +1,15 @@ /* simple dialog boxes, used by both whiptail and tcl dialog bindings */ +#include "config.h" #include #include #include #include #include +#include +#include +#include "nls.h" #include "dialogboxes.h" #include "newt.h" #include "newt_pr.h" @@ -16,20 +20,35 @@ /* globals -- ick */ static int buttonHeight = 1; + +int max (int a, int b) +{ + return (a > b) ? a : b; +} + +int min (int a, int b) +{ + return ( a < b) ? a : b ; +} + static newtComponent (*makeButton)(int left, int right, const char * text) = newtCompactButton; static void addButtons(int height, int width, newtComponent form, newtComponent * okay, newtComponent * cancel, int flags) { + // FIXME: DO SOMETHING ABOUT THE HARD-CODED CONSTANTS if (flags & FLAG_NOCANCEL) { - *okay = makeButton((width - 8) / 2, height - buttonHeight - 1, "Ok"); - *cancel = NULL; + *okay = makeButton((width - 8) / 2, height - buttonHeight - 1, + dgettext(PACKAGE, "Ok")); + *cancel = NULL; newtFormAddComponent(form, *okay); } else { - *okay = makeButton((width - 18) / 3, height - buttonHeight - 1, "Ok"); + *okay = makeButton((width - 18) / 3, height - buttonHeight - 1, + dgettext(PACKAGE,"Ok")); *cancel = makeButton(((width - 18) / 3) * 2 + 9, - height - buttonHeight - 1, "Cancel"); + height - buttonHeight - 1, + dgettext(PACKAGE,"Cancel")); newtFormAddComponents(form, *okay, *cancel, NULL); } } @@ -135,6 +154,7 @@ int inputBox(const char * text, int height, int width, poptContext optCon, int flags, const char ** result) { newtComponent form, entry, okay, cancel, answer, tb; const char * val; + int pFlag = (flags & FLAG_PASSWORD) ? NEWT_FLAG_PASSWORD : 0; int rc = DLG_OKAY; int top; @@ -144,7 +164,7 @@ int inputBox(const char * text, int height, int width, poptContext optCon, form = newtForm(NULL, NULL, 0); entry = newtEntry(1, top + 1, val, width - 2, &val, - NEWT_FLAG_SCROLL | NEWT_FLAG_RETURNEXIT); + NEWT_FLAG_SCROLL | NEWT_FLAG_RETURNEXIT | pFlag); newtFormAddComponents(form, tb, entry, NULL); @@ -153,14 +173,45 @@ int inputBox(const char * text, int height, int width, poptContext optCon, answer = newtRunForm(form); if (answer == cancel) rc = DLG_CANCEL; + else if (answer == NULL) + rc = DLG_ESCAPE; *result = val; return rc; } +static int mystrncpyw(char *dest, const char *src, int n, int *maxwidth) +{ + int i = 0; + int w = 0, cw; + wchar_t c; + mbstate_t ps; + const char *p = src; + char *d = dest; + + memset(&ps, 0, sizeof(ps)); + + for (;;) { + int ret = mbrtowc(&c, p, MB_CUR_MAX, &ps); + if (ret <= 0) break; + if (ret + i >= n) break; + cw = wcwidth(c); + if (cw < 0) break; + if (cw + w > *maxwidth) break; + w += cw; + memcpy(d, p, ret); + d += ret; + p += ret; + i += ret; + } + dest[i] = '\0'; + *maxwidth = w; + return i; +} + int listBox(const char * text, int height, int width, poptContext optCon, - int flags, const char ** result) { + int flags, const char *default_item, const char ** result) { newtComponent form, okay, tb, answer, listBox; newtComponent cancel = NULL; const char * arg; @@ -170,10 +221,12 @@ int listBox(const char * text, int height, int width, poptContext optCon, int allocedItems = 5; int i, top; int rc = DLG_OKAY; - char buf[MAXBUF], format[MAXFORMAT]; + char buf[MAXBUF]; int maxTagWidth = 0; int maxTextWidth = 0; + int defItem = -1; int scrollFlag; + int lineWidth, textWidth, tagWidth; struct { const char * text; const char * tag; @@ -192,6 +245,9 @@ int listBox(const char * text, int height, int width, poptContext optCon, } itemInfo[numItems].tag = arg; + if (default_item && (strcmp(default_item, arg) == 0)) { + defItem = numItems; + } if (!(arg = poptGetArg(optCon))) return DLG_ERROR; if (!(flags & FLAG_NOITEM)) { @@ -226,17 +282,47 @@ int listBox(const char * text, int height, int width, poptContext optCon, i = 2; } - listBox = newtListbox(3 + ((width - 10 - maxTagWidth - maxTextWidth - i) - / 2), - top + 1, listHeight, - NEWT_FLAG_RETURNEXIT | scrollFlag); + lineWidth = min(maxTagWidth + maxTextWidth + i, SLtt_Screen_Cols - 10); + listBox = newtListbox( (width - lineWidth) / 2 , top + 1, listHeight, + NEWT_FLAG_RETURNEXIT | scrollFlag); - snprintf(format, MAXFORMAT, "%%-%ds %%s", maxTagWidth); - for (i = 0; i < numItems; i++) { - snprintf(buf, MAXBUF, format, itemInfo[i].tag, itemInfo[i].text); - newtListboxAddEntry(listBox, buf, (void *) i); + textWidth = maxTextWidth; + tagWidth = maxTagWidth; + if (maxTextWidth == 0) { + tagWidth = lineWidth; + } else { + if (maxTextWidth + maxTagWidth + i > lineWidth) + tagWidth = textWidth = (lineWidth / 2) - 2; + else { + tagWidth++; + textWidth++; + } } + if (!(flags & FLAG_NOTAGS)) { + for (i = 0; i < numItems; i++) { + int w = tagWidth; + int len, j; + len = mystrncpyw(buf, itemInfo[i].tag, MAXBUF, &w); + for (j = 0; j < tagWidth - w; j++) { + if (len >= MAXBUF) break; + buf[len++] = ' '; + } + buf[len] = '\0'; + w = textWidth; + mystrncpyw(buf + len, itemInfo[i].text, MAXBUF-len, &w); + newtListboxAddEntry(listBox, buf, (void *) i); + } + } else { + for (i = 0; i < numItems; i++) { + snprintf(buf, MAXBUF, "%s", itemInfo[i].text); + newtListboxAddEntry(listBox, buf, (void *) i); + } + } + + if (defItem != -1) + newtListboxSetCurrent (listBox, defItem); + newtFormAddComponents(form, tb, listBox, NULL); addButtons(height, width, form, &okay, &cancel, flags); @@ -244,6 +330,8 @@ int listBox(const char * text, int height, int width, poptContext optCon, answer = newtRunForm(form); if (answer == cancel) rc = DLG_CANCEL; + if (answer == NULL) + rc = DLG_ESCAPE; i = (int) newtListboxGetCurrent(listBox); *result = itemInfo[i].tag; @@ -263,7 +351,7 @@ int checkList(const char * text, int height, int width, poptContext optCon, int i; int numSelected; int rc = DLG_OKAY; - char buf[80], format[20]; + char buf[MAXBUF], format[MAXFORMAT]; int maxWidth = 0; int top; struct { @@ -307,7 +395,6 @@ int checkList(const char * text, int height, int width, poptContext optCon, numBoxes++; } - form = newtForm(NULL, NULL, 0); tb = textbox(height - 3 - buttonHeight - listHeight, width - 2, @@ -335,6 +422,7 @@ int checkList(const char * text, int height, int width, poptContext optCon, cbInfo[i].comp = newtCheckbox(4, top + 1 + i, buf, cbStates[i], NULL, cbStates + i); + newtCheckboxSetFlags(cbInfo[i].comp, NEWT_FLAG_RETURNEXIT, NEWT_FLAGS_SET); newtFormAddComponent(subform, cbInfo[i].comp); } @@ -348,6 +436,8 @@ int checkList(const char * text, int height, int width, poptContext optCon, answer = newtRunForm(form); if (answer == cancel) rc = DLG_CANCEL; + if (answer == NULL) + rc = DLG_ESCAPE; if (useRadio) { answer = newtRadioGetCurrent(cbInfo[0].comp); @@ -383,6 +473,7 @@ int checkList(const char * text, int height, int width, poptContext optCon, int messageBox(const char * text, int height, int width, int type, int flags) { newtComponent form, yes, tb, answer; newtComponent no = NULL; + int rc = DLG_OKAY; int tFlag = (flags & FLAG_SCROLL_TEXT) ? NEWT_FLAG_SCROLL : 0; form = newtForm(NULL, NULL, 0); @@ -397,13 +488,16 @@ int messageBox(const char * text, int height, int width, int type, int flags) { case MSGBOX_INFO: break; case MSGBOX_MSG: - yes = makeButton((width - 8) / 2, height - 1 - buttonHeight, "Ok"); + // FIXME Do something about the hard-coded constants + yes = makeButton((width - 8) / 2, height - 1 - buttonHeight, + dgettext(PACKAGE,"Ok")); newtFormAddComponent(form, yes); break; default: - yes = makeButton((width - 16) / 3, height - 1 - buttonHeight, "Yes"); + yes = makeButton((width - 16) / 3, height - 1 - buttonHeight, + dgettext(PACKAGE,"Yes")); no = makeButton(((width - 16) / 3) * 2 + 9, height - 1 - buttonHeight, - "No"); + dgettext(PACKAGE,"No")); newtFormAddComponents(form, yes, no, NULL); if (flags & FLAG_DEFAULT_NO) @@ -411,7 +505,8 @@ int messageBox(const char * text, int height, int width, int type, int flags) { } if ( type != MSGBOX_INFO ) { - newtRunForm(form); + if (newtRunForm(form) == NULL) + rc = DLG_ESCAPE; answer = newtFormGetCurrent(form); @@ -425,7 +520,7 @@ int messageBox(const char * text, int height, int width, int type, int flags) { - return DLG_OKAY; + return rc; } void useFullButtons(int state) { diff --git a/dialogboxes.h b/dialogboxes.h index 27f072c..b182464 100644 --- a/dialogboxes.h +++ b/dialogboxes.h @@ -11,17 +11,22 @@ #define FLAG_NOCANCEL (1 << 1) #define FLAG_SCROLL_TEXT (1 << 2) #define FLAG_DEFAULT_NO (1 << 3) +#define FLAG_PASSWORD (1 << 4) #define FLAG_NOTAGS (1 << 5) #define DLG_ERROR -1 #define DLG_OKAY 0 #define DLG_CANCEL 1 +#define DLG_ESCAPE 2 + +int min(int a, int b); +int max(int a, int b); int messageBox(const char * text, int height, int width, int type, int flags); int checkList(const char * text, int height, int width, poptContext optCon, int useRadio, int flags, const char *** selections); int listBox(const char * text, int height, int width, poptContext optCon, - int flags, const char ** result); + int flags, const char *default_item, const char ** result); int inputBox(const char * text, int height, int width, poptContext optCon, int flags, const char ** result); int gauge(const char * text, int height, int width, poptContext optCon, int fd, diff --git a/entry.c b/entry.c index 2b28954..8676369 100644 --- a/entry.c +++ b/entry.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "newt.h" #include "newt_pr.h" @@ -90,8 +91,8 @@ newtComponent newtEntry(int left, int top, const char * initialValue, int width, else co->takesFocus = 0; - if (initialValue && wstrlen(initialValue,-1) > (unsigned int)width) { - en->bufAlloced = wstrlen(initialValue,-1) + 1; + if (initialValue && strlen(initialValue) > (unsigned int)width) { + en->bufAlloced = strlen(initialValue) + 1; } en->buf = malloc(en->bufAlloced); en->resultPtr = resultPtr; @@ -111,11 +112,48 @@ newtComponent newtEntry(int left, int top, const char * initialValue, int width, return co; } +static int visible_width(const char *str, int start, int end) +{ + int width = wstrlen(str + start, end-start); + int len, w = 0; + wchar_t wc; + + len = mbtowc(&wc, str+end, MB_CUR_MAX); + if (len == 0) + w = 1; + else if (len > 0) + w = wcwidth(wc); + return width + w; +} + +static void scroll(struct entry *en, int width) +{ + wchar_t wc; + int len, w; + int newwidth = visible_width(en->buf, en->firstChar, en->cursorPosition); + + while (newwidth > width) { + len = mbtowc(&wc, en->buf+en->firstChar, MB_CUR_MAX); + if (!len) { + en->firstChar++; + break; + } + if (len < 0) + break; + w = wcwidth(wc); + if (w < 0) + break; + en->firstChar += len; + newwidth -= w; + } +} + static void entryDraw(newtComponent co) { struct entry * en = co->data; int i; char * chptr; int len; + char *tmpptr = NULL; if (!co->isMapped) return; @@ -138,16 +176,15 @@ static void entryDraw(newtComponent co) { if (en->cursorPosition < en->firstChar) { /* scroll to the left */ en->firstChar = en->cursorPosition; - } else if ((en->firstChar + co->width) <= en->cursorPosition) { + } else { /* scroll to the right */ - en->firstChar = en->cursorPosition - co->width + 1; + scroll(en, co->width); } chptr = en->buf + en->firstChar; if (en->flags & NEWT_FLAG_PASSWORD) { - char *tmpptr, *p; - + char *p; tmpptr = alloca(strlen(chptr)+2); strcpy(tmpptr, chptr); for (p = tmpptr; *p; p++) @@ -159,19 +196,22 @@ static void entryDraw(newtComponent co) { if (len <= co->width) { i = len; + /* BIDI: do not replaced, because it will not work. + * More work needed */ SLsmg_write_string(chptr); while (i < co->width) { SLsmg_write_char('_'); i++; } } else { + /* BIDI: will not work for RTL text */ SLsmg_write_nstring(chptr, co->width); } if (en->flags & NEWT_FLAG_HIDDEN) newtGotorc(co->top, co->left); else - newtGotorc(co->top, co->left + (en->cursorPosition - en->firstChar)); + newtGotorc(co->top, co->left + wstrlen(en->buf+en->firstChar, en->cursorPosition - en->firstChar)); } void newtEntrySetFlags(newtComponent co, int flags, enum newtFlagsSense sense) { @@ -212,7 +252,7 @@ static struct eventResult entryEvent(newtComponent co, newtGotorc(co->top, co->left); else newtGotorc(co->top, co->left + - (en->cursorPosition - en->firstChar)); + wstrlen(en->buf + en->firstChar, en->cursorPosition - en->firstChar)); er.result = ER_SWALLOWED; break; @@ -252,6 +292,30 @@ static struct eventResult entryEvent(newtComponent co, return er; } +static int previous_char(const char *buf, int pos) +{ + int len = 0; + int off = 0; + + while (off < pos) { + len = mblen(buf+off, MB_CUR_MAX); + if (len <= 0) + return pos; + off+=len; + } + return off-len; +} + +static int next_char(const char *buf, int pos) +{ + int len = mblen(buf + pos, MB_CUR_MAX); + if (len < 0) + return pos; + if (len == 0) + return ++pos; + return pos+len; +} + static struct eventResult entryHandleKey(newtComponent co, int key) { struct entry * en = co->data; struct eventResult er; @@ -282,49 +346,63 @@ static struct eventResult entryHandleKey(newtComponent co, int key) { memset(en->buf + en->bufUsed, 0, en->bufAlloced - en->bufUsed); break; + case '\025': /* ^U */ + en->bufUsed -= en->cursorPosition; + memmove(en->buf, en->buf + en->cursorPosition, en->bufUsed); + en->cursorPosition = 0; + memset(en->buf + en->bufUsed, 0, en->bufAlloced - en->bufUsed); + break; + case '\002': /* ^B */ case NEWT_KEY_LEFT: if (en->cursorPosition) - en->cursorPosition--; + en->cursorPosition = previous_char(en->buf, en->cursorPosition); break; case '\004': case NEWT_KEY_DELETE: chptr = en->buf + en->cursorPosition; if (*chptr) { - chptr++; - while (*chptr) { - *(chptr - 1) = *chptr; - chptr++; + int delta = next_char(en->buf, en->cursorPosition)-en->cursorPosition; + if (delta) { + chptr+=delta; + while (*chptr) { + *(chptr - delta) = *chptr; + chptr++; + } + memset(chptr - delta, 0, delta); + en->bufUsed-=delta; } - *(chptr - 1) = '\0'; - en->bufUsed--; } break; - case NEWT_KEY_BKSPC: - if (en->cursorPosition) { + case NEWT_KEY_BKSPC: { + int prev = previous_char(en->buf, en->cursorPosition); + if (en->cursorPosition != prev) { /* if this isn't true, there's nothing to erase */ + int delta = en->cursorPosition - prev; chptr = en->buf + en->cursorPosition; - en->bufUsed--; - en->cursorPosition--; + en->bufUsed-=delta; + en->cursorPosition-=delta; while (*chptr) { - *(chptr - 1) = *chptr; + *(chptr - delta) = *chptr; chptr++; } - *(chptr - 1) = '\0'; + memset(chptr - delta, 0, delta); + } } break; case '\006': /* ^B */ case NEWT_KEY_RIGHT: if (en->cursorPosition < en->bufUsed) - en->cursorPosition++; + en->cursorPosition = next_char(en->buf, en->cursorPosition); break; default: - if ((key >= 0x20 && key <= 0x7e) || (key >= 0xa0 && key <= 0xff)) { - if (!(en->flags & NEWT_FLAG_SCROLL) && en->bufUsed >= co->width) { + if ((key >= 0x20 && key <= 0x7e) || (key >= 0x80 && key <= 0xff)) { + if (!(en->flags & NEWT_FLAG_SCROLL) && wstrlen(en->buf, -1) >= co->width) { + /* FIXME this is broken */ SLtt_beep(); break; } @@ -357,7 +435,7 @@ static struct eventResult entryHandleKey(newtComponent co, int key) { } } - + /* FIXME */ en->buf[en->cursorPosition++] = key; } else { er.result = ER_IGNORED; diff --git a/form.c b/form.c index 534ca29..63188ea 100644 --- a/form.c +++ b/form.c @@ -471,6 +471,7 @@ newtComponent newtForm(newtComponent vertBar, void * help, int flags) { if (!(form->flags & NEWT_FLAG_NOF12)) { newtFormAddHotKey(co, NEWT_KEY_F12); } + /* TEST */ newtFormAddHotKey (co, NEWT_KEY_ESCAPE); if (vertBar) form->vertBar = vertBar; diff --git a/label.c b/label.c index ad8665b..6f666f7 100644 --- a/label.c +++ b/label.c @@ -69,7 +69,9 @@ static void labelDraw(newtComponent co) { SLsmg_set_color(COLORSET_LABEL); newtGotorc(co->top, co->left); - SLsmg_write_string(la->text); + /* BIDI: need to check if nstring is really needed. + * Where it is used? */ + write_nstring_int(la->text, co->width, NULL); } static void labelDestroy(newtComponent co) { diff --git a/listbox.c b/listbox.c index a895737..7573940 100644 --- a/listbox.c +++ b/listbox.c @@ -527,7 +527,7 @@ static void listboxDraw(newtComponent co) else SLsmg_set_color(NEWT_COLORSET_LISTBOX); - SLsmg_write_nstring(item->text, li->curWidth); + write_nstring_int(item->text, li->curWidth, NULL); } newtGotorc(co->top + (li->currItem - li->startShowItem) + li->bdyAdjust, diff --git a/newt.0.52.ver b/newt.0.52.ver new file mode 100644 index 0000000..e80cb62 --- /dev/null +++ b/newt.0.52.ver @@ -0,0 +1,6 @@ +NEWT_0.52 { + global: newt*; + _newt_wstrlen; + local: *; +}; + diff --git a/newt.c b/newt.c index 4cc22d0..25ca7ad 100644 --- a/newt.c +++ b/newt.c @@ -18,6 +18,459 @@ #include "newt.h" #include "newt_pr.h" +#if defined(HAVE_FRIBIDI_FRIBIDI_H) && defined(HAVE_DLFCN_H) +#include +#include + +/* No sense in enabling shaping if we don't have BIDI support. */ +typedef struct +{ + int is_shaped; + wchar_t isolated; + wchar_t initial; + wchar_t medial; + wchar_t final; +} +arabic_char_node; + +#define ARABIC_BASE 0x621 +#define ARABIC_END 0x64A + +static const arabic_char_node arabic_shaping_table[] = { +/* 0x621 */ { TRUE , 0xFE80, 0x0000, 0x0000, 0x0000}, +/* 0x622 */ { TRUE , 0xFE81, 0x0000, 0x0000, 0xFE82}, +/* 0x623 */ { TRUE , 0xFE83, 0x0000, 0x0000, 0xFE84}, +/* 0x624 */ { TRUE , 0xFE85, 0x0000, 0x0000, 0xFE86}, +/* 0x625 */ { TRUE , 0xFE87, 0x0000, 0x0000, 0xFE88}, +/* 0x626 */ { TRUE , 0xFE89, 0xFE8B, 0xFE8C, 0xFE8A}, +/* 0x627 */ { TRUE , 0xFE8D, 0x0000, 0x0000, 0xFE8E}, +/* 0x628 */ { TRUE , 0xFE8F, 0xFE91, 0xFE92, 0xFE90}, +/* 0x629 */ { TRUE , 0xFE93, 0x0000, 0x0000, 0xFE94}, +/* 0x62A */ { TRUE , 0xFE95, 0xFE97, 0xFE98, 0xFE96}, +/* 0x62B */ { TRUE , 0xFE99, 0xFE9B, 0xFE9C, 0xFE9A}, +/* 0x62C */ { TRUE , 0xFE9D, 0xFE9F, 0xFEA0, 0xFE9E}, +/* 0x62D */ { TRUE , 0xFEA1, 0xFEA3, 0xFEA4, 0xFEA2}, +/* 0x62E */ { TRUE , 0xFEA5, 0xFEA7, 0xFEA8, 0xFEA6}, +/* 0x62F */ { TRUE , 0xFEA9, 0x0000, 0x0000, 0xFEAA}, +/* 0x630 */ { TRUE , 0xFEAB, 0x0000, 0x0000, 0xFEAC}, +/* 0x631 */ { TRUE , 0xFEAD, 0x0000, 0x0000, 0xFEAE}, +/* 0x632 */ { TRUE , 0xFEAF, 0x0000, 0x0000, 0xFEB0}, +/* 0x633 */ { TRUE , 0xFEB1, 0xFEB3, 0xFEB4, 0xFEB2}, +/* 0x634 */ { TRUE , 0xFEB5, 0xFEB7, 0xFEB8, 0xFEB6}, +/* 0x635 */ { TRUE , 0xFEB9, 0xFEBB, 0xFEBC, 0xFEBA}, +/* 0x636 */ { TRUE , 0xFEBD, 0xFEBF, 0xFEC0, 0xFEBE}, +/* 0x637 */ { TRUE , 0xFEC1, 0xFEC3, 0xFEC4, 0xFEC2}, +/* 0x638 */ { TRUE , 0xFEC5, 0xFEC7, 0xFEC8, 0xFEC6}, +/* 0x639 */ { TRUE , 0xFEC9, 0xFECB, 0xFECC, 0xFECA}, +/* 0x63A */ { TRUE , 0xFECD, 0xFECF, 0xFED0, 0xFECE}, +/* 0x63B */ { FALSE, 0x0000, 0x0000, 0x0000, 0x0000}, +/* 0x63C */ { FALSE, 0x0000, 0x0000, 0x0000, 0x0000}, +/* 0x63D */ { FALSE, 0x0000, 0x0000, 0x0000, 0x0000}, +/* 0x63E */ { FALSE, 0x0000, 0x0000, 0x0000, 0x0000}, +/* 0x63F */ { FALSE, 0x0000, 0x0000, 0x0000, 0x0000}, +/* 0x640 */ { TRUE , 0x0640, 0x0640, 0x0640, 0x0640}, +/* 0x641 */ { TRUE , 0xFED1, 0xFED3, 0xFED4, 0xFED2}, +/* 0x642 */ { TRUE , 0xFED5, 0xFED7, 0xFED8, 0xFED6}, +/* 0x643 */ { TRUE , 0xFED9, 0xFEDB, 0xFEDC, 0xFEDA}, +/* 0x644 */ { TRUE , 0xFEDD, 0xFEDF, 0xFEE0, 0xFEDE}, +/* 0x645 */ { TRUE , 0xFEE1, 0xFEE3, 0xFEE4, 0xFEE2}, +/* 0x646 */ { TRUE , 0xFEE5, 0xFEE7, 0xFEE8, 0xFEE6}, +/* 0x647 */ { TRUE , 0xFEE9, 0xFEEB, 0xFEEC, 0xFEEA}, +/* 0x648 */ { TRUE , 0xFEED, 0x0000, 0x0000, 0xFEEE}, +/* 0x649 */ { TRUE , 0xFEEF, 0x0000, 0x0000, 0xFEF0}, +/* 0x64A */ { TRUE , 0xFEF1, 0xFEF3, 0xFEF4, 0xFEF2} +}; + +typedef struct { + wchar_t c; + arabic_char_node ar_node; +} +extra_char_node; + +#define EXTRA_BASE 0x67E +#define EXTRA_END 0x6CC +static const extra_char_node extra_shaping_table[] = { + {0x067E, {TRUE, 0xFB56, 0xFB58, 0xFB59, 0xFB57}}, + {0x0686, {TRUE, 0xFB7A, 0xFB7C, 0xFB7D, 0xFB7B}}, + {0x0698, {TRUE, 0xFB8A, 0x0000, 0x0000, 0xFB8B}}, + {0x06A9, {TRUE, 0xFB8E, 0xFB90, 0xFB91, 0xFB8F}}, + {0x06AF, {TRUE, 0xFB92, 0xFB94, 0xFB95, 0xFB93}}, + {0x06CC, {TRUE, 0xFBFC, 0xFBFE, 0xFBFF, 0xFBFD}}, + {0x0000, {FALSE, 0x0000, 0x0000, 0x0000, 0x0000}}, +}; + +static const arabic_char_node *get_char_node(wchar_t w) +{ + if (w >= ARABIC_BASE && w <= ARABIC_END) + return &arabic_shaping_table[w - ARABIC_BASE]; + else if (w >= EXTRA_BASE && w <= EXTRA_END) { + const extra_char_node *node = extra_shaping_table; + + while (node->c) { + if (node->c == w) + return &node->ar_node; + node++; + } + return NULL; + } + return NULL; +} + +static int do_shaping(wchar_t *buf, int len) { + int i,j; + + wchar_t *newbuf; + + if (len < 1) + return 0; + + newbuf = (wchar_t *)malloc(sizeof(wchar_t)*len); + + for (i = 0, j = 0; i < len; i++, j++) { + int have_previous = FALSE, have_next = FALSE; + const arabic_char_node *node, *node1; + int prev, next; + + if (buf[i] == L'\0') + break; + + /* If it is non-joiner, ignore it */ + if (buf[i] == 0x200C) { + j--; + continue; + } + + newbuf[j] = buf[i]; + + /* If it's not in our range, skip it. */ + node = get_char_node(buf[i]); + if (!node) + { + continue; + } + + /* The character wasn't included in the unicode shaping table. */ + if (!node->is_shaped) + { + continue; + } + + for (prev = i - 1; prev >= 0; prev--) + if (wcwidth(buf[prev]) || buf[prev] == 0x200C) + break; + + if (prev >= 0 && (node1 = get_char_node(buf[prev])) + && ( node1->initial || node1->medial)) + { + have_previous = TRUE; + } + + for (next = i + 1; next < len; next++) + if (wcwidth(buf[next]) || buf[next] == 0x200C) + break; + + if (next < len && (node1 = get_char_node(buf[next])) + && (node1->medial || node1->final)) + { + have_next = TRUE; + } + + /* + * FIXME: do not make ligature if there are combining + * characters between two parts. + */ + if (buf[i] == 0x644 && have_next && next == i + 1) + { + switch (buf[next]) + { + case 0x622: + newbuf[j] = 0xFEF5 + (have_previous ? 1 : 0); + i++; + continue; + case 0x623: + newbuf[j] = 0xFEF7 + (have_previous ? 1 : 0); + i++; + continue; + case 0x625: + newbuf[j] = 0xFEF9 + (have_previous ? 1 : 0); + i++; + continue; + case 0x627: + newbuf[j] = 0xFEFB + (have_previous ? 1 : 0); + i++; + continue; + default: + break; + } + } + + /** Medial **/ + if (have_previous && have_next && node->medial) + { + newbuf[j] = node->medial; + } + + /** Final **/ + else if (have_previous && node->final) + { + newbuf[j] = node->final; + } + + /** Initial **/ + else if (have_next && node->initial) + { + newbuf[j] = node->initial; + } + + /** Isolated **/ + else if (node->isolated) + { + newbuf[j] = node->isolated; + } + } + for (i = 0; i < len && i < j; i++) { + buf[i] = newbuf[i]; + } + while (i < len) { + buf[i++] = L'\0'; + } + + free(newbuf); + return 0; +} + +/* Converts bidi wchar text {in} to visual wchar text which is displayable + * in text mode. Uses {base_dir} as default base direction. + * Returns malloc'ed converted text or NULL in case of error or if {need_out} is + * not set. Modifies {base_dir} to reflect actual direction. + */ +static wchar_t* wchar_to_textmod_visual(wchar_t *in,unsigned int len,FriBidiCharType *base_dir, int need_out) +{ + FriBidiChar *out = NULL; + static void *handle = NULL; + + fribidi_boolean (*func_ptr) (FriBidiChar *, FriBidiStrIndex, + FriBidiCharType *, FriBidiChar *, + FriBidiStrIndex *, FriBidiStrIndex *, + FriBidiLevel *); + + if (!handle) + handle = dlopen("/usr/lib/libfribidi.so.0", RTLD_LAZY | RTLD_GLOBAL); + if (!handle) + handle = dlopen("/lib/libfribidi.so.0", RTLD_LAZY | RTLD_GLOBAL); + if (!handle) + return NULL; + + func_ptr = dlsym(handle, "fribidi_log2vis"); + if (!func_ptr) { + dlclose(handle); + handle = NULL; + return NULL; + } + + if (need_out) { + out = (FriBidiChar *)malloc(sizeof(FriBidiChar)*(len+1)); + if(!out) + { + dlclose(handle); + handle = NULL; + return (wchar_t *) out; + } + + do_shaping(in, len); + len = wcsnlen(in, len); + } + (*func_ptr)(in, len, base_dir, out, NULL, NULL, NULL); + + return (wchar_t *) out; +} + +/* + * Converts text given in {str} from logical order to visual order. + * Uses {dir} as base direction ('N', 'R', 'L'). + * Returns malloc'ed converted string. Modifies {dir} to reflect actual + * direction. + */ +static char *_newt_log2vis(const char *str, char *dir) +{ + wchar_t *wcs; + char *rstr = NULL; + int len = strlen(str); + int ret; + FriBidiCharType basedir; + + switch (*dir) + { + case 'R': basedir = FRIBIDI_TYPE_R; + break; + case 'L': basedir = FRIBIDI_TYPE_L; + break; + default: basedir = FRIBIDI_TYPE_ON; + break; + } + + if (len) { + wchar_t *owcs; + int newlen; + + wcs = malloc(sizeof(*wcs) * (len + 1)); + if (!wcs) + return NULL; + ret = mbstowcs(wcs, str, len + 1); + if (ret < 0) { + free(wcs); + return NULL; + } + owcs = wchar_to_textmod_visual(wcs, ret, &basedir, 1); + if (FRIBIDI_DIR_TO_LEVEL(basedir)) + *dir = 'R'; + else + *dir = 'L'; + + free(wcs); + if (!owcs) + return NULL; + + newlen = wcstombs(NULL, owcs, 0); + if (newlen < 0) { + free(owcs); + return NULL; + } + rstr = malloc(newlen + 1); + if (!rstr) { + free(owcs); + return NULL; + } + ret = wcstombs(rstr, owcs, newlen + 1); + free(owcs); + if (ret < 0) { + free(rstr); + return NULL; + } + } + return rstr; +} + +/* Returns base direction of text given in {str}. + */ +char get_text_direction(const char *str) +{ + int len = strlen(str); + char dir = 'N'; + wchar_t *wcs; + int ret; + + FriBidiCharType basedir = FRIBIDI_TYPE_ON; + + if (len) { + wcs = malloc(sizeof(*wcs) * (len + 1)); + if (!wcs) + return dir; + ret = mbstowcs(wcs, str, len + 1); + if (ret < 0) { + free(wcs); + return dir; + } + wchar_to_textmod_visual(wcs, ret, &basedir, 1); + free(wcs); + if (FRIBIDI_DIR_TO_LEVEL(basedir)) + dir = 'R'; + else + dir = 'L'; + } + return dir; +} + +/* If width of string {str} is less then {width} adds + * final spaces to make it {width} position wide. + * Returns malloc'ed padded string or NULL in case of errors + * or if string does not need padding. + */ +static char *pad_line(const char *str, int width) +{ + int len = strlen(str); + int w = _newt_wstrlen(str, len); + + if (w < width) { + char *newstr = malloc(len + 1 + (width - w)); + if (!newstr) + return NULL; + memcpy(newstr, str, len); + memset(newstr + len, ' ', width - w); + newstr[len+width-w] = '\0'; + return newstr; + } + return NULL; +} + +/* + * Writes string {str}. Uses {dir} as default direction. + * Returns direction of the string in {dir}. + */ +void write_string_int(const char *str, char *dir) +{ + char dummy; + char *tmpstr; + + if (!dir) { + dummy = 'N'; + dir = &dummy; + } + + tmpstr = _newt_log2vis(str, dir); + if (tmpstr) + str = tmpstr; + SLsmg_write_string(str); + if (tmpstr) + free(tmpstr); +} + +/* Writes at most {n} positions of the string {str}. + * Adds final (logical) spaces if string width is less than {n}. + * Uses {dir} as default direction. + * Returns direction of the string in {dir} + */ +void write_nstring_int(const char *str, int n, char *dir) +{ + char dummy; + char *tmpstr, *tmpstr1; + + if (!dir) { + dummy = 'N'; + dir = &dummy; + } + + tmpstr1 = pad_line(str, n); + if (tmpstr1) + str = tmpstr1; + tmpstr = _newt_log2vis(str, dir); + if (tmpstr) { + free(tmpstr1); + str = tmpstr; + } + SLsmg_write_nstring(str, n); + if (tmpstr) + free(tmpstr); + else + free(tmpstr1); +} +#else +void write_string_int(const char *str, char *dir) +{ + SLsmg_write_string(str); +} + +void write_nstring_int(const char *str, int w, char *dir) +{ + SLsmg_write_nstring(str, w); +} + +char get_text_direction(const char *str) +{ + return 'L'; +} +#endif + struct Window { int height, width, top, left; SLsmg_Char_Type * buffer; @@ -66,34 +519,38 @@ const struct newtColors newtDefaultColorPalette = { "blue", /* scale full */ "red", /* scale empty */ "blue", "lightgray", /* disabled entry fg, bg */ - "white", "blue", /* compact button fg, bg */ + "black", "lightgray", /* compact button fg, bg */ "yellow", "red", /* active & sel listbox */ "black", "brown" /* selected listbox */ }; static const struct keymap keymap[] = { - { "\033OA", NEWT_KEY_UP, "kh" }, - { "\033[A", NEWT_KEY_UP, "ku" }, + { "\033OA", NEWT_KEY_UP, "ku" }, + { "\020", NEWT_KEY_UP, NULL }, /* emacs ^P */ { "\033OB", NEWT_KEY_DOWN, "kd" }, - { "\033[B", NEWT_KEY_DOWN, "kd" }, - { "\033[C", NEWT_KEY_RIGHT, "kr" }, + { "\016", NEWT_KEY_DOWN, NULL }, /* emacs ^N */ { "\033OC", NEWT_KEY_RIGHT, "kr" }, - { "\033[D", NEWT_KEY_LEFT, "kl" }, + { "\006", NEWT_KEY_RIGHT, NULL }, /* emacs ^F */ { "\033OD", NEWT_KEY_LEFT, "kl" }, - { "\033[H", NEWT_KEY_HOME, "kh" }, - { "\033[1~", NEWT_KEY_HOME, "kh" }, + { "\002", NEWT_KEY_LEFT, NULL }, /* emacs ^B */ + { "\033OH", NEWT_KEY_HOME, "kh" }, + { "\033[1~", NEWT_KEY_HOME, NULL }, + { "\001", NEWT_KEY_HOME, NULL }, /* emacs ^A */ { "\033Ow", NEWT_KEY_END, "kH" }, - { "\033[4~", NEWT_KEY_END, "kH" }, + { "\033[4~", NEWT_KEY_END, "@7" }, + { "\005", NEWT_KEY_END, NULL }, /* emacs ^E */ - { "\033[3~", NEWT_KEY_DELETE, "kl" }, - { "\033[2~", NEWT_KEY_INSERT, NULL }, + { "\033[3~", NEWT_KEY_DELETE, "kD" }, + { "\004", NEWT_KEY_DELETE, NULL }, /* emacs ^D */ + { "\033[2~", NEWT_KEY_INSERT, "kI" }, - { "\033\t", NEWT_KEY_UNTAB, NULL }, + { "\033\t", NEWT_KEY_UNTAB, "kB" }, - { "\033[5~", NEWT_KEY_PGUP, NULL }, - { "\033[6~", NEWT_KEY_PGDN, NULL }, - { "\033V", NEWT_KEY_PGUP, "kH" }, - { "\033v", NEWT_KEY_PGUP, "kH" }, + { "\033[5~", NEWT_KEY_PGUP, "kP" }, + { "\033[6~", NEWT_KEY_PGDN, "kN" }, + { "\033V", NEWT_KEY_PGUP, NULL }, + { "\033v", NEWT_KEY_PGUP, NULL }, + { "\026", NEWT_KEY_PGDN, NULL }, { "\033[[A", NEWT_KEY_F1, NULL }, { "\033[[B", NEWT_KEY_F2, NULL }, @@ -106,23 +563,27 @@ static const struct keymap keymap[] = { { "\033OR", NEWT_KEY_F3, NULL }, { "\033OS", NEWT_KEY_F4, NULL }, - { "\033[11~", NEWT_KEY_F1, NULL }, - { "\033[12~", NEWT_KEY_F2, NULL }, - { "\033[13~", NEWT_KEY_F3, NULL }, - { "\033[14~", NEWT_KEY_F4, NULL }, - { "\033[15~", NEWT_KEY_F5, NULL }, - { "\033[17~", NEWT_KEY_F6, NULL }, - { "\033[18~", NEWT_KEY_F7, NULL }, - { "\033[19~", NEWT_KEY_F8, NULL }, - { "\033[20~", NEWT_KEY_F9, NULL }, - { "\033[21~", NEWT_KEY_F10, NULL }, - { "\033[23~", NEWT_KEY_F11, NULL }, - { "\033[24~", NEWT_KEY_F12, NULL }, - { "\033", NEWT_KEY_ESCAPE, NULL }, - + { "\033[11~", NEWT_KEY_F1, "k1" }, + { "\033[12~", NEWT_KEY_F2, "k2" }, + { "\033[13~", NEWT_KEY_F3, "k3" }, + { "\033[14~", NEWT_KEY_F4, "k4" }, + { "\033[15~", NEWT_KEY_F5, "k5" }, + { "\033[17~", NEWT_KEY_F6, "k6" }, + { "\033[18~", NEWT_KEY_F7, "k7" }, + { "\033[19~", NEWT_KEY_F8, "k8" }, + { "\033[20~", NEWT_KEY_F9, "k9" }, + { "\033[21~", NEWT_KEY_F10, "k;" }, + { "\033[23~", NEWT_KEY_F11, "F1" }, + { "\033[24~", NEWT_KEY_F12, "F2" }, + { "\033", NEWT_KEY_ESCAPE, "@2" }, + { "\033", NEWT_KEY_ESCAPE, "@9" }, + + { "\177", NEWT_KEY_BKSPC, NULL }, + { "\010", NEWT_KEY_BKSPC, NULL }, + { 0 }, /* LEAVE this one */ }; -static char keyPrefix = '\033'; +static void initKeymap(); static const char ident[] = // ident friendly "$Version: Newt windowing library v" VERSION " $" @@ -145,7 +606,7 @@ static int getkeyInterruptHook(void) { return -1; } -int wstrlen(const char *str, int len) { +int _newt_wstrlen(const char *str, int len) { mbstate_t ps; wchar_t tmp; int nchars = 0; @@ -169,6 +630,37 @@ int wstrlen(const char *str, int len) { return nchars; } +/** Trim a string to fit + * @param title - string. NULL will be inserted if necessary + * @param chrs - available space. (character cells) + */ +void trim_string(char *title, int chrs) +{ + char *p = title; + int ln = chrs; + int x = 0,y = 0; + wchar_t tmp; + mbstate_t ps; + + memset(&ps, 0, sizeof(ps)); + + while (*p) { + x = mbrtowc(&tmp, p, ln, &ps); + if (x < 0) { // error + *p = '\0'; + return; + } + y = wcwidth(tmp); + if (y > ln) { + *p = '\0'; + return; + } else { + p += x; + ln -= y; + } + } +} + static int getkey() { int c; @@ -251,6 +743,8 @@ int newtInit(void) { SLtt_get_terminfo(); SLtt_get_screen_size(); + // there is no such function in slang?! + // SLutf8_enable(-1); /* init. utf8 according to locale */ MonoValue = getenv(MonoEnv); if ( MonoValue == NULL ) { @@ -266,7 +760,7 @@ int newtInit(void) { newtSetColors(newtDefaultColorPalette); newtCursorOff(); - /*initKeymap();*/ + initKeymap(); /*memset(&sa, 0, sizeof(sa)); sa.sa_handler = handleSigwinch; @@ -360,10 +854,142 @@ void newtSetColors(struct newtColors colors) { colors.selListboxBg); } +/* Keymap handling - rewritten by Henning Makholm , + * November 2003. + */ + +struct kmap_trie_entry { + char c ; /* character got from terminal */ + int code; /* newt key, or 0 if c does not make a complete sequence */ + struct kmap_trie_entry *contseq; /* sub-trie for character following c */ + struct kmap_trie_entry *next; /* try this if char received != c */ +}; +/* Here are some static entries that will help in handling esc O foo and + esc [ foo as variants of each other: */ +static struct kmap_trie_entry + kmap_trie_escO = { 'O', 0, 0, 0 }, + kmap_trie_escBrack = { '[', 0, 0, &kmap_trie_escO }, + kmap_trie_root = { '\033', 0, &kmap_trie_escBrack, 0 }; +static int keyreader_buf_len = 10 ; +static unsigned char default_keyreader_buf[10]; +static unsigned char *keyreader_buf = default_keyreader_buf; + +#if 0 /* for testing of the keymap manipulation code */ +static void dumpkeys_recursive(struct kmap_trie_entry *curr, int i, FILE *f) { + int j, ps ; + char seen[256]={0}; + if( curr && i >= keyreader_buf_len ) { + fprintf(f,"ARGH! Too long sequence!\n") ; + return ; + } + for(;curr;curr=curr->next) { + keyreader_buf[i] = curr->c ; + ps = seen[(unsigned char)curr->c]++ ; + if( ps || curr->code || (!curr->code && !curr->contseq) ) { + for(j=0;j<=i;j++) { + if( keyreader_buf[j] > 32 && keyreader_buf[j]<127 && + keyreader_buf[j] != '^' && keyreader_buf[j] != '\\' ) + fprintf(f,"%c",keyreader_buf[j]); + else if( keyreader_buf[j] > 0 && keyreader_buf[j]<=32 ) + fprintf(f,"^%c",keyreader_buf[j] + 0x40); + else + fprintf(f,"\\%03o", + (unsigned)(unsigned char)keyreader_buf[j]); + } + if( curr->code ) + fprintf(f,": 0x%X\n",curr->code); + else + fprintf(f,": (just keymap)\n"); + } + dumpkeys_recursive(curr->contseq,i+1,f); + } +} +static void dump_keymap(void) { + FILE *f = fopen("newt.keydump","wt"); + if (f) { + dumpkeys_recursive(&kmap_trie_root,0,f); + fclose(f); + } +} +#endif + +/* newtBindKey may overwrite a binding that is there already */ +static void newtBindKey(char *keyseq, int meaning) { + struct kmap_trie_entry *root = &kmap_trie_root ; + struct kmap_trie_entry **curptr = &root ; + + /* Try to make sure the common matching buffer is long enough. */ + if( strlen(keyseq) > keyreader_buf_len ) { + int i = strlen(keyseq)+10; + unsigned char *newbuf = malloc(i); + if (newbuf) { + if (keyreader_buf != default_keyreader_buf) + free(keyreader_buf); + keyreader_buf = newbuf; + keyreader_buf_len = i; + } + } + + if (*keyseq == 0) return; /* binding the empty sequence is meaningless */ + + while(1) { + while ((*curptr) && (*curptr)->c != *keyseq) + curptr = &(*curptr)->next; + if ((*curptr)==0) { + struct kmap_trie_entry* fresh + = calloc(strlen(keyseq),sizeof(struct kmap_trie_entry)); + if (fresh == 0) return; /* despair! */ + *curptr = fresh; + while (keyseq[1]) { + fresh->contseq = fresh+1; + (fresh++)->c = *(keyseq++); + } + fresh->c = *keyseq; + fresh->code = meaning; + return; + } + if (keyseq[1]==0) { + (*curptr)->code = meaning; + return; + } else { + curptr = &(*curptr)->contseq; + keyseq++; + } + } +} + +/* This function recursively inserts all entries in the "to" trie into + corresponding positions in the "from" trie, except positions that + are already defined in the "from" trie. */ +static void kmap_trie_fallback(struct kmap_trie_entry *to, + struct kmap_trie_entry **from) { + if (*from == NULL) + *from = to ; + if (*from == to) + return ; + for (;to!=NULL;to=to->next) { + struct kmap_trie_entry **fromcopy = from ; + while ((*fromcopy) && (*fromcopy)->c != to->c) + fromcopy = &(*fromcopy)->next ; + if (*fromcopy) { + if ((*fromcopy)->code == 0) + (*fromcopy)->code = to->code; + kmap_trie_fallback(to->contseq, &(*fromcopy)->contseq); + } else { + *fromcopy = malloc(sizeof(struct kmap_trie_entry)); + if (*fromcopy) { + **fromcopy = *to ; + (*fromcopy)->next = 0 ; + } + } + } +} + int newtGetKey(void) { int key; - char buf[10], * chptr = buf; - const struct keymap * curr; + unsigned char *chptr = keyreader_buf, *lastmatch; + int lastcode; + struct kmap_trie_entry *curr = &kmap_trie_root; do { key = getkey(); @@ -387,66 +1013,35 @@ int newtGetKey(void) { suspendCallback(suspendCallbackData); } while (key == NEWT_KEY_SUSPEND); - switch (key) { - case 'v' | 0x80: - case 'V' | 0x80: - return NEWT_KEY_PGUP; - - case 22: - return NEWT_KEY_PGDN; - - return NEWT_KEY_BKSPC; - case 0x7f: - return NEWT_KEY_BKSPC; - - case 0x08: - return NEWT_KEY_BKSPC; - - default: - if (key != keyPrefix) return key; - } - - memset(buf, 0, sizeof(buf)); - - *chptr++ = key; - while (SLang_input_pending(5)) { - key = getkey(); - if (key == keyPrefix) { - /* he hit unknown keys too many times -- start over */ - memset(buf, 0, sizeof(buf)); - chptr = buf; - } - - *chptr++ = key; - - /* this search should use bsearch(), but when we only look through - a list of 20 (or so) keymappings, it's probably faster just to - do a inline linear search */ - - for (curr = keymap; curr->code; curr++) { - if (curr->str) { - if (!strcmp(curr->str, buf)) - return curr->code; - } - } - } - - for (curr = keymap; curr->code; curr++) { - if (curr->str) { - if (!strcmp(curr->str, buf)) - return curr->code; - } + /* Read more characters, matching against the trie as we go */ + lastcode = *chptr = key; + lastmatch = chptr ; + while(1) { + while (curr->c != key) { + curr = curr->next ; + if (curr==NULL) goto break2levels; + } + if (curr->code) { + lastcode = curr->code; + lastmatch = chptr; + } + curr = curr->contseq; + if (curr==NULL) break; + + if (SLang_input_pending(5) <= 0) + break; + + if (chptr==keyreader_buf+keyreader_buf_len-1) break; + *++chptr = key = getkey(); } - - /* Looks like we were a bit overzealous in reading characters. Return - just the first character, and put everything else back in the buffer - for later */ - - chptr--; - while (chptr > buf) - SLang_ungetkey(*chptr--); - - return *chptr; + break2levels: + + /* The last time the trie matched was at position lastmatch. Back + * up if we have read too many characters. */ + while (chptr > lastmatch) + SLang_ungetkey(*chptr--); + + return lastcode; } /** @@ -477,7 +1072,8 @@ void newtClearKeyBuffer(void) { * @param title - title string * @return zero on success (currently no errors reported) */ -int newtOpenWindow(unsigned left, unsigned top, unsigned width, unsigned height, +int newtOpenWindow(unsigned int left, unsigned int top, + unsigned int width, unsigned int height, const char * title) { int j, row, col; int n; @@ -521,9 +1117,12 @@ int newtOpenWindow(unsigned left, unsigned top, unsigned width, unsigned height, newtTrashScreen(); SLsmg_set_color(NEWT_COLORSET_BORDER); + SLsmg_set_char_set(1); SLsmg_draw_box(top - 1, left - 1, height + 2, width + 2); + SLsmg_set_char_set(0); if (currentWindow->title) { + trim_string (currentWindow->title, width-4); i = wstrlen(currentWindow->title,-1) + 4; i = ((width - i) / 2) + left; SLsmg_gotorc(top - 1, i); @@ -532,7 +1131,7 @@ int newtOpenWindow(unsigned left, unsigned top, unsigned width, unsigned height, SLsmg_set_char_set(0); SLsmg_write_char(' '); SLsmg_set_color(NEWT_COLORSET_TITLE); - SLsmg_write_string((char *)currentWindow->title); + write_string_int((char *)currentWindow->title, NULL); SLsmg_set_color(NEWT_COLORSET_BORDER); SLsmg_write_char(' '); SLsmg_set_char_set(1); @@ -562,8 +1161,9 @@ int newtOpenWindow(unsigned left, unsigned top, unsigned width, unsigned height, * @param title - fixed title * @returns 0. No errors reported */ -int newtCenteredWindow(unsigned width, unsigned height, const char * title) { - unsigned top, left; +int newtCenteredWindow(unsigned int width,unsigned int height, + const char * title) { + unsigned int top, left; top = (SLtt_Screen_Rows - height) / 2; @@ -662,32 +1262,44 @@ void newtClearBox(int left, int top, int width, int height) { SLsmg_fill_region(top, left, height, width, ' '); } -#if 0 -/* This doesn't seem to work quite right. I don't know why not, but when - I rsh from an rxvt into a box and run this code, the machine returns - console key's (\033[B) rather then xterm ones (\033OB). */ static void initKeymap(void) { - struct keymap * curr; + const struct keymap * curr; + /* First bind built-in default bindings. They may be shadowed by + the termcap entries that get bound later. */ for (curr = keymap; curr->code; curr++) { - if (!curr->str) - curr->str = SLtt_tgetstr(curr->tc); + if (curr->str) + newtBindKey(curr->str,curr->code); } - /* Newt's keymap handling is a bit broken. It assumes that any extended - keystrokes begin with ESC. If you're using a homebrek terminal you - will probably need to fix this, or just yell at me and I'll be so - ashamed of myself for doing it this way I'll fix it */ + /* Then bind strings from termcap entries */ + for (curr = keymap; curr->code; curr++) { + if (curr->tc) { + char *pc = SLtt_tgetstr(curr->tc); + if (pc) { + newtBindKey(pc,curr->code); + } + } + } - keyPrefix = 0x1b; /* ESC */ + /* Finally, invent lowest-priority keybindings that correspond to + searching for esc-O-foo if esc-[-foo was not found and vice + versa. That is needed because of strong confusion among + different emulators of VTxxx terminals; some terminfo/termcap + descriptions are apparently written by people who were not + aware of the differences between "applicataion" and "terminal" + keypad modes. Or perhaps they were, but tried to make their + description work with a program that puts the keyboard in the + wrong emulation mode. In short, one needs this: */ + kmap_trie_fallback(kmap_trie_escO.contseq, &kmap_trie_escBrack.contseq); + kmap_trie_fallback(kmap_trie_escBrack.contseq, &kmap_trie_escO.contseq); } -#endif /** * @brief Delay for a specified number of usecs * @param int - number of usecs to wait for. */ -void newtDelay(unsigned usecs) { +void newtDelay(unsigned int usecs) { fd_set set; struct timeval tv; @@ -731,7 +1343,7 @@ void newtRedrawHelpLine(void) { buf[SLtt_Screen_Cols] = '\0'; } SLsmg_gotorc(SLtt_Screen_Rows - 1, 0); - SLsmg_write_string(buf); + write_string_int(buf, NULL); } void newtPushHelpLine(const char * text) { @@ -772,7 +1384,7 @@ void newtDrawRootText(int col, int row, const char * text) { } SLsmg_gotorc(row, col); - SLsmg_write_string((char *)text); + write_string_int((char *)text, NULL); } int newtSetFlags(int oldFlags, int newFlags, enum newtFlagsSense sense) { diff --git a/newt.spec b/newt.spec index 54f72a0..2e5e031 100644 --- a/newt.spec +++ b/newt.spec @@ -2,12 +2,14 @@ Summary: A development library for text mode user interfaces. Name: newt -Version: 0.51.7 -Release: 2 +%define version 0.52.0 +Version: %{version} +Release: 0 License: LGPL Group: System Environment/Libraries Source: newt-%{version}.tar.gz -BuildRequires: python,python-devel,perl, slang-devel +Patch1: newt-0.51.6-if1close.patch +BuildRequires: python,python-devel,perl, slang-devel, tcl-devel Requires: slang Provides: snack BuildRoot: %{_tmppath}/%{name}-%{version}-root @@ -38,11 +40,13 @@ newt. %prep %setup -q +%patch1 -p1 -b .if1close %build # gpm support seems to smash the stack w/ we use help in anaconda?? #./configure --with-gpm-support %configure +make depend make %{?_smp_mflags} all chmod 0644 peanuts.py popcorn.py @@ -69,9 +73,10 @@ rm -rf $RPM_BUILD_ROOT %files %defattr (-,root,root) -%doc CHANGES COPYING +%doc COPYING %{_bindir}/whiptail %{_libdir}/libnewt.so.* +%{_libdir}/whiptcl.so %{_libdir}/python%{pythonver}/site-packages/* %files devel @@ -82,15 +87,36 @@ rm -rf $RPM_BUILD_ROOT %{_libdir}/libnewt.so %changelog -* Thu Apr 8 2004 Adrian Havill 0.51.7-2 -- incorporated some debian memcheck fixes, whiptail features -- change .spec to not use hardcoded python version much like up2date (#114419) +* Wed Sep 21 2005 Petr Rockai - 0.52.0-0 +- new upstream version -* Fri Dec 5 2003 Jeremy Katz 0.51.7-1 -- rebuild against new slang +* Fri Sep 02 2005 Petr Rockai +- use versioned symbols, patch by Alastair McKinstry, mckinstry at + debian dot org, thanks +- need private wstrlen due to versioned syms, patch from debian + package of newt +- both of the above needed to be forward-ported -* Mon Nov 17 2003 Nalin Dahyabhai -- fix newtCheckboxGetValue not working if checkbox created with non-NULL result +* Sun Mar 06 2005 Petr Rockai +- rebuild + +* Mon Nov 8 2004 Jeremy Katz - 0.51.6-6 +- rebuild for python 2.4 + +* Fri Oct 15 2004 Adrian Havill 0.51.6-5 +- only do gpmclose if gpmopen succeeed (#118530) + +* Thu Oct 14 2004 Adrian Havill 0.51.6-4 +- make the python version dynamic (#114419) + +* Tue Jun 15 2004 Elliot Lee +- rebuilt + +* Tue Mar 02 2004 Elliot Lee +- rebuilt + +* Fri Feb 13 2004 Elliot Lee +- rebuilt * Thu Nov 6 2003 Jeremy Katz 0.51.6-2 - rebuild for python 2.3 diff --git a/newt_pr.h b/newt_pr.h index 911898c..08df449 100644 --- a/newt_pr.h +++ b/newt_pr.h @@ -80,6 +80,11 @@ void newtDefaultMappedHandler(newtComponent c, int isMapped); struct eventResult newtDefaultEventHandler(newtComponent c, struct event ev); -int wstrlen(const char *str, int len); +int _newt_wstrlen(const char *str, int len); +void write_string_int(const char *, char *); +void write_nstring_int(const char *, int, char *); +char get_text_direction(const char *); + +#define wstrlen(str,len) _newt_wstrlen((str),(len)) #endif /* H_NEWT_PR */ diff --git a/nls.h b/nls.h new file mode 100644 index 0000000..7becf84 --- /dev/null +++ b/nls.h @@ -0,0 +1,16 @@ + + +#ifndef LOCALEDIR +#define LOCALEDIR "/usr/share/locale" +#endif + +# include + +# include +# define _(Text) gettext (Text) +# ifdef gettext_noop +# define N_(String) gettext_noop (String) +# else +# define N_(String) (String) +# endif + diff --git a/snack.py b/snack.py index b0f51d5..ff6d8d4 100644 --- a/snack.py +++ b/snack.py @@ -282,7 +282,7 @@ hotkeys = { "F1" : _snack.KEY_F1, "F2" : _snack.KEY_F2, "F3" : _snack.KEY_F3, "F4" : _snack.KEY_F4, "F5" : _snack.KEY_F5, "F6" : _snack.KEY_F6, "F7" : _snack.KEY_F7, "F8" : _snack.KEY_F8, "F9" : _snack.KEY_F9, "F10" : _snack.KEY_F10, "F11" : _snack.KEY_F11, - "F12" : _snack.KEY_F12, " " : ord(" ") } + "F12" : _snack.KEY_F12, "ESC" : _snack.KEY_ESC , " " : ord(" ") } for n in hotkeys.keys(): hotkeys[hotkeys[n]] = n @@ -925,5 +925,5 @@ class CListbox(Grid): def setCurrent(self, item): self.listbox.setCurrent(item) - def clear(self): - self.listbox.clear() + def clear(self): + self.listbox.clear() diff --git a/snackmodule.c b/snackmodule.c index f992e44..deb3867 100644 --- a/snackmodule.c +++ b/snackmodule.c @@ -12,6 +12,7 @@ #include #include "Python.h" +#include "nls.h" #include "newt.h" #include "newt_pr.h" @@ -1219,6 +1220,10 @@ static PyObject * pywstrlen(PyObject * s, PyObject * args) void init_snack(void) { PyObject * d, * m; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + m = Py_InitModule("_snack", snackModuleMethods); d = PyModule_GetDict(m); @@ -1251,6 +1256,7 @@ void init_snack(void) { PyDict_SetItemString(d, "KEY_F10", PyInt_FromLong(NEWT_KEY_F10)); PyDict_SetItemString(d, "KEY_F11", PyInt_FromLong(NEWT_KEY_F11)); PyDict_SetItemString(d, "KEY_F12", PyInt_FromLong(NEWT_KEY_F12)); + PyDict_SetItemString(d, "KEY_ESC", PyInt_FromLong(NEWT_KEY_ESCAPE)); PyDict_SetItemString(d, "FLAG_DISABLED", PyInt_FromLong(NEWT_FLAG_DISABLED)); PyDict_SetItemString(d, "FLAGS_SET", PyInt_FromLong(NEWT_FLAGS_SET)); diff --git a/textbox.c b/textbox.c index 68742cf..5a43df0 100644 --- a/textbox.c +++ b/textbox.c @@ -91,6 +91,8 @@ newtComponent newtTextbox(int left, int top, int width, int height, int flags) { tb = malloc(sizeof(*tb)); co->data = tb; + if (width < 2) width = 2; + co->ops = &textboxOps; co->height = height; @@ -337,20 +339,42 @@ static void textboxDraw(newtComponent c) { int i; struct textbox * tb = c->data; int size; - + char dir = 'N'; + int newdir = 1; + int dw = 0; + if (tb->sb) { size = tb->numLines - c->height; newtScrollbarSet(tb->sb, tb->topLine, size ? size : 0); tb->sb->ops->draw(tb->sb); + dw = 2; } SLsmg_set_color(NEWT_COLORSET_TEXTBOX); + /* find direction of first visible paragraph */ + for (i = 0; i < tb->topLine; i++) { + if (!*tb->lines[i]) { + /* new line: new paragraph starts at the next line */ + newdir = 1; + dir = 'N'; + } + else if (newdir) { + /* get current paragraph direction, if possible */ + dir = get_text_direction(tb->lines[i]); + newdir = (dir == 'N') ? 1 : 0; + } + } + for (i = 0; (i + tb->topLine) < tb->numLines && i < c->height; i++) { newtGotorc(c->top + i, c->left); SLsmg_write_string(tb->blankline); newtGotorc(c->top + i, c->left); - SLsmg_write_string(tb->lines[i + tb->topLine]); + /* BIDI: we need *nstring* here to properly align lines */ + write_nstring_int(tb->lines[i + tb->topLine], c->width - dw, &dir); + /* Does new paragraph follow? */ + if (!*tb->lines[i + tb->topLine]) + dir = 'N'; } } diff --git a/whiptail.1 b/whiptail.1 new file mode 100644 index 0000000..1d2e63d --- /dev/null +++ b/whiptail.1 @@ -0,0 +1,268 @@ +.TH WHIPTAIL 1 "20 September 2004" "Whiptail Version 0.51.6" +.SH NAME +whiptail \- display dialog boxes from shell scripts +.SH SYNOPSIS +.B whiptail +[ +.B \-\-title +.I title +] +[ +.B \-\-backtitle +.I backtitle +] +[ +.B \-\-clear +] +[ +.B \-\-default\-item +.I string +] +[ +.B \-\-defaultno +] +[ +.B \-\-fb +] +[ +.B \-\-nocancel +] +[ +.B \-\-noitem +[ +] +.B \-\-output\-fd +.I fd +] +[ +.B \-\-separate\-output +] +[ +.B \-\-scrolltext +] +.B box-options +.SH DESCRIPTION +.B whiptail +is a program that will let you present a variety of questions or +display messages using dialog boxes from a shell script. Currently, +these types of dialog boxes are implemented: +.LP +.BR yes/no " box," " menu" " box," " input" " box," +.BR message " box," " text" " box," " info" " box," +.BR checklist " box," " radiolist" " box" " gauge" " box, and" +.BR password " box." +.SH OPTIONS +.TP +.B \-\-clear +The screen will be cleared to the +.BR "screen attribute" " on exit." +This doesn't work in an xterm (and descendants) if alternate screen +switching is enabled, because in that case slang writes to (and clears) +an alternate screen. +.TP +.B \-\-defaultno +The dialog box will open with the cursor over the +.BR No " button." +.TP +.B \-\-default\-item "string" +Set the default item in a menu box. +Normally the first item in the box is the default. +.TP +.B \-\-fb +Use full buttons. (By default, +.B whiptail +uses compact buttons). +.TP +.B \-\-nocancel +The dialog box won't have a +.BR Cancel " button". +.TP +.B \-\-noitem +The menu, checklist and radiolist widgets will display tags only, not +the item strings. +.TP +.BI \-\-separate\-output +For checklist widgets, output result one line at a time, with no +quoting. This facilitates parsing by another program. +.TP +.BI \-\-output\-fd " fd" +Direct output to the given file descriptor. Most +.B whiptail +scripts +write to standard error, but error messages may also be +written there, depending on your script. +.TP +.BI \-\-title " title" +Specifies a +.I title +string to be displayed at the top of the dialog box. +.TP +.BI \-\-backtitle " backtitle" +Specifies a +.I backtitle +string to be displayed on the backdrop, at the top of the screen. +.TP +.BI \-\-scrolltext +Force the display of a vertical scrollbar. +.TP +.B Box Options +.TP +.BI \-\-yesno " text height width" +.RB A " yes/no" " dialog box of size" +.I height +rows by +.I width +columns will be displayed. The string specified by +.I text +is displayed inside the dialog box. If this string is too long to be fit +in one line, it will be automatically divided into multiple lines at +appropriate places. The +.I text +string may also contain the sub-string +.I +"\en" +or newline characters +.I `\en' +to control line breaking explicitly. This dialog box is useful for +asking questions that require the user to answer either yes or no. +.RB "The dialog box has a" " Yes" " button and a " No +button, in which the user can switch between by pressing the +.IR TAB " key." +.TP +.BI \-\-msgbox " text height width" +.RB A " message" " box is very similar to a" " yes/no" " box." +The only difference between a +.B message +box and a +.B yes/no +box is that a +.B message +box has only a single +.B OK +button. You can use this dialog box to display any message you like. +After reading the message, the user can press the +.I ENTER +key so that +.B whiptail +will exit and the calling shell script can continue its operation. +.TP +.BI \-\-infobox " text height width" +.RB An " info" " box is basically a" " message" " box." +However, in this case, +.B whiptail +will exit immediately after displaying the message to the user. The +screen is not cleared when +.B whiptail +exits, so that the message will remain on the screen until the calling +shell script clears it later. This is useful when you want to inform +the user that some operations are carrying on that may require some +time to finish. +.TP +.BI \-\-inputbox " text height width [init]" +.RB "An " input " box is useful when you want to ask questions that" +require the user to input a string as the answer. If init is supplied +it is used to initialize the input string. +When inputing the +string, the +.I BACKSPACE +key can be used to correct typing errors. If the input string is longer than +the width of the dialog box, the input field will be scrolled. On exit, +the input string will be printed on +.IR stderr "." +.TP +.BI \-\-passwordbox " text height width [init]" +.RB "A " password " box is similar to an input box, except the text the user" +enters is not displayed. This is useful when prompting for passwords or other +sensitive information. Be aware that if anything is passed in "init", it +will be visible in the system's process table to casual snoopers. Also, it +is very confusing to the user to provide them with a default password they +cannot see. For these reasons, using "init" is highly discouraged. +.TP +.BI \-\-textbox " file height width" +.RB A " text" " box lets you display the contents of a text file in a" +dialog box. It is like a simple text file viewer. The user can move +through the file by using the +.IR UP/DOWN ", " PGUP/PGDN +.RI and " HOME/END" " keys available on most keyboards." +If the lines are too long to be displayed in the box, the +.I LEFT/RIGHT +keys can be used to scroll the text region horizontally. For more +convenience, forward and backward searching functions are also provided. +.IP "\fB\-\-menu \fItext height width menu-height \fR[ \fItag item \fR] \fI..." +As its name suggests, a +.B menu +box is a dialog box that can be used to present a list of choices in +the form of a menu for the user to choose. Each menu entry consists of a +.IR tag " string and an " item " string. The" +.I tag +gives the entry a name to distinguish it from the other entries in the +menu. The +.I item +is a short description of the option that the entry represents. The +user can move between the menu entries by pressing the +.I UP/DOWN +keys, the first letter of the +.I tag +as a hot-key. There are +.I menu-height +entries displayed in the menu at one time, but the menu will be +scrolled if there are more entries than that. When +.B whiptail +exits, the +.I tag +of the chosen menu entry will be printed on +.IR stderr "." +.IP "\fB\-\-checklist \fItext height width list-height \fR[ \fItag item status \fR] \fI..." +.RB "A " checklist " box is similar to a " menu " box in that there are" +multiple entries presented in the form of a menu. +You can select and deselect items using the SPACE key. +The initial on/off state of each entry is specified by +.IR status "." +On exit, a list of the +.I tag +strings of those entries that are turned on will be printed on +.IR stderr "." + +.IP "\fB\-\-radiolist \fItext height width list-height \fR [ \fItag item status \fR] \fI..." +.RB "A " radiolist " box is similar to a " menu " box. The only difference is" +that you can indicate which entry is currently selected, by setting its +.IR status " to " on "." + +.IP "\fB\-\-gauge \fItext height width percent\fR" +.RB "A " gauge " box displays a meter along the bottom of the box. +The meter indicates a percentage. New percentages are read from +standard input, one integer per line. The meter is updated +to reflect each new percentage. If stdin is XXX, then subsequent +lines up to another XXX are used for a new prompt. +The gauge exits when EOF is reached on stdin. + +.SH NOTES +whiptail interprets arguments starting with a dash "\-" as being arguments. +To avoid this, and start some text in, for example, a menubox item, with a +dash, whiptail honours the getopt convention of accepting the special +argument "\-\-" which means that all following arguments with dashes are to +be treated verbatim and not parsed as options. +.SH DIAGNOSTICS +Exit status is 0 if +.BR whiptail " is exited by pressing the " Yes " or " OK +button, and 1 if the +.BR No " or " Cancel +button is pressed. Otherwise, if errors occur inside +.B whiptail +or +.B whiptail +is exited by pressing the +.I ESC +key, the exit status is -1. +.SH AUTHOR +Based on the man page for dialog(1) by: +.LP +Savio Lam (lam836@cs.cuhk.hk) - version 0.3 +.LP +Stuart Herbert (S.Herbert@sheffield.ac.uk) - patch for version 0.4 +.LP +Modifications for whiptail by: +.LP +Enrique Zanardi (ezanard@debian.org) +.LP +Alastair McKinstry (mckinstry@debian.org) diff --git a/whiptail.c b/whiptail.c index 6b9a4fb..70bbe9f 100644 --- a/whiptail.c +++ b/whiptail.c @@ -1,18 +1,24 @@ -/* a reasonable dialog */ - +#include "config.h" #include #include #include #include #include +#include #include +#include +#include +#include "nls.h" #include "dialogboxes.h" #include "newt.h" +#include "newt_pr.h" + +enum { NO_ERROR = 0, WAS_ERROR = 1 }; enum mode { MODE_NONE, MODE_INFOBOX, MODE_MSGBOX, MODE_YESNO, MODE_CHECKLIST, - MODE_INPUTBOX, MODE_RADIOLIST, MODE_MENU, MODE_GAUGE, - MODE_TEXTBOX }; + MODE_INPUTBOX, MODE_RADIOLIST, MODE_MENU, MODE_GAUGE , + MODE_TEXTBOX, MODE_PASSWORDBOX}; #define OPT_MSGBOX 1000 #define OPT_CHECKLIST 1001 @@ -23,12 +29,267 @@ enum mode { MODE_NONE, MODE_INFOBOX, MODE_MSGBOX, MODE_YESNO, MODE_CHECKLIST, #define OPT_RADIOLIST 1006 #define OPT_GAUGE 1007 #define OPT_INFOBOX 1008 -#define OPT_TEXTBOX 1009 +#define OPT_TEXTBOX 1009 +#define OPT_PASSWORDBOX 1010 + +static void usage(int err) { + newtFinished(); + fprintf (err ? stderr : stdout, + _("Box options: \n" + "\t--msgbox \n" + "\t--yesno \n" + "\t--infobox \n" + "\t--inputbox [init] \n" + "\t--passwordbox [init] \n" + "\t--textbox \n" + "\t--menu [tag item] ...\n" + "\t--checklist [tag item status]...\n" + "\t--radiolist [tag item stautus]...\n" + "\t--gauge \n" + "Options: (depend on box-option)\n" + "\t--clear clear screen on exit\n" + "\t-defaultno default no button\n" + "\t--default-item set default string\n" + "\t--fb use full buttons\n" + "\t--nocancel no cancel button\n" + "\t--noitem display tags only\n" + "\t--separate-output output one line at a time\n" + "\t--output-fd output to fd, not stdout\n" + "\t--title display title\n" + "\t--backtitle <backtitle> display backtitle\n" + "\t--scrolltext force verical scrollbars\n\n")); + exit(err ? DLG_ERROR : 0 ); +} + +static void print_version(void) { + fprintf (stdout, _("whiptail (newt): %s\n"), VERSION); +} + +static void handleSighup(int signum) { + exit(DLG_ERROR); +} + +#if 0 +/* FIXME Copied from newt.c + * Place somewhere better -- dialogboxes? -- amck + */ +int wstrlen(const char *str, int len) { + mbstate_t ps; + wchar_t tmp; + int nchars = 0; + + if (!str) return 0; + if (!len) return 0; + if (len < 0) len = strlen(str); + memset(&ps,0,sizeof(mbstate_t)); + while (len > 0) { + int x,y; + + x = mbrtowc(&tmp,str,len,&ps); + if (x >0) { + str += x; + len -= x; + y = wcwidth(tmp); + if (y>0) + nchars+=y; + } else break; + } + return nchars; +} +#endif + +/* + * The value of *width is increased if it is not as large as the width of + * the line. + */ +static const char * lineWidth(int * width, const char * line, int *chrs) +{ + const char * s = line; + + if ( line == NULL ) + return 0; + + while ( *s != '\0' && *s != '\n' ) + s++; + + if ( *s == '\n' ) + s++; + + *chrs = _newt_wstrlen (line, s - line ); + *width = max(*width, *chrs); -static void usage(void) { - newtFinished(); - fprintf(stderr, "whiptail: bad parameters (see man whiptail(1) for details)\n"); - exit(DLG_ERROR); + return s; +} + + +/* + * cleanNewlines + * Handle newlines in text. Hack. + */ +void cleanNewlines (char *text) +{ + char *p = text; + while (*p) { + if ((*p == '\\') && (*(p+1) == 'n')) { + *p = '\n'; + *(p+1) = ' '; + } else { + p++; + } + } +} + +/* + * The height of a text string is added to height, and width is increased + * if it is not big enough to store the text string. + */ +static const char * textSize(int * height, int * width, + int maxWidth, + const char * text) +{ + int h = 0; + int w = 0; + int chrs = 0; + + + if ( text == NULL ) + return 0; + + while ( *text != '\0' ) { + h++; + text = lineWidth(width, text, &chrs); + /* Allow for text overflowing. May overestimate a bit */ + h += chrs / maxWidth; + } + + h += 2; + w += 2; + + *height += h; + *width += w; + + *width = min(*width, maxWidth); + return text; +} + +/* + * Add space for buttons. + * NOTE: when this is internationalized, the button width might change. + */ +static void spaceForButtons(int * height, int * width, int count, int full) { + /* Make space for the buttons */ + if ( full ) { + *height += 4; + if ( count == 1 ) + *width = max(*width, 7); + else + *width = max(*width, 20); + } + else { + *height += 2; + if ( count == 1 ) + *width = max(*width, 7); + else + *width = max(*width, 19); + } +} + +static int menuSize(int * height, int * width, enum mode mode, + poptContext options) { + char ** argv = poptGetArgs(options); + char * * items = argv; + int h = 0; + int tagWidth = 0; + int descriptionWidth = 0; + int overhead = 10; + static char buf[20]; + + if ( argv == 0 || *argv == 0 ) + return 0; + + argv++; + if ( mode == MODE_MENU ) + overhead = 5; + + while ( argv[0] != 0 && argv[1] ) { + tagWidth = max(tagWidth, strlen(argv[0])); + descriptionWidth = max(descriptionWidth, strlen(argv[1])); + + if ( mode == MODE_MENU ) + argv += 2; + else + argv += 3; + h++; + } + + *width = max(*width, tagWidth + descriptionWidth + overhead); + *width = min(*width, SLtt_Screen_Cols); + + h = min(h, SLtt_Screen_Rows - *height - 4); + *height = *height + h + 1; + sprintf(buf, "%d", h); + *items = buf; + return 0; +} + +/* + * Guess the size of a window, given what will be displayed within it. + */ +static void guessSize(int * height, int * width, enum mode mode, + int * flags, int fullButtons, + const char * title, const char * text, + poptContext options) { + + int w = 0, h = 0, chrs = 0; + + textSize(&h, &w, SLtt_Screen_Cols -4 , text); /* Width and height for text */ + lineWidth(&w, title, &chrs); /* Width for title */ + + if ( w > 0 ) + w += 4; + + switch ( mode ) { + case MODE_CHECKLIST: + case MODE_RADIOLIST: + case MODE_MENU: + spaceForButtons(&h, &w, *flags & FLAG_NOCANCEL ? 1 : 2, + fullButtons); + menuSize(&h, &w, mode, options); + break; + case MODE_YESNO: + case MODE_MSGBOX: + spaceForButtons(&h, &w, 1, fullButtons); + break; + case MODE_INPUTBOX: + spaceForButtons(&h, &w, *flags & FLAG_NOCANCEL ? 1 : 2, + fullButtons); + h += 1; + break; + case MODE_GAUGE: + h += 2; + break; + case MODE_NONE: + break; + default: + break; + }; + + /* + * Fixed window-border overhead. + * NOTE: This will change if we add a way to turn off drop-shadow and/or + * box borders. That would be desirable for display-sized screens. + */ + w += 2; + h += 2; + + if ( h > SLtt_Screen_Rows - 1 ) { + h = SLtt_Screen_Rows - 1; + *flags |= FLAG_SCROLL_TEXT; + w += 2; /* Add width of slider - is this right? */ + } + + *width = min(max(*width, w), SLtt_Screen_Cols); + *height = max(*height, h); } char * @@ -44,7 +305,7 @@ readTextFile(const char * filename) } if ( (buf = malloc(s.st_size)) == 0 ) - fprintf(stderr, "%s: too large to display.\n", filename); + fprintf(stderr, _("%s: too large to display.\n"), filename); if ( read(fd, buf, s.st_size) != s.st_size ) { perror(filename); @@ -59,9 +320,10 @@ int main(int argc, const char ** argv) { poptContext optCon; int arg; const char * optArg; - const char * text; + char * text; const char * nextArg; char * end; + struct sigaction sa; int height; int width; int fd = -1; @@ -71,16 +333,19 @@ int main(int argc, const char ** argv) { int noItem = 0; int clear = 0; int scrollText = 0; - int rc = 1; + int rc = DLG_CANCEL; int flags = 0; int defaultNo = 0; int separateOutput = 0; + int fullButtons = 0; int outputfd = 2; FILE *output = stderr; const char * result; const char ** selections, ** next; char * title = NULL; + char *default_item = NULL; char * backtitle = NULL; + int help = 0, version = 0; struct poptOption optionsTable[] = { { "backtitle", '\0', POPT_ARG_STRING, &backtitle, 0 }, { "checklist", '\0', 0, 0, OPT_CHECKLIST }, @@ -95,16 +360,25 @@ int main(int argc, const char ** argv) { { "msgbox", '\0', 0, 0, OPT_MSGBOX }, { "nocancel", '\0', 0, &noCancel, 0 }, { "noitem", '\0', 0, &noItem, 0 }, + { "default-item", '\0', POPT_ARG_STRING, &default_item, 0}, { "notags", '\0', 0, &noTags, 0 }, { "radiolist", '\0', 0, 0, OPT_RADIOLIST }, { "scrolltext", '\0', 0, &scrollText, 0 }, { "separate-output", '\0', 0, &separateOutput, 0 }, { "title", '\0', POPT_ARG_STRING, &title, 0 }, - { "yesno", '\0', 0, 0, OPT_YESNO }, { "textbox", '\0', 0, 0, OPT_TEXTBOX }, + { "yesno", '\0', 0, 0, OPT_YESNO }, + { "passwordbox", '\0', 0, 0, OPT_PASSWORDBOX }, + { "output-fd", '\0', POPT_ARG_INT, &outputfd, 0 }, + { "help", 'h', 0, &help, 0, NULL, NULL }, + { "version", 'v', 0, &version, 0, NULL, NULL }, { 0, 0, 0, 0, 0 } }; - + + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + optCon = poptGetContext("whiptail", argc, argv, optionsTable, 0); while ((arg = poptGetNextOpt(optCon)) > 0) { @@ -112,54 +386,71 @@ int main(int argc, const char ** argv) { switch (arg) { case OPT_INFOBOX: - if (mode != MODE_NONE) usage(); + if (mode != MODE_NONE) usage(WAS_ERROR); mode = MODE_INFOBOX; break; case OPT_MENU: - if (mode != MODE_NONE) usage(); + if (mode != MODE_NONE) usage(WAS_ERROR); mode = MODE_MENU; break; case OPT_MSGBOX: - if (mode != MODE_NONE) usage(); + if (mode != MODE_NONE) usage(WAS_ERROR); mode = MODE_MSGBOX; break; - case OPT_TEXTBOX: - if (mode != MODE_NONE) usage(); - mode = MODE_TEXTBOX; - break; + + case OPT_TEXTBOX: + if (mode != MODE_NONE) usage(WAS_ERROR); + mode = MODE_TEXTBOX; + break; + + case OPT_PASSWORDBOX: + if (mode != MODE_NONE) usage(WAS_ERROR); + mode = MODE_PASSWORDBOX; + break; + case OPT_RADIOLIST: - if (mode != MODE_NONE) usage(); + if (mode != MODE_NONE) usage(WAS_ERROR); mode = MODE_RADIOLIST; break; case OPT_CHECKLIST: - if (mode != MODE_NONE) usage(); + if (mode != MODE_NONE) usage(WAS_ERROR); mode = MODE_CHECKLIST; break; case OPT_FULLBUTTONS: + fullButtons = 1; useFullButtons(1); break; case OPT_YESNO: - if (mode != MODE_NONE) usage(); + if (mode != MODE_NONE) usage(WAS_ERROR); mode = MODE_YESNO; break; case OPT_GAUGE: - if (mode != MODE_NONE) usage(); + if (mode != MODE_NONE) usage(WAS_ERROR); mode = MODE_GAUGE; break; case OPT_INPUTBOX: - if (mode != MODE_NONE) usage(); + if (mode != MODE_NONE) usage(WAS_ERROR); mode = MODE_INPUTBOX; break; } } + if (help) { + usage(NO_ERROR); + exit(0); + } + if (version) { + print_version(); + exit(0); + } + if (arg < -1) { fprintf(stderr, "%s: %s\n", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), @@ -173,19 +464,19 @@ int main(int argc, const char ** argv) { exit (DLG_ERROR); } - if (mode == MODE_NONE) usage(); + if (mode == MODE_NONE) usage(WAS_ERROR); - if (!(text = poptGetArg(optCon))) usage(); + if (!(text = poptGetArg(optCon))) usage(WAS_ERROR); - if ( mode == MODE_TEXTBOX ) text = readTextFile(text); + if (mode == MODE_TEXTBOX ) text = readTextFile(text); - if (!(nextArg = poptGetArg(optCon))) usage(); + if (!(nextArg = poptGetArg(optCon))) usage(WAS_ERROR); height = strtoul(nextArg, &end, 10); - if (*end) usage(); + if (*end) usage(WAS_ERROR); - if (!(nextArg = poptGetArg(optCon))) usage(); + if (!(nextArg = poptGetArg(optCon))) usage(WAS_ERROR); width = strtoul(nextArg, &end, 10); - if (*end) usage(); + if (*end) usage(WAS_ERROR); if (mode == MODE_GAUGE) { fd = dup(0); @@ -193,12 +484,24 @@ int main(int argc, const char ** argv) { if (open("/dev/tty", O_RDWR) != 0) perror("open /dev/tty"); } + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handleSighup; + sigaction(SIGWINCH, &sa, NULL); + newtInit(); newtCls(); + + cleanNewlines(text); + + if ( height <= 0 || width <= 0 ) + guessSize(&height, &width, mode, &flags, fullButtons, title, text, + optCon); + width -= 2; height -= 2; - newtOpenWindow((80 - width) / 2, (24 - height) / 2, width, height, title); + newtOpenWindow((SLtt_Screen_Cols - width) / 2, + (SLtt_Screen_Rows - height) / 2, width, height, title); if (backtitle) newtDrawRootText(0, 0, backtitle); @@ -224,18 +527,24 @@ int main(int argc, const char ** argv) { case MODE_INPUTBOX: rc = inputBox(text, height, width, optCon, flags, &result); - if (rc == DLG_OKAY) fprintf(output, "%s", result); + if (rc == DLG_OKAY) fprintf(output, "%s", result); + break; + + case MODE_PASSWORDBOX: + rc = inputBox(text, height, width, optCon, flags | FLAG_PASSWORD, + &result); + if (rc == DLG_OKAY) fprintf (output, "%s", result); break; case MODE_MENU: - rc = listBox(text, height, width, optCon, flags, &result); + rc = listBox(text, height, width, optCon, flags, default_item, &result); if (rc == DLG_OKAY) fprintf(output, "%s", result); break; case MODE_RADIOLIST: rc = checkList(text, height, width, optCon, 1, flags, &selections); - if (rc == DLG_OKAY) { - fprintf(output, "%s", selections[0]); + if (rc == DLG_OKAY) { + fprintf(output, "%s", selections[0]); free(selections); } break; @@ -263,14 +572,14 @@ int main(int argc, const char ** argv) { break; default: - usage(); + usage(WAS_ERROR); } - if (rc == -1) usage(); + if (rc == DLG_ERROR) usage(WAS_ERROR); if (clear) newtPopWindow(); newtFinished(); - return rc; + return ( rc == DLG_ESCAPE ) ? -1 : rc; } diff --git a/whiptcl.c b/whiptcl.c index 6e496f8..7d01f34 100644 --- a/whiptcl.c +++ b/whiptcl.c @@ -1,6 +1,8 @@ +#include "config.h" #include <string.h> #include <stdlib.h> +#include "nls.h" #include "dialogboxes.h" #include "newt.h" #include "popt.h" @@ -72,6 +74,7 @@ static int wtCmd(ClientData clientData, Tcl_Interp * interp, int argc, const char * result; const char ** selections, ** next; char * title = NULL; + char *default_item = NULL; struct poptOption optionsTable[] = { { "checklist", '\0', 0, 0, OPT_CHECKLIST }, { "defaultno", '\0', 0, &defaultNo, 0 }, @@ -83,10 +86,15 @@ static int wtCmd(ClientData clientData, Tcl_Interp * interp, int argc, { "radiolist", '\0', 0, 0, OPT_RADIOLIST }, { "scrolltext", '\0', 0, &scrollText, 0 }, { "title", '\0', POPT_ARG_STRING, &title, 0 }, + { "default-item", '\0', POPT_ARG_STRING, &default_item, 0 }, { "yesno", '\0', 0, 0, OPT_YESNO }, { 0, 0, 0, 0, 0 } }; - + + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + optCon = poptGetContext("whiptcl", argc, argv, optionsTable, 0); while ((arg = poptGetNextOpt(optCon)) > 0) { @@ -194,15 +202,15 @@ static int wtCmd(ClientData clientData, Tcl_Interp * interp, int argc, case MODE_INPUTBOX: rc = inputBox(text, height, width, optCon, flags, &result); - if (!rc) { + if (rc ==DLG_OKAY) { interp->result = strdup(result); interp->freeProc = TCL_DYNAMIC; } break; case MODE_MENU: - rc = listBox(text, height, width, optCon, flags, &result); - if (!rc) { + rc = listBox(text, height, width, optCon, flags, default_item, &result); + if (rc==DLG_OKAY) { interp->result = strdup(result); interp->freeProc = TCL_DYNAMIC; } @@ -210,7 +218,7 @@ static int wtCmd(ClientData clientData, Tcl_Interp * interp, int argc, case MODE_RADIOLIST: rc = checkList(text, height, width, optCon, 1, flags, &selections); - if (!rc) { + if (rc==DLG_OKAY) { interp->result = strdup(selections[0]); interp->freeProc = TCL_DYNAMIC; } @@ -219,7 +227,7 @@ static int wtCmd(ClientData clientData, Tcl_Interp * interp, int argc, case MODE_CHECKLIST: rc = checkList(text, height, width, optCon, 0, flags, &selections); - if (!rc) { + if (rc==DLG_OKAY) { for (next = selections; *next; next++) Tcl_AppendElement(interp, *next); @@ -241,6 +249,8 @@ static int wtCmd(ClientData clientData, Tcl_Interp * interp, int argc, Tcl_SetVar(interp, "whiptcl_canceled", (rc == DLG_CANCEL) ? "1" : "0", 0); + Tcl_SetVar(interp, "whiptcl_escaped", (rc == DLG_ESCAPE) ? "1" : "0", + 0); return TCL_OK; }