Commit 87c9982c authored by rswindell's avatar rswindell

Add mouse hot spot support:

- hot spots are clickable screen areas (e.g. in menus and prompts) that   generate key-strokes
- commands may be from 1 to 127 ASCII-characters in length
- currently using the X10 mouse reporting mode, may change
- all mnemonics strings (~Example) are automatically hot-spots
- The new ~ @-code defines a hot spot
- Any screen-clear operation clears all hot spots
- sbbs now tracks the current screen (cursor position) row
- eliminated the old "tos" (top-of-screen) boolean (row == 0 indicates "tos")
- created an sbbs_t::ungetstr() method
- keep track if in pause (hit a key) prompt, for special mouse behavior

new JS console object:
- row property
- tos property is now read-only (and deprecated)
- new methods:
  add_hotspot()
  clear_hotspots()
  scroll_hotspots()

redrwstr() gets some UTF8 touch-ups as part of this commit. <shrug>
parent 949d4ea5
......@@ -277,6 +277,8 @@ bool sbbs_t::ansi_gotoxy(int x, int y)
comprintf("\x1b[%d;%dH",y,x);
if(x>0)
column=x-1;
if(y>0)
row = y - 1;
lncntr=0;
return true;
}
......@@ -300,3 +302,10 @@ bool sbbs_t::ansi_restore(void)
}
return false;
}
int sbbs_t::ansi_mouse(enum ansi_mouse_mode mode, bool enable)
{
char str[32] = "";
SAFEPRINTF2(str, "\x1b[?%u%c", mode, enable ? 'h' : 'l');
return putcom(str);
}
......@@ -305,7 +305,7 @@ bool sbbs_t::answer()
"\r" /* Move cursor left (in case previous char printed) */
);
i=l=0;
tos=1;
row=0;
lncntr=0;
center(str);
......
......@@ -239,6 +239,19 @@ const char* sbbs_t::atcode(char* sp, char* str, size_t maxlen, long* pmode, bool
str[0]=0;
if(*sp == '~' && *(sp + 1)) {
sp++;
tp = strchr(sp + 1, '~');
if(tp == NULL)
tp = sp;
else {
*tp = 0;
tp++;
}
add_hotspot(tp, column, column + strlen(sp) - 1, row);
return sp;
}
if(strncmp(sp, "U+", 2) == 0) { // UNICODE
enum unicode_codepoint codepoint = (enum unicode_codepoint)strtoul(sp + 2, &tp, 16);
if(tp == NULL || *tp == 0)
......
......@@ -35,6 +35,7 @@
****************************************************************************/
#include "sbbs.h"
#include "utf8.h"
/****************************************************************************/
/* Redraws str using i as current cursor position and l as length */
......@@ -42,14 +43,22 @@
/****************************************************************************/
void sbbs_t::redrwstr(char *strin, int i, int l, long mode)
{
cursor_left(i);
if(i <= 0)
i = 0;
else
cursor_left(i);
if(l < 0)
l = 0;
if(mode)
bprintf(mode, "%-*.*s",l,l,strin);
else
column+=rprintf("%-*.*s",l,l,strin);
cleartoeol();
if(i<l)
cursor_left(l-i);
if(i<l) {
if(mode&P_UTF8)
l = utf8_str_total_width(strin);
cursor_left(l-i);
}
}
int sbbs_t::uselect(int add, uint n, const char *title, const char *item, const uchar *ar)
......@@ -67,6 +76,7 @@ int sbbs_t::uselect(int add, uint n, const char *title, const char *item, const
if(!uselect_total)
bprintf(text[SelectItemHdr],title);
uselect_num[uselect_total++]=n;
add_hotspot((ulong)uselect_total);
bprintf(text[SelectItemFmt],uselect_total,item);
return(0);
}
......
......@@ -616,7 +616,7 @@ int sbbs_t::outchar(char ch)
}
}
if(ch==FF && lncntr > 0 && !tos) {
if(ch==FF && lncntr > 0 && row > 0) {
lncntr=0;
newline();
if(!(sys_status&SS_PAUSEOFF)) {
......@@ -633,14 +633,8 @@ int sbbs_t::outchar(char ch)
ch=text[YNQP][3];
if(text[YNQP][2]==0 || ch==0) ch='X';
}
if(ch==FF) {
if(term&ANSI)
putcom("\x1b[2J\x1b[H"); /* clear screen, home cursor */
else if(term&PETSCII)
outcom(PETSCII_CLEAR);
else
outcom(FF);
}
if(ch==FF)
clearscreen(term);
else if(ch == '\t') {
outcom(' ');
column++;
......@@ -687,16 +681,16 @@ int sbbs_t::outchar(char ch)
}
break;
case '\n': // 10
inc_row(1);
if(lncntr || lastlinelen)
lncntr++;
lbuflen=0;
tos=0;
column=0;
break;
case FF: // 12
lncntr=0;
lbuflen=0;
tos=1;
row=0;
column=0;
case '\r': // 13
lastlinelen = column;
......@@ -750,9 +744,18 @@ void sbbs_t::inc_column(int count)
if(column >= cols) { // assume terminal has/will auto-line-wrap
lncntr++;
lbuflen = 0;
tos = 0;
lastlinelen = column;
column = 0;
inc_row(1);
}
}
void sbbs_t::inc_row(int count)
{
row += count;
if(row >= rows) {
scroll_hotspots((row - rows) + 1);
row = rows - 1;
}
}
......@@ -826,6 +829,17 @@ void sbbs_t::newline(int count)
}
}
void sbbs_t::clearscreen(long term)
{
clear_hotspots();
if(term&ANSI)
putcom("\x1b[2J\x1b[H"); /* clear screen, home cursor */
else if(term&PETSCII)
outcom(PETSCII_CLEAR);
else
outcom(FF);
}
void sbbs_t::clearline(void)
{
carriage_return();
......@@ -841,7 +855,7 @@ void sbbs_t::cursor_home(void)
outcom(PETSCII_HOME);
else
outchar(FF); /* this will clear some terminals, do nothing with others */
tos=1;
row=0;
column=0;
}
......
......@@ -244,8 +244,8 @@ int32_t * sbbs_t::getintvar(csi_t *bin, uint32_t name)
return((int32_t *)&dte_rate);
case 0x7fbf958e:
return((int32_t *)&lncntr);
case 0x5c1c1500:
return((int32_t *)&tos);
// case 0x5c1c1500:
// return((int32_t *)&tos);
case 0x613b690e:
return((int32_t *)&rows);
case 0x205ace36:
......
......@@ -59,6 +59,7 @@ int sbbs_t::exec_msg(csi_t *csi)
else outchar(' ');
if(i<9) outchar(' ');
if(i<99) outchar(' ');
add_hotspot(i+1);
bprintf(text[CfgGrpLstFmt]
,i+1, cfg.grp[usrgrp[i]]->lname); }
}
......@@ -70,7 +71,7 @@ int sbbs_t::exec_msg(csi_t *csi)
if(!j)
j=curgrp;
else
j--;
j--;
}
sprintf(str,"subs%u",usrgrp[j]+1);
if(!menu(str, P_NOERROR)) {
......@@ -84,6 +85,7 @@ int sbbs_t::exec_msg(csi_t *csi)
,getposts(&cfg,usrsub[j][i]));
if(i<9) outchar(' ');
if(i<99) outchar(' ');
add_hotspot(i+1);
bputs(str); }
}
sprintf(str,text[JoinWhichSub],cursub[j]+1);
......@@ -211,6 +213,7 @@ int sbbs_t::exec_msg(csi_t *csi)
outchar('*');
else outchar(' ');
if(i<9) outchar(' ');
add_hotspot(i+1);
bprintf(text[GrpLstFmt],i+1
,cfg.grp[usrgrp[i]]->lname,nulstr,usrsubs[i]);
}
......@@ -232,6 +235,7 @@ int sbbs_t::exec_msg(csi_t *csi)
,getposts(&cfg,usrsub[curgrp][i]));
if(i<9) outchar(' ');
if(i<99) outchar(' ');
add_hotspot(i+1);
bputs(str);
}
return(0);
......
......@@ -321,6 +321,7 @@ void sbbs_t::mnemonics(const char *str)
}
l=0L;
long term = term_supports();
while(str[l]) {
if(str[l]=='~' && str[l+1]!=0) {
if(!(term&(ANSI|PETSCII)))
......@@ -328,6 +329,7 @@ void sbbs_t::mnemonics(const char *str)
l++;
if(!ctrl_a_codes)
attr(cfg.color[clr_mnehigh]);
add_hotspot(str[l], column, column);
outchar(str[l]);
l++;
if(!(term&(ANSI|PETSCII)))
......@@ -537,8 +539,9 @@ void sbbs_t::pause()
long l=K_UPPER;
size_t len;
if(sys_status&SS_ABORT)
if((sys_status&SS_ABORT) || pause_inside)
return;
pause_inside = true;
lncntr=0;
if(online==ON_REMOTE)
rioctl(IOFI);
......@@ -557,6 +560,7 @@ void sbbs_t::pause()
getnodedat(cfg.node_num,&thisnode,0);
nodesync();
attr(tempattrs);
pause_inside = false;
}
/****************************************************************************/
......@@ -580,3 +584,14 @@ void sbbs_t::ungetkey(char ch, bool insert)
}
#endif
}
/****************************************************************************/
/* Puts a string into the input buffer */
/****************************************************************************/
void sbbs_t::ungetstr(const char* str, bool insert)
{
size_t i;
for(i = 0; str[i] != '\0'; i++)
ungetkey(str[i], insert);
}
......@@ -180,13 +180,13 @@ void sbbs_t::show_msghdr(smb_t* smb, smbmsg_t* msg, const char* subject, const c
current_msg_to = to;
attr(LIGHTGRAY);
if(!tos) {
if(row != 0) {
if(useron.misc&CLRSCRN)
outchar(FF);
else
CRLF;
}
msghdr_tos = tos;
msghdr_tos = (row == 0);
if(!menu("msghdr", P_NOERROR)) {
bprintf(pmode, msghdr_text(msg, MsgSubj), current_msg_subj);
if(msg->tags && *msg->tags)
......
......@@ -324,6 +324,42 @@ char sbbs_t::handle_ctrlkey(char ch, long mode)
j++;
continue;
}
if(i == 0 && ch == 'M' && mouse_mode != MOUSE_MODE_OFF) {
int button = kbincom(this, 100);
ch = kbincom(this, 100);
if(ch < '!') {
lprintf(LOG_DEBUG, "Unexpected mouse-button (0x%02X) tracking char: 0x%02X < '!'"
, button, ch);
continue;
}
int x = ch - '!';
ch = kbincom(this, 100);
if(ch < '!') {
lprintf(LOG_DEBUG, "Unexpected mouse-button (0x%02X) tracking char: 0x%02X < '!'"
, button, ch);
continue;
}
int y = ch - '!';
lprintf(LOG_DEBUG, "Mouse button-click (0x%02X) reported at: %u x %u", button, x, y);
if(button == 0x22) // Right-click
return handle_ctrlkey(TERM_KEY_ABORT, mode);
if(button != 0x20) // Left-click
continue;
for(list_node_t* node = mouse_hotspots.first; node != NULL; node = node->next) {
struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
if(spot->y != y)
continue;
if(x < spot->minx || x > spot->maxx)
continue;
ungetstr(spot->cmd);
if(pause_inside)
return handle_ctrlkey(TERM_KEY_ABORT, mode);
return 0;
}
if(pause_inside)
return '\r';
return 0;
}
if(ch!=';' && !isdigit((uchar)ch) && ch!='R') { /* other ANSI */
str[i]=0;
switch(ch) {
......@@ -393,3 +429,95 @@ char sbbs_t::handle_ctrlkey(char ch, long mode)
}
return(ch);
}
void sbbs_t::set_mouse(long flags)
{
if(term_supports(ANSI)) {
long mode = mouse_mode & ~flags;
if(mode & MOUSE_MODE_X10) ansi_mouse(ANSI_MOUSE_X10, false);
if(mode & MOUSE_MODE_NORM) ansi_mouse(ANSI_MOUSE_NORM, false);
if(mode & MOUSE_MODE_BTN) ansi_mouse(ANSI_MOUSE_BTN, false);
if(mode & MOUSE_MODE_ANY) ansi_mouse(ANSI_MOUSE_ANY, false);
if(mode & MOUSE_MODE_EXT) ansi_mouse(ANSI_MOUSE_EXT, false);
mode = flags & ~mouse_mode;
if(mode & MOUSE_MODE_X10) ansi_mouse(ANSI_MOUSE_X10, true);
if(mode & MOUSE_MODE_NORM) ansi_mouse(ANSI_MOUSE_NORM, true);
if(mode & MOUSE_MODE_BTN) ansi_mouse(ANSI_MOUSE_BTN, true);
if(mode & MOUSE_MODE_ANY) ansi_mouse(ANSI_MOUSE_ANY, true);
if(mode & MOUSE_MODE_EXT) ansi_mouse(ANSI_MOUSE_EXT, true);
mouse_mode = flags;
}
}
void sbbs_t::add_hotspot(struct mouse_hotspot* spot)
{
if(spot->y < 0)
spot->y = row;
if(spot->minx < 0)
spot->minx = 0;
if(spot->maxx < 0)
spot->maxx = cols - 1;
#ifdef _DEBUG
lprintf(LOG_DEBUG, "Adding mouse hot spot %u-%u x %u = '%s'"
,spot->minx, spot->maxx, spot->y, spot->cmd);
#endif
listPushNodeData(&mouse_hotspots, spot, sizeof(*spot));
set_mouse(MOUSE_MODE_X10);
}
void sbbs_t::clear_hotspots(void)
{
#ifdef _DEBUG
long spots = listCountNodes(&mouse_hotspots);
if(spots)
lprintf(LOG_DEBUG, "Clearing %ld mouse hot spots", spots);
#endif
set_mouse(MOUSE_MODE_OFF);
listFreeNodes(&mouse_hotspots);
}
void sbbs_t::scroll_hotspots(long count)
{
long spots = 0;
for(list_node_t* node = mouse_hotspots.first; node != NULL; node = node->next) {
struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
spot->y -= count;
spots++;
}
#ifdef _DEBUG
if(spots)
lprintf(LOG_DEBUG, "Scrolled %ld mouse hot-spots %ld rows", spots, count);
#endif
}
void sbbs_t::add_hotspot(char cmd, long minx, long maxx, long y)
{
struct mouse_hotspot spot = {0};
spot.cmd[0] = cmd;
spot.minx = minx;
spot.maxx = maxx;
spot.y = y;
add_hotspot(&spot);
}
void sbbs_t::add_hotspot(ulong num, long minx, long maxx, long y)
{
struct mouse_hotspot spot = {0};
SAFEPRINTF(spot.cmd, "%lu", num);
spot.minx = minx;
spot.maxx = maxx;
spot.y = y;
add_hotspot(&spot);
}
void sbbs_t::add_hotspot(const char* cmd, long minx, long maxx, long y)
{
struct mouse_hotspot spot = {0};
SAFECOPY(spot.cmd, cmd);
spot.minx = minx;
spot.maxx = maxx;
spot.y = y;
add_hotspot(&spot);
}
......@@ -50,6 +50,7 @@ enum {
,CON_PROP_LASTLINELEN
,CON_PROP_ATTR
,CON_PROP_TOS
,CON_PROP_ROW
,CON_PROP_ROWS
,CON_PROP_COLUMNS
,CON_PROP_TABSTOP
......@@ -110,7 +111,10 @@ static JSBool js_console_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
val=sbbs->curatr;
break;
case CON_PROP_TOS:
val=sbbs->tos;
val=sbbs->row == 0;
break;
case CON_PROP_ROW:
val=sbbs->row;
break;
case CON_PROP_ROWS:
val=sbbs->rows;
......@@ -251,8 +255,9 @@ static JSBool js_console_set(JSContext *cx, JSObject *obj, jsid id, JSBool stric
sbbs->attr(val);
JS_RESUMEREQUEST(cx, rc);
break;
case CON_PROP_TOS:
sbbs->tos = val ? true : false;
case CON_PROP_ROW:
if(val >= 0 && val < TERM_ROWS_MAX)
sbbs->row = val;
break;
case CON_PROP_ROWS:
if(val >= TERM_ROWS_MIN && val <= TERM_ROWS_MAX)
......@@ -345,10 +350,11 @@ static jsSyncPropertySpec js_console_properties[] = {
{ "status" ,CON_PROP_STATUS ,CON_PROP_FLAGS ,310},
{ "line_counter" ,CON_PROP_LNCNTR ,CON_PROP_FLAGS ,310},
{ "current_row" ,CON_PROP_ROW ,CON_PROP_FLAGS ,31800},
{ "current_column" ,CON_PROP_COLUMN ,CON_PROP_FLAGS ,315},
{ "last_line_length" ,CON_PROP_LASTLINELEN ,CON_PROP_FLAGS ,317},
{ "attributes" ,CON_PROP_ATTR ,CON_PROP_FLAGS ,310},
{ "top_of_screen" ,CON_PROP_TOS ,CON_PROP_FLAGS ,310},
{ "top_of_screen" ,CON_PROP_TOS ,JSPROP_ENUMERATE|JSPROP_READONLY ,310},
{ "screen_rows" ,CON_PROP_ROWS ,CON_PROP_FLAGS ,310},
{ "screen_columns" ,CON_PROP_COLUMNS ,CON_PROP_FLAGS ,311},
{ "tabstop" ,CON_PROP_TABSTOP ,CON_PROP_FLAGS ,31700},
......@@ -380,10 +386,11 @@ static jsSyncPropertySpec js_console_properties[] = {
static const char* con_prop_desc[] = {
"status bit-field (see <tt>CON_*</tt> in <tt>sbbsdefs.js</tt> for bit definitions)"
,"current 0-based line counter (used for automatic screen pause)"
,"current 0-based row counter"
,"current 0-based column counter (used to auto-increment <i>line_counter</i> when screen wraps)"
,"length of last line sent to terminal (before a carriage-return or line-wrap)"
,"current display attributes (set with number or string value)"
,"set to <i>true</i> if the terminal cursor is already at the top of the screen"
,"set to <i>true</i> if the terminal cursor is already at the top of the screen - <small>READ ONLY</small>"
,"number of remote terminal screen rows (in lines)"
,"number of remote terminal screen columns (in character cells)"
,"current tab stop interval (tab size), in columns"
......@@ -541,6 +548,83 @@ js_putbyte(JSContext *cx, uintN argc, jsval *arglist)
return JS_TRUE ;
}
static JSBool
js_add_hotspot(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
sbbs_t* sbbs;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
return JS_FALSE;
if(argc < 1) {
JS_ReportError(cx, "Invalid number of arguments to function");
return JS_FALSE;
}
JSString* js_str = JS_ValueToString(cx, argv[0]);
if(js_str == NULL)
return JS_FALSE;
int32 min_x = -1;
int32 max_x = -1;
int32 y = -1;
uintN argn = 1;
if(argc > argn) {
if(!JS_ValueToInt32(cx,argv[argn], &min_x))
return JS_FALSE;
argn++;
}
if(argc > argn) {
if(!JS_ValueToInt32(cx,argv[argn], &max_x))
return JS_FALSE;
argn++;
}
if(argc > argn) {
if(!JS_ValueToInt32(cx,argv[argn], &y))
return JS_FALSE;
argn++;
}
char* p = NULL;
JSSTRING_TO_MSTRING(cx, js_str, p, NULL);
if(p == NULL)
return JS_FALSE;
sbbs->add_hotspot(p, min_x, max_x, y);
free(p);
return JS_TRUE;
}
static JSBool js_scroll_hotspots(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
sbbs_t* sbbs;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
return JS_FALSE;
int32 rows = 1;
if(argc > 0 && !JS_ValueToInt32(cx,argv[0], &rows))
return JS_FALSE;
sbbs->scroll_hotspots(rows);
return JS_TRUE;
}
static JSBool js_clear_hotspots(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
sbbs_t* sbbs;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
return JS_FALSE;
sbbs->clear_hotspots();
return JS_TRUE;
}
static JSBool
js_handle_ctrlkey(JSContext *cx, uintN argc, jsval *arglist)
......@@ -972,7 +1056,7 @@ js_clear(JSContext *cx, uintN argc, jsval *arglist)
}
rc=JS_SUSPENDREQUEST(cx);
sbbs->CLS;
sbbs->clearscreen(sbbs->term_supports());
JS_RESUMEREQUEST(cx, rc);
return(JS_TRUE);
}
......@@ -2210,6 +2294,18 @@ static jsSyncMethodSpec js_console_functions[] = {
,JSDOCSTR("sends an unprocessed byte value to the remote terminal")
,31700
},
{"add_hotspot", js_add_hotspot, 1, JSTYPE_VOID, JSDOCSTR("cmd [,min_x] [,max_x] [,y]")
,JSDOCSTR("adds a mouse hot-spot (a clickable screen area that generates keyboard input)")
,31800
},
{"clear_hotspots", js_clear_hotspots, 0, JSTYPE_VOID, JSDOCSTR("")
,JSDOCSTR("clear all current mouse hot-spots")
,31800
},
{"scroll_hotspots", js_scroll_hotspots, 0, JSTYPE_VOID, JSDOCSTR("[rows=1]")
,JSDOCSTR("scroll all current mouse hot-spots by the specific number of rows")
,31800
},
{0}
};
......
......@@ -3361,6 +3361,7 @@ sbbs_t::sbbs_t(ushort node_num, union xp_sockaddr *addr, size_t addr_len, const
rio_abortable=false;
mouse_mode = MOUSE_MODE_OFF;
console = 0;
online = 0;
outchar_esc = 0;
......@@ -3373,6 +3374,7 @@ sbbs_t::sbbs_t(ushort node_num, union xp_sockaddr *addr, size_t addr_len, const
readmail_inside = false;
scanposts_inside = false;
scansubs_inside = false;
pause_inside = false;
timeleft = 60*10; /* just incase this is being used for calling gettimeleft() */
last_sysop_auth = 0;
uselect_total = 0;
......@@ -3391,9 +3393,9 @@ sbbs_t::sbbs_t(ushort node_num, union xp_sockaddr *addr, size_t addr_len, const
listInit(&savedlines, /* flags: */0);
sys_status=lncntr=criterrs=0L;
tos = false;
msghdr_tos = false;
listInit(&smb_list, /* flags: */0);
listInit(&mouse_hotspots, /* flags: */0);
column=0;
tabstop=8;
lastlinelen=0;
......@@ -3903,6 +3905,7 @@ sbbs_t::~sbbs_t()
listFree(&savedlines);
listFree(&smb_list);
listFree(&mouse_hotspots);
#ifdef USE_CRYPTLIB
while(ssh_mutex_created && pthread_mutex_destroy(&ssh_mutex)==EBUSY)
......@@ -4292,6 +4295,7 @@ void sbbs_t::reset_logon_vars(void)
question[0]=0;
menu_dir[0]=0;
menu_file[0]=0;
row = 0;
rows = TERM_ROWS_DEFAULT;
cols = TERM_COLS_DEFAULT;
lncntr=0;
......