qxw-20110923/ 0000755 0001750 0001750 00000000000 11637123151 010404 5 ustar mo mo qxw-20110923/qxw.h 0000644 0001750 0001750 00000007416 11637123103 011401 0 ustar mo mo /* $Id: qxw.h -1 $ */
/*
Qxw is a program to help construct and publish crosswords.
Copyright 2011 Mark Owen
http://www.quinapalus.com
E-mail: qxw@quinapalus.com
This file is part of Qxw.
Qxw is free software: you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License
as published by the Free Software Foundation.
Qxw 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. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Qxw. If not, see or
write to the Free Software Foundation, Inc., 51 Franklin Street,
Fifth Floor, Boston, MA 02110-1301, USA.
*/
extern int dir,curx,cury;
extern int unsaved;
extern char bname[SLEN];
extern char filename[SLEN+50];
extern int symmr,symmd,symmm;
extern int gtype,ndir[NGTYPE],gshape[NGTYPE];
extern int width,height;
extern char gtitle[SLEN];
extern char gauthor[SLEN];
extern int uhead,utail,uhwm;
extern int st_lc[MXSZ+1];
extern int st_lucc[MXSZ+1];
extern int st_locc[MXSZ+1];
extern int st_lsc[MXSZ+1];
extern int st_lmnc[MXSZ+1];
extern int st_lmxc[MXSZ+1];
extern int st_sc;
extern int st_ce;
extern int st_2u,st_3u;
extern int st_tlf,st_vltlf;
extern struct sprop dsp;
extern struct lprop dlp;
// functions called by grid filler
extern void updatefeas(void);
extern void updategrid(void);
extern void mkfeas(void);
// interface functions to gsq[][]
extern int getflags(int x,int y);
extern int getbgcol(int x,int y);
extern int getfgcol(int x,int y);
extern int getfstyle(int x,int y);
extern int getdech(int x,int y);
extern int getnumber(int x,int y);
extern void a_load(void);
extern void a_save(void);
extern void a_filenew(void);
extern void saveprefs(void);
extern void a_editblock (int k,int x,int y,int d);
extern void a_editcutout(int k,int x,int y,int d);
extern void a_editempty (int k,int x,int y,int d);
extern void a_editmerge (int k,int x,int y,int d);
extern void a_editbar (int k,int x,int y,int d);
extern int symmrmask(void);
extern int symmmmask(void);
extern int symmdmask(void);
extern int cbits(ABM x);
extern int logbase2(ABM x);
extern void undo_push(void);
extern void undo_pop(void);
extern int getlightd(int*lx,int*ly,int x,int y,int d);
extern int getlightchp(char**p,int x,int y,int d);
extern int getstartoflight(int*lx,int*ly,int x,int y,int d);
extern int getlight(int*lx,int*ly,int x,int y,int d);
extern int isstartoflight(int x,int y,int d);
extern int issellight(int x,int y,int d);
extern void sellight(int x,int y,int d,int k);
extern int isclear(int x,int y);
extern int isbar(int x,int y,int d);
extern int ismerge(int x,int y,int d);
extern int isingrid(int x,int y);
extern int sqexists(int i,int j);
extern int clearbefore(int x,int y,int d);
extern int clearafter(int x,int y,int d);
extern int getword(int x,int y,int d,char*s);
extern int getmergegroupd(int*gx,int*gy,int x,int y,int d);
extern int getmergegroup(int*gx,int*gy,int x,int y);
extern int getmergedir(int x,int y);
extern void getmergerep(int*mx,int*my,int x,int y);
extern int isownmergerep(int x,int y);
extern void getmergerepd(int*mx,int*my,int x,int y,int d);
extern int compute(void);
extern void symmdo(void f(int,int,int,int),int k,int x,int y,int d);
extern char getechar(int x,int y);
extern int setechar(int x,int y,char c);
extern void clrcont(int x,int y);
extern void stepback(int*x,int*y,int d);
extern void donumbers(void);
extern int stepbackifingrid (int*x,int*y,int d);
extern void stepforw (int*x,int*y,int d);
extern int stepforwmifingrid(int*x,int*y,int d);
extern char*titlebyauthor(void);
extern int preexport(void);
extern void postexport(void);
extern void resetlp(struct lprop*lp);
qxw-20110923/dicts.h 0000644 0001750 0001750 00000003352 11637123101 011661 0 ustar mo mo // $Id: dicts.h -1 $
/*
Qxw is a program to help construct and publish crosswords.
Copyright 2011 Mark Owen
http://www.quinapalus.com
E-mail: qxw@quinapalus.com
This file is part of Qxw.
Qxw is free software: you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License
as published by the Free Software Foundation.
Qxw 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. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Qxw. If not, see or
write to the Free Software Foundation, Inc., 51 Franklin Street,
Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __DICTS_H__
#define __DICTS_H__
#define MAXNDICTS 9 // must fit in a word
extern char dfnames[MAXNDICTS][SLEN];
extern char dsfilters[MAXNDICTS][SLEN];
extern char dafilters[MAXNDICTS][SLEN];
extern char lemdesc[NLEM][LEMDESCLEN];
extern char*lemdescADVP[NLEM];
extern int lcount[MXSZ+1]; // number of lights of each length
extern int atotal; // total answers
extern int ltotal; // total lights
extern int ultotal; // total uniquified lights
extern char*aused; // answer already used while filling
extern char*lused; // light already used while filling
extern int loaddicts(int sil);
extern void freedicts(void);
extern int loaddefdicts(void);
extern int getinitflist(int**l,int*ll,struct lprop*lp,int wlen);
extern int pregetinitflist(void);
extern int postgetinitflist(void);
extern char*loadtpi(void);
extern void unloadtpi(void);
#endif
qxw-20110923/gui.c 0000644 0001750 0001750 00000262175 11637123102 011345 0 ustar mo mo // $Id: gui.c -1 $
/*
Qxw is a program to help construct and publish crosswords.
Copyright 2011 Mark Owen
http://www.quinapalus.com
E-mail: qxw@quinapalus.com
This file is part of Qxw.
Qxw is free software: you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License
as published by the Free Software Foundation.
Qxw 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. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Qxw. If not, see or
write to the Free Software Foundation, Inc., 51 Franklin Street,
Fifth Floor, Boston, MA 02110-1301, USA.
*/
// GTK
#include
#include
#include
#include
#include
#include "common.h"
#include "qxw.h"
#include "filler.h"
#include "dicts.h"
#include "gui.h"
#include "draw.h"
int selmode=0;
int nsel=0; // number of things selected
int pxsq;
int zoomf=2;
int zoompx[]={18,26,36,52,72};
int curmf=1; // cursor has moved since last redraw
static char*gtypedesc[NGTYPE]={
"Plain rectangular",
"Hex with vertical lights",
"Hex with horizontal lights",
"Circular",
"Circular with half-cell offset",
"Join left and right edges",
"Join top and bottom edges",
"Join left and right edges with flip",
"Join top and bottom edges with flip",
"Torus",
};
static int spropdia(int g);
static int lpropdia(int g);
static int mvldia(int v);
static int dictldia(void);
static int treatdia(void);
static int gpropdia(void);
static int ccontdia(void);
static int prefsdia(void);
static int statsdia(void);
static int filedia(char*but,char*ext,int write);
static int box(int type,const char*t,const char*u,const char*v);
static void gridchangen(void);
static void syncselmenu(void);
GtkWidget *grid_da;
static GtkItemFactory *item_factory; // for menus
static GtkWidget *mainw,*grid_sw,*list_sw,*vbox,*hbox,*paned,*menubar,*poss_label,*clist; // main window and content
static GtkWidget*stats=NULL; // statistics window (if visible)
static GtkWidget*(st_te[MXSZ+2][5]),*(st_r[9]); // statistics table
static GtkWidget*currentdia; // for `Filling' dialogue box (so that we can close it automatically when filling completes)
// set main window title
static void setwintitle() {char t[SLEN*3];
sprintf(t,"Qxw: %s",titlebyauthor());
gtk_window_set_title(GTK_WINDOW(mainw),t);
}
// set name of crossword and title bar of main window
void setbname(char*s) {
strncpy(bname,s,SLEN-1);bname[SLEN-1]='\0';
if(strlen(bname)>=4&&!strcmp(bname+strlen(bname)-4,".qxw")) bname[strlen(bname)-4]='\0';
setwintitle();
}
// GTK MENU HANDLERS
// simple menu handlers
static void m_filenew(GtkWidget*w,gpointer data) {if(!unsaved||areyousure()) a_filenew();}
static void m_fileopen(GtkWidget*w,gpointer data) {if(!unsaved||areyousure()) if(filedia("Open",".qxw",0)) a_load();}
static void m_filesave(GtkWidget*w,gpointer data) {if(filedia("Save",".qxw",1)) a_save();}
static void m_filequit(void) {if(!unsaved||areyousure()) gtk_main_quit();}
static void m_undo(GtkWidget*w,gpointer data) {if((uhead+UNDOS-1)%UNDOS==utail) return;undo_pop();gridchangen();syncgui();}
static void m_redo(GtkWidget*w,gpointer data) {if(uhead==uhwm) return;uhead=(uhead+2)%UNDOS;undo_pop();gridchangen();syncgui();}
static void m_editgprop(GtkWidget*w,gpointer data) {gpropdia();}
static void m_dsprop(GtkWidget*w,gpointer data) {spropdia(1);}
static void m_sprop(GtkWidget*w,gpointer data) {spropdia(0);}
static void m_dlprop(GtkWidget*w,gpointer data) {lpropdia(1);}
static void m_lprop(GtkWidget*w,gpointer data) {lpropdia(0);}
static void m_cellcont(GtkWidget*w,gpointer data) {ccontdia();}
static void m_afctreat(GtkWidget*w,gpointer data) {treatdia();}
static void m_editprefs(GtkWidget*w,gpointer data) {prefsdia();}
static void m_showstats(GtkWidget*w,gpointer data) {statsdia();}
static void m_symm0(GtkWidget*w,gpointer data) {symmr=(int)data&0xff;}
static void m_symm1(GtkWidget*w,gpointer data) {symmm=(int)data&0xff;}
static void m_symm2(GtkWidget*w,gpointer data) {symmd=(int)data&0xff;}
static void m_zoom(GtkWidget*w,gpointer data) {int u;
u=(int)data;
if(u==-1) zoomf++;
else if(u==-2) zoomf--;
else zoomf=u;
if(zoomf<0) zoomf=0;
if(zoomf>4) zoomf=4;
pxsq=zoompx[zoomf];
syncgui();
}
static void m_fileexport(GtkWidget*w,gpointer data) {
switch((int)data) {
case 0x401: if(filedia("Export blank grid as EPS",".blank.eps",1)) a_exportg(filename,4,0);break;
case 0x402: if(filedia("Export blank grid as PNG",".blank.png",1)) a_exportg(filename,4,1);break;
case 0x403:
if(gshape[gtype]!=0) break;
if(filedia("Export blank grid as HTML",".blank.html",1)) a_exportgh(0x05,"");
break;
case 0x411: if(filedia("Export filled grid as EPS",".eps",1)) a_exportg(filename,2,0);break;
case 0x412: if(filedia("Export filled grid as PNG",".png",1)) a_exportg(filename,2,1);break;
case 0x413:
if(gshape[gtype]!=0) break;
if(filedia("Export filled grid as HTML",".html",1)) a_exportgh(0x03,"");
break;
case 0x420: if(filedia("Export answers as text",".ans.txt",1)) a_exporta(0);break;
case 0x423: if(filedia("Export answers as HTML",".ans.html",1)) a_exporta(1);break;
case 0x433:
if(gshape[gtype]!=0) break;
if(filedia("Export puzzle as HTML",".html",1)) a_exportgh(0x0d,"");
break;
case 0x434:if(filedia("Export puzzle as HTML+PNG",".html",1)) a_exporthp(0);break;
case 0x443:
if(gshape[gtype]!=0) break;
if(filedia("Export solution as HTML",".html",1)) a_exportgh(0x13,"");
break;
case 0x444:if(filedia("Export solution as HTML+PNG",".html",1)) a_exporthp(1);break;
default: assert(0);
}
}
void selchange(void) {int i,j,d;
nsel=0;
if (selmode==0) for(i=0;i=100) {
if(dir>=100+nvl) return;
if(selmode!=2) m_selnone(w,data);
selmode=2;
vls[dir-100].sel=!vls[dir-100].sel;
}
else {
if(selmode!=1) m_selnone(w,data);
selmode=1;
sellight(curx,cury,dir,!issellight(curx,cury,dir));
}
selchange();
}
// all lights parallel
static void m_sellpar(GtkWidget*w,gpointer data) {int f,i,j;
if(dir>=100) return;
if(selmode!=1) m_selnone(w,data);
selmode=1;
f=issellight(curx,cury,dir);
for(i=0;i=100||dir>=100) refreshsel();
odir=dir;
curmf=1;
}
void gridchangen(void) {
int d,i,j;
// only squares at start of lights can have dsel information
for(i=0;iflbmh;k=cbits(m); // get hints bitmap
// printf("%d %d %16llx %d\n",x,y,m,k);
if(k==1) gsq[x][y].ct[i][j]=ltochar[logbase2(m)];
e++;
}
}
refreshsqmg(x,y);
}
}
gridchange();
}
// run filler
static void m_autofill(GtkWidget*w,gpointer data) {
if(fillmode) return; // already running?
fillmode=(int)data; // selected mode (1=all, 2=selection)
if(fillmode==2&&selmode!=0) {
sel_toc();
selchange();
}
if(compute()) { // start
reperr("Could not start filler");
fillmode=0;
return;
}
box(GTK_MESSAGE_INFO,"\n Filling in progress \n"," Stop ",0); // this box is closed manually or by the filler when it completes
fillmode=0; // filling done
}
// grid edit operations
static void editblock (int x,int y) { symmdo(a_editblock ,0,x,y,0);gridchange();}
static void editempty (int x,int y) {clrcont(x,y);symmdo(a_editempty ,0,x,y,0);gridchange();}
static void editcutout(int x,int y) { symmdo(a_editcutout,0,x,y,0);gridchange();}
// called from menu
static void m_editblock (GtkWidget*w,gpointer data) {editblock (curx,cury);}
static void m_editempty (GtkWidget*w,gpointer data) {editempty (curx,cury);}
static void m_editcutout(GtkWidget*w,gpointer data) {editcutout(curx,cury);}
// bar behind cursor
static void m_editbarb(GtkWidget*w,gpointer data) {
if(dir>=100) return;
symmdo(a_editbar,!isbar(curx,cury,dir+ndir[gtype]),curx,cury,dir+ndir[gtype]);
gridchange();
}
// merge/join ahead
static void m_editmerge(GtkWidget*w,gpointer data) {
if(dir>=100) return;
symmdo(a_editmerge,!ismerge(curx,cury,dir),curx,cury,dir);
gridchange();
}
// flip grid in main diagonal
static void m_editflip(GtkWidget*w,gpointer data) {int i,j,k,t;struct square s;struct lprop l;char p[MXSZ+1];
if(gshape[gtype]>2) return;
for(i=0;i>1); // swap bars around
else t=((t&1)<<2)|( t&2 )|((t&4)>>2);
gsq[i][j].bars=t;
t=gsq[i][j].merge;
if(gshape[gtype]==0) t=((t&1)<<1)|((t&2)>>1); // swap merges around
else t=((t&1)<<2)|( t&2 )|((t&4)>>2);
gsq[i][j].merge=t;
t=gsq[i][j].dsel;
if(gshape[gtype]==0) t=((t&1)<<1)|((t&2)>>1); // swap directional selects around
else t=((t&1)<<2)|( t&2 )|((t&4)>>2);
gsq[i][j].dsel=t;
k=(gshape[gtype]==0)?1:2;
l=gsq[i][j].lp[0];gsq[i][j].lp[0]=gsq[i][j].lp[k];gsq[i][j].lp[k]=l;
if(getdech(i,j)) strcpy(p,gsq[i][j].ct[0]),strcpy(gsq[i][j].ct[0],gsq[i][j].ct[k]),strcpy(gsq[i][j].ct[k],p);
}
for(i=0;i=0 ;i--) u=gsq[i][j],gsq[i][j]=t,t=u;}
gridchange();
selchange();
syncgui();
}
static void m_vlnew(GtkWidget*w,gpointer data) {
if(nvl>=NVL) {reperr("Limit on number of\nfree lights reached");return;}
m_selnone(w,data);
selmode=2;
vls[nvl].x[0]=curx;
vls[nvl].y[0]=cury;
vls[nvl].l=1;
vls[nvl].sel=1;
resetlp(&vls[nvl].lp);
nvl++;
gridchange();
selchange();
}
static int getselvl() {int i;
if(selmode!=2) return -1;
if(nsel!=1) return -1;
for(i=0;i=MXSZ) {reperr("Limit on length of\nfree light reached");return;}
vls[i].x[vls[i].l]=curx;
vls[i].y[vls[i].l]=cury;
vls[i].l++;
DEB1 printf("VL %d l=%d\n",i,vls[i].l);
gridchange();
selchange();
}
static void m_vldelete(GtkWidget*w,gpointer data) {int i,j;
if(selmode!=2) return;
for(i=0,j=0;ikeyval; f=event->state;
if(k>='a'&&k<='z') k+='A'-'a';
DEB1 printf("keypress event: %x %x\n",k,f);
f&=12; // mask off flags except CTRL, ALT
if((k==GDK_Tab&&f==0)||(((k>='A'&&k<='Z')||(k>='0'&&k<='9'))&&f==0)) { // tab and letters, digits
r=1;
if(setechar(curx,cury,(k==GDK_Tab)?' ':k)) ccontdia(); // open dialogue if not a simple square
refreshsqmg(curx,cury);
if(dir<100) stepforwmifingrid(&curx,&cury,dir);
undo_push();
}
if(k==' '&&f==0) {r=1; if(dir<100) stepforwmifingrid(&curx,&cury,dir);}
if(k==GDK_Left &&f==0) {r=1; if(dir>=100) dir=0; x=curx;y=cury;moveleft (&x,&y);if(isingrid(x,y)) curx=x,cury=y;}
if(k==GDK_Right&&f==0) {r=1; if(dir>=100) dir=0; x=curx;y=cury;moveright(&x,&y);if(isingrid(x,y)) curx=x,cury=y;}
if(k==GDK_Up &&f==0) {r=1; if(dir>=100) dir=0; x=curx;y=cury;moveup (&x,&y);if(isingrid(x,y)) curx=x,cury=y;}
if(k==GDK_Down &&f==0) {r=1; if(dir>=100) dir=0; x=curx;y=cury;movedown (&x,&y);if(isingrid(x,y)) curx=x,cury=y;}
if(k==GDK_Home &&f==0) {r=1; x=curx;y=cury;movehome (&x,&y);if(isingrid(x,y)) curx=x,cury=y;}
if(k==GDK_End &&f==0) {r=1; x=curx;y=cury;moveend (&x,&y);if(isingrid(x,y)) curx=x,cury=y;}
if(k==GDK_Page_Up &&f==0) {r=1; prevdir();}
if(k==GDK_Page_Down &&f==0) {r=1; nextdir();}
if(k==GDK_BackSpace&&f==0) {r=1; if(dir<100) stepbackifingrid(&curx,&cury,dir);}
if(r) { // if we have processed the key, update both old and new cursor positions
curmoved();
compute();
}
return r;
}
// convert screen coords to internal square coords; k is bitmap of sufficiently nearby edges
void ptrtosq(int*x,int*y,int*k,int x0,int y0) {float u,v,u0,v0,r=0,t=0,xa=0,ya=0,xb=0,yb=0;int i,j;
u0=((float)x0-bawdpx)/pxsq;v0=((float)y0-bawdpx)/pxsq;
*k=0;
switch(gshape[gtype]) {
case 0:
i=floor(u0);u=u0-i;
j=floor(v0);v=v0-j;
*x=i;*y=j;
break;
case 1:
i=(int)floor(u0/1.2);u=u0-i*1.2;
if((i&1)==0) {
j=(int)floor(v0/1.4);v=v0-j*1.4;
if(u>=0.4) {*x=i;*y=j;break;}
if( 0.7*u+0.4*v<0.28) {*x=i-1;*y=j-1;break;}
if(-0.7*u+0.4*v>0.28) {*x=i-1;*y=j; break;}
*x=i;*y=j;break;
} else {
j=(int)floor((v0-0.7)/1.4);v=v0-0.7-j*1.4;
if(u>=0.4) {*x=i;*y=j;break;}
if( 0.7*u+0.4*v<0.28) {*x=i-1;*y=j; break;}
if(-0.7*u+0.4*v>0.28) {*x=i-1;*y=j+1;break;}
*x=i;*y=j;break;
}
case 2:
j=(int)floor(v0/1.2);v=v0-j*1.2;
if((j&1)==0) {
i=(int)floor(u0/1.4);u=u0-i*1.4;
if(v>=0.4) {*y=j;*x=i;break;}
if( 0.7*v+0.4*u<0.28) {*y=j-1;*x=i-1;break;}
if(-0.7*v+0.4*u>0.28) {*y=j-1;*x=i; break;}
*y=j;*x=i;break;
} else {
i=(int)floor((u0-0.7)/1.4);u=u0-0.7-i*1.4;
if(v>=0.4) {*y=j;*x=i;break;}
if( 0.7*v+0.4*u<0.28) {*y=j-1;*x=i; break;}
if(-0.7*v+0.4*u>0.28) {*y=j-1;*x=i+1;break;}
*y=j;*x=i;break;
}
case 3:case 4:
u=u0-height;v=v0-height;
r=sqrt(u*u+v*v);
if(r<1e-3) {*x=-1;*y=-1;return;}
r=height-r;
t=atan2(u,-v)*width/2/PI;
if(gtype==4) t+=.5;
if(t<0) t+=width;
*x=(int)floor(t);
*y=(int)floor(r);
break;
}
switch(gshape[gtype]) { // click-near-edge bitmap
case 0:case 1:case 2:
for(i=0;i1-PXEDGE ) *k|=2;
if(r-*y< PXEDGE ) *k|=8;
r=height-r;
if(t-*x>1-PXEDGE/((r<2)?1:(r-1))) *k|=1;
if(t-*x< PXEDGE/((r<2)?1:(r-1))) *k|=4;
break;
}
}
int dragflag=-1; // store selectedness state of square where shift-drag starts, or -1 if none in progress
static void mousel(int x,int y) { // left button
if(selmode!=0) m_selnone(0,0);
selmode=0;
if(dragflag==-1) dragflag=gsq[x][y].fl&16; // set f if at beginning of shift-drag
if((gsq[x][y].fl&16)==dragflag) {
selcell(x,y,!dragflag);
selchange();
}
}
static void mouser(int x,int y) { // right button
if(dir>=100) return;
if(selmode!=1) m_selnone(0,0);
selmode=1;
if(dragflag==-1) dragflag=issellight(x,y,dir);
if(issellight(x,y,dir)==dragflag) {
sellight(x,y,dir,!dragflag);
selchange();
}
}
// pointer motion
static gint mousemove(GtkWidget*widget,GdkEventMotion*event) {
int e,k,x,y;
ptrtosq(&x,&y,&e,event->x,event->y);
k=(int)(event->state); // buttons and modifiers
DEB2 printf("mouse move event (%d,%d) %d e=%02x\n",x,y,k,e);
if(!isingrid(x,y)) return 0;
if((k&GDK_SHIFT_MASK)==0) {dragflag=-1;return 0;} // shift not held down: reset f
if (k&GDK_BUTTON3_MASK) mouser(x,y);
else if(k&GDK_BUTTON1_MASK) mousel(x,y);
else dragflag=-1; // button not held down: reset dragflag
return 0;
}
// click in grid area
static gint button_press_event(GtkWidget*widget,GdkEventButton*event) {
int b,e,ee,k,x,y;
ptrtosq(&x,&y,&e,event->x,event->y);
k=event->state;
DEB2 printf("button press event (%f,%f) -> (%d,%d) e=%02x button=%08x type=%08x state=%08x\n",event->x,event->y,x,y,e,(int)event->button,(int)event->type,k);
if(event->type==GDK_BUTTON_RELEASE) dragflag=-1;
if(event->type!=GDK_BUTTON_PRESS) goto ew0;
if(k&GDK_SHIFT_MASK) {
if (event->button==3) mouser(x,y);
else if(event->button==1) mousel(x,y);
return 0;
}
if(event->button!=1) goto ew0; // only left clicks do anything now
if(!isingrid(x,y)) goto ew0;
ee=!!(e&(e-1)); // more than one bit set in e?
if(clickblock&&ee) { // flip between block and space when a square is clicked near a corner
if((gsq[x][y].fl&1)==0) {editblock(x,y);gridchange();goto ew1;}
else {editempty(x,y);gridchange();goto ew1;}
}
else if(clickbar&&e&&!ee) { // flip bar if clicked in middle of edge
b=logbase2(e);
DEB2 printf(" x=%d y=%d e=%d b=%d isbar(x,y,b)=%d\n",x,y,e,b,isbar(x,y,b));
symmdo(a_editbar,!isbar(x,y,b),x,y,b);
gridchange();
goto ew1;
}
if(x==curx&&y==cury) nextdir(); // flip direction for click in square of cursor
curx=x; cury=y; // not trapped as producing bar or block, so move cursor
curmoved();
gridchange();
ew1:
gtk_window_set_focus(GTK_WINDOW(mainw),grid_da);
ew0:
DEB2 printf("Exiting button_press_event()\n");
return FALSE;
}
// word list entry selected
static void selrow(GtkWidget*widget,gint row,gint column, GdkEventButton*event,gpointer data) {
int i,l,lx[MXSZ],ly[MXSZ],nc;
char*chp[MXSZ];
DEB1 printf("row select event\n");
l=getlight(lx,ly,curx,cury,dir);
if(l<2) return;
nc=getlightchp(chp,curx,cury,dir);
if(nc<2) return;
if(!llistp) return;
if(row<0||row>=llistn) return;
if(strlen(lts[llistp[row]].s)!=nc) return;
for(i=0;iallocation.width,widget->allocation.height);
if(widget==grid_da) {
}
return TRUE;
}
// redraw the screen from the backing pixmap
static gint expose_event(GtkWidget*widget,GdkEventExpose*event) {float u,v;
cairo_t*cr;
DEB4 printf("expose event x=%d y=%d w=%d h=%d\n",event->area.x,event->area.y,event->area.width,event->area.height),fflush(stdout);
if(widget==grid_da) {
cr=gdk_cairo_create(widget->window);
cairo_rectangle(cr,event->area.x,event->area.y,event->area.width,event->area.height);
cairo_clip(cr);
repaint(cr);
cairo_destroy(cr);
if(curmf) {
mgcentre(&u,&v,curx,cury,0,1);
DEB1 printf("curmoved: %f %f %d %d\n",u,v,pxsq,bawdpx);
gtk_adjustment_clamp_page(gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(grid_sw)),(u-.5)*pxsq+bawdpx-3,(u+.5)*pxsq+bawdpx+3); // scroll window to follow cursor
gtk_adjustment_clamp_page(gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(grid_sw)),(v-.5)*pxsq+bawdpx-3,(v+.5)*pxsq+bawdpx+3);
curmf=0;
}
}
return FALSE;
}
void invaldarect(int x0,int y0,int x1,int y1) {GdkRectangle r;
r.x=x0; r.y=y0; r.width=x1-x0; r.height=y1-y0;
DEB4 printf("invalidate(%d,%d - %d,%d)\n",x0,y0,x1,y1);
gdk_window_invalidate_rect(grid_da->window,&r,0);
}
void invaldaall() {
DEB4 printf("invalidate all\n");
gdk_window_invalidate_rect(grid_da->window,0,0);
}
// GTK DIALOGUES
// general filename dialogue: button text in but, default file extension in ext
static int filedia(char*but,char*ext,int write) {
GtkWidget*dia;
int i;
char*p,t[SLEN+50];
dia=gtk_file_chooser_dialog_new(but,0,
write?GTK_FILE_CHOOSER_ACTION_SAVE:GTK_FILE_CHOOSER_ACTION_OPEN,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_OK, GTK_RESPONSE_OK,
NULL);
strcpy(filename,bname);strcat(filename,ext);
strcpy(t,filename);
if(write) gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dia),basename(t));
ew0:
i=gtk_dialog_run(GTK_DIALOG(dia));
i=i==GTK_RESPONSE_OK;
if(i) {
p=gtk_file_chooser_get_filename((GtkFileChooser*)dia);
if(strlen(p)>SLEN) {reperr("File name too long");goto ew0;}
strcpy(filename,p);
}
else strcpy(filename,"");
gtk_widget_destroy(dia);
return i; // return 1 if got name successfully (although may be empty string)
}
// cell contents dialogue
static int ccontdia() {
GtkWidget*dia,*vb,*l0,*m[MAXNDIR],*lm[MAXNDIR],*e[MAXNDIR];
int i,j,k,u,x,y;
char c,s[100],t[MXSZ+1];
x=curx; y=cury;
getmergerep(&x,&y,x,y);
if(!isclear(x,y)) {reperr("Please place the cursor on a cell\nthat can contain characters");return 1;}
dia=gtk_dialog_new_with_buttons("Cell contents",
GTK_WINDOW(mainw),GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_CANCEL,GTK_RESPONSE_CANCEL,GTK_STOCK_OK,GTK_RESPONSE_OK,NULL);
vb=gtk_vbox_new(0,2); // box to hold everything
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dia)->vbox),vb,TRUE,TRUE,0);
u=getdech(x,y);
if(u) {
l0=gtk_label_new("Contribution from cell");
gtk_misc_set_alignment(GTK_MISC(l0),0,0.5);
gtk_box_pack_start(GTK_BOX(vb),l0,TRUE,TRUE,0);
}
for(i=0;ibgcol;
gcbg.red =((i>>16)&255)*257;
gcbg.green=((i>> 8)&255)*257;
gcbg.blue =((i )&255)*257;
i=sps[0]->fgcol;
gcfg.red =((i>>16)&255)*257;
gcfg.green=((i>> 8)&255)*257;
gcfg.blue =((i )&255)*257;
dia=gtk_dialog_new_with_buttons(g?"Default cell properties":"Selected cell properties",
GTK_WINDOW(mainw),GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_CANCEL,GTK_RESPONSE_CANCEL,GTK_STOCK_OK,GTK_RESPONSE_OK,NULL);
vb=gtk_vbox_new(0,2); // box to hold everything
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dia)->vbox),vb,TRUE,TRUE,0);
int setactive() {int k;
k=g||gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(or));
gtk_widget_set_sensitive(cbg,k);
gtk_widget_set_sensitive(cfg,k);
gtk_widget_set_sensitive(l0,k);
gtk_widget_set_sensitive(l1,k);
gtk_widget_set_sensitive(l2,k);
gtk_widget_set_sensitive(w20,k);
gtk_widget_set_sensitive(tr,k);
gtk_widget_set_sensitive(w21,k);
return 1;
}
if(!g) {
or=gtk_check_button_new_with_mnemonic("Override default cell properties");
gtk_box_pack_start(GTK_BOX(vb),or,TRUE,TRUE,0);
gtk_signal_connect(GTK_OBJECT(or),"clicked",GTK_SIGNAL_FUNC(setactive),0);
w=gtk_hseparator_new();
gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
}
w=gtk_hbox_new(0,2);
l0=gtk_label_new("Background colour: ");
gtk_label_set_width_chars(GTK_LABEL(l0),18);
gtk_misc_set_alignment(GTK_MISC(l0),1,0.5);
gtk_box_pack_start(GTK_BOX(w),l0,FALSE,FALSE,0);
cbg=gtk_color_button_new_with_color(&gcbg);
gtk_box_pack_start(GTK_BOX(w),cbg,FALSE,FALSE,0);
gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
w=gtk_hbox_new(0,2);
l1=gtk_label_new("Foreground colour: ");
gtk_label_set_width_chars(GTK_LABEL(l1),18);
gtk_misc_set_alignment(GTK_MISC(l1),1,0.5);
gtk_box_pack_start(GTK_BOX(w),l1,FALSE,FALSE,0);
cfg=gtk_color_button_new_with_color(&gcfg);
gtk_box_pack_start(GTK_BOX(w),cfg,FALSE,FALSE,0);
gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
w=gtk_hbox_new(0,2); gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
l2=gtk_label_new("Font style: "); gtk_box_pack_start(GTK_BOX(w),l2,FALSE,FALSE,0);
gtk_label_set_width_chars(GTK_LABEL(l2),18);
gtk_misc_set_alignment(GTK_MISC(l2),1,0.5);
w20=gtk_combo_box_new_text(); gtk_box_pack_start(GTK_BOX(w),w20,FALSE,FALSE,0);
gtk_combo_box_append_text(GTK_COMBO_BOX(w20),"Normal");
gtk_combo_box_append_text(GTK_COMBO_BOX(w20),"Bold");
gtk_combo_box_append_text(GTK_COMBO_BOX(w20),"Italic");
gtk_combo_box_append_text(GTK_COMBO_BOX(w20),"Bold italic");
i=sps[0]->fstyle;
if(i<0) i=0;
if(i>3) i=3;
gtk_combo_box_set_active(GTK_COMBO_BOX(w20),i);
w=gtk_hseparator_new();
gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
tr=gtk_check_button_new_with_mnemonic("Flag for _answer treatment");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tr),sps[0]->ten);
gtk_box_pack_start(GTK_BOX(vb),tr,TRUE,TRUE,0);
w=gtk_hbox_new(0,2); gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
l3=gtk_label_new("Lights intersecting here "); gtk_box_pack_start(GTK_BOX(w),l3,FALSE,FALSE,0);
gtk_misc_set_alignment(GTK_MISC(l3),1,0.5);
w21=gtk_combo_box_new_text(); gtk_box_pack_start(GTK_BOX(w),w21,FALSE,FALSE,0);
gtk_combo_box_append_text(GTK_COMBO_BOX(w21),"must agree");
gtk_combo_box_append_text(GTK_COMBO_BOX(w21),"need not agree: horizontal display");
gtk_combo_box_append_text(GTK_COMBO_BOX(w21),"need not agree: vertical display");
i=sps[0]->dech;
if(i<0) i=0;
if(i>2) i=2;
gtk_combo_box_set_active(GTK_COMBO_BOX(w21),i);
if(!g) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(or),(sps[0]->spor)&1);
setactive();
gtk_widget_show_all(dia);
i=gtk_dialog_run(GTK_DIALOG(dia));
if(i==GTK_RESPONSE_OK) {
gtk_color_button_get_color(GTK_COLOR_BUTTON(cbg),&gcbg);
gtk_color_button_get_color(GTK_COLOR_BUTTON(cfg),&gcfg);
for(j=0;jspor=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(or));
sps[j]->bgcol=
(((gcbg.red >>8)&255)<<16)+
(((gcbg.green>>8)&255)<< 8)+
(((gcbg.blue >>8)&255) );
sps[j]->fgcol=
(((gcfg.red >>8)&255)<<16)+
(((gcfg.green>>8)&255)<< 8)+
(((gcfg.blue >>8)&255) );
i=gtk_combo_box_get_active(GTK_COMBO_BOX(w20)); if(i>=0&&i<4) sps[j]->fstyle=i;
sps[j]->ten=!!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tr));
i=gtk_combo_box_get_active(GTK_COMBO_BOX(w21)); if(i>=0&&i<3) sps[j]->dech=i;
}
}
gtk_widget_destroy(dia);
gridchange();
refreshall();
return 1;
}
// light properties dialogue
static int lpropdia(int g) {
GtkWidget*dia,*e[MAXNDICTS],*f[NLEM],*l,*w,*vb,*tr;
struct lprop*lps[MXSZ*MXSZ*MAXNDIR];
int nlps;
int d,i,j;
char s[SLEN];
nlps=0;
if(g) {lps[0]=&dlp;nlps=1;}
else {
if(selmode==1)
for(d=0;dvbox),vb,TRUE,TRUE,0);
int setactive() {int i,k;
k=g||gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(l));
for(i=0;i15) {strcat(s,"...");strcat(s,dfnames[i]+strlen(dfnames[i])-12);}
else if(strlen(dfnames[i])==0) strcat(s,"");
else strcat(s,dfnames[i]);
strcat(s,")");
e[i]=gtk_check_button_new_with_mnemonic(s);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e[i]),((lps[0]->dmask)>>i)&1);
gtk_box_pack_start(GTK_BOX(vb),e[i],TRUE,TRUE,0);
}
w=gtk_hseparator_new();
gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
tr=gtk_check_button_new_with_mnemonic("_Enable answer treatment");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tr),lps[0]->ten);
gtk_box_pack_start(GTK_BOX(vb),tr,TRUE,TRUE,0);
w=gtk_hseparator_new();
gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
for(i=0;iemask)>>i)&1);
gtk_box_pack_start(GTK_BOX(vb),f[i],TRUE,TRUE,0);
}
if(!g) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(l),(lps[0]->lpor)&1);
setactive();
gtk_widget_show_all(dia);
i=gtk_dialog_run(GTK_DIALOG(dia));
if(i==GTK_RESPONSE_OK) for(j=0;jlpor=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(l));
lps[j]->dmask=0;
for(i=0;idmask|=1<emask=0;
for(i=0;iemask|=1<ten=!!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tr));
}
gtk_widget_destroy(dia);
gridchange();
return 1;
}
// treatment dialogue
static int treatdia(void) {
GtkWidget*dia,*e[NMSG],*f,*m[NMSG],*w,*c,*l,*b,*vb,*b0,*lm[NMSG];
int i;
char s[SLEN],*p;
char*tnames[NATREAT]={
" None",
" Playfair cipher",
" Substitution cipher",
" Fixed Caesar/Vigenère cipher",
" Variable Caesar cipher (clue order)",
" Misprint (clue order)",
" Delete single occurrence of letter (clue order)",
" Letters latent: delete all occurrences of letter (clue order)",
" Insert single letter (clue order)",
" Custom plug-in"};
char*lab[NATREAT][NMSG]={
{0, 0, },
{"Keyword: ", 0, },
{"Encode ABC...Z as: ", 0, },
{"Key letter/word: ", 0, },
{"Encodings of A: ", 0, },
{"Correct letters: ", "Misprinted letters: "},
{"Letters to delete: ", 0, },
{"Letters to delete: ", 0, },
{"Letters to insert: ", 0, },
{"Message 1: ", "Message 2: " }};
dia=gtk_dialog_new_with_buttons("Answer treatment",GTK_WINDOW(mainw),GTK_DIALOG_DESTROY_WITH_PARENT,GTK_STOCK_CANCEL,GTK_RESPONSE_CANCEL,GTK_STOCK_OK,GTK_RESPONSE_OK,NULL);
vb=gtk_vbox_new(0,2); // box to hold everything
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dia)->vbox),vb,TRUE,TRUE,0);
c=gtk_combo_box_new_text();
for(i=0;iNATREAT) treatmode=0;
if(treatmode==NATREAT-1) {
if((p=loadtpi())) {
sprintf(s,"Error loading custom plug-in\n%.100s",p);
reperr(s);
}
}
else unloadtpi();
}
gtk_widget_destroy(dia);
gridchange();
return 1;
}
// dictionary list dialogue
static int dictldia(void) {
GtkWidget*dia,*e[MAXNDICTS],*l,*b,*t,*f0[MAXNDICTS],*f1[MAXNDICTS];
int i,j;
char s[SLEN];
dia=gtk_dialog_new_with_buttons("Dictionaries",GTK_WINDOW(mainw),GTK_DIALOG_DESTROY_WITH_PARENT,GTK_STOCK_CANCEL,GTK_RESPONSE_CANCEL,GTK_STOCK_OK,GTK_RESPONSE_OK,NULL);
t=gtk_table_new(MAXNDICTS+1,5,0);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dia)->vbox),t,TRUE,TRUE,0);
l=gtk_label_new(NULL);gtk_label_set_markup(GTK_LABEL(l),"File" ); gtk_table_attach(GTK_TABLE(t),l,1,2,0,1,0,0,0,0);
l=gtk_label_new(""); gtk_table_attach(GTK_TABLE(t),l,3,4,0,1,0,0,10,0);
l=gtk_label_new(NULL);gtk_label_set_markup(GTK_LABEL(l),"File filter" ); gtk_table_attach(GTK_TABLE(t),l,4,5,0,1,0,0,0,0);
l=gtk_label_new(NULL);gtk_label_set_markup(GTK_LABEL(l),"Answer filter"); gtk_table_attach(GTK_TABLE(t),l,5,6,0,1,0,0,0,0);
for(i=0;i%d",i+1);
l=gtk_label_new(NULL);
gtk_label_set_markup(GTK_LABEL(l),s);
gtk_table_attach(GTK_TABLE(t),l,0,1,i+1,i+2,0,0,5,0);
e[i]=gtk_entry_new();
gtk_entry_set_max_length(GTK_ENTRY(e[i]),SLEN);
gtk_entry_set_text(GTK_ENTRY(e[i]),dfnames[i]);
gtk_table_attach(GTK_TABLE(t),e[i],1,2,i+1,i+2,0,0,0,0);
b=gtk_button_new_with_label("Browse...");
gtk_table_attach(GTK_TABLE(t),b,2,3,i+1,i+2,0,0,0,0);
f0[i]=gtk_entry_new();
gtk_entry_set_max_length(GTK_ENTRY(f0[i]),SLEN);
gtk_entry_set_text(GTK_ENTRY(f0[i]),dsfilters[i]);
gtk_table_attach(GTK_TABLE(t),f0[i],4,5,i+1,i+2,0,0,5,0);
f1[i]=gtk_entry_new();
gtk_entry_set_max_length(GTK_ENTRY(f1[i]),SLEN);
gtk_entry_set_text(GTK_ENTRY(f1[i]),dafilters[i]);
gtk_table_attach(GTK_TABLE(t),f1[i],5,6,i+1,i+2,0,0,5,0);
gtk_signal_connect(GTK_OBJECT(b),"clicked",GTK_SIGNAL_FUNC(browse),(void*)i);
}
gtk_widget_show_all(dia);
i=gtk_dialog_run(GTK_DIALOG(dia));
if(i==GTK_RESPONSE_OK) {
for(j=0;jvbox),vb,TRUE,TRUE,0);
t=gtk_table_new(2,2,0); gtk_box_pack_start(GTK_BOX(vb),t,TRUE,TRUE,0);
l=gtk_label_new("Title: "); gtk_table_attach(GTK_TABLE(t),l,0,1,0,1,0,0,0,0);
gtk_label_set_width_chars(GTK_LABEL(l),10);
gtk_misc_set_alignment(GTK_MISC(l),1,0.5);
ttl=gtk_entry_new(); gtk_table_attach(GTK_TABLE(t),ttl,1,2,0,1,0,0,0,0);
gtk_entry_set_max_length(GTK_ENTRY(ttl),SLEN);
gtk_entry_set_text(GTK_ENTRY(ttl),gtitle);
l=gtk_label_new("Author: "); gtk_table_attach(GTK_TABLE(t),l,0,1,1,2,0,0,0,0);
gtk_label_set_width_chars(GTK_LABEL(l),10);
gtk_misc_set_alignment(GTK_MISC(l),1,0.5);
aut=gtk_entry_new(); gtk_table_attach(GTK_TABLE(t),aut,1,2,1,2,0,0,0,0);
gtk_entry_set_max_length(GTK_ENTRY(aut),SLEN);
gtk_entry_set_text(GTK_ENTRY(aut),gauthor);
w=gtk_hseparator_new(); gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
w=gtk_hbox_new(0,2); gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
l=gtk_label_new("Grid type: "); gtk_box_pack_start(GTK_BOX(w),l,FALSE,FALSE,0);
gtk_label_set_width_chars(GTK_LABEL(l),10);
gtk_misc_set_alignment(GTK_MISC(l),1,0.5);
w20=gtk_combo_box_new_text(); gtk_box_pack_start(GTK_BOX(w),w20,FALSE,FALSE,0);
for(i=0;i=1&&i<=MXSZ) width=i;
i=gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(w31)); if(i>=1&&i<=MXSZ) height=i;
i=gtk_combo_box_get_active(GTK_COMBO_BOX(w20)); if(i>=0&&ivbox),vb,TRUE,TRUE,0);
l=gtk_label_new("Enter a coordinate pair for each cell in the path on a"); gtk_box_pack_start(GTK_BOX(vb),l,FALSE,TRUE,0);
gtk_misc_set_alignment(GTK_MISC(l),0,0.5);
l=gtk_label_new("separate line. Coordinates are counted from zero."); gtk_box_pack_start(GTK_BOX(vb),l,FALSE,TRUE,0);
gtk_misc_set_alignment(GTK_MISC(l),0,0.5);
for(i=0,p=s;i0) {
vls[v].l=i;
memcpy(vls[v].x,x,i*sizeof(int));
memcpy(vls[v].y,y,i*sizeof(int));
}
gridchange();
selchange();
}
gtk_widget_destroy(dia);
return 1;
err0: reperr("Each line must contain\ntwo numeric coordinate values\nseparated by a comma or space"); goto ew0;
err1: reperr("Free light length limit reached"); goto ew0;
}
// preferences dialogue
static int prefsdia(void) {
GtkWidget*dia,*l,*w,*w30,*w31,*w00,*w01,*w02,*w20,*w21,*w10,*w11,*w12,*w14,*vb;
int i;
dia=gtk_dialog_new_with_buttons("Preferences",GTK_WINDOW(mainw),GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_CANCEL,GTK_RESPONSE_CANCEL,GTK_STOCK_OK,GTK_RESPONSE_OK,NULL);
vb=gtk_vbox_new(0,3); // box to hold all the options
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dia)->vbox),vb,TRUE,TRUE,0);
l=gtk_label_new(NULL);gtk_label_set_markup(GTK_LABEL(l),"Export preferences"); gtk_box_pack_start(GTK_BOX(vb),l,TRUE,TRUE,0);
w=gtk_hbox_new(0,3); gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
l=gtk_label_new("EPS export square size: "); gtk_box_pack_start(GTK_BOX(w),l,FALSE,FALSE,0);
w30=gtk_spin_button_new_with_range(10,72,1); gtk_box_pack_start(GTK_BOX(w),w30,FALSE,FALSE,0);
l=gtk_label_new(" points"); gtk_box_pack_start(GTK_BOX(w),l,FALSE,FALSE,0);
w=gtk_hseparator_new(); gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
w=gtk_hbox_new(0,3); gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
l=gtk_label_new("HTML/PNG export square size: "); gtk_box_pack_start(GTK_BOX(w),l,FALSE,FALSE,0);
w31=gtk_spin_button_new_with_range(10,72,1); gtk_box_pack_start(GTK_BOX(w),w31,FALSE,FALSE,0);
l=gtk_label_new(" pixels"); gtk_box_pack_start(GTK_BOX(w),l,FALSE,FALSE,0);
w=gtk_hseparator_new(); gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
l=gtk_label_new(NULL);gtk_label_set_markup(GTK_LABEL(l),"Editing preferences"); gtk_box_pack_start(GTK_BOX(vb),l,TRUE,TRUE,0);
w00=gtk_check_button_new_with_label("Clicking corners makes blocks"); gtk_box_pack_start(GTK_BOX(vb),w00,TRUE,TRUE,0);
w=gtk_hseparator_new(); gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
w01=gtk_check_button_new_with_label("Clicking edges makes bars"); gtk_box_pack_start(GTK_BOX(vb),w01,TRUE,TRUE,0);
w=gtk_hseparator_new(); gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
w02=gtk_check_button_new_with_label("Show numbers while editing"); gtk_box_pack_start(GTK_BOX(vb),w02,TRUE,TRUE,0);
w=gtk_hseparator_new(); gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
l=gtk_label_new(NULL);gtk_label_set_markup(GTK_LABEL(l),"Statistics preferences"); gtk_box_pack_start(GTK_BOX(vb),l,TRUE,TRUE,0);
l=gtk_label_new("Desirable checking ratios");gtk_misc_set_alignment(GTK_MISC(l),0,0.5); gtk_box_pack_start(GTK_BOX(vb),l,TRUE,TRUE,0);
w=gtk_hbox_new(0,3); gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
l=gtk_label_new("Minimum: "); gtk_box_pack_start(GTK_BOX(w),l,FALSE,FALSE,0);
w20=gtk_spin_button_new_with_range(0,100,1); gtk_box_pack_start(GTK_BOX(w),w20,FALSE,FALSE,0);
l=gtk_label_new("% Maximum: "); gtk_box_pack_start(GTK_BOX(w),l,FALSE,FALSE,0);
w21=gtk_spin_button_new_with_range(0,100,1); gtk_box_pack_start(GTK_BOX(w),w21,FALSE,FALSE,0);
l=gtk_label_new("% plus one cell"); gtk_box_pack_start(GTK_BOX(w),l,FALSE,FALSE,0);
w=gtk_hseparator_new(); gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
l=gtk_label_new(NULL);gtk_label_set_markup(GTK_LABEL(l),"Autofill preferences"); gtk_box_pack_start(GTK_BOX(vb),l,TRUE,TRUE,0);
w10=gtk_radio_button_new_with_label_from_widget(NULL,"Deterministic"); gtk_box_pack_start(GTK_BOX(vb),w10,TRUE,TRUE,0);
w11=gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(w10),"Slightly randomised"); gtk_box_pack_start(GTK_BOX(vb),w11,TRUE,TRUE,0);
w12=gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(w11),"Highly randomised"); gtk_box_pack_start(GTK_BOX(vb),w12,TRUE,TRUE,0);
w=gtk_hseparator_new(); gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
w14=gtk_check_button_new_with_label("Prevent duplicate answers and lights"); gtk_box_pack_start(GTK_BOX(vb),w14,TRUE,TRUE,0);
w=gtk_hseparator_new(); gtk_box_pack_start(GTK_BOX(vb),w,TRUE,TRUE,0);
// set widgets from current preferences values
gtk_spin_button_set_value(GTK_SPIN_BUTTON(w30),eptsq);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(w31),hpxsq);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w00),clickblock);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w01),clickbar);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w02),shownums);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w14),afunique);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(w20),mincheck);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(w21),maxcheck);
if(afrandom==0) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w10),1);
if(afrandom==1) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w11),1);
if(afrandom==2) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w12),1);
gtk_widget_show_all(dia);
i=gtk_dialog_run(GTK_DIALOG(dia));
if(i==GTK_RESPONSE_OK) {
// set preferences values back from values (with bounds checking)
eptsq=gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(w30));
if(eptsq<10) eptsq=10;
if(eptsq>72) eptsq=72;
hpxsq=gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(w31));
if(hpxsq<10) hpxsq=10;
if(hpxsq>72) hpxsq=72;
clickblock=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w00))&1;
clickbar=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w01))&1;
shownums=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w02))&1;
mincheck=gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(w20));
if(mincheck< 0) mincheck= 0;
if(mincheck>100) mincheck=100;
maxcheck=gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(w21));
if(maxcheck< 0) maxcheck= 0;
if(maxcheck>100) maxcheck=100;
afunique=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w14));
if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w10))) afrandom=0;
if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w11))) afrandom=1;
if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w12))) afrandom=2;
saveprefs(); // save to preferences file (failing silently)
}
gtk_widget_destroy(dia);
compute(); // because ofnew checking ratios
invaldaall();
return 1;
}
// update statistics window if in view
// main widget table is st_te[][], rows below in st_r[]
void stats_upd(void) {
int i,j;
char s[SLEN];
if(!stats) return;
for(i=2;i<=MXSZ;i++) { // one row for each word length
if(st_lc[i]>0) {
sprintf(s,"%d",i); gtk_label_set_text(GTK_LABEL(st_te[i][0]),s);
sprintf(s,"%d",st_lc[i]); gtk_label_set_text(GTK_LABEL(st_te[i][1]),s);
sprintf(s,st_lucc[i]?"%d (%.1f%%)":" ",st_lucc[i],100.0*st_lucc[i]/st_lc[i]); gtk_label_set_text(GTK_LABEL(st_te[i][2]),s);
sprintf(s,st_locc[i]?"%d (%.1f%%)":" ",st_locc[i],100.0*st_locc[i]/st_lc[i]); gtk_label_set_text(GTK_LABEL(st_te[i][3]),s);
sprintf(s,st_lc[i]?"%.2f:%.2f:%.2f":" " ,1.0*st_lmnc[i]/i,1.0*st_lsc[i]/st_lc[i]/i,1.0*st_lmxc[i]/i); gtk_label_set_text(GTK_LABEL(st_te[i][4]),s);
for(j=0;j<5;j++) gtk_widget_show(st_te[i][j]); // show row if non-empty...
}
else for(j=0;j<5;j++) {gtk_label_set_text(GTK_LABEL(st_te[i][j])," ");gtk_widget_hide(st_te[i][j]);} // ... and hide row if empty
}
sprintf(s,"Total lights: %d",nw); gtk_label_set_text(GTK_LABEL(st_r[0]),s);
if(nw>0) sprintf(s,"Mean length: %.1f",(double)nc/nw);
else strcpy (s,"Mean length: -"); gtk_label_set_text(GTK_LABEL(st_r[1]),s);
if(nc>0) sprintf(s,"Checked light letters: %d/%d (%.1f%%)",st_sc,nc,100.0*st_sc/nc);
else strcpy (s,"Checked light letters: -"); gtk_label_set_text(GTK_LABEL(st_r[2]),s);
if(ne>0) sprintf(s,"Checked grid cells: %d/%d (%.1f%%)",st_ce,ne,100.0*st_ce/ne);
else strcpy (s,"Checked grid cells: -"); gtk_label_set_text(GTK_LABEL(st_r[3]),s);
sprintf(s,"Lights with double unches: %d",st_2u-st_3u); gtk_label_set_text(GTK_LABEL(st_r[4]),s);
sprintf(s,"Lights with triple, quadruple etc. unches: %d",st_3u); gtk_label_set_text(GTK_LABEL(st_r[5]),s);
sprintf(s,"Lights too long for filler: %d",st_tlf); gtk_label_set_text(GTK_LABEL(st_r[6]),s);
sprintf(s,"Total free lights: %d",nvl); gtk_label_set_text(GTK_LABEL(st_r[7]),s);
sprintf(s,"Free lights too long for filler: %d",st_vltlf); gtk_label_set_text(GTK_LABEL(st_r[8]),s);
}
// create statistics dialogue
static void stats_init(void) {
int i,j;
GtkWidget*w0,*w1,*vb;
for(i=0;ivbox),vb,FALSE,TRUE,0);
w0=gtk_table_new(MXSZ+2,5,FALSE);
st_te[0][0]=gtk_label_new(" Length ");
st_te[0][1]=gtk_label_new(" Count ");
st_te[0][2]=gtk_label_new(" Underchecked ");
st_te[0][3]=gtk_label_new(" Overchecked ");
st_te[0][4]=gtk_label_new(" Check ratio min:mean:max ");
for(j=0;j<5;j++) gtk_widget_show(st_te[0][j]);
w1=gtk_hseparator_new();gtk_table_attach_defaults(GTK_TABLE(w0),w1,0,5,1,2);gtk_widget_show(w1);
for(i=2;i<=MXSZ;i++) for(j=0;j<5;j++) st_te[i][j]=gtk_label_new(""); // initialise all table entries to empty strings
for(i=0;i<=MXSZ;i++) for(j=0;j<5;j++) if(st_te[i][j]) gtk_table_attach_defaults(GTK_TABLE(w0),st_te[i][j],j,j+1,i,i+1);
w1=gtk_hseparator_new();gtk_table_attach_defaults(GTK_TABLE(w0),w1,0,5,MXSZ+1,MXSZ+2);gtk_widget_show(w1);
gtk_box_pack_start(GTK_BOX(vb),w0,FALSE,TRUE,0);gtk_widget_show(w0);
for(j=0;j<9;j++) {
st_r[j]=gtk_label_new(" "); // blank rows at the bottom for now
gtk_misc_set_alignment(GTK_MISC(st_r[j]),0,0.5);
gtk_box_pack_start(GTK_BOX(vb),st_r[j],FALSE,TRUE,0);
gtk_widget_show(st_r[j]);
}
gtk_widget_show(vb);
}
// remove statistics window (if not already gone or in the process of going)
void stats_quit(GtkDialog*w,int i0,void*p0) {
DEB4 printf("stats_quit()\n"),fflush(stdout);
if(i0!=GTK_RESPONSE_DELETE_EVENT&&stats!=NULL) gtk_widget_destroy(stats);
stats=NULL;
DEB4 printf("stats_quit()\n"),fflush(stdout);
}
// open stats window if not already open, and update it
static int statsdia(void) {
if(!stats) stats_init();
stats_upd();
gtk_widget_show(stats);
gtk_signal_connect(GTK_OBJECT(stats),"response",GTK_SIGNAL_FUNC(stats_quit),NULL); // does repeating this matter?
return 0;
}
// general question-and-answer box: title, question, yes-text, no-text
static int box(int type,const char*t,const char*u,const char*v) {
GtkWidget*dia;
int i;
dia=gtk_message_dialog_new(
GTK_WINDOW(mainw),
GTK_DIALOG_DESTROY_WITH_PARENT,
type,
GTK_BUTTONS_NONE,
"%s",t
);
gtk_dialog_add_buttons(GTK_DIALOG(dia),u,GTK_RESPONSE_ACCEPT,v,GTK_RESPONSE_REJECT,NULL);
currentdia=dia;
i=gtk_dialog_run(GTK_DIALOG(dia));
currentdia=0;
gtk_widget_destroy(dia);
return i==GTK_RESPONSE_ACCEPT;
}
void killcurrdia(void) {
if(currentdia) gtk_dialog_response(GTK_DIALOG(currentdia),GTK_RESPONSE_CANCEL); // kill the current dialogue box
}
void setposslabel(char*s) {
gtk_label_set_text(GTK_LABEL(poss_label),s);
}
// update feasible list to screen
void updatefeas(void) {int i; char t0[MXSZ*2+100],t1[MXSZ+50],*u[2],p0[SLEN],p1[SLEN];
u[0]=t0;
u[1]=t1;
p1[0]='\0';
gtk_clist_freeze(GTK_CLIST(clist)); // avoid glitchy-looking updates
gtk_clist_clear(GTK_CLIST(clist));
if(!isclear(curx,cury)) goto ew0;
for(i=0;iscore));
gtk_clist_append(GTK_CLIST(clist),u);
}
if(getechar(curx,cury)==' ') {
if(gsq[curx][cury].e0->flbm==0) strcpy(p0,"");
else getposs(gsq[curx][cury].e0,p0,0); // get feasible letter list
if(strlen(p0)==0) sprintf(p1," No feasible characters");
else sprintf(p1," Feasible character%s: %s",(strlen(p0)==1)?"":"s",p0);
} else p1[0]=0;
ew0:
gtk_clist_thaw(GTK_CLIST(clist)); // make list visible
DEB1 printf("Setting poss label to >%s<\n",p1);
setposslabel(p1);
return;
}
// box for information purposes only
void okbox(const char*t) {box(GTK_MESSAGE_INFO,t,GTK_STOCK_CLOSE,0);}
// box for errors, with `details' option
void reperr(const char*s) {box(GTK_MESSAGE_ERROR,s,GTK_STOCK_CLOSE,0);}
void oomerr() {box(GTK_MESSAGE_ERROR,"Out of memory",GTK_STOCK_CLOSE,0);}
void fnferr() {box(GTK_MESSAGE_ERROR,"File not found",GTK_STOCK_CLOSE,0);}
void fsgerr() {box(GTK_MESSAGE_ERROR,"Filing system error",GTK_STOCK_CLOSE,0);}
void fserror() {char s[SLEN],t[SLEN*2];
if(strerror_r(errno,s,SLEN)) strcpy(s,"general error");
sprintf(t,"Filing system error: %s",s);
reperr(t);
}
int areyousure(void) { // general-purpose are-you-sure dialogue
return box(GTK_MESSAGE_QUESTION,"\n Your work is not saved. \n Are you sure you want to proceed? \n"," Proceed ",GTK_STOCK_CANCEL);
}
static void syncselmenu() {
DEB1 printf("syncselmenu mode=%d,n=%d\n",selmode,nsel);
gtk_widget_set_sensitive((GtkWidget*)gtk_item_factory_get_widget_by_action(item_factory,0xf100),selmode==0&&nsel>0);
gtk_widget_set_sensitive((GtkWidget*)gtk_item_factory_get_widget_by_action(item_factory,0xf201),selmode==2&&nsel==1);
gtk_widget_set_sensitive((GtkWidget*)gtk_item_factory_get_widget_by_action(item_factory,0xf202),selmode==2&&nsel==1);
gtk_widget_set_sensitive((GtkWidget*)gtk_item_factory_get_widget_by_action(item_factory,0xf203),selmode==2&&nsel==1);
gtk_widget_set_sensitive((GtkWidget*)gtk_item_factory_get_widget_by_action(item_factory,0xf204),selmode==2&&nsel>0);
gtk_widget_set_sensitive((GtkWidget*)gtk_item_factory_get_widget_by_action(item_factory,0xf300),selmode==0&&nsel>0);
gtk_widget_set_sensitive((GtkWidget*)gtk_item_factory_get_widget_by_action(item_factory,0xf301),(selmode==1||selmode==2)&&nsel>0);
}
static void syncsymmmenu() {int i,m;
gtk_menu_item_activate((GtkMenuItem*)gtk_item_factory_get_widget_by_action(item_factory,0x100+symmr));
gtk_menu_item_activate((GtkMenuItem*)gtk_item_factory_get_widget_by_action(item_factory,0x200+symmm));
gtk_menu_item_activate((GtkMenuItem*)gtk_item_factory_get_widget_by_action(item_factory,0x300+symmd));
m=symmrmask(); for(i=1;i<=12;i++) gtk_widget_set_sensitive((GtkWidget*)gtk_item_factory_get_widget_by_action(item_factory,0x100+i),(m>>i)&1);
m=symmmmask(); for(i=0;i<= 3;i++) gtk_widget_set_sensitive((GtkWidget*)gtk_item_factory_get_widget_by_action(item_factory,0x200+i),(m>>i)&1);
m=symmdmask(); for(i=0;i<= 3;i++) gtk_widget_set_sensitive((GtkWidget*)gtk_item_factory_get_widget_by_action(item_factory,0x300+i),(m>>i)&1);
i=(gshape[gtype]==3||gshape[gtype]==4);
gtk_widget_set_sensitive((GtkWidget*)gtk_item_factory_get_widget_by_action(item_factory,0xf000),!i);
gtk_widget_set_sensitive((GtkWidget*)gtk_item_factory_get_widget_by_action(item_factory,0xf001), i);
gtk_widget_set_sensitive((GtkWidget*)gtk_item_factory_get_widget_by_action(item_factory,0xf002), i);
i=(gshape[gtype]>0);
gtk_widget_set_sensitive((GtkWidget*)gtk_item_factory_get_widget_by_action(item_factory,0x403),!i);
gtk_widget_set_sensitive((GtkWidget*)gtk_item_factory_get_widget_by_action(item_factory,0x413),!i);
gtk_widget_set_sensitive((GtkWidget*)gtk_item_factory_get_widget_by_action(item_factory,0x433),!i);
gtk_widget_set_sensitive((GtkWidget*)gtk_item_factory_get_widget_by_action(item_factory,0x443),!i);
}
// sync GUI with possibly new grid properties, flags, symmetry, width, height
void syncgui(void) {
if(!isingrid(curx,cury)) {curx=0,cury=0;} // keep cursor in grid
if(dir>=ndir[gtype]&&dir<100) dir=0; // make sure direction is feasible
if(dir>=100+nvl) dir=0;
gtk_drawing_area_size(GTK_DRAWING_AREA(grid_da),dawidth()+3,daheight()+3);
gtk_widget_show(grid_da);
syncsymmmenu();
syncselmenu();
setwintitle();
draw_init();
refreshall(0,shownums?7:3);
curmoved();
}
// menus
static GtkItemFactoryEntry menu_items[] = {
{ "/_File", 0, 0, 0, ""},
{ "/File/_New", "N", m_filenew, 0, "",GTK_STOCK_NEW},
{ "/File/sep0", 0, 0, 0, ""},
{ "/File/_Open...", "O", m_fileopen, 0, "",GTK_STOCK_OPEN},
{ "/File/_Save as...", "S", m_filesave, 0, "",GTK_STOCK_SAVE_AS},
{ "/File/sep1", 0, 0, 0, ""},
{ "/File/Export _blank grid image", 0, 0, 0, ""},
{ "/File/Export blank grid image/as _EPS...", 0, m_fileexport, 0x401},
{ "/File/Export blank grid image/as _PNG...", 0, m_fileexport, 0x402},
{ "/File/Export blank grid image/as _HTML...", 0, m_fileexport, 0x403},
{ "/File/Export _filled grid image", 0, 0, 0, ""},
{ "/File/Export filled grid image/as _EPS...", 0, m_fileexport, 0x411},
{ "/File/Export filled grid image/as _PNG...", 0, m_fileexport, 0x412},
{ "/File/Export filled grid image/as _HTML...", 0, m_fileexport, 0x413},
{ "/File/Export _answers", 0, 0, 0, ""},
{ "/File/Export answers/As _text...", 0, m_fileexport, 0x420},
{ "/File/Export answers/As _HTML...", 0, m_fileexport, 0x423},
{ "/File/Export _puzzle", 0, 0, 0, ""},
{ "/File/Export puzzle/As _HTML...", 0, m_fileexport, 0x433},
{ "/File/Export puzzle/As HTML+_PNG...", 0, m_fileexport, 0x434},
{ "/File/Export so_lution", 0, 0, 0, ""},
{ "/File/Export solution/As _HTML...", 0, m_fileexport, 0x443},
{ "/File/Export solution/As HTML+_PNG...", 0, m_fileexport, 0x444},
{ "/File/sep2", 0, 0, 0, ""},
{ "/File/_Quit", "Q", m_filequit, 0, "",GTK_STOCK_QUIT},
{ "/_Edit", 0, 0, 0, ""},
{ "/Edit/_Undo", "Z", m_undo, 0, "",GTK_STOCK_UNDO},
{ "/Edit/_Redo", "Y", m_redo, 0, "",GTK_STOCK_REDO},
{ "/Edit/sep1", 0, 0, 0, ""},
{ "/Edit/_Solid block", "Insert", m_editblock},
{ "/Edit/_Bar before", "Return", m_editbarb},
{ "/Edit/_Empty", "Delete", m_editempty},
{ "/Edit/_Cutout", "C", m_editcutout},
{ "/Edit/_Merge with next", "M", m_editmerge},
{ "/Edit/sep2", 0, 0, 0, ""},
{ "/Edit/Cell c_ontents...", "I", m_cellcont, 0},
{ "/Edit/Clear _all cells", "X", m_eraseall, 0, "",GTK_STOCK_CLEAR},
{ "/Edit/C_lear selected cells", "X", m_erasesel, 0xf100,"",GTK_STOCK_CLEAR},
{ "/Edit/sep3", 0, 0, 0, ""},
{ "/Edit/_Free light", 0, 0, 0, ""},
{ "/Edit/Free light/_Start new", 0, m_vlnew, 0xf200},
{ "/Edit/Free light/_Extend selected", "E", m_vlextend, 0xf201},
{ "/Edit/Free light/_Shorten selected", "D", m_vlcurtail, 0xf202},
{ "/Edit/Free light/_Modify selected", 0, m_vlmodify, 0xf203},
{ "/Edit/Free light/_Delete selected", 0, m_vldelete, 0xf204},
{ "/Edit/sep4", 0, 0, 0, ""},
{ "/Edit/Flip in main _diagonal", 0, m_editflip, 0xf000},
{ "/Edit/Rotate cloc_kwise", "greater", m_editrot, 0xf001},
{ "/Edit/Rotate a_nticlockwise", "less", m_editrot, 0xf002},
{ "/Edit/sep5", 0, 0, 0, ""},
{ "/Edit/_Zoom", 0, 0, 0, ""},
{ "/Edit/Zoom/_Out", "minus", m_zoom, -2},
{ "/Edit/Zoom/_1 50%", "8", m_zoom, 0},
{ "/Edit/Zoom/_2 71%", "9", m_zoom, 1},
{ "/Edit/Zoom/_3 100%", "0", m_zoom, 2},
{ "/Edit/Zoom/_4 141%", "1", m_zoom, 3},
{ "/Edit/Zoom/_5 200%", "2", m_zoom, 4},
{ "/Edit/Zoom/_In", "plus", m_zoom, -1},
{ "/Edit/Show s_tatistics", 0, m_showstats},
{ "/Edit/_Preferences...", 0, m_editprefs, 0, "", GTK_STOCK_PREFERENCES},
{ "/_Properties", 0, 0, 0, ""},
{ "/Properties/_Grid properties...", 0, m_editgprop, 0, "",GTK_STOCK_PROPERTIES},
{ "/Properties/Default _cell properties...", 0, m_dsprop, 0},
{ "/Properties/Selected c_ell properties...", 0, m_sprop, 0xf300},
{ "/Properties/Default _light properties...", 0, m_dlprop, 0},
{ "/Properties/Selected l_ight properties...", 0, m_lprop, 0xf301},
{ "/_Select", 0, 0, 0, ""},
{ "/Select/Current _cell", "C", m_selcell},
{ "/Select/Current _light", "L", m_sellight},
{ "/Select/Cell _mode <> light mode", "M", m_selmode},
{ "/Select/_Free light", "F", m_selfvl},
{ "/Select/sep0", 0, 0, 0, ""},
{ "/Select/_All", "A", m_selall},
{ "/Select/_Invert", "I", m_selinv},
{ "/Select/_Nothing", "N", m_selnone},
{ "/Select/sep1", 0, 0, 0, ""},
{ "/Select/Cell_s", 0, 0, 0, ""},
{ "/Select/Cells/overriding default _properties", 0, m_selcover},
{ "/Select/Cells/flagged for _answer treatment", 0, m_selctreat},
{ "/Select/Li_ghts", 0, 0, 0, ""},
{ "/Select/Lights/_in current direction", 0, m_sellpar},
{ "/Select/Lights/overriding default _properties", 0, m_sellover},
{ "/Select/Lights/with answer treatment _enabled", 0, m_selltreat},
{ "/Select/Lights/with _double or more unches", 0, m_selviol, 1},
{ "/Select/Lights/with _triple or more unches", 0, m_selviol, 2},
{ "/Select/Lights/that are _underchecked", 0, m_selviol, 4},
{ "/Select/Lights/that are _overchecked", 0, m_selviol, 8},
{ "/Sy_mmetry", 0, 0, 0, ""},
{ "/Symmetry/No rotational", 0, m_symm0, 0x0101,""},
{ "/Symmetry/Twofold rotational", 0, m_symm0, 0x0102,"/Symmetry/No rotational" },
{ "/Symmetry/Threefold rotational", 0, m_symm0, 0x0103,"/Symmetry/Twofold rotational" },
{ "/Symmetry/Fourfold rotational", 0, m_symm0, 0x0104,"/Symmetry/Threefold rotational" },
{ "/Symmetry/Fivefold rotational", 0, m_symm0, 0x0105,"/Symmetry/Fourfold rotational" },
{ "/Symmetry/Sixfold rotational", 0, m_symm0, 0x0106,"/Symmetry/Fivefold rotational" },
{ "/Symmetry/Sevenfold rotational", 0, m_symm0, 0x0107,"/Symmetry/Sixfold rotational" },
{ "/Symmetry/Eightfold rotational", 0, m_symm0, 0x0108,"/Symmetry/Sevenfold rotational" },
{ "/Symmetry/Ninefold rotational", 0, m_symm0, 0x0109,"/Symmetry/Eightfold rotational" },
{ "/Symmetry/Tenfold rotational", 0, m_symm0, 0x010a,"/Symmetry/Ninefold rotational" },
{ "/Symmetry/Elevenfold rotational", 0, m_symm0, 0x010b,"/Symmetry/Tenfold rotational" },
{ "/Symmetry/Twelvefold rotational", 0, m_symm0, 0x010c,"/Symmetry/Elevenfold rotational" },
{ "/Symmetry/sep1", 0, 0, 0, ""},
{ "/Symmetry/No mirror", 0, m_symm1, 0x0200,""},
{ "/Symmetry/Left-right mirror", 0, m_symm1, 0x0201,"/Symmetry/No mirror"},
{ "/Symmetry/Up-down mirror", 0, m_symm1, 0x0202,"/Symmetry/Left-right mirror"},
{ "/Symmetry/Both", 0, m_symm1, 0x0203,"/Symmetry/Up-down mirror"},
{ "/Symmetry/sep2", 0, 0, 0, ""},
{ "/Symmetry/No duplication", 0, m_symm2, 0x0300,""},
{ "/Symmetry/Left-right duplication", 0, m_symm2, 0x0301,"/Symmetry/No duplication"},
{ "/Symmetry/Up-down duplication", 0, m_symm2, 0x0302,"/Symmetry/Left-right duplication"},
{ "/Symmetry/Both", 0, m_symm2, 0x0303,"/Symmetry/Up-down duplication"},
{ "/_Autofill", 0, 0, 0, ""},
{ "/Autofill/_Dictionaries...", 0, m_dictionaries},
{ "/Autofill/Answer _treatment...", 0, m_afctreat},
{ "/Autofill/sep1", 0, 0, 0, ""},
{ "/Autofill/Auto_fill", "G", m_autofill, 1, "",GTK_STOCK_EXECUTE},
{ "/Autofill/Autofill _selected cells", "G", m_autofill, 2, "",GTK_STOCK_EXECUTE},
{ "/Autofill/Accept _hints", "A", m_accept},
{ "/Help", 0, 0, 0, ""},
{ "/Help/About", 0, m_helpabout, 0, "",GTK_STOCK_ABOUT},
};
// build main window and other initialisation
void startgtk(void) {
GtkAccelGroup*accel_group;
pxsq=zoompx[zoomf];
mainw=gtk_window_new(GTK_WINDOW_TOPLEVEL); // main window
gtk_widget_set_name(mainw,"Qxw");
gtk_window_set_default_size(GTK_WINDOW(mainw),780,560);
gtk_window_set_title(GTK_WINDOW(mainw),"Qxw");
gtk_window_set_position(GTK_WINDOW(mainw),GTK_WIN_POS_CENTER);
// box in the window
vbox=gtk_vbox_new(FALSE,0);
gtk_container_add(GTK_CONTAINER(mainw),vbox);
// menu in the vbox
accel_group=gtk_accel_group_new();
item_factory=gtk_item_factory_new(GTK_TYPE_MENU_BAR,"",accel_group);
gtk_item_factory_create_items(item_factory,sizeof(menu_items)/sizeof(menu_items[0]),menu_items,NULL);
gtk_window_add_accel_group(GTK_WINDOW(mainw),accel_group);
menubar=gtk_item_factory_get_widget(item_factory,"");
gtk_box_pack_start(GTK_BOX(vbox),menubar,FALSE,TRUE,0);
// window is divided into two parts, or `panes'
paned=gtk_hpaned_new();
gtk_box_pack_start(GTK_BOX(vbox),paned,TRUE,TRUE,0);
// scrolled windows in the panes
grid_sw=gtk_scrolled_window_new(NULL,NULL);
gtk_container_set_border_width(GTK_CONTAINER(grid_sw),10);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(grid_sw),GTK_POLICY_AUTOMATIC,GTK_POLICY_AUTOMATIC);
gtk_paned_pack1(GTK_PANED(paned),grid_sw,1,1);
list_sw=gtk_scrolled_window_new(NULL,NULL);
gtk_container_set_border_width(GTK_CONTAINER(list_sw),10);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(list_sw),GTK_POLICY_AUTOMATIC,GTK_POLICY_AUTOMATIC);
gtk_paned_pack2(GTK_PANED(paned),list_sw,0,1);
gtk_paned_set_position(GTK_PANED(paned),560);
// drawing area for grid and events it captures
grid_da=gtk_drawing_area_new();
gtk_drawing_area_size(GTK_DRAWING_AREA(grid_da),100,100);
gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(grid_sw),grid_da);
GTK_WIDGET_SET_FLAGS(grid_da,GTK_CAN_FOCUS);
gtk_widget_set_events(grid_da,GDK_EXPOSURE_MASK|GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK|GDK_KEY_PRESS_MASK|GDK_POINTER_MOTION_MASK);
// list of feasible words
// clist=gtk_clist_new(2);
clist=gtk_clist_new(1);
gtk_clist_set_column_width(GTK_CLIST(clist),0,180);
// gtk_clist_set_column_width(GTK_CLIST(clist),1,80);
gtk_clist_set_column_title(GTK_CLIST(clist),0,"Feasible words");
// gtk_clist_set_column_title(GTK_CLIST(clist),1,"Scores");
gtk_clist_column_titles_passive(GTK_CLIST(clist));
gtk_clist_column_titles_show(GTK_CLIST(clist));
gtk_clist_set_selection_mode(GTK_CLIST(clist),GTK_SELECTION_SINGLE);
gtk_container_add(GTK_CONTAINER(list_sw),clist);
// box for widgets across the bottom of the window
hbox=gtk_hbox_new(FALSE,0);
poss_label=gtk_label_new(" Feasible characters:");
gtk_box_pack_start(GTK_BOX(hbox),poss_label,FALSE,FALSE,0);
gtk_box_pack_end(GTK_BOX(vbox),hbox,FALSE,FALSE,0);
gtk_signal_connect(GTK_OBJECT(grid_da),"expose_event",GTK_SIGNAL_FUNC(expose_event),NULL);
gtk_signal_connect(GTK_OBJECT(grid_da),"configure_event",GTK_SIGNAL_FUNC(configure_event),NULL);
gtk_signal_connect(GTK_OBJECT(clist),"select_row",GTK_SIGNAL_FUNC(selrow),NULL);
gtk_signal_connect(GTK_OBJECT(grid_da),"button_press_event",GTK_SIGNAL_FUNC(button_press_event),NULL);
gtk_signal_connect(GTK_OBJECT(grid_da),"button_release_event",GTK_SIGNAL_FUNC(button_press_event),NULL);
gtk_signal_connect_after(GTK_OBJECT(grid_da),"key_press_event",GTK_SIGNAL_FUNC(keypress),NULL);
gtk_signal_connect(GTK_OBJECT(grid_da),"motion_notify_event",GTK_SIGNAL_FUNC(mousemove),NULL);
gtk_signal_connect(GTK_OBJECT(mainw),"delete_event",GTK_SIGNAL_FUNC(w_delete),NULL);
gtk_signal_connect(GTK_OBJECT(mainw),"destroy",GTK_SIGNAL_FUNC(w_destroy),NULL);
gtk_widget_show_all(mainw);
gtk_window_set_focus(GTK_WINDOW(mainw),grid_da);
}
void stopgtk(void) {
}
qxw-20110923/filler.c 0000644 0001750 0001750 00000035171 11637123102 012030 0 ustar mo mo // $Id: filler.c -1 $
/*
Qxw is a program to help construct and publish crosswords.
Copyright 2011 Mark Owen
http://www.quinapalus.com
E-mail: qxw@quinapalus.com
This file is part of Qxw.
Qxw is free software: you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License
as published by the Free Software Foundation.
Qxw 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. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Qxw. If not, see or
write to the Free Software Foundation, Inc., 51 Franklin Street,
Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include
#include "common.h"
#include "filler.h"
#include "dicts.h"
#include "qxw.h"
static int ct_malloc=0,ct_free=0; // counters for debugging
static int phase=0; // state machine
static int winit=0; // count of words initialised so far
static int rcode=0; // return code: -3, -4: initflist errors; -2: out of stack; -1: out of memory; 1: no fill found; 2: fill found
// the following stacks keep track of the filler state as it recursively tries to fill the grid
#define MXSS (MXSZ*MXSZ+2)
static int sdep=-1; // stack pointer
static int sent[MXSS]; // entry considered at this depth
static int spp[MXSS]; // which possibility we are currently trying (index into sposs)
static char*sposs[MXSS]; // possibilities for this entry, 0-terminated
static int**sflist[MXSS]; // pointers to restore feasible word list flist
static int*sflistlen[MXSS]; // pointers to restore flistlen
static int*scommit[MXSS]; // word (if any - -1 otherwise) committed at this depth
static ABM*sentryfl[MXSS]; // feasible letter bitmap for this entry
#define isused(l) (lused[lts[l].uniq]|aused[lts[l].ans])
#define setused(l,v) lused[lts[l].uniq]=v,aused[lts[l].ans]=v
// phase 16: sort the feasible word list ready for display
static int sortwlist(void) {
// now done in mkfeas()
return 17;
}
// phase 17: transfer the feasible word list to the display
static int mkclist(void) {
updatefeas();
return 18;
}
// transfer progress info to display
static void progress(void) {
DEB1 printf("ct_malloc=%d ct_free=%d diff=%d\n",ct_malloc,ct_free,ct_malloc-ct_free);
updategrid();
}
// intersect light list q length l with letter position wp masked by bitmap m: result is stored in p and new length is returned
static int listisect(int*p,int*q,int l,int wp,ABM m) {int i,j;
for(i=0,j=0;i%d\n",wp,m,l,j);
return j;
}
// find the entry to expand next, or -1 if all done
static int findcritent(void) {int i,j,m;float k,l;
for(m=2;m>0;m--) { // m=2: loop over checked entries; then m=1: loop over unchecked entries
j=-1;l=BIGF;
for(i=0;i=m) { // not already entered?
k=entries[i].crux; // get the priority for this entry
if(k=1 otherwise
static int settleents(void) {struct entry*e;struct word*w;int f,i,l; int*p;
DEB1 printf("settleents() sdep=%d\n",sdep);
f=0;
for(i=0;iupd; // generate cell updated flags from entry updated flags
for(i=0;iflist;
l=w->flistlen;
if(sflistlen[sdep][w-words]==-1) { // then we mustn't trash words[].flist
sflist [sdep][w-words]=w->flist;
sflistlen[sdep][w-words]=w->flistlen;
w->flist=(int*)malloc(l*sizeof(int)); // new list can be at most as long as old one
if(w->flist==NULL) return -1; // out of memory
ct_malloc++;
}
w->flistlen=listisect(w->flist,p,l,cells[i].wp,e->flbm); // generate new feasible word list
if(w->flistlen!=l) {w->upd=1;f++;} // word list has changed: feasible letter lists will need updating
if(w->flistlen==0&&!w->fe) return -2; // no options left, not a fully-entered word
if(w->flistlen==1&&w->commit==-1) { // down to a single word?
if(afunique&&isused(w->flist[0])) { // in no-duplicates mode and only answer left is already used? (could check all on list)
w->flistlen=0; // abort
return -2;
}
else { // otherwise, if down to one word, commit it
// printf("committing word %d (%s)\n",w,lts[w->flist[0]].s);fflush(stdout);
setused(w->flist[0],1); // flag as used
w->commit=w->flist[0];
scommit[sdep][w-words]=w->flist[0];
}
}
}
for(i=0;ie->ch;
if(ch!=' ') assert((ch>='A'&&ch<='Z')||(ch>='0'&&ch<='9')),cellfl[k]=1LL<e; // propagate from cell to entry
if(e->flbm&~cellfl[j]) { // has this entry been changed by the additional constraint?
e->flbm&=cellfl[j];
e->upd=1;f++; // flag that it will need updating
// printf("E%d %16llx\n",k,entries[k].flbm);fflush(stdout);
}
}
}
for(i=0;i=0) { // avoid zero score if we've committed
if(l==1) for(k=0;kscore[chartol[(int)lts[p[0]].s[k]]]+=1.0;
else assert(l==0);
}
else {
for(j=0;jscore;
for(k=0;kscore[chartol[(int)lts[p[j]].s[k]]]+=f; // add in its score to this cell's score
}
}
}
for(i=0;ilength];
// if(f!=0.0) f=1.0/f;
f=1.0;
for(j=0;jscore[j]*=f*cells[i].score[j]; // copy scores to entries, scaled by total word count at this length
}
for(i=0;iscore[i]>j&&e->score[i]score[i]; // peel off scores from top down
DEB2 printf("getposs(%d): j=%g\n",e-entries,j);
if(j<=0) break;
for(i=0;iscore[i]==j) s[l++]=ltochar[i]; // add to output string
k=j;} // get next highest set of equal scores
s[l]='\0';
if(r==0) return;
for(i=0;i=0&&m=0);state_restore();sdep--;}
// clear state stacks and free allocated memory
static void state_finit(void) {
while(sdep>=0) state_pop();
freestack();
}
// phase 10: initialise for search
static int searchinit(void) {int u,i,j,t0;
t0=clock();
for(i=winit;iten) clueorderindex++;
if(clock()-t0>CLOCKS_PER_SEC/(20)) {
DEB1 printf("Intercalated return while initialising word lists\n");
winit=i+1;
return 10;
}
}
if(postgetinitflist()) {rcode=-4;return 0;}
memset(aused,0,atotal);
memset(lused,0,ultotal);
return 14; // update internal data from initial grid
}
// phase 11: one level deeper in seach tree
static int searchdeeper(void) {int e; char s[MAXNL+1];
if(sdep==MXSS-1) {rcode=-2;return 15;}
DEB1 printf("mkscores...\n");
mkscores();
DEB1 {
int w;
for(w=0;wCLOCKS_PER_SEC*3||ct1-ct0<0) {progress();ct0=clock();} // update display every three seconds or so
return 14; // update internal data from new entry
}
// phase 13: backtrack when all possibilities in an entry have been exhausted without success
static int searchbacktrack(void) {
state_pop();
if(sdep==-1) {rcode=1;return 15;} // all done, no solution found
return 12; // try next possibility at level above
}
// phase 14: update internal data
static int searchsettle(void) {int f;
f=settleents(); // rescan entries
if(f==-2) return 13;
if(f==-1) {rcode=-1;return 15;} // out of memory: abort
f=settlewds(); // rescan words
if(f==0) return 11; // no changes: proceed to next search level
return 14; // need to iterate until everything settles down
}
// phase 15
static int searchdone(void) {int i;
progress(); // copy results to display
mkfeas(); // construct feasible word list
state_finit();
// if(fillmode&¤tdia) gtk_dialog_response(GTK_DIALOG(currentdia),GTK_RESPONSE_CANCEL);
for(i=0;i> ct_malloc=%d ct_free=%d diff=%d\n",ct_malloc,ct_free,ct_malloc-ct_free);fflush(stdout);
// j=0; for(i=0;i