commit c3e152642868ef546b1edfcc2e1c5c4ac70dab40
parent cac5b29726e7921c332a8428fad7d141d30fdfa8
Author: pancake <pancake@nopcode.org>
Date:   Wed, 25 Aug 2010 05:00:20 +0200
initial implementation of multiline input widget
swk_text is the widget to enter multiple lines of text
supports scrolling, cursor and basic text manipulation
filter keycodes at backend level on x11 and sdl
add support for 5 button mouse to gi_x11
add animation between screens in t/test.c
enter key no longers activates the widget, press control+enter
Diffstat:
| Makefile | | | 8 | ++++---- | 
| config.def.h | | | 2 | +- | 
| gi_sdl.c | | | 12 | ++++++++++-- | 
| gi_x11.c | | | 37 | +++++++++++++++++++++++++++++++++---- | 
| swk.c | | | 44 | +++++++++++++++++++++++++++++--------------- | 
| swk.h | | | 37 | +++++++++++++++++++++++++++++++++++-- | 
| t/test.c | | | 40 | +++++++++++++++++++++++++++++++++++++++- | 
| text.c | | | 292 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
8 files changed, 443 insertions(+), 29 deletions(-)
diff --git a/Makefile b/Makefile
@@ -38,7 +38,7 @@ config.mk: config.h
 clean:
 	echo >swk.mk
 	cd t && ${MAKE} clean
-	rm -f swk.pc swk.mk libswk.a libswk.so swk.o ${GI_OBJS}
+	rm -f swk.pc swk.mk libswk.a libswk.so swk.o text.o ${GI_OBJS}
 
 install:
 	mkdir -p ${DESTDIR}/${INCDIR}
@@ -62,14 +62,14 @@ static: libswk.a
 
 shared: libswk.so
 
-libswk.so: config.mk swk.o ${GI_OBJS}
+libswk.so: config.mk swk.o text.o ${GI_OBJS}
 	${CC} ${CFLAGS} -fPIC -shared swk.c ${GI_SRCS} -o libswk.so
 
 swk.o: config.mk
 
-libswk.a: config.mk swk.o ${GI_OBJS}
+libswk.a: config.mk swk.o text.o ${GI_OBJS}
 	rm -f libswk.a
-	ar qcvf libswk.a swk.o ${GI_OBJS}
+	ar qcvf libswk.a text.o swk.o ${GI_OBJS}
 	echo SWKINCS+=-I${PREFIX}/include > swk.mk
 	echo SWKLIB+=${PREFIX}/lib/libswk.a >> swk.mk
 	echo SWKLIBS+=${GI_LIBS} >> swk.mk
