#if 0
gcc ${CFLAGS:--s -O2} -c -Wno-multichar bindings.c `sdl-config --cflags`
exit
#endif
/*
This program is part of Free Hero Mesh and is public domain.
*/
#define HEROMESH_BINDINGS
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "SDL.h"
#include "sqlite3.h"
#include "smallxrm.h"
#include "quarks.h"
#include "heromesh.h"
#include "cursorshapes.h"
#define MOD_SHIFT 1
#define MOD_CTRL 2
#define MOD_ALT 4
#define MOD_META 8
#define MOD_NUMLOCK 14
typedef struct {
UserCommand m[16];
} KeyBinding;
static KeyBinding*editor_bindings[SDLK_LAST];
static KeyBinding*game_bindings[SDLK_LAST];
static KeyBinding*editor_mouse_bindings[4];
static KeyBinding*game_mouse_bindings[4];
static int cur_modifiers,loose_modifiers;
static void set_binding(KeyBinding**pkb,int mod,const char*txt) {
int i;
KeyBinding*kb=*pkb;
UserCommand*uc;
if(!*txt) return;
if(!kb) kb=*pkb=calloc(1,sizeof(KeyBinding));
if(!kb) fatal("Allocation failed\n");
uc=kb->m+mod;
if(uc->cmd) return;
switch(*txt) {
case '^': // Miscellaneous
uc->cmd='^';
uc->n=txt[1];
break;
case '=': case '-': case '+': // Restart, rewind, advance
uc->cmd=*txt;
uc->n=strtol(txt+1,0,0);
break;
case '\'': // Input move
uc->cmd='\'';
for(i=1;i<256;i++) if(heromesh_key_names[i] && !strcmp(txt+1,heromesh_key_names[i])) {
uc->n=i;
return;
}
fatal("Error in key binding: %s\nInvalid Hero Mesh key name\n",txt);
break;
case '!': // System command
uc->cmd='!';
uc->txt=txt+1;
break;
case 'A' ... 'Z': case 'a' ... 'z': // Execute SQL statement
uc->cmd='s';
if(i=sqlite3_prepare_v3(userdb,txt,-1,SQLITE_PREPARE_PERSISTENT,&uc->stmt,0)) fatal("Error in key binding: %s\n%s\n",txt,sqlite3_errmsg(userdb));
break;
default:
fatal("Error in key binding: %s\nUnrecognized character\n",txt);
}
}
#define BINDING_MODIFIER(a,b) \
if(q==a && !(cur_modifiers&b)) { \
cur_modifiers|=b; \
xrm_enumerate(sub,cb_2,usr); \
cur_modifiers&=~b; \
if(loose) --loose_modifiers; \
return 0; \
}
static void*cb_2(xrm_db*db,void*usr,int loose,xrm_quark q) {
KeyBinding**kb=usr;
const char*txt;
xrm_db*sub=xrm_sub(db,loose,q);
if(loose) ++loose_modifiers;
BINDING_MODIFIER(Q_shift,MOD_SHIFT);
BINDING_MODIFIER(Q_ctrl,MOD_CTRL);
BINDING_MODIFIER(Q_alt,MOD_ALT);
BINDING_MODIFIER(Q_meta,MOD_META);
BINDING_MODIFIER(Q_numLock,MOD_NUMLOCK);
if(txt=xrm_get(sub)) {
if(usr==editor_mouse_bindings || usr==game_mouse_bindings) {
switch(q) {
case Q_left: kb+=SDL_BUTTON_LEFT; break;
case Q_middle: kb+=SDL_BUTTON_MIDDLE; break;
case Q_right: kb+=SDL_BUTTON_RIGHT; break;
default: goto stop;
}
} else {
if(q>=FirstKeyQuark && q<=LastKeyQuark) kb+=quark_to_key[q-FirstKeyQuark]; else goto stop;
}
set_binding(kb,cur_modifiers,txt);
if(loose_modifiers) {
int i;
for(i=0;i<16;i++) if((i&cur_modifiers)==cur_modifiers && !kb[0]->m[i].cmd) kb[0]->m[i]=kb[0]->m[cur_modifiers];
}
}
stop:
if(loose) --loose_modifiers;
return 0;
}
static void*cb_1(xrm_db*db,void*usr) {
xrm_enumerate(db,cb_2,usr);
return 0;
}
void load_key_bindings(void) {
printStatus("Loading key bindings...\n");
cur_modifiers=loose_modifiers=0;
optionquery[1]=Q_editKey;
xrm_search(resourcedb,optionquery,optionquery,2,cb_1,editor_bindings);
optionquery[1]=Q_gameKey;
xrm_search(resourcedb,optionquery,optionquery,2,cb_1,game_bindings);
optionquery[1]=Q_editClick;
xrm_search(resourcedb,optionquery,optionquery,2,cb_1,editor_mouse_bindings);
optionquery[1]=Q_gameClick;
xrm_search(resourcedb,optionquery,optionquery,2,cb_1,game_mouse_bindings);
printStatus("Done\n");
}
const UserCommand*find_key_binding(SDL_Event*ev,int editing) {
KeyBinding*kb;
static const UserCommand nul={cmd:0};
SDLMod m;
int i;
if(ev->type==SDL_KEYDOWN) {
m=ev->key.keysym.mod;
if(ev->key.keysym.sym>=SDLK_LAST) return &nul;
kb=(editing?editor_bindings:game_bindings)[ev->key.keysym.sym];
} else if(ev->type==SDL_MOUSEBUTTONDOWN) {
m=SDL_GetModState();
if(ev->button.button>3) return &nul;
kb=(editing?editor_mouse_bindings:game_mouse_bindings)[ev->button.button];
} else {
return &nul;
}
if(!kb) return &nul;
i=0;
if(m&KMOD_CTRL) i|=MOD_CTRL;
if(m&KMOD_SHIFT) i|=MOD_SHIFT;
if(m&KMOD_ALT) i|=MOD_ALT;
if(m&KMOD_META) i|=MOD_META;
if((m&KMOD_NUM) && kb->m[i|MOD_NUMLOCK].cmd) i|=MOD_NUMLOCK;
return kb->m+i;
}
static void sql_interactive(void) {
const char*t=screen_prompt("<SQL>");
const char*u;
char*err=0;
char**tab=0;
SDL_Rect r;
SDL_Event ev;
int n,i,j,y;
int k=0;
int nr=0;
int nc=0;
if(!t) return;
sqlite3_get_table(userdb,t,&tab,&nr,&nc,&err);
if(!tab) {
if(err) screen_message(err);
sqlite3_free(err);
return;
}
redraw:
r.x=r.y=0;
r.w=screen->w;
r.h=screen->h;
SDL_FillRect(screen,&r,0xF0);
SDL_LockSurface(screen);
if(!k) draw_text(0,0,t,0xF8,0xFB);
y=8-k;
for(n=0;n<nr;n++) {
if(y>=0) {
if(y>screen->h-8) break;
for(i=j=0;i<nc;i++) {
u=tab[nc*(n+1)+i];
draw_text(j,y,"\xB3",0xF0,0xF9),j+=8;
if(u) draw_text(j,y,u,0xF0,0xF7),j+=strlen(u)*8; else draw_text(j,y,"\x04",0xF0,0xF4),j+=8;
}
}
y+=8;
}
if(y>=0 && y<=screen->h-8) {
if(!err) draw_text(0,y,"--END--",0xF8,0xFA); else draw_text(0,y,err,0xF8,0xFC);
}
SDL_UnlockSurface(screen);
SDL_Flip(screen);
while(SDL_WaitEvent(&ev)) switch(ev.type) {
case SDL_KEYDOWN:
switch(ev.key.keysym.sym) {
case SDLK_ESCAPE: case SDLK_RETURN: case SDLK_KP_ENTER: goto done;
case SDLK_UP: case SDLK_KP8: k-=8; if(k<0) k=0; break;
case SDLK_DOWN: case SDLK_KP2: k+=8; break;
case SDLK_HOME: case SDLK_KP7: k=0; break;
case SDLK_PAGEUP: case SDLK_KP9: k-=screen->h; if(k<0) k=0; break;
case SDLK_PAGEDOWN: case SDLK_KP3: k+=screen->h; break;
}
goto redraw;
case SDL_VIDEOEXPOSE:
goto redraw;
case SDL_QUIT:
SDL_PushEvent(&ev);
goto done;
}
done: if(tab) sqlite3_free_table(tab); sqlite3_free(err);
}
static int key_xy(void) {
char buf[32];
SDL_Rect r;
SDL_Event ev;
int x,y;
int xc=pfwidth/2+1;
int yc=pfheight/2+1;
set_cursor(XC_hand1);
redraw:
r.x=r.y=0;
r.h=screen->h;
r.w=left_margin;
SDL_FillRect(screen,&r,0xF0);
r.x=left_margin-1;
r.w=1;
SDL_FillRect(screen,&r,0xF7);
r.x=left_margin;
r.w=screen->w-r.x;
SDL_FillRect(screen,&r,back_color);
for(x=1;x<=pfwidth;x++) for(y=1;y<=pfheight;y++) draw_cell(x,y);
SDL_LockSurface(screen);
snprintf(buf,8,"(%2d,%2d)",xc,yc);
draw_text(0,0,buf,0xF0,0xF7);
SDL_UnlockSurface(screen);
r.x=left_margin+(xc-1)*picture_size+picture_size/2;
r.y=(yc-1)*picture_size;
r.w=1;
r.h=picture_size;
SDL_FillRect(screen,&r,0xF6);
r.x=left_margin+(xc-1)*picture_size;
r.y=(yc-1)*picture_size+picture_size/2;
r.w=picture_size;
r.h=1;
SDL_FillRect(screen,&r,0xF6);
r.x=left_margin+(xc-1)*picture_size+picture_size/2;
r.y=(yc-1)*picture_size+picture_size/2;
r.w=r.h=2;
SDL_FillRect(screen,&r,0xFE);
SDL_Flip(screen);
while(SDL_WaitEvent(&ev)) switch(ev.type) {
case SDL_KEYDOWN:
switch(ev.key.keysym.sym) {
case SDLK_h: case SDLK_LEFT: case SDLK_KP4: if(xc>1) xc--; break;
case SDLK_j: case SDLK_DOWN: case SDLK_KP2: if(yc<pfheight) yc++; break;
case SDLK_k: case SDLK_UP: case SDLK_KP8: if(yc>1) yc--; break;
case SDLK_l: case SDLK_RIGHT: case SDLK_KP6: if(xc<pfwidth) xc++; break;
case SDLK_RETURN: case SDLK_KP_ENTER: case SDLK_SPACE: return xc+yc*64;
case SDLK_ESCAPE: case SDLK_DELETE: case SDLK_q: return -1;
}
goto redraw;
case SDL_MOUSEBUTTONDOWN:
if(ev.button.x<left_margin) break;
x=(ev.button.x-left_margin)/picture_size+1;
y=ev.button.y/picture_size+1;
if(x>0 && x<=pfwidth && y>0 && y<=pfheight) {
xc=x,yc=y;
if(ev.button.button==2) return xc+yc*64;
if(ev.button.button==3) return -1;
goto redraw;
}
break;
case SDL_VIDEOEXPOSE:
goto redraw;
case SDL_QUIT:
SDL_PushEvent(&ev);
return -1;
}
return -1;
}
int exec_key_binding(SDL_Event*ev,int editing,int x,int y,int(*cb)(int prev,int cmd,int number,int argc,sqlite3_stmt*args,void*aux),void*aux) {
const UserCommand*cmd=find_key_binding(ev,editing);
int prev=0;
int i,j,k;
const char*name;
if(ev->type==SDL_MOUSEBUTTONDOWN && !x && !y && ev->button.x>=left_margin) {
x=(ev->button.x-left_margin)/picture_size+1;
y=ev->button.y/picture_size+1;
if(x<1 || y<1 || x>pfwidth || y>pfheight) return 0;
}
switch(cmd->cmd) {
case 0:
return 0;
case '^':
return cb(0,cmd->n*'\0\1'+'^\0',y*64+x,0,0,aux);
case '=': case '-': case '+':
return cb(0,cmd->cmd*'\1\0'+'\0 ',cmd->n,0,0,aux);
case '\'':
return cb(0,'\' ',cmd->n,0,0,aux);
case '!':
i=system(cmd->txt);
return 0;
case 's':
sqlite3_reset(cmd->stmt);
sqlite3_clear_bindings(cmd->stmt);
if(sqlite3_bind_parameter_count(cmd->stmt)) {
for(i=sqlite3_bind_parameter_count(cmd->stmt);i;--i) if(name=sqlite3_bind_parameter_name(cmd->stmt,i)) {
if(*name=='$') {
if(!sqlite3_stricmp(name+1,"X")) {
if(x) sqlite3_bind_int(cmd->stmt,i,x);
} else if(!sqlite3_stricmp(name+1,"Y")) {
if(y) sqlite3_bind_int(cmd->stmt,i,y);
} else if(!sqlite3_stricmp(name+1,"LEVEL")) {
sqlite3_bind_int(cmd->stmt,i,level_ord);
} else if(!sqlite3_stricmp(name+1,"LEVEL_ID")) {
sqlite3_bind_int(cmd->stmt,i,level_id);
} else if(!sqlite3_stricmp(name+1,"KEY_XY")) {
j=key_xy();
if(j<0) return 0;
sqlite3_bind_int(cmd->stmt,i,j);
}
} else if(*name==':') {
name=screen_prompt(name);
if(!name) return 0;
sqlite3_bind_text(cmd->stmt,i,name,-1,SQLITE_TRANSIENT);
}
}
}
while((i=sqlite3_step(cmd->stmt))==SQLITE_ROW) {
if(i=sqlite3_data_count(cmd->stmt)) {
j=(i>1&&sqlite3_column_type(cmd->stmt,1)!=SQLITE_NULL)?sqlite3_column_int(cmd->stmt,1):y*64+x;
if((name=sqlite3_column_text(cmd->stmt,0)) && *name) {
if(name[0]==':') {
switch(name[1]) {
case '!': if(i>1) i=system(sqlite3_column_text(cmd->stmt,1)?:(const unsigned char*)"# "); break;
case ';': i=SQLITE_DONE; goto done;
case '?': if(i>1) puts(sqlite3_column_text(cmd->stmt,1)?:(const unsigned char*)"(null)"); break;
case 'm': if(i>1) screen_message(sqlite3_column_text(cmd->stmt,1)?:(const unsigned char*)"(null)"); break;
case 's': malloc_stats(); fprintf(stderr,"SQLite memory use: %lld %lld\n",(long long)sqlite3_memory_used(),(long long)sqlite3_memory_highwater(1)); break;
case 'x': sql_interactive(); break;
}
} else {
k=name[0]*'\1\0'+name[1]*'\0\1';
while(i && sqlite3_column_type(cmd->stmt,i-1)==SQLITE_NULL) i--;
prev=cb(prev,k,j,i,cmd->stmt,aux);
if(prev<0) {
i=SQLITE_DONE;
goto done;
}
}
}
}
}
done:
sqlite3_reset(cmd->stmt);
sqlite3_clear_bindings(cmd->stmt);
if(i!=SQLITE_DONE) fprintf(stderr,"SQL error in key binding: %s\n",sqlite3_errmsg(userdb)?:"unknown error");
return prev;
default:
fprintf(stderr,"Confusion in exec_key_binding()\n");
return 0;
}
}