mirror of
https://github.com/dyne/FreeJ.git
synced 2026-02-11 07:19:32 +01:00
Remove jutils.h references from slw. Calling 'src' from 'lib' is nonsense. Signed-off-by: Luca Bigliardi <shammash@artha.org>
502 lines
9.0 KiB
C++
502 lines
9.0 KiB
C++
/* S-Lang console widgets
|
|
*
|
|
* thread-safe text console implementation using linklist
|
|
*
|
|
* (C) Copyright 2004-2006 Denis Rojo <jaromil@dyne.org>
|
|
*
|
|
* This source code is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Public License as published
|
|
* by the Free Software Foundation; either version 2 of the License,
|
|
* or (at your option) any later version.
|
|
*
|
|
* This source code is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
* Please refer to the GNU Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Public License along with
|
|
* this source code; if not, write to:
|
|
* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
|
|
#include <keycodes.h>
|
|
#include <abs_text_console.h>
|
|
|
|
#define ROWCHUNK 128 // chunk to add/sub when modifying the text line
|
|
|
|
|
|
TextConsole::TextConsole() {
|
|
|
|
// create the first row
|
|
cur_row = new Row();
|
|
rows.append(cur_row);
|
|
|
|
vis_row_in = cur_row;
|
|
|
|
}
|
|
|
|
TextConsole::~TextConsole() {
|
|
func("~TextConsole destroy");
|
|
|
|
// delete all rows (if any)
|
|
Row *l;
|
|
l = (Row*) rows.begin();
|
|
|
|
while(l) {
|
|
delete l;
|
|
l = (Row*) rows.begin();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TextConsole::feed(int key) {
|
|
// interprets a keycode and perform the action (or write a letter)
|
|
|
|
Row *r;
|
|
|
|
switch(key) {
|
|
|
|
case KEY_NEWLINE:
|
|
case KEY_ENTER:
|
|
|
|
r = new Row();
|
|
|
|
rows.insert_after(r, cur_row);
|
|
|
|
if(cur_row->pos < cur_row->len) {
|
|
// if cursor is not at the end of the row
|
|
|
|
move_string(r, cur_row,
|
|
cur_row->len - cur_row->pos);
|
|
|
|
}
|
|
|
|
cur_y++;
|
|
|
|
// scrolling
|
|
if(cur_y>h) {
|
|
|
|
vis_row_in = (Row*) vis_row_in->next;
|
|
cur_y = h;
|
|
|
|
} // else refresh_below();
|
|
|
|
cur_row = r;
|
|
cur_x = 0;
|
|
|
|
refresh();
|
|
|
|
break;
|
|
|
|
case KEY_CTRL_L:
|
|
refresh();
|
|
break;
|
|
|
|
case KEY_BACKSPACE_ASCII:
|
|
case KEY_BACKSPACE:
|
|
case KEY_BACKSPACE_APPLE:
|
|
case KEY_BACKSPACE_SOMETIMES:
|
|
|
|
cur_row->backspace();
|
|
|
|
if(cur_x > 0) {
|
|
|
|
cur_x--; // backwards with cursor
|
|
refresh_current();
|
|
|
|
} else // backspace at the beginning of a row
|
|
if(cur_row->prev) { // there is an upper row
|
|
|
|
r = (Row*) cur_row->prev;
|
|
|
|
// upper row is empty, just delete it
|
|
if(r->len <1) {
|
|
|
|
r->rem();
|
|
delete r;
|
|
cur_y--;
|
|
|
|
// upper row is not empty, append to end
|
|
} else {
|
|
|
|
r->pos = r->len;
|
|
move_string(r, cur_row, cur_row->len);
|
|
// delete current row (now empty)
|
|
cur_row->rem();
|
|
delete cur_row;
|
|
cur_y--;
|
|
|
|
cur_row = r;
|
|
}
|
|
|
|
if(cur_y <1) { // we are up in the screen
|
|
|
|
vis_row_in = cur_row;
|
|
cur_y = 0;
|
|
refresh();
|
|
|
|
} else { // or there is more up
|
|
|
|
// so delete a row
|
|
// cur_row->rem();
|
|
// delete cur_row;
|
|
|
|
// move cursor up one line
|
|
// cur_y--;
|
|
// refresh_below();
|
|
refresh();
|
|
|
|
}
|
|
|
|
cur_x = r->pos;
|
|
}
|
|
|
|
break;
|
|
|
|
case KEY_TAB:
|
|
if(cur_x < w) cur_x += 8;
|
|
if(cur_x > w) cur_x = w;
|
|
break;
|
|
|
|
case KEY_LEFT:
|
|
if(cur_x<=0) break;
|
|
cur_x--;
|
|
cur_row->pos--;
|
|
break;
|
|
|
|
case KEY_RIGHT:
|
|
if(cur_x >= w) break;
|
|
if(cur_row->pos >= cur_row->len) break;
|
|
cur_x++;
|
|
cur_row->pos++;
|
|
break;
|
|
|
|
case KEY_UP:
|
|
if(cur_y>0) {
|
|
|
|
cur_y--;
|
|
cur_row = (Row*) cur_row->prev;
|
|
if(cur_x > cur_row->len)
|
|
cur_x = cur_row->len;
|
|
cur_row->pos = cur_x;
|
|
|
|
} else { // scroll
|
|
|
|
if(!cur_row->prev) break;
|
|
vis_row_in = cur_row = (Row*) cur_row->prev;
|
|
refresh();
|
|
|
|
}
|
|
break;
|
|
|
|
case KEY_DOWN:
|
|
if(cur_y<h) {
|
|
|
|
if(cur_row->next) {
|
|
|
|
cur_y++;
|
|
cur_row = (Row*) cur_row->next;
|
|
// cursor positioning
|
|
if(cur_x > cur_row->len)
|
|
cur_x = cur_row->len;
|
|
cur_row->pos = cur_x;
|
|
//
|
|
}
|
|
|
|
} else { // scrolling
|
|
|
|
if(!cur_row->next) break;
|
|
cur_row = (Row*) cur_row->next;
|
|
// scroll down the pointer to upper row
|
|
vis_row_in = (Row*) vis_row_in->next;
|
|
refresh();
|
|
|
|
}
|
|
break;
|
|
|
|
|
|
default: // insert a new char
|
|
|
|
// fits in widget width?
|
|
if(cur_x < w) {
|
|
|
|
cur_row->insert_char(key);
|
|
|
|
refresh_current();
|
|
|
|
cur_x++;
|
|
|
|
} // else wrap to next line (TODO)
|
|
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
bool TextConsole::refresh() {
|
|
Row *r;
|
|
register int c;
|
|
|
|
if(!vis_row_in) return false;
|
|
else r = vis_row_in;
|
|
|
|
// tell the renderer to blank the surface for a refresh
|
|
// this is a pure virtual function here
|
|
blank();
|
|
|
|
for(c = 0; c < h; c++ ) {
|
|
|
|
if(r->text)
|
|
putnch(r->text, 0, c, r->len);
|
|
|
|
if(!r->next) break;
|
|
else r = (Row*) r->next;
|
|
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void TextConsole::refresh_current() {
|
|
register int c;
|
|
Row *r;
|
|
|
|
if(!vis_row_in) return;
|
|
else r = vis_row_in;
|
|
|
|
for( c = 0; c < h ; c++ ) {
|
|
|
|
if(r == cur_row) {
|
|
|
|
// implementation function
|
|
blank_row(c);
|
|
|
|
// widget console write
|
|
putnch(r->text, 0, c, r->len);
|
|
|
|
break;
|
|
}
|
|
|
|
if(!r->next) break;
|
|
else r = (Row*) r->next;
|
|
}
|
|
|
|
}
|
|
|
|
// void TextConsole::refresh_below() {
|
|
// register int c;
|
|
// Row *r;
|
|
// bool refr;
|
|
// bool blank_only;
|
|
|
|
// if(!vis_row_in) return;
|
|
// else r = vis_row_in;
|
|
|
|
// refr = false;
|
|
// blank_only = false;
|
|
|
|
// for(c = 0; c < h; c++) {
|
|
|
|
// if(r == cur_row) refr = true;
|
|
|
|
// if(refr) {
|
|
|
|
// blank_row(c);
|
|
|
|
// if(!blank_only)
|
|
// putnch(r->text, 0, c, r->len);
|
|
|
|
// }
|
|
|
|
// if(!r->next) blank_only = true;
|
|
// else r = (Row*)r->next;
|
|
// }
|
|
// }
|
|
|
|
int TextConsole::move_string(Row *dest, Row *src, int len) {
|
|
int dlen = len; // const int arg
|
|
|
|
// func("move string");
|
|
|
|
// insert the string in destination
|
|
dlen = dest->insert_string( &src->text[ src->pos ], dlen );
|
|
|
|
// delete the string from the source
|
|
dlen = src->delete_string( dlen );
|
|
|
|
return dlen;
|
|
}
|
|
|
|
Row::Row() {
|
|
text = NULL;
|
|
// initializing with a minimum chunk allocation??
|
|
// fit( 1 );
|
|
len = 0;
|
|
max = 0;
|
|
pos = 0;
|
|
}
|
|
|
|
Row::~Row() {
|
|
// free buffer
|
|
if(text) free(text);
|
|
}
|
|
|
|
int Row::insert_string(CHAR *str, int inlen) {
|
|
register int c;
|
|
|
|
|
|
// xpand the buffer if needed
|
|
fit( inlen );
|
|
|
|
// we are growing
|
|
len += inlen;
|
|
|
|
// slide right
|
|
for( c = len ; c > pos; c-- )
|
|
text[c] = text[c-inlen];
|
|
|
|
// termination
|
|
text[len+1] = EOT;
|
|
|
|
// new string at current position
|
|
for( c = 0; c < inlen; c++ )
|
|
text[pos+c] = str[c];
|
|
|
|
// advance position
|
|
// pos += inlen;
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
int Row::insert_char(CHAR ch) {
|
|
register int c;
|
|
|
|
// check if we need to expand the buffer
|
|
fit( 1 );
|
|
|
|
|
|
len++; // we are growing
|
|
|
|
// slide right
|
|
for( c = len; c > pos; c-- )
|
|
text[c] = text[c-1];
|
|
|
|
// termination
|
|
text[len+1] = EOT;
|
|
|
|
// new char at current position
|
|
text[pos] = ch;
|
|
|
|
pos++; // advance position
|
|
|
|
return len;
|
|
}
|
|
|
|
int Row::delete_string( int dlen ) {
|
|
register int c;
|
|
|
|
/* shortcut in case it's until the end of the row
|
|
* we got some optimization ongoing here ;) small graph:
|
|
*
|
|
* 0 pos len dlen
|
|
* |----*--------|----:
|
|
* xxxxx */
|
|
if( dlen + pos >= len ) {
|
|
// there is nothing to move on the right hand of deletion
|
|
len = pos; // reduce length without overwriting chars
|
|
return len;
|
|
}
|
|
|
|
|
|
/* proceed deleting dlen chars on the right
|
|
* sliding the rightmost chars to the left */
|
|
for( c = 0 ; c < dlen; c++) {
|
|
if( c + pos > len ) break;
|
|
text[c+pos] = text[c+pos+dlen];
|
|
}
|
|
|
|
len -= dlen;
|
|
|
|
return c;
|
|
}
|
|
|
|
int Row::backspace() {
|
|
register int c;
|
|
|
|
// left boundary
|
|
if(pos < 1) return len;
|
|
|
|
if(pos>0) {
|
|
|
|
if(pos >= len) {
|
|
// if at the end of the line just step one back
|
|
text[pos-1] = text[pos];
|
|
text[pos] = CHAR_BLANK;
|
|
} else {
|
|
// move all right of the cursor to 1 step left
|
|
for ( c = pos; c <= len; c++) {
|
|
text[c-1] = text[c];
|
|
text[c] = CHAR_BLANK;
|
|
}
|
|
}
|
|
|
|
pos--; // backwards with position
|
|
len--; // shrink down one
|
|
text[len+1] = EOT;
|
|
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
bool Row::fit(int l) {
|
|
int enlarge = 0;
|
|
|
|
while(len + l >= max + enlarge)
|
|
enlarge += ROWCHUNK;
|
|
|
|
// can fit without problems
|
|
if(!enlarge) return true;
|
|
|
|
if(!text) {
|
|
|
|
// this is the first time we allocate the buffer
|
|
text = (CHAR*) calloc(max + enlarge, sizeof(CHAR));
|
|
if( ! text ) {
|
|
fprintf(stderr, "can't allocate new text buffer for a row: %s",
|
|
strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
} else {
|
|
|
|
// is gonna overload the boundary
|
|
// so we must reallocate a larger buffer
|
|
// preserving the contents of the old
|
|
text = (CHAR*) realloc(text, (max + enlarge) * sizeof(CHAR) );
|
|
if( ! text) {
|
|
fprintf(stderr, "can't reallocate text buffer for a row: %s",
|
|
strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
max += enlarge;
|
|
|
|
return true;
|
|
|
|
}
|