diff --git a/config.def.h b/config.def.h
@@ -22,7 +22,7 @@
 
 /* key bindings */
 static SwkKeyBind keys[] = {
-	{ 0, '\n',   swk_focus_activate},
+	{ Ctrl, '\n',   swk_focus_activate},
 	{ Ctrl, 'j',   swk_focus_next },
 	{ Ctrl, 'k',   swk_focus_prev },
 	//{ Ctrl,  8 ,   swk_focus_first },
diff --git a/gi_sdl.c b/gi_sdl.c
@@ -171,7 +171,6 @@ swk_gi_event(SwkWindow *w, int dowait) {
 		break;
 	case SDL_MOUSEBUTTONUP:
 		//fprintf(stderr, "event: up %d (%d,%d)\n", event.button.button,event.button.x,event.button.y);
-
 		mousedown = 0;
 		if(!mousemoved) {
 			ret->type = EClick;
@@ -199,8 +198,17 @@ swk_gi_event(SwkWindow *w, int dowait) {
 			ret->data.key.modmask |= Meta;
 		if(ret->data.key.keycode != 0 && event.key.keysym.unicode != 0) {
 			ret->data.key.keycode = event.key.keysym.unicode;
-		} else // TODO key aliases defined in config.h
+		}
 		switch((int)event.key.keysym.sym) {
+		case 13:
+			ret->data.key.keycode = '\n';
+			break;
+		case 275:
+			ret->data.key.keycode = KRight;
+			break;
+		case 276:
+			ret->data.key.keycode = KLeft;
+			break;
 		case 1073741906: // n900 up key
 		case 273:
 			ret->data.key.keycode = KUp;
diff --git a/gi_x11.c b/gi_x11.c
@@ -13,8 +13,8 @@
 #define SWK
 #include "config.h"
 
-#define FONTNAME "-*-*-medium-*-*-*-14-*-*-*-*-*-*-*"
-//#define FONTNAME "10x20"
+//#define FONTNAME "-*-*-medium-*-*-*-14-*-*-*-*-*-*-*"
+#define FONTNAME "10x20"
 
 static int fs = FONTSIZE; // TODO: we need fsW and fsH
 static Window window;
@@ -127,7 +127,24 @@ swk_gi_event(SwkWindow *w, int dowait) {
 		mousedown = 0;
 		if(!mousemoved) {
 			ret->type = EClick;
-			ret->data.click.button = event.xbutton.state;
+			switch(event.xbutton.state) {
+			case 4096:
+				ret->data.click.button = 4;
+				break;
+			case 2048:
+				ret->data.click.button = 5;
+				break;
+			case 1024:
+				ret->data.click.button = 2;
+				break;
+			case 512:
+				ret->data.click.button = 3;
+				break;
+			case 256:
+				ret->data.click.button = 1;
+				break;
+			}
+printf ("STATE=%d\n", event.xbutton.state);
 			ret->data.click.point.x = event.xbutton.x / fs;
 			ret->data.click.point.y = event.xbutton.y / fs;
 		}
@@ -144,6 +161,12 @@ swk_gi_event(SwkWindow *w, int dowait) {
 		printf("ksym=%d\n", (int)ksym);
 
 		switch(ksym) {
+		case 65535: // supr
+			ret->data.key.keycode = 127;
+			break;
+		case 65511:
+			ret->data.key.keycode = KUp;
+			break;
 		case 65362:
 			ret->data.key.keycode = KUp;
 			break;
@@ -220,7 +243,8 @@ swk_gi_fill(Rect r, int color, int lil) {
 	} else
 	if(lil==2) {
 		area.x/=3;
-		area.width/=4;
+		area.x-=2;
+		area.width=2;///=4;
 		area.y+=4;
 		area.height-=4;
 	} else if (lil==3) {
@@ -247,6 +271,11 @@ swk_gi_text(Rect r, const char *text) {
 	if(!text||!*text)
 		return;
 	XSetForeground(dc->dpy, dc->gc, col[ColorFG]);
+//	XmbDrawString(dc->dpy, dc->canvas, dc->font.set, dc->gc, x, y, text, strlen(text));
+	if(dc->font.xfont)
+		XSetFont(dc->dpy, dc->gc, dc->font.xfont->fid);
+	//printf("## %d\n", dc->font.xfont);
+	//XmbDrawString(dc->dpy, dc->canvas, dc->font.set, 5+r.x*fs, ((1+r.y)*fs)-3, text, strlen (text));
 	XDrawString(dc->dpy, dc->canvas, dc->gc, 5+r.x*fs, ((1+r.y)*fs)-3, text, strlen (text));
 }
 
diff --git a/swk.c b/swk.c
@@ -71,7 +71,8 @@ swk_update() {
 		SwkBox *b = w->boxes[0];
 		swk_fit(w);
 		swk_gi_clear();
-		if(!w->colpos) {
+		//if(!w->colpos) {
+		if(w->colpos<2) {
 			b = w->boxes[1];
 			count--;
 			col = w->r.w;
@@ -347,13 +348,20 @@ swk_focus_prev() {
 
 void
 swk_label(SwkEvent *e) {
+	char text[128]; // XXX
+	int cut, len;
 	Rect r;
 	switch(e->type) {
 	case EExpose:
 		r = e->box->r;
-		r.w += 6;
-		swk_gi_text(r, e->box->text);
-		r.w -= 6;
+		r.w += 4;
+		cut = r.w*2;
+		strncpy(text, e->box->text, sizeof(text)-1);
+		len = strlen(text);
+		if (len>cut)
+			text[cut]=0;
+		swk_gi_text(r, text);
+		r.w -= 4;
 		if(e->win->box == e->box)
 			swk_gi_line(r.x, r.y+1, r.w, 0, ColorHI);
 		break;
@@ -422,18 +430,24 @@ swk_entry(SwkEvent *e) {
 		break;
 	case EExpose:
 		// XXX: add support for cursor (handle arrow keys)
-	#ifdef USE_SDL
-		len = 4*e->box->r.x;
-		len += 2*strlen(e->box->text)+1;
-	#else
-		len = 3*e->box->r.x;
-		len += strlen(e->box->text)+1;
-	#endif
 		swk_gi_fill(e->box->r, ColorBG, 1);
 		swk_label(e);
-		{
-		Rect r = {len, e->box->r.y, 1, 1 };
-		swk_gi_fill(r, ColorFG, 2);
+		/* cursor */ {
+			int cut = e->box->r.w*2;
+			#ifdef USE_SDL
+			len = 4*e->box->r.x;
+			#else
+			len = 3*e->box->r.x;
+			#endif
+			if (strlen(e->box->text)>cut)
+				len += cut;
+			#ifdef USE_SDL
+			else len += 2*strlen(e->box->text)+1;
+			#else
+			else len += strlen(e->box->text)+1;
+			#endif
+			Rect r = { len, e->box->r.y, 1, 1 };
+			swk_gi_fill(r, ColorFG, 2);
 		}
 		break;
 	}
@@ -565,7 +579,7 @@ swk_image(SwkEvent *e) {
 	if(e->box->data == NULL) {
 		e->box->data = swk_gi_img_load(e->box->text);
 		if(!e->box->data)
-			fprintf(stderr, "Cannot find image %s\n", e->box->text);
+			fprintf(stderr, "Cannot open image %s\n", e->box->text);
 	}
 	switch(e->type) {
 	case EExpose:
diff --git a/swk.h b/swk.h
@@ -8,7 +8,7 @@
 typedef enum { EVoid, EClick, EMotion, EKey, EExpose, EQuit, ELast } SwkEventType;
 typedef enum { Shift=1, Ctrl=2, Alt=4, Meta=8 } SwkKeyMod;
 typedef enum { ColorFG, ColorBG, ColorHI, ColorTF, ColorCC, ColorLast } Palete;
-typedef enum { KUp=0xe0, KDown=0xe1, KLeft=0xe2, KRight=0xe3 } SwkKeyCode;
+typedef enum { KDel=8, KSupr=127, KUp=0xe0, KDown=0xe1, KLeft=0xe2, KRight=0xe3 } SwkKeyCode;
 
 typedef struct SwkBox SwkBox;
 typedef struct SwkEvent SwkEvent;
@@ -124,7 +124,7 @@ void swk_gi_flip();
 void swk_gi_line(int x1, int y1, int x2, int y2, int color);
 void swk_gi_fill(Rect r, int color, int lil);
 void swk_gi_rect(Rect r, int color);
-void swk_gi_text(Rect r, const char *text);
+void swk_gi_text(Rect r, const char *t);
 
 /* images */
 void swk_gi_img(Rect r, void *img);
@@ -133,3 +133,36 @@ void* swk_gi_img_load(const char *str);
 void swk_gi_img_free(void *s);
 void swk_gi_img_set(void *img, int x, int y, int color);
 int swk_gi_img_get(void *img, int x, int y);
+
+/* text.c */
+typedef struct {
+	const char *otext;
+	int cur;
+	int xcur;
+	int ycur;
+	char *text;
+	int len;
+	int size;
+	int yscroll;
+	int sel[2];
+	int selmode;
+} Text;
+
+int text_rowcount(Text *t);
+int text_rowoff(Text *t, int row);
+int text_rowcol(Text *t, int off, int *col);
+int text_off(Text *t, int col, int row);
+char * text_sub(Text *t, int col, int row, int rows);
+void text_init(Text *t, const char *text);
+void text_set(Text *t, const char *text);
+char * text_get(Text *t, int from, int to);
+void text_sync(Text *t);
+void text_cur(Text *t, int num, int dir);
+void text_ins(Text *t, const char *str, int app);
+void text_insc(Text *t, char ch, int app);
+void text_del(Text *t, int num, int dir);
+void text_sel(Text *t, int begin, int end);
+void text_sel_mode(Text *t, int enable);
+
+/* text.c widgets */
+void swk_text(SwkEvent *e);
diff --git a/t/test.c b/t/test.c
@@ -63,6 +63,28 @@ static SwkBox about[] = {
 	{ .cb=NULL }
 };
 
+static void animate(SwkWindow *w, int s) {
+	int i;
+	if(w->colpos<1)
+		w->colpos = 1;
+	if(s<0 && w->colpos>w->r.w)
+		w->colpos=w->r.w;
+	s*=(w->r.w/20);
+	for(i=0;i<100;i++) {
+		if (w->colpos<1) {
+			w->colpos=0;
+			break;
+		}
+		if (w->colpos>w->r.w) {
+			w->colpos=w->r.w;
+			break;
+		}
+		w->colpos+=s;
+		swk_update(w);
+		usleep(10000);
+	}
+	w->col=(w->colpos>0)?1:0;
+}
 static void mybutton_about_ok(SwkEvent *e) {
 	if(e->type == EClick) {
 		e->win->boxes[e->win->col] = helloworld;
@@ -73,12 +95,21 @@ static void mybutton_about_ok(SwkEvent *e) {
 
 static void mybutton_about(SwkEvent *e) {
 	if(e->type == EClick) {
+		animate(e->win, -1);
 		e->win->boxes[e->win->col] = about;
 		swk_update(e->win);
 	}
 	swk_button(e);
 }
 
+static void mybutton_shrink(SwkEvent *e) {
+	if(e->type == EClick) {
+		animate(e->win, 1);
+		swk_update(e->win);
+	}
+	swk_button(e);
+}
+
 static void mybutton_close(SwkEvent *e) {
 	if(e->type == EClick) {
 		e->win->boxes[e->win->col] = helloworld;
@@ -111,6 +142,8 @@ static void mybutton_numscroll(SwkEvent *e) {
 	swk_button(e);
 }
 
+Text txt = {"Hello\nworld\n"};
+
 static SwkBox helloworld[] = {
 	{ .cb=swk_label, .text="Press a button", },
 	SWK_BOX_NEWLINE(1),
@@ -125,6 +158,10 @@ static SwkBox helloworld[] = {
 	{ .cb=swk_label, .text="Click here ->" },
 	{ .cb=swk_sketch },
 	SWK_BOX_NEWLINE(2),
+	{ .cb=swk_label, .text="multiline:" },
+	{ .cb=swk_text, .text="Hello\nWorld\n", .data=&txt, .r.h=4, .r.w=10 },
+	{ .cb=swk_filler, },
+	SWK_BOX_NEWLINE(1),
 	{ .cb=swk_image, .text="image.png" },
 	{ .cb=swk_image, .text="image.png" },
 	{ .cb=swk_image, .text="image.png" },
@@ -151,7 +188,8 @@ static SwkBox helloworld[] = {
 static SwkBox column[] = {
 	{ .cb=swk_label, .text="this is a column", },
 	SWK_BOX_NEWLINE(-1),
-	{ .cb=swk_label, .text="text in column", },
+	{ .cb=mybutton_shrink, .text="shrink", },
+	{ .cb=swk_label, .text="hide this column", },
 	{ .cb=NULL }
 };
 
diff --git a/text.c b/text.c
@@ -0,0 +1,292 @@
+/* See LICENSE file for copyright and license details. */
+#define _BSD_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <swk.h>
+
+int
+text_rowcount(Text *t) {
+	int rows = 1;
+	char *p;
+	for(p=t->text;*p;p++)
+		if(*p=='\n')
+			rows++;
+	return rows;
+}
+
+int
+text_rowoff(Text *t, int row) {
+	char *p;
+	int off = 0;
+	if(row<1)
+		return 0;
+	for(p=t->text;row&&*p;p++) {
+		if(*p=='\n')
+			row--;
+		off++;
+	}
+	return off;
+}
+
+int
+text_rowcol(Text *t, int off, int *col) {
+	char *p,*nl = NULL;
+	char *e = t->text+off;
+	int r = 0;
+	int c = 0;
+	for(p=t->text;*p&&p<=e;p++) {
+		if(*p=='\n') {
+			r++;
+			c = 0;
+			nl = p+1;
+		} else c++;
+	}
+	if(col) *col = c;
+	return r;
+}
+int
+text_off(Text *t, int col, int row) {
+	int off = text_rowoff(t, row);
+	int len = strlen (t->text);
+	if (col>len)
+		col = len;
+	return off+(t->text[off])?col:0;
+}
+
+char *
+text_sub(Text *t, int col, int row, int rows) {
+	// find row number N in text
+	// +=col if < \n
+	// count N rows and \0
+	return NULL;
+}
+
+void
+text_init(Text *t, const char *str) {
+	if(!t->text) {
+		t->len = strlen (t->otext);
+		t->size = (t->len+1)*2;
+		t->text = malloc (t->size);
+		if (str) strcpy(t->text, t->otext);
+		else strcpy(t->text, t->otext);
+	}
+}
+
+void
+text_set(Text *t, const char *text) {
+	int len = strlen (text);
+	if(t->text) {
+		if(len>t->size) {
+			t->size = (len+10)*2;
+			t->text = realloc(t->text, t->size);
+		}
+		strcpy(t->text, text);
+	} else text_init(t, text);
+	text_sync(t);
+}
+
+char *
+text_get(Text *t, int from, int to) {
+	/* get buffer between from and to offsets */
+	char *p;
+	if(to!=-1&&(to<from || from>t->len))
+		return strdup ("");
+	if(to>t->len||to==-1)
+		to = t->len;
+	if (!t->text)
+		if (t->otext)
+			t->text = t->otext;
+		else t->text = "";
+	p = strdup (t->text+from);
+	//if(to!=-1) p[to-from] = '\0';
+	return p;
+}
+
+void
+text_sync(Text *t) {
+	// count cols, rows, fix xcur, ycur from cur, etc..
+	t->len = strlen(t->text);
+	t->ycur = text_rowcol(t, t->cur, &t->xcur);
+}
+
+void
+text_cur(Text *t, int num, int dir) {
+	if(dir) t->cur += (num*dir);
+	else t->cur = num;
+	if(t->cur<0)
+		t->cur = 0;
+	if(t->cur>t->len)
+		t->cur = t->len;
+	if(t->text[t->cur]=='\n')
+		t->cur+=dir;
+}
+
+void
+text_resize(Text *t, int newsize) {
+	if (newsize>t->size) {
+		t->text = realloc(t->text, newsize);
+		t->size = newsize;
+	}
+}
+
+void
+text_insc(Text *t, char ch, int app) {
+	char str[2] = {ch};
+	text_ins(t, str, app);
+}
+
+void
+text_ins(Text *t, const char *str, int app) {
+	int len = strlen(str);
+	text_resize(t, (2*len)+t->size);
+	if(app) {
+		char *tmp = strdup(t->text+t->cur);
+		strcpy(t->text+t->cur, str);
+		strcat(t->text+t->cur+len, tmp);
+		free(tmp);
+	} else memcpy(t->text+t->cur, str, len);
+	t->cur += len;
+}
+
+void
+text_del(Text *t, int num, int dir) {
+	switch(dir) {
+	case -1:
+		if(t->cur-num<0)
+			num = 0;
+		strcpy(t->text+t->cur-num, t->text+t->cur);
+		t->cur -= num;
+		break;
+	case 0:
+		t->text[num] = 0;
+		t->len = strlen (t->text);
+		if (t->cur>t->len)
+			t->cur = t->len;
+		break;
+	case 1:
+		if (t->cur+num>=t->len)
+			num = t->len-t->cur;
+		strcpy(t->text+t->cur, t->text+t->cur+num);
+		break;
+	}
+	text_sync(t);
+}
+
+void
+text_sel(Text *t, int begin, int end) { // if off != off2 text is selected
+	t->sel[0] = begin;
+	t->sel[1] = end;
+}
+
+void
+text_sel_mode(Text *t, int enable) {
+	t->selmode = enable;
+}
+
+/* swk widget */
+void
+swk_text(SwkEvent *e) {
+	Text *t = (Text*)e->box->data;
+	int row, len, key;
+	char *ptr;
+
+	text_init(e->box->data, e->box->text);
+	text_sync(e->box->data);
+	switch(e->type) {
+	case EClick:
+		// TODO: text_sel//
+		switch(e->data.click.button) {
+		case 1:
+			//text_sel_mode(1)
+			break;
+		case 2:
+			// activate link (TODO: implement hyperlinks)
+			break;
+		case 3:
+			//text_sel_mode(0)
+			break;
+		}
+		break;
+	case EKey:
+		key = e->data.key.keycode;
+		if(e->data.key.modmask&Ctrl)
+			return;
+		if(key == KDel)
+			text_del(e->box->data, 1, -1);
+		else if(key == KSupr)
+			text_del(e->box->data, 1, 1);
+		else if(key=='\n'||(key>=' '&&key<='~'))
+			text_insc(e->box->data, key, 1);
+		else if(key==KUp)
+			text_cur(e->box->data, 10, -1); //XXX
+		else if(key==KDown)
+			text_cur(e->box->data, 10, 1); //XXX
+		else if(key==KLeft)
+			text_cur(e->box->data, 1, -1);
+		else if(key==KRight)
+			text_cur(e->box->data, 1, 1);
+		break;
+	case EExpose:
+		swk_gi_fill(e->box->r, ColorBG, 0);
+		row = t->ycur-2;
+		/* text */{
+		int len, rows = 0;
+		int y = e->box->r.y--;
+		char *p, *p0, *str;
+		str = text_get (e->box->data, text_rowoff(e->box->data,row), -1);
+		if(row<0) row = 0;
+		//printf("str(%s)\n", str);
+		for(p=p0=str;*p;p++) {
+			if(*p=='\n') {
+				if(++rows>e->box->r.h)
+					break;
+				*p = 0;
+				e->box->text = p0;
+				e->box->r.y++;
+				len = strlen(p0);
+				if(len>=e->box->r.w*3)
+					p0[(e->box->r.w*3)-1]=0;
+				swk_gi_text(e->box->r, p0);
+				p0 = p+1;
+			}
+		}
+		if((rows<=e->box->r.h-1)&&p0&&*p0) {
+			e->box->r.y++;
+			swk_gi_text(e->box->r, p0);
+		}
+		e->box->r.y = y;
+		free(str);
+		}
+		/* cursor */ {
+			int len, cut = e->box->r.w*2;
+			int row = ((Text*)e->box->data)->ycur;
+			int col = ((Text*)e->box->data)->xcur;
+		if(row>=e->box->r.h-1) row = e->box->r.h-2;
+#if 1
+			#ifdef USE_SDL
+			len = 4*e->box->r.x;
+			#else
+			len = 3*e->box->r.x;
+			#endif
+			if(col>cut)
+				len += cut;
+			#ifdef USE_SDL
+			else len += 2*col+1;
+			#else
+			else len += col+1;
+			#endif
+#endif
+			Text* t = e->box->data;
+			text_sync(e->box->data);
+
+			Rect r = { (e->box->r.x*3)+col, e->box->r.y+row, 1, 1};
+			swk_gi_fill(r, ColorHI, 2);
+		}
+		swk_gi_rect(e->box->r, ColorFG);
+		break;
+	default:
+		swk_label(e);
+		break;
+	}
+